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
|
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)
|
||||||
|
|
||||||
|
@ -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)
|
||||||
|
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>
|
<!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>
|
</html>
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
|
@ -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}
|
||||||
|
Loading…
Reference in New Issue
Block a user