こんにちは.初投稿です.
めっちゃわかりにくいとは思いますが、部分的にでも参考になれば幸いです.
原神っていろんなステータスがあるんですけど、
確認するときっていちいち原神を開かないといけないんですよね.
HoYoLabという公式ツールもあるのですが、
それで確認できるのはほんとに最小限という感じで、聖遺物のステータスなどは確認できません.
なのでDiscordから聖遺物を確認できるBotを作ってみました.
botを招待して確認したい方はこちらからどうぞ.
https://discord.com/api/oauth2/authorize?client_id=1014120722404220998&permissions=2147485696&scope=bot
こんな感じです.
キャラ情報も取得できます.
武器の詳細については現在作成中です.
今回作るbotの材料
原神のステータスを取得するためのAPIには、
ステータスを一発で画像にしてくれると有名な、
https://enka.network/
こちらのサイトを利用させていただきました.
画像などもこちらのサイトから取得できるので便利です.
botにはPycordを使います.
python3 -m pip install pycord
でインストールします.
一応バージョンは2.0.0を使用します.
コードを書く
とりあえずみんな大好き(?)なCogを使っていきます.
Cogというのは私もよくわかっていないのですが、
たくさんのコマンドをまとめられるやつだと思っています...
Cogsフォルダにコマンドを書いたpythonファイルを突っ込むと動きます.
(Cogについてよくわかれば追記します.)
まずはmainのやつから
from discord.ext import commands
bot = commands.Bot()
path = "./cogs"
@bot.event
async def on_application_command_error(ctx, error):
if isinstance(error, commands.CommandOnCooldown):
await ctx.respond(error)
if isinstance(error, commands.MissingPermissions):
await ctx.respond(content="BOT管理者限定コマンドです", ephemeral=True)
@bot.event
async def on_ready():
print(f"Bot名:{bot.user} On ready!!")
bot.load_extension('cogs.genshin', store=False)
bot.run("TOKEN")
最後の「TOKEN」にはbotのトークンを貼り付けてください.
botの作成の仕方は今回は省略とさせていただきます。
こちらのmain.pyと同じディレクトリに「cogs」というフォルダを作成してください.
その中に、「genshin.py」というファイルを作成します.
ファイル名を変える場合には、main.pyの
bot.load_extension('cogs.genshin')
の部分を
bot.load_extension('cogs.任意のファイル名.py')
としてください.
import discord
from discord.ui import Select,View,Button
from discord.ext import commands
from discord import Option, SlashCommandGroup
import aiohttp
from lib.yamlutil import yaml
import lib.getStat as getStat
from typing import List
dataYaml = yaml(path='genshin_avater.yaml')
data = dataYaml.load_yaml()
charactersYaml = yaml(path='characters.yaml')
characters = charactersYaml.load_yaml()
genshinJpYaml = yaml(path='genshinJp.yaml')
genshinJp = genshinJpYaml.load_yaml()
l: list[discord.SelectOption] = []
class TicTacToeButton(discord.ui.Button["TicTacToe"]):
def __init__(self, label: str, uid: str, dict):
super().__init__(style=discord.ButtonStyle.secondary, label=label)
self.dict = dict
self.uid = uid
async def callback(self, interaction: discord.Interaction):
assert self.view is not None
view: TicTacToe = self.view
self.style = discord.ButtonStyle.success
content = self.label
#ラベル(名前)からIDを割り出す
#多分「名前:iD」ってなってるはず
id = self.dict[self.label]
print(interaction.user.id)
for child in self.view.children:
child.style = discord.ButtonStyle.gray
await interaction.response.edit_message(content=content, embed=await getStat.get(self.uid, id), view=None)
class TicTacToe(discord.ui.View):
children: List[TicTacToeButton]
def __init__(self, data, uid):
super().__init__(timeout=190)
names = []
dict = {}
#入ってきたidを名前にしてリスト化
for id in data:
id = str(id)
print(id)
name = characters[id]["NameId"]
print(name)
name = genshinJp[name]
print(name)
names.append(name)
dict.update({name: id})
print(dict)
#名前をラベル、ついでにdictとuidも送り付ける
for v in names:
self.add_item(TicTacToeButton(v,uid,dict))
class GenshinCog(commands.Cog):
def __init__(self, bot):
print('genshin初期化')
self.bot = bot
async def getApi(self,uid):
url = f"https://enka.network/u/{uid}/__data.json"
async with aiohttp.ClientSession() as session:
async with session.get(url) as response:
resp = await response.json()
try:
embed = discord.Embed(
title=f"{resp['playerInfo']['nickname']}の原神ステータス",
color=0x1e90ff,
description=f"uid: {uid}",
url=url
)
hoge = data[resp['playerInfo']['profilePicture']['avatarId']]['iconName']
embed.set_thumbnail(url=f"https://enka.network/ui/{hoge}.png")
try:
embed.add_field(inline=False,name="ステータスメッセージ",value=resp['playerInfo']['signature'])
except:
print("hoge")
embed.add_field(inline=False,name="冒険ランク",value=resp['playerInfo']['level'])
embed.add_field(inline=False,name="世界ランク",value=resp['playerInfo']['worldLevel'])
embed.add_field(inline=False,name="深境螺旋",value=f"第{resp['playerInfo']['towerFloorIndex']}層 第{resp['playerInfo']['towerLevelIndex']}間")
return embed
except:
embed = discord.Embed(
title=f"エラーが発生しました。APIを確認してからもう一度お試しください。\n{url}",
color=0x1e90ff,
url=url
)
return embed
genshin = SlashCommandGroup('genshinstat', 'test')
@genshin.command(name="get", description="UIDからキャラ情報を取得します")
async def genshin_get(
self,
ctx: discord.ApplicationContext,
uid: Option(str, required=True, description='UIDを入力してください.'),
):
await ctx.respond(content="アカウント情報読み込み中...", ephemeral=True)
embed = await GenshinCog.getApi(self,uid)
await ctx.respond(content="キャラ情報読み込み中...", ephemeral=True)
url = f"https://enka.network/u/{uid}/__data.json"
async with aiohttp.ClientSession() as session:
async with session.get(url) as response:
resp = await response.json()
resalt = []
for id in resp["playerInfo"]["showAvatarInfoList"]:
resalt.append(id["avatarId"])
await ctx.respond(content=None,embed=embed,view=TicTacToe(resalt,uid))
def setup(bot):
bot.add_cog(GenshinCog(bot))
くっそ長いですね.
長すぎるので細かくは紹介しません.
多分ほんとはもっと省略できそうですが、私の技術力ではできませんでした...
簡単に解説していきます.
コマンド部分
@genshin.command(name="get", description="UIDからキャラ情報を取得します")
async def genshin_get(
self,
ctx: discord.ApplicationContext,
uid: Option(str, required=True, description='UIDを入力してください.'),
):
await ctx.respond(content="アカウント情報読み込み中...", ephemeral=True)
embed = await GenshinCog.getApi(self,uid)
await ctx.respond(content="キャラ情報読み込み中...", ephemeral=True)
url = f"https://enka.network/u/{uid}/__data.json"
async with aiohttp.ClientSession() as session:
async with session.get(url) as response:
resp = await response.json()
resalt = []
for id in resp["playerInfo"]["showAvatarInfoList"]:
resalt.append(id["avatarId"])
await ctx.respond(content=None,embed=embed,view=TicTacToe(resalt,uid))
ここでスラッシュコマンドを定義しておきます.
やってることは単純で、
①まずrespond(返信)をする(読み込みが長くタイムアウトしてしまう可能性があるため)
②アカウントの情報(レベルなど)をembedとして取得
③そのアカウントの持っているキャラ情報の取得
です.
キャラ情報については、キャラのIDをリストとして、ボタンの処理について書かれたクラスにUIDと一緒に渡します.
class TicTacToe(discord.ui.View):
children: List[TicTacToeButton]
def __init__(self, data, uid):
super().__init__(timeout=190)
names = []
dict = {}
#入ってきたidを名前にしてリスト化
for id in data:
id = str(id)
name = genshinJp[characters[str(id)]["NameId"]]
names.append(name)
dict.update({name: id})
#名前をラベル、ついでにdictとuidも送り付ける
for v in names:
self.add_item(TicTacToeButton(v,uid,dict))
こちらはボタン部分です.
Discordで見るとこのようになっていますね.
ここから見たいキャラを選ぶ感じです.
ここにはリストになったキャラのidが突っ込まれてくるので、
idからキャラ名を取得
日本語化して表示しています.
また、キャラ詳細でidが必要なので、一緒に辞書型としてボタンの処理部分に丸投げします.
class TicTacToeButton(discord.ui.Button["TicTacToe"]):
def __init__(self, label: str, uid: str, dict):
super().__init__(style=discord.ButtonStyle.secondary, label=label)
self.dict = dict
self.uid = uid
async def callback(self, interaction: discord.Interaction):
assert self.view is not None
view: TicTacToe = self.view
self.style = discord.ButtonStyle.success
content = self.label
#ラベル(名前)からIDを割り出す
#多分「名前:iD」ってなってるはず
id = self.dict[self.label]
print(interaction.user.id)
for child in self.view.children:
child.style = discord.ButtonStyle.gray
await interaction.response.edit_message(content=content, embed=await getStat.get(self.uid, id), view=None)
ボタンの処理部分です.
さっきのボタンの部分にidがくっついているので、押されたボタンのラベル(キャラ名)に関連したidを、キャラ詳細を表示するbotにuidと一緒に丸投げします.
async def get(uid,id):
url = f"https://enka.network/u/{uid}/__data.json"
async with aiohttp.ClientSession() as session:
async with session.get(url) as response:
resp = await response.json()
name = characters[id]["NameId"]
name = genshinJp[name]
print(id)
id = int(id)
try:
for n in resp['avatarInfoList']:
if n["avatarId"] == id:
chara = n
print("hogehogheogheohgpoihvgp;ogiazwqp;oabwo")
break
else:
continue
for n in resp['playerInfo']["showAvatarInfoList"]:
print(n["avatarId"])
if n["avatarId"] == id:
level = n["level"]
print("hogehogehoge")
break
else:
continue
except:
embed = discord.Embed(
title="エラー",
color=0x1e90ff,
description=f"キャラ詳細が非公開です。原神の設定で公開設定にしてください。",
url=url
)
return embed
try:
embed = discord.Embed(
title=name,
color=0x1e90ff,
description=f"{level}lv",
url=url
)
hoge = characters[str(id)]["sideIconName"]
embed.set_thumbnail(url=f"https://enka.network/ui/{hoge}.png")
embed.add_field(inline=True,name="キャラレベル",value=f"{level}lv")
embed.add_field(inline=True,name="キャラ突破レベル",value=str(chara["propMap"]["1002"]["ival"]))
embed.add_field(inline=True,name="HP",
value=f'{str(round(chara["fightPropMap"]["1"]))} + {str(round(chara["fightPropMap"]["2000"]) - round(chara["fightPropMap"]["1"]))} = {str(round(chara["fightPropMap"]["2000"]))}'
)
embed.add_field(inline=True,name="攻撃力",
value=f'{str(round(chara["fightPropMap"]["4"]))} + {str(round(chara["fightPropMap"]["2001"]) - round(chara["fightPropMap"]["4"]))} = {str(round(chara["fightPropMap"]["2001"]))}'
)
embed.add_field(inline=True,name="防御力",
value=f'{str(round(chara["fightPropMap"]["7"]))} + {str(round(chara["fightPropMap"]["2002"]) - round(chara["fightPropMap"]["7"]))} = {str(round(chara["fightPropMap"]["2002"]))}'
)
embed.add_field(inline=True,name="会心率",
value=f'{str(round(chara["fightPropMap"]["20"] *100))}%'
)
embed.add_field(inline=True,name="会心ダメージ",
value=f'{str(round(chara["fightPropMap"]["22"]*100))}%'
)
embed.add_field(inline=True,name="元素チャージ効率",
value=f'{str(round(chara["fightPropMap"]["23"]*100))}%'
)
embed.add_field(inline=True,name="元素熟知",
value=f'{str(round(chara["fightPropMap"]["28"]))}'
)
buf = 1
if round(chara["fightPropMap"]["30"]*100) > 0:
embed.add_field(inline=True,name="物理ダメージ",
value=f'{str(round(chara["fightPropMap"]["30"]*100))}%'
)
buf += round(chara["fightPropMap"]["30"])
elif round(chara["fightPropMap"]["40"]*100) > 0:
embed.add_field(inline=True,name="炎元素ダメージ",
value=f'{str(round(chara["fightPropMap"]["40"]*100))}%'
)
buf += round(chara["fightPropMap"]["40"])
elif round(chara["fightPropMap"]["41"]*100) > 0:
embed.add_field(inline=True,name="雷元素ダメージ",
value=f'{str(round(chara["fightPropMap"]["41"]*100))}%'
)
buf += round(chara["fightPropMap"]["41"])
elif round(chara["fightPropMap"]["42"]*100) > 0:
embed.add_field(inline=True,name="水元素ダメージ",
value=f'{str(round(chara["fightPropMap"]["42"]*100))}%'
)
buf += round(chara["fightPropMap"]["42"])
elif round(chara["fightPropMap"]["43"]*100) > 0:
embed.add_field(inline=True,name="草元素ダメージ",
value=f'{str(round(chara["fightPropMap"]["43"]*100))}%'
)
buf += round(chara["fightPropMap"]["42"])
elif round(chara["fightPropMap"]["44"]*100) > 0:
embed.add_field(inline=True,name="風元素ダメージ",
value=f'{str(round(chara["fightPropMap"]["44"]*100))}%'
)
buf += round(chara["fightPropMap"]["44"])
elif round(chara["fightPropMap"]["45"]*100) > 0:
embed.add_field(inline=True,name="岩元素ダメージ",
value=f'{str(round(chara["fightPropMap"]["45"]*100))}%'
)
buf += round(chara["fightPropMap"]["45"])
elif round(chara["fightPropMap"]["46"]*100) > 0:
embed.add_field(inline=True,name="氷元素ダメージ",
value=f'{str(round(chara["fightPropMap"]["46"]*100))}%'
)
buf += round(chara["fightPropMap"]["46"])
temp = []
for myvalue in chara["skillLevelMap"].values():
temp.append(f"{myvalue}")
embed.add_field(inline=False,name="天賦レベル",
value="\n".join(temp)
)
for n in chara["equipList"]:
name = genshinJp[n["flat"]["setNameTextMapHash"]]
equip = genshinJp[n["flat"]["equipType"]]
main = genshinJp[n["flat"]["reliquaryMainstat"]["mainPropId"]]
hoge = []
for b in n["flat"]["reliquarySubstats"]:
name_ = genshinJp[b["appendPropId"]]
value_ = b["statValue"]
hoge.append(f"{name_}:{value_}")
embed.add_field(inline=True,name=f'聖遺物:{equip}\n{name}\n{main}:{n["flat"]["reliquaryMainstat"]["statValue"]}\n{n["reliquary"]["level"]-1}lv\n',
value="\n".join(hoge)
)
return embed
except KeyError:
return embed
キャラ詳細表示部分です.
main.pyのディレクトリlibというフォルダを作り、その中にgetStat.pyというファイルを入れています.
こちらは長いだけなので特に複雑な処理はしていません.
丸投げされたUIDとキャラIDから、その人のキャラ詳細のJsonが帰ってくるので
それを分解して表示するだけです.
武器なども表示できると思いますが、時間がなくてまだできていません.
今後追記すると思います.
おわりに
以上となります。
多分これから修正点がたくさん見つかると思いますが、随時追記していきます.
Qiitaを書くのは初めてなのでわかりずらいとは思いますが、少しでも参考になれば幸いです.
また、手伝っていただいたにかわみかんさん、ありがとうございました。