3
0

v0.2.0 支持圣遗物评分、地图资源查询

This commit is contained in:
xtaodada 2022-01-16 16:48:05 +08:00
parent 6c07eae843
commit b314a6e69b
No known key found for this signature in database
GPG Key ID: EE4DC37B55E24736
11 changed files with 523 additions and 46 deletions

BIN
assets/icon/0.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

BIN
assets/icon/box.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.5 KiB

BIN
assets/icon/box_alpha.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

39
defs/artifact_rate.py Normal file
View File

@ -0,0 +1,39 @@
import requests
import json
ocr_url = "https://api.genshin.pub/api/v1/app/ocr"
rate_url = "https://api.genshin.pub/api/v1/relic/rate"
head = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) "
"Chrome/92.0.4515.131 Safari/537.36 Edg/92.0.902.67 ",
"Content-Type": "application/json; charset=UTF-8",
"Connection": "close"
}
async def get_artifact_attr(b64_str):
upload_json = json.dumps(
{
"image": b64_str
}
)
try:
req = requests.post(ocr_url, data=upload_json, headers=head, timeout=8)
except requests.exceptions.RequestException as e:
raise e
data = json.loads(req.text)
if req.status_code != 200:
return {"err": "未知错误", "full": data}
return data
async def rate_artifact(artifact_attr: dict):
upload_json_str = json.dumps(artifact_attr, ensure_ascii=False).encode('utf-8')
try:
req = requests.post(rate_url, data=upload_json_str, headers=head, timeout=8)
except requests.exceptions.RequestException as e:
raise e
data = json.loads(req.text)
if req.status_code != 200:
return {"err": "未知错误", "full": data}
return data

View File

@ -0,0 +1,315 @@
from PIL import Image, ImageMath
from io import BytesIO
import json
import os
import time
import httpx
import asyncio
LABEL_URL = 'https://api-static.mihoyo.com/common/blackboard/ys_obc/v1/map/label/tree?app_sn=ys_obc'
POINT_LIST_URL = 'https://api-static.mihoyo.com/common/blackboard/ys_obc/v1/map/point/list?map_id=2&app_sn=ys_obc'
MAP_URL = "https://api-static.mihoyo.com/common/map_user/ys_obc/v1/map/info?map_id=2&app_sn=ys_obc&lang=zh-cn"
header = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/84.0.4147.105 Safari/537.36'
FILE_PATH = "assets"
MAP_PATH = os.path.join(FILE_PATH, "icon", "map_icon.jpg")
Image.MAX_IMAGE_PIXELS = None
CENTER = None
MAP_ICON = None
zoom = 0.5
resource_icon_offset = (-int(150 * 0.5 * zoom), -int(150 * zoom))
data = {
"all_resource_type": {
# 这个字典保存所有资源类型,
# "1": {
# "id": 1,
# "name": "传送点",
# "icon": "",
# "parent_id": 0,
# "depth": 1,
# "node_type": 1,
# "jump_type": 0,
# "jump_target_id": 0,
# "display_priority": 0,
# "children": []
# },
},
"can_query_type_list": {
# 这个字典保存所有可以查询的资源类型名称和ID这个字典只有名称和ID
# 上边字典里"depth": 2的类型才可以查询"depth": 1的是1级目录不能查询
# "七天神像":"2"
# "风神瞳":"5"
},
"all_resource_point_list": [
# 这个列表保存所有资源点的数据
# {
# "id": 2740,
# "label_id": 68,
# "x_pos": -1789,
# "y_pos": 2628,
# "author_name": "✟紫灵心✟",
# "ctime": "2020-10-29 10:41:21",
# "display_state": 1
# },
],
"date": "" # 记录上次更新"all_resource_point_list"的日期
}
async def download_icon(url):
# 下载图片返回Image对象
async with httpx.AsyncClient() as client:
resp = await client.get(url=url)
if resp.status_code != 200:
raise ValueError(f"获取图片数据失败,错误代码 {resp.status_code}")
icon = resp.content
return Image.open(BytesIO(icon))
async def download_json(url):
# 获取资源数据,返回 JSON
async with httpx.AsyncClient() as client:
resp = await client.get(url=url)
if resp.status_code != 200:
raise ValueError(f"获取资源点数据失败,错误代码 {resp.status_code}")
return resp.json()
async def up_icon_image(sublist):
# 检查是否有图标,没有图标下载保存到本地
id = sublist["id"]
icon_path = os.path.join(FILE_PATH, "icon", f"{id}.png")
if not os.path.exists(icon_path):
icon_url = sublist["icon"]
icon = await download_icon(icon_url)
icon = icon.resize((150, 150))
box_alpha = Image.open(os.path.join(FILE_PATH, "icon", "box_alpha.png")).getchannel("A")
box = Image.open(os.path.join(FILE_PATH, "icon", "box.png"))
try:
icon_alpha = icon.getchannel("A")
icon_alpha = ImageMath.eval("convert(a*b/256, 'L')", a=icon_alpha, b=box_alpha)
except ValueError:
# 米游社的图有时候会没有alpha导致报错这时候直接使用box_alpha当做alpha就行
icon_alpha = box_alpha
icon2 = Image.new("RGBA", (150, 150), "#00000000")
icon2.paste(icon, (0, -10))
bg = Image.new("RGBA", (150, 150), "#00000000")
bg.paste(icon2, mask=icon_alpha)
bg.paste(box, mask=box)
with open(icon_path, "wb") as icon_file:
bg.save(icon_file)
async def up_label_and_point_list():
# 更新label列表和资源点列表
label_data = await download_json(LABEL_URL)
for label in label_data["data"]["tree"]:
data["all_resource_type"][str(label["id"])] = label
for sublist in label["children"]:
data["all_resource_type"][str(sublist["id"])] = sublist
data["can_query_type_list"][sublist["name"]] = str(sublist["id"])
await up_icon_image(sublist)
label["children"] = []
test = await download_json(POINT_LIST_URL)
data["all_resource_point_list"] = test["data"]["point_list"]
data["date"] = time.strftime("%d")
async def up_map():
# 更新地图文件 并按照资源点的范围自动裁切掉不需要的地方
# 裁切地图需要最新的资源点位置,所以要先调用 up_label_and_point_list 再更新地图
global CENTER
global MAP_ICON
map_info = await download_json(MAP_URL)
map_info = map_info["data"]["info"]["detail"]
map_info = json.loads(map_info)
map_url_list = map_info['slices'][0]
origin = map_info["origin"]
x_start = map_info['total_size'][1]
y_start = map_info['total_size'][1]
x_end = 0
y_end = 0
for resource_point in data["all_resource_point_list"]:
x_pos = resource_point["x_pos"] + origin[0]
y_pos = resource_point["y_pos"] + origin[1]
x_start = min(x_start, x_pos)
y_start = min(y_start, y_pos)
x_end = max(x_end, x_pos)
y_end = max(y_end, y_pos)
x_start -= 200
y_start -= 200
x_end += 200
y_end += 200
CENTER = [origin[0] - x_start, origin[1] - y_start]
x = int(x_end - x_start)
y = int(y_end - y_start)
MAP_ICON = Image.new("RGB", (x, y))
x_offset = 0
for i in map_url_list:
map_url = i["url"]
map_icon = await download_icon(map_url)
MAP_ICON.paste(map_icon,
(int(-x_start) + x_offset, int(-y_start)))
x_offset += map_icon.size[0]
async def init_point_list_and_map():
await up_label_and_point_list()
await up_map()
class Resource_map(object):
def __init__(self, resource_name):
self.resource_id = str(data["can_query_type_list"][resource_name])
# self.map_image = Image.open(MAP_PATH)
self.map_image = MAP_ICON.copy()
self.map_size = self.map_image.size
# 地图要要裁切的左上角和右下角坐标
# 这里初始化为地图的大小
self.x_start = self.map_size[0]
self.y_start = self.map_size[1]
self.x_end = 0
self.y_end = 0
self.resource_icon = Image.open(self.get_icon_path())
self.resource_icon = self.resource_icon.resize((int(150 * zoom), int(150 * zoom)))
self.resource_xy_list = self.get_resource_point_list()
def get_icon_path(self):
# 检查有没有图标,有返回正确图标,没有返回默认图标
icon_path = os.path.join(FILE_PATH, "icon", f"{self.resource_id}.png")
if os.path.exists(icon_path):
return icon_path
else:
return os.path.join(FILE_PATH, "icon", "0.png")
def get_resource_point_list(self):
temp_list = []
for resource_point in data["all_resource_point_list"]:
if str(resource_point["label_id"]) == self.resource_id:
# 获取xy坐标然后加上中心点的坐标完成坐标转换
x = resource_point["x_pos"] + CENTER[0]
y = resource_point["y_pos"] + CENTER[1]
temp_list.append((int(x), int(y)))
return temp_list
def paste(self):
for x, y in self.resource_xy_list:
# 把资源图片贴到地图上
# 这时地图已经裁切过了,要以裁切后的地图左上角为中心再转换一次坐标
x -= self.x_start
y -= self.y_start
self.map_image.paste(self.resource_icon, (x + resource_icon_offset[0], y + resource_icon_offset[1]),
self.resource_icon)
def crop(self):
# 把大地图裁切到只保留资源图标位置
for x, y in self.resource_xy_list:
# 找出4个方向最远的坐标用于后边裁切
self.x_start = min(x, self.x_start)
self.y_start = min(y, self.y_start)
self.x_end = max(x, self.x_end)
self.y_end = max(y, self.y_end)
# 先把4个方向扩展150像素防止把资源图标裁掉
self.x_start -= 150
self.y_start -= 150
self.x_end += 150
self.y_end += 150
# 如果图片裁切的太小会看不出资源的位置在哪检查图片裁切的长和宽看够不够1000不到1000的按1000裁切
if (self.x_end - self.x_start) < 1000:
center = int((self.x_end + self.x_start) / 2)
self.x_start = center - 500
self.x_end = center + 500
if (self.y_end - self.y_start) < 1000:
center = int((self.y_end + self.y_start) / 2)
self.y_start = center - 500
self.y_end = center + 500
self.map_image = self.map_image.crop((self.x_start, self.y_start, self.x_end, self.y_end))
def gen_jpg(self):
if not self.resource_xy_list:
return "没有这个资源的信息"
self.crop()
self.paste()
self.map_image.save(f'temp{os.sep}map.jpg', format='JPEG')
def get_resource_count(self):
return len(self.resource_xy_list)
async def get_resource_map_mes(name):
if data["date"] != time.strftime("%d"):
await init_point_list_and_map()
if not (name in data["can_query_type_list"]):
return f"没有 {name} 这种资源。\n发送 资源列表 查看所有资源名称"
map = Resource_map(name)
count = map.get_resource_count()
if not count:
return f"没有找到 {name} 资源的位置可能米游社wiki还没更新。"
map.gen_jpg()
mes = f"资源 {name} 的位置如图所示"
mes += f"\n\n{name} 一共找到 {count} 个位置点\n※ 数据来源于米游社wiki"
return mes
def get_resource_list_mes():
temp = {}
for id in data["all_resource_type"].keys():
# 先找1级目录
if data["all_resource_type"][id]["depth"] == 1:
temp[id] = []
for id in data["all_resource_type"].keys():
# 再找2级目录
if data["all_resource_type"][id]["depth"] == 2:
temp[str(data["all_resource_type"][id]["parent_id"])].append(id)
mes = "当前资源列表如下:\n"
for resource_type_id in temp.keys():
if resource_type_id in ["1", "12", "50", "51", "95", "131"]:
# 在游戏里能查到的数据这里就不列举了,不然消息太长了
continue
mes += f"{data['all_resource_type'][resource_type_id]['name']}"
for resource_id in temp[resource_type_id]:
mes += f"{data['all_resource_type'][resource_id]['name']}"
mes += "\n"
return mes

13
init.py Normal file
View File

@ -0,0 +1,13 @@
from typing import Coroutine
import asyncio
from defs.query_resource_points import init_point_list_and_map
def sync(coroutine: Coroutine):
loop = asyncio.get_event_loop()
return loop.run_until_complete(coroutine)
print("初始化资源点列表和资源点映射...")
sync(init_point_list_and_map())
print("初始化完成")

44
plugins/artifact_rate.py Normal file
View File

@ -0,0 +1,44 @@
import requests
from pyrogram import Client
from pyrogram.types import Message
from defs.artifact_rate import *
from base64 import b64encode
from os import remove
def get_format_sub_item(artifact_attr):
msg = ""
for i in artifact_attr["sub_item"]:
msg += f'{i["name"]:\u3000<6} | {i["value"]}\n'
return msg
async def artifact_rate_msg(client: Client, message: Message):
if not message.photo:
return await message.reply("图呢?\n*请命令将与截图一起发送", quote=True)
msg = await message.reply("正在下载图片。。。", quote=True)
path = await message.download()
with open(path, "rb") as f:
image_b64 = b64encode(f.read()).decode()
remove(path)
try:
artifact_attr = await get_artifact_attr(image_b64)
except (requests.exceptions.ConnectionError, requests.exceptions.ReadTimeout):
return await msg.edit("连接超时")
if 'err' in artifact_attr.keys():
err_msg = artifact_attr["full"]["message"]
return await msg.edit(f"发生了点小错误:\n{err_msg}")
await msg.edit("识图成功!\n正在评分中...")
rate_result = await rate_artifact(artifact_attr)
if 'err' in rate_result.keys():
err_msg = rate_result["full"]["message"]
return await msg.edit(f"发生了点小错误:\n{err_msg}")
format_result = f'圣遗物评分结果:\n' \
f'主属性:{artifact_attr["main_item"]["name"]}\n' \
f'{get_format_sub_item(artifact_attr)}'\
f'`------------------------------`\n' \
f'总分:{rate_result["total_percent"]}\n'\
f'主词条:{rate_result["main_percent"]}\n' \
f'副词条:{rate_result["sub_percent"]}\n' \
f'评分、识图均来自 genshin.pub'
await msg.edit(format_result)

View File

@ -7,6 +7,8 @@ from plugins.challenge import tf_msg, wq_msg, zb_msg
from plugins.character import character_msg, mz_msg
from plugins.weapons import weapon_msg
from plugins.fortunate import fortunate_msg, set_fortunate_img
from plugins.artifact_rate import artifact_rate_msg
from plugins.query_resource_points import inquire_resource_points, inquire_resource_list
from plugins.mys import mys_msg, promote_command
from defs.log import log
@ -35,12 +37,12 @@ async def process_private_msg(client: Client, message: Message):
# 天赋
if '天赋' in message.text:
await tf_msg(client, message)
await log(client, message, '查询角色天赋')
# 武器查询
if '武器资料' in message.text or '武器查询' in message.text:
await weapon_msg(client, message)
await log(client, message, '查询武器资料')
return
await log(client, message, '查询天赋副本')
# # 武器查询
# if '武器资料' in message.text or '武器查询' in message.text:
# await weapon_msg(client, message)
# await log(client, message, '查询武器资料')
# return
# 副本武器
if '武器' in message.text:
await wq_msg(client, message)
@ -49,14 +51,14 @@ async def process_private_msg(client: Client, message: Message):
if message.text == '周本':
await zb_msg(client, message)
await log(client, message, '查询周本')
# 角色查询
if '角色资料' in message.text or '角色简介' in message.text or '角色查询' in message.text:
await character_msg(client, message)
await log(client, message, '查询角色资料')
# 命座查询
if '命座' in message.text:
await mz_msg(client, message)
await log(client, message, '查询角色命座')
# # 角色查询
# if '角色资料' in message.text or '角色简介' in message.text or '角色查询' in message.text:
# await character_msg(client, message)
# await log(client, message, '查询角色资料')
# # 命座查询
# if '命座' in message.text:
# await mz_msg(client, message)
# await log(client, message, '查询角色命座')
# 设置运势
if '设置运势' in message.text:
await set_fortunate_img(client, message)
@ -66,6 +68,17 @@ async def process_private_msg(client: Client, message: Message):
if '运势' in message.text:
await fortunate_msg(client, message)
await log(client, message, '查询今日运势')
# 圣遗物评分
if '圣遗物评分' in message.text:
await message.reply("图呢?\n*请将命令与截图一起发送", quote=True)
# 资源查询
if '哪里有' in message.text:
await inquire_resource_points(client, message)
await log(client, message, '查询地图资源')
# 资源列表
if '资源列表' in message.text:
await inquire_resource_list(client, message)
await log(client, message, '查询资源列表')
# 账号信息cookie 过期过快 不推荐启用)
# if '账号信息' in message.text or '用户信息' in message.text:
# await mys_msg(client, message)
@ -78,11 +91,11 @@ async def process_group_msg(client: Client, message: Message):
# 帮助消息
if msg_list[0] == '/help':
await help_command(client, message)
# 武器查询
if text.startswith('武器查询') or text.startswith('武器资料'):
await weapon_msg(client, message)
await log(client, message, '查询武器资料')
return
# # 武器查询
# if text.startswith('武器查询') or text.startswith('武器资料'):
# await weapon_msg(client, message)
# await log(client, message, '查询武器资料')
# return
# 副本武器
if text[-2:] == '武器':
await wq_msg(client, message)
@ -94,19 +107,19 @@ async def process_group_msg(client: Client, message: Message):
# 天赋
if text[-2:] == '天赋':
await tf_msg(client, message)
await log(client, message, '查询角色天赋')
await log(client, message, '查询天赋副本')
# 周本
if message.text == '周本':
await zb_msg(client, message)
await log(client, message, '查询周本')
# 角色查询
if text.startswith('角色资料') or text.startswith('角色简介') or text.startswith('角色查询'):
await character_msg(client, message)
await log(client, message, '查询角色资料')
# 命座查询
if text.startswith('命座'):
await mz_msg(client, message)
await log(client, message, '查询角色命座')
# # 角色查询
# if text.startswith('角色资料') or text.startswith('角色简介') or text.startswith('角色查询'):
# await character_msg(client, message)
# await log(client, message, '查询角色资料')
# # 命座查询
# if text.startswith('命座'):
# await mz_msg(client, message)
# await log(client, message, '查询角色命座')
# 运势查询
if text.startswith('运势') or text.startswith('今日运势'):
await fortunate_msg(client, message)
@ -115,6 +128,26 @@ async def process_group_msg(client: Client, message: Message):
if text.startswith('设置运势'):
await set_fortunate_img(client, message)
await log(client, message, '设置运势角色')
# 圣遗物评分
if text.startswith('圣遗物评分'):
await message.reply("图呢?\n*请将命令与截图一起发送")
# 资源查询
if text.startswith('哪里有') or text.endswith('哪里有'):
await inquire_resource_points(client, message)
await log(client, message, '查询地图资源')
# 资源列表
if text.startswith('资源列表'):
await inquire_resource_list(client, message)
await log(client, message, '查询资源列表')
@Client.on_message(Filters.photo)
async def process_photo(client: Client, message: Message):
text = message.caption if message.caption else ""
if text.startswith('圣遗物评分'):
await artifact_rate_msg(client, message)
await log(client, message, '圣遗物评分')
@Client.on_message(Filters.new_chat_members)

View File

@ -0,0 +1,29 @@
from pyrogram import Client
from pyrogram.types import Message
from os import sep
from defs.query_resource_points import get_resource_map_mes, get_resource_list_mes, init_point_list_and_map
init_resource_map = False
async def inquire_resource_points(client: Client, message: Message):
global init_resource_map
if not init_resource_map:
await init_point_list_and_map()
init_resource_map = True
resource_name = message.text.replace("哪里有", "").strip()
if resource_name == "":
return await message.reply("没有想好要找的资源吗?试试 `资源列表`", quote=True)
text = await get_resource_map_mes(resource_name)
if text.find("没有") != -1:
return await message.reply(text, quote=True)
await message.reply_photo(f"temp{sep}map.jpg", caption=text, quote=True)
async def inquire_resource_list(client: Client, message: Message):
global init_resource_map
if not init_resource_map:
await init_point_list_and_map()
init_resource_map = True
text = get_resource_list_mes()
await message.reply(text, quote=True)

View File

@ -7,12 +7,12 @@ from pyrogram.types import Message
async def welcome_command(client: Client, message: Message):
# 发送欢迎消息
await message.reply('你好!我是原神小助手 - 派蒙 。')
await message.reply('你好!我是原神小助手 - 派蒙 。', quote=True)
async def ping_command(client: Client, message: Message):
# 提醒在线状态
await message.reply("poi~")
await message.reply("poi~", quote=True)
async def leave_command(client: Client, message: Message):
@ -39,19 +39,23 @@ async def leave_command(client: Client, message: Message):
async def help_command(client: Client, message: Message):
text = '<a href="https://git.io/JcbTD">PaimonBot</a> 0.1.1beta By Xtao-Labs\n\n' \
text = '<a href="https://git.io/JcbTD">PaimonBot</a> 0.2.0beta By Xtao-Labs\n\n' \
'🔅 以下是小派蒙我学会了的功能(部分):\n' \
'1⃣ [武器/今日武器] 查看今日武器材料和武器\n' \
'2⃣ [天赋/今日天赋] 查看今日天赋材料和角色\n' \
'3⃣ [周本] 查看周本材料和人物\n' \
'4⃣ [武器查询 武器名] 查看武器资料\n' \
' 💠 <code>武器查询 沐浴龙血的剑</code>\n' \
'5⃣ [角色查询 名字] 查看人物简介\n' \
' 💠 <code>角色查询 重云</code>\n' \
'6⃣ [命座 名字] 查看人物命座\n' \
' 💠 <code>命座 重云一命</code>\n' \
'7⃣ [运势 (名字)] 查看今日运势\n' \
'① [武器/今日武器] 查看今日武器材料和武器\n' \
'② [天赋/今日天赋] 查看今日天赋材料和角色\n' \
'③ [周本] 查看周本材料和人物\n' \
'④ [运势 (名字)] 查看今日运势\n' \
' 💠 <code>运势 (重云)</code>\n' \
' 💠 <code>设置运势 (重云)</code>\n' \
'8⃣ [原神黄历] 查看随机生成的原神黄历'
'⑤ [原神黄历] 查看随机生成的原神黄历\n' \
'⑥ [圣遗物评分] 我也想拥有这种分数的圣遗物(切实)\n' \
'⑦ [哪里有 (资源名)] 查看资源的位置\n' \
'⑧ [资源列表] 查看原神所有资源'
await message.reply(text, quote=True, disable_web_page_preview=True)
# '④ [武器查询 武器名] 查看武器资料\n' \
# ' 💠 <code>武器查询 沐浴龙血的剑</code>\n' \
# '⑤ [角色查询 名字] 查看人物简介\n' \
# ' 💠 <code>角色查询 重云</code>\n' \
# '⑥ [命座 名字] 查看人物命座\n' \
# ' 💠 <code>命座 重云一命</code>\n' \

View File

@ -1,10 +1,10 @@
Pyrogram>=1.2.9
Tgcrypto>=1.2.2
Pyrogram>=1.3.5
Tgcrypto>=1.2.3
Pillow>=8.1.0
Redis>=3.5.3
bs4>=0.0.1
beautifulsoup4>=4.9.3
requests>=2.25.1
requests>=2.27.1
xpinyin>=0.7.6
lxml>=4.6.3
httpx>=0.18.2
httpx>=0.21.3