mirror of
https://github.com/PaiGramTeam/PaiGram.git
synced 2024-11-16 04:35:49 +00:00
✨ 推荐三个深渊队伍
Co-authored-by: Li Chuangbo <im@chuangbo.li> Co-authored-by: SiHuaN <sihuan@sakuya.love>
This commit is contained in:
parent
a03e226448
commit
c44b785118
@ -1,4 +1,3 @@
|
||||
import secrets
|
||||
import time
|
||||
from typing import List, Optional, Any
|
||||
|
||||
@ -11,15 +10,19 @@ class Member(BaseModel):
|
||||
attr: str
|
||||
name: str
|
||||
|
||||
@validator("name")
|
||||
def name_validator(cls, v): # pylint: disable=R0201
|
||||
return "空" if v == "旅行者" else v
|
||||
|
||||
|
||||
class TeamRate(BaseModel):
|
||||
rate: float
|
||||
formation: List[Member]
|
||||
owner_num: Optional[int]
|
||||
|
||||
@validator('rate', pre=True)
|
||||
@validator("rate", pre=True)
|
||||
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):
|
||||
@ -30,10 +33,11 @@ class FullTeamRate(BaseModel):
|
||||
|
||||
@property
|
||||
def rate(self) -> float:
|
||||
return self.up.rate + self.down.rate
|
||||
return (self.up.rate + self.down.rate) / 2
|
||||
|
||||
|
||||
class TeamRateResult(BaseModel):
|
||||
version: str
|
||||
rate_list_up: List[TeamRate]
|
||||
rate_list_down: List[TeamRate]
|
||||
rate_list_full: List[FullTeamRate] = []
|
||||
@ -50,28 +54,35 @@ class TeamRateResult(BaseModel):
|
||||
def sort(self, characters: List[str]):
|
||||
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.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)
|
||||
|
||||
def random_team(self, characters: List[str]) -> FullTeamRate:
|
||||
self.sort(characters)
|
||||
max_nice = self.rate_list_full[0].nice
|
||||
nice_teams: List[FullTeamRate] = []
|
||||
def random_team(self) -> List[FullTeamRate]:
|
||||
data: List[FullTeamRate] = []
|
||||
for team in self.rate_list_full:
|
||||
if team.nice < max_nice:
|
||||
break
|
||||
nice_teams.append(team)
|
||||
return secrets.choice(nice_teams)
|
||||
add = True
|
||||
for team_ in data:
|
||||
if {member.name for member in team.up.formation} & {member.name for member in team_.up.formation}:
|
||||
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:
|
||||
TEAM_RATE_API = "https://www.youchuang.fun/gamerole/formationRate"
|
||||
HEADERS = {
|
||||
'Host': 'www.youchuang.fun',
|
||||
'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) '
|
||||
'Mobile/15E148 MicroMessenger/8.0.20(0x1800142f) NetType/WIFI Language/zh_CN',
|
||||
'content-type': 'application/json'
|
||||
"Host": "www.youchuang.fun",
|
||||
"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) "
|
||||
"Mobile/15E148 MicroMessenger/8.0.20(0x1800142f) NetType/WIFI Language/zh_CN",
|
||||
"content-type": "application/json",
|
||||
}
|
||||
VERSION = "3.1"
|
||||
|
||||
@ -87,9 +98,12 @@ class AbyssTeamData:
|
||||
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_json = data_down.json()["result"]
|
||||
self.data = TeamRateResult(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.data = TeamRateResult(
|
||||
version=self.VERSION,
|
||||
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()
|
||||
return self.data.copy(deep=True)
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
from telegram import Update, User
|
||||
from telegram import Update
|
||||
from telegram.constants import ChatAction
|
||||
from telegram.ext import CallbackContext, CommandHandler, MessageHandler, filters
|
||||
|
||||
@ -20,69 +20,67 @@ from utils.log import logger
|
||||
class AbyssTeam(Plugin, BasePlugin):
|
||||
"""深境螺旋推荐配队查询"""
|
||||
|
||||
def __init__(self, user_service: UserService = None, template_service: TemplateService = None,
|
||||
assets: AssetsService = None):
|
||||
def __init__(
|
||||
self, user_service: UserService = None, template_service: TemplateService = None, assets: AssetsService = None
|
||||
):
|
||||
self.template_service = template_service
|
||||
self.user_service = user_service
|
||||
self.assets_service = assets
|
||||
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(MessageHandler, filters=filters.Regex("^深渊推荐配队(.*)"), block=False)
|
||||
@restricts()
|
||||
@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
|
||||
message = update.effective_message
|
||||
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)
|
||||
team_data = await self.team_data.get_data()
|
||||
# 尝试获取用户已绑定的原神账号信息
|
||||
user_data = await self._get_data_from_user(user)
|
||||
random_team = team_data.random_team(user_data)
|
||||
abyss_team_data = {
|
||||
"up": [],
|
||||
"down": []
|
||||
}
|
||||
for i in random_team.up.formation:
|
||||
temp = {
|
||||
"icon": (await self.assets_service.avatar(roleToId(i.name)).icon()).as_uri(),
|
||||
"name": i.name,
|
||||
"background": self._get_role_star_bg(i.star),
|
||||
"hava": (i.name in user_data) if user_data else True,
|
||||
characters = await client.get_genshin_characters(client.uid)
|
||||
user_data = [character.name for character in characters]
|
||||
team_data.sort(user_data)
|
||||
random_team = team_data.random_team()
|
||||
abyss_teams_data = {"uid": client.uid, "version": team_data.version, "teams": []}
|
||||
for i in random_team:
|
||||
team = {
|
||||
"up": [],
|
||||
"up_rate": f"{i.up.rate * 100: .2f}%",
|
||||
"down": [],
|
||||
"down_rate": f"{i.down.rate * 100: .2f}%",
|
||||
}
|
||||
abyss_team_data["up"].append(temp)
|
||||
for i in random_team.down.formation:
|
||||
temp = {
|
||||
"icon": (await self.assets_service.avatar(roleToId(i.name)).icon()).as_uri(),
|
||||
"name": i.name,
|
||||
"background": self._get_role_star_bg(i.star),
|
||||
"hava": (i.name in user_data) if user_data else True,
|
||||
}
|
||||
abyss_team_data["down"].append(temp)
|
||||
|
||||
for lane in ["up", "down"]:
|
||||
for member in getattr(i, lane).formation:
|
||||
temp = {
|
||||
"icon": (await self.assets_service.avatar(roleToId(member.name)).icon()).as_uri(),
|
||||
"name": member.name.replace("空", "旅行者"),
|
||||
"star": member.star,
|
||||
"hava": (member.name in user_data) if user_data else True,
|
||||
}
|
||||
team[lane].append(temp)
|
||||
|
||||
abyss_teams_data["teams"].append(team)
|
||||
|
||||
await message.reply_chat_action(ChatAction.UPLOAD_PHOTO)
|
||||
png_data = await self.template_service.render('genshin/abyss_team', "abyss_team.html", abyss_team_data,
|
||||
{"width": 865, "height": 504}, full_page=False)
|
||||
await message.reply_photo(png_data, filename=f"abyss_team_{user.id}.png",
|
||||
allow_sending_without_reply=True)
|
||||
png_data = await self.template_service.render(
|
||||
"genshin/abyss_team",
|
||||
"abyss_team.html",
|
||||
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)
|
||||
|
BIN
resources/genshin/abyss/background/abyss-bg-grad.png
Normal file
BIN
resources/genshin/abyss/background/abyss-bg-grad.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 185 KiB |
@ -1,69 +1,90 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-ch">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<title>abyss</title>
|
||||
<link type="text/css" href="../../styles/tailwind.min.css" rel="stylesheet">
|
||||
<link type="text/css" href="../../styles/public.css" rel="stylesheet">
|
||||
<meta
|
||||
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>
|
||||
#container {
|
||||
width: 865px;
|
||||
height: 504px;
|
||||
background: url("./../abyss/background/lookback-bg.png");
|
||||
background-size: cover;
|
||||
}
|
||||
#container {
|
||||
max-width: 865px;
|
||||
background-image: url("./../abyss/background/abyss-bg-grad.png");
|
||||
background-color: rgb(11, 23, 44);
|
||||
}
|
||||
|
||||
.character-icon {
|
||||
width: 128px;
|
||||
height: 128px;
|
||||
}
|
||||
.item-not-owned {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.character-side-icon {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
}
|
||||
|
||||
.item-not-owned:before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
width: 128px;
|
||||
height: 152px;
|
||||
background-color: rgb(0 0 0 / 50%);
|
||||
z-index: 2;
|
||||
border-radius: 5px;
|
||||
}
|
||||
.item-not-owned::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background-color: rgb(0 0 0 / 50%);
|
||||
border-radius: 5px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="mx-auto flex flex-col h-full bg-no-repeat bg-cover" id="container">
|
||||
<div class="title text-2xl my-4 text-yellow-500 mx-auto">深境螺旋 - 推荐配队</div>
|
||||
<div class="base-info flex flex-col px-20 py-1 text-black my-1">
|
||||
<div class="text-center mr-auto text-yellow-500">第 12 层上半</div>
|
||||
<div class="mx-auto flex my-2">
|
||||
{% 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 class="character-icon rounded-br-3xl bg-cover overflow-hidden"
|
||||
style="background: url({{i.background}});background-size: cover;">
|
||||
<img src="{{i.icon}}" alt=""></div>
|
||||
<div class="text-center">{{i.name}}</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</head>
|
||||
<body>
|
||||
<div
|
||||
class="mx-auto flex flex-col h-full bg-contain bg-no-repeat py-6"
|
||||
id="container"
|
||||
>
|
||||
<div class="title text-2xl my-4 text-yellow-500 text-center">
|
||||
深境螺旋 - 推荐配队
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="flex flex-row px-6 py-1 my-1 text-neutral-100 bg-white bg-opacity-10 justify-evenly text-sm"
|
||||
>
|
||||
<div>UID {{ uid }}</div>
|
||||
<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 class="base-info flex flex-col px-20 py-1 text-black my-1">
|
||||
<div class="text-center mr-auto text-yellow-500">第 12 层下半</div>
|
||||
<div class="mx-auto flex my-2">
|
||||
{% for i in down %}
|
||||
<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"
|
||||
style="background: url({{i.background}});background-size: cover;">
|
||||
<img src="{{i.icon}}" alt=""></div>
|
||||
<div class="text-center">{{i.name}}</div>
|
||||
{% for lane in ["up", "down"] %}
|
||||
<div class="flex my-2 space-x-4">
|
||||
{% for i in team[lane] %}
|
||||
<div
|
||||
class="bg-neutral-200 flex-shrink-0 rounded-lg overflow-hidden {% if not i.hava %}item-not-owned{% endif %}"
|
||||
>
|
||||
<div
|
||||
class="w-32 h-32 rounded-br-2xl bg-cover overflow-hidden"
|
||||
style="background-image: url('./../abyss/background/roleStarBg{{ i.star }}.png');"
|
||||
>
|
||||
<img src="{{ i.icon }}" alt="" />
|
||||
</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>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endfor %}
|
||||
|
||||
<div class="mt-6 text-center p-1 text-neutral-400 text-xs">
|
||||
数据来源:游创工坊
|
||||
</div>
|
||||
</div>
|
||||
<div class="my-2"></div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
</body>
|
||||
</html>
|
||||
|
@ -30,7 +30,7 @@ async def test_abyss_team_data(abyss_team_data: AbyssTeamData):
|
||||
team_data.sort(["迪奥娜", "芭芭拉", "凯亚", "琴"])
|
||||
assert isinstance(team_data.rate_list_full[0], 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)
|
||||
member_up = {i.name for i in random_team.up.formation}
|
||||
member_down = {i.name for i in random_team.down.formation}
|
||||
|
Loading…
Reference in New Issue
Block a user