推荐三个深渊队伍

Co-authored-by: Li Chuangbo <im@chuangbo.li>
Co-authored-by: SiHuaN <sihuan@sakuya.love>
This commit is contained in:
xtaodada 2022-10-10 18:43:18 +08:00
parent a03e226448
commit c44b785118
Signed by: xtaodada
GPG Key ID: 4CBB3F4FA8C85659
5 changed files with 163 additions and 130 deletions

View File

@ -1,4 +1,3 @@
import secrets
import time import time
from typing import List, Optional, Any from typing import List, Optional, Any
@ -11,15 +10,19 @@ class Member(BaseModel):
attr: str attr: str
name: str name: str
@validator("name")
def name_validator(cls, v): # pylint: disable=R0201
return "" if v == "旅行者" else v
class TeamRate(BaseModel): class TeamRate(BaseModel):
rate: float rate: float
formation: List[Member] formation: List[Member]
owner_num: Optional[int] owner_num: Optional[int]
@validator('rate', pre=True) @validator("rate", pre=True)
def str2float(cls, v): # pylint: disable=R0201 def str2float(cls, v): # pylint: disable=R0201
return float(v.replace('%', '')) / 100.0 if isinstance(v, str) else v return float(v.replace("%", "")) / 100.0 if isinstance(v, str) else v
class FullTeamRate(BaseModel): class FullTeamRate(BaseModel):
@ -30,10 +33,11 @@ class FullTeamRate(BaseModel):
@property @property
def rate(self) -> float: def rate(self) -> float:
return self.up.rate + self.down.rate return (self.up.rate + self.down.rate) / 2
class TeamRateResult(BaseModel): class TeamRateResult(BaseModel):
version: str
rate_list_up: List[TeamRate] rate_list_up: List[TeamRate]
rate_list_down: List[TeamRate] rate_list_down: List[TeamRate]
rate_list_full: List[FullTeamRate] = [] rate_list_full: List[FullTeamRate] = []
@ -50,28 +54,35 @@ class TeamRateResult(BaseModel):
def sort(self, characters: List[str]): def sort(self, characters: List[str]):
for team in self.rate_list_full: for team in self.rate_list_full:
team.owner_num = sum(member.name in characters for member in team.up.formation + team.down.formation) team.owner_num = sum(member.name in characters for member in team.up.formation + team.down.formation)
team.nice = team.owner_num / 8 * team.rate team.nice = team.owner_num / 8 + team.rate
self.rate_list_full.sort(key=lambda x: x.nice, reverse=True) self.rate_list_full.sort(key=lambda x: x.nice, reverse=True)
def random_team(self, characters: List[str]) -> FullTeamRate: def random_team(self) -> List[FullTeamRate]:
self.sort(characters) data: List[FullTeamRate] = []
max_nice = self.rate_list_full[0].nice
nice_teams: List[FullTeamRate] = []
for team in self.rate_list_full: for team in self.rate_list_full:
if team.nice < max_nice: add = True
break for team_ in data:
nice_teams.append(team) if {member.name for member in team.up.formation} & {member.name for member in team_.up.formation}:
return secrets.choice(nice_teams) add = False
break
if {member.name for member in team.down.formation} & {member.name for member in team_.down.formation}:
add = False
break
if add:
data.append(team)
if len(data) >= 3:
break
return data
class AbyssTeamData: class AbyssTeamData:
TEAM_RATE_API = "https://www.youchuang.fun/gamerole/formationRate" TEAM_RATE_API = "https://www.youchuang.fun/gamerole/formationRate"
HEADERS = { HEADERS = {
'Host': 'www.youchuang.fun', "Host": "www.youchuang.fun",
'Referer': 'https://servicewechat.com/wxce4dbe0cb0f764b3/91/page-frame.html', "Referer": "https://servicewechat.com/wxce4dbe0cb0f764b3/91/page-frame.html",
'User-Agent': 'Mozilla/5.0 (iPad; CPU OS 15_3 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) ' "User-Agent": "Mozilla/5.0 (iPad; CPU OS 15_3 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) "
'Mobile/15E148 MicroMessenger/8.0.20(0x1800142f) NetType/WIFI Language/zh_CN', "Mobile/15E148 MicroMessenger/8.0.20(0x1800142f) NetType/WIFI Language/zh_CN",
'content-type': 'application/json' "content-type": "application/json",
} }
VERSION = "3.1" VERSION = "3.1"
@ -87,9 +98,12 @@ class AbyssTeamData:
data_up_json = data_up.json()["result"] data_up_json = data_up.json()["result"]
data_down = await self.client.post(self.TEAM_RATE_API, json={"version": self.VERSION, "layer": 2}) data_down = await self.client.post(self.TEAM_RATE_API, json={"version": self.VERSION, "layer": 2})
data_down_json = data_down.json()["result"] data_down_json = data_down.json()["result"]
self.data = TeamRateResult(rate_list_up=parse_obj_as(List[TeamRate], data_up_json["rateList"]), self.data = TeamRateResult(
rate_list_down=parse_obj_as(List[TeamRate], data_down_json["rateList"]), version=self.VERSION,
user_count=data_up_json["userCount"]) rate_list_up=parse_obj_as(List[TeamRate], data_up_json["rateList"]),
rate_list_down=parse_obj_as(List[TeamRate], data_down_json["rateList"]),
user_count=data_up_json["userCount"],
)
self.time = time.time() self.time = time.time()
return self.data.copy(deep=True) return self.data.copy(deep=True)

View File

@ -1,4 +1,4 @@
from telegram import Update, User from telegram import Update
from telegram.constants import ChatAction from telegram.constants import ChatAction
from telegram.ext import CallbackContext, CommandHandler, MessageHandler, filters from telegram.ext import CallbackContext, CommandHandler, MessageHandler, filters
@ -20,69 +20,67 @@ from utils.log import logger
class AbyssTeam(Plugin, BasePlugin): class AbyssTeam(Plugin, BasePlugin):
"""深境螺旋推荐配队查询""" """深境螺旋推荐配队查询"""
def __init__(self, user_service: UserService = None, template_service: TemplateService = None, def __init__(
assets: AssetsService = None): self, user_service: UserService = None, template_service: TemplateService = None, assets: AssetsService = None
):
self.template_service = template_service self.template_service = template_service
self.user_service = user_service self.user_service = user_service
self.assets_service = assets self.assets_service = assets
self.team_data = AbyssTeamData() self.team_data = AbyssTeamData()
@staticmethod
def _get_role_star_bg(value: int):
if value == 4:
return "./../abyss/background/roleStarBg4.png"
elif value == 5:
return "./../abyss/background/roleStarBg5.png"
else:
raise ValueError("错误的数据")
@staticmethod
async def _get_data_from_user(user: User):
try:
logger.debug("尝试获取已绑定的原神账号")
client = await get_genshin_client(user.id)
logger.debug(f"获取成功, UID: {client.uid}")
characters = await client.get_genshin_characters(client.uid)
return [character.name for character in characters]
except (UserNotFoundError, CookiesNotFoundError):
logger.info(f"未查询到用户({user.full_name} {user.id}) 所绑定的账号信息")
return []
@handler(CommandHandler, command="abyss_team", block=False) @handler(CommandHandler, command="abyss_team", block=False)
@handler(MessageHandler, filters=filters.Regex("^深渊推荐配队(.*)"), block=False) @handler(MessageHandler, filters=filters.Regex("^深渊推荐配队(.*)"), block=False)
@restricts() @restricts()
@error_callable @error_callable
async def command_start(self, update: Update, _: CallbackContext) -> None: async def command_start(self, update: Update, context: CallbackContext) -> None:
user = update.effective_user user = update.effective_user
message = update.effective_message message = update.effective_message
logger.info(f"用户 {user.full_name}[{user.id}] 查深渊推荐配队命令请求") logger.info(f"用户 {user.full_name}[{user.id}] 查深渊推荐配队命令请求")
try:
client = await get_genshin_client(user.id)
except (CookiesNotFoundError, UserNotFoundError):
reply_message = await message.reply_text("未查询到账号信息,请先私聊派蒙绑定账号")
if filters.ChatType.GROUPS.filter(message):
self._add_delete_message_job(context, reply_message.chat_id, reply_message.message_id, 10)
self._add_delete_message_job(context, message.chat_id, message.message_id, 10)
return
await message.reply_chat_action(ChatAction.TYPING) await message.reply_chat_action(ChatAction.TYPING)
team_data = await self.team_data.get_data() team_data = await self.team_data.get_data()
# 尝试获取用户已绑定的原神账号信息 # 尝试获取用户已绑定的原神账号信息
user_data = await self._get_data_from_user(user) characters = await client.get_genshin_characters(client.uid)
random_team = team_data.random_team(user_data) user_data = [character.name for character in characters]
abyss_team_data = { team_data.sort(user_data)
"up": [], random_team = team_data.random_team()
"down": [] abyss_teams_data = {"uid": client.uid, "version": team_data.version, "teams": []}
} for i in random_team:
for i in random_team.up.formation: team = {
temp = { "up": [],
"icon": (await self.assets_service.avatar(roleToId(i.name)).icon()).as_uri(), "up_rate": f"{i.up.rate * 100: .2f}%",
"name": i.name, "down": [],
"background": self._get_role_star_bg(i.star), "down_rate": f"{i.down.rate * 100: .2f}%",
"hava": (i.name in user_data) if user_data else True,
} }
abyss_team_data["up"].append(temp)
for i in random_team.down.formation: for lane in ["up", "down"]:
temp = { for member in getattr(i, lane).formation:
"icon": (await self.assets_service.avatar(roleToId(i.name)).icon()).as_uri(), temp = {
"name": i.name, "icon": (await self.assets_service.avatar(roleToId(member.name)).icon()).as_uri(),
"background": self._get_role_star_bg(i.star), "name": member.name.replace("", "旅行者"),
"hava": (i.name in user_data) if user_data else True, "star": member.star,
} "hava": (member.name in user_data) if user_data else True,
abyss_team_data["down"].append(temp) }
team[lane].append(temp)
abyss_teams_data["teams"].append(team)
await message.reply_chat_action(ChatAction.UPLOAD_PHOTO) await message.reply_chat_action(ChatAction.UPLOAD_PHOTO)
png_data = await self.template_service.render('genshin/abyss_team', "abyss_team.html", abyss_team_data, png_data = await self.template_service.render(
{"width": 865, "height": 504}, full_page=False) "genshin/abyss_team",
await message.reply_photo(png_data, filename=f"abyss_team_{user.id}.png", "abyss_team.html",
allow_sending_without_reply=True) abyss_teams_data,
{"width": 785, "height": 800},
full_page=True,
query_selector=".bg-contain",
)
await message.reply_photo(png_data, filename=f"abyss_team_{user.id}.png", allow_sending_without_reply=True)

Binary file not shown.

After

Width:  |  Height:  |  Size: 185 KiB

View File

@ -1,69 +1,90 @@
<!DOCTYPE html> <!DOCTYPE html>
<html lang="zh-ch"> <html lang="zh-ch">
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8" />
<title>abyss</title> <title>abyss</title>
<link type="text/css" href="../../styles/tailwind.min.css" rel="stylesheet"> <meta
<link type="text/css" href="../../styles/public.css" rel="stylesheet"> name="viewport"
content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no, viewport-fit=cover"
/>
<script src="../../js/tailwindcss-3.1.8.js"></script>
<link type="text/css" href="../../styles/public.css" rel="stylesheet" />
<style> <style>
#container { #container {
width: 865px; max-width: 865px;
height: 504px; background-image: url("./../abyss/background/abyss-bg-grad.png");
background: url("./../abyss/background/lookback-bg.png"); background-color: rgb(11, 23, 44);
background-size: cover; }
}
.character-icon { .item-not-owned {
width: 128px; position: relative;
height: 128px; }
}
.character-side-icon { .item-not-owned::after {
width: 32px; content: "";
height: 32px; position: absolute;
} top: 0;
left: 0;
.item-not-owned:before { right: 0;
content: ""; bottom: 0;
position: absolute; background-color: rgb(0 0 0 / 50%);
width: 128px; border-radius: 5px;
height: 152px; }
background-color: rgb(0 0 0 / 50%);
z-index: 2;
border-radius: 5px;
}
</style> </style>
</head> </head>
<body> <body>
<div class="mx-auto flex flex-col h-full bg-no-repeat bg-cover" id="container"> <div
<div class="title text-2xl my-4 text-yellow-500 mx-auto">深境螺旋 - 推荐配队</div> class="mx-auto flex flex-col h-full bg-contain bg-no-repeat py-6"
<div class="base-info flex flex-col px-20 py-1 text-black my-1"> id="container"
<div class="text-center mr-auto text-yellow-500">第 12 层上半</div> >
<div class="mx-auto flex my-2"> <div class="title text-2xl my-4 text-yellow-500 text-center">
{% for i in up %} 深境螺旋 - 推荐配队
<div class="rounded-lg mx-2 overflow-hidden {% if not i.hava %}item-not-owned{% endif %}" style="background-color: rgb(233, 229, 220)"> </div>
<div class="character-icon rounded-br-3xl bg-cover overflow-hidden"
style="background: url({{i.background}});background-size: cover;"> <div
<img src="{{i.icon}}" alt=""></div> class="flex flex-row px-6 py-1 my-1 text-neutral-100 bg-white bg-opacity-10 justify-evenly text-sm"
<div class="text-center">{{i.name}}</div> >
</div> <div>UID {{ uid }}</div>
{% endfor %} <div>版本 {{ version }}</div>
</div>
{% for team in teams %}
<div class="mx-auto flex flex-col px-6 py-1 text-black my-1">
<div class="text-center mr-auto text-yellow-500">
推荐配队 {{ loop.index }}
</div> </div>
</div> {% for lane in ["up", "down"] %}
<div class="base-info flex flex-col px-20 py-1 text-black my-1"> <div class="flex my-2 space-x-4">
<div class="text-center mr-auto text-yellow-500">第 12 层下半</div> {% for i in team[lane] %}
<div class="mx-auto flex my-2"> <div
{% for i in down %} class="bg-neutral-200 flex-shrink-0 rounded-lg overflow-hidden {% if not i.hava %}item-not-owned{% endif %}"
<div class="rounded-lg mx-2 overflow-hidden {% if not i.hava %}item-not-owned{% endif %}" style="background-color: rgb(233, 229, 220)"> >
<div class="character-icon rounded-br-3xl bg-cover overflow-hidden" <div
style="background: url({{i.background}});background-size: cover;"> class="w-32 h-32 rounded-br-2xl bg-cover overflow-hidden"
<img src="{{i.icon}}" alt=""></div> style="background-image: url('./../abyss/background/roleStarBg{{ i.star }}.png');"
<div class="text-center">{{i.name}}</div> >
<img src="{{ i.icon }}" alt="" />
</div> </div>
{% endfor %} <div class="text-center">{{ i.name }}</div>
</div>
{% endfor %}
<div>
<div class="text-neutral-300">
{{ lane == 'up' and '上' or '下' }}半
</div>
<div class="text-neutral-400 text-sm">
使用率 {{ team[lane + "_rate"] }}
</div>
</div>
</div> </div>
{% endfor %}
</div>
{% endfor %}
<div class="mt-6 text-center p-1 text-neutral-400 text-xs">
数据来源:游创工坊
</div>
</div> </div>
<div class="my-2"></div> </body>
</div>
</body>
</html> </html>

View File

@ -30,7 +30,7 @@ async def test_abyss_team_data(abyss_team_data: AbyssTeamData):
team_data.sort(["迪奥娜", "芭芭拉", "凯亚", ""]) team_data.sort(["迪奥娜", "芭芭拉", "凯亚", ""])
assert isinstance(team_data.rate_list_full[0], FullTeamRate) assert isinstance(team_data.rate_list_full[0], FullTeamRate)
assert isinstance(team_data.rate_list_full[-1], FullTeamRate) assert isinstance(team_data.rate_list_full[-1], FullTeamRate)
random_team = team_data.random_team(["迪奥娜", "芭芭拉", "凯亚", ""]) random_team = team_data.random_team()[0]
assert isinstance(random_team, FullTeamRate) assert isinstance(random_team, FullTeamRate)
member_up = {i.name for i in random_team.up.formation} member_up = {i.name for i in random_team.up.formation}
member_down = {i.name for i in random_team.down.formation} member_down = {i.name for i in random_team.down.formation}