♻ 重写抽卡模拟器插件
@ -3,7 +3,7 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import functools
|
import functools
|
||||||
from typing import Any, Generic, ItemsView, Iterator, KeysView, TypeVar
|
from typing import Any, Generic, ItemsView, Iterator, KeysView, TypeVar, Optional
|
||||||
|
|
||||||
import ujson as json
|
import ujson as json
|
||||||
|
|
||||||
@ -21,6 +21,8 @@ __all__ = [
|
|||||||
"honey_id_to_game_id",
|
"honey_id_to_game_id",
|
||||||
"game_id_to_role_id",
|
"game_id_to_role_id",
|
||||||
"Data",
|
"Data",
|
||||||
|
"weapon_to_game_id",
|
||||||
|
"avatar_to_game_id"
|
||||||
]
|
]
|
||||||
|
|
||||||
K = TypeVar("K")
|
K = TypeVar("K")
|
||||||
@ -99,3 +101,15 @@ def game_id_to_role_id(gid: str) -> int | None:
|
|||||||
return next(
|
return next(
|
||||||
(int(key.split("-")[0]) for key, value in AVATAR_DATA.items() if value["icon"].split("_")[-1] == gid), None
|
(int(key.split("-")[0]) for key, value in AVATAR_DATA.items() if value["icon"].split("_")[-1] == gid), None
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def weapon_to_game_id(name: str) -> Optional[int]:
|
||||||
|
return next(
|
||||||
|
(int(key) for key, value in WEAPON_DATA.items() if value['name'] == name), None
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def avatar_to_game_id(name: str) -> Optional[int]:
|
||||||
|
return next(
|
||||||
|
(int(key) for key, value in AVATAR_DATA.items() if value['name'] == name), None
|
||||||
|
)
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import asyncio
|
import asyncio
|
||||||
import re
|
import re
|
||||||
import time
|
import time
|
||||||
|
from datetime import datetime
|
||||||
from json import JSONDecodeError
|
from json import JSONDecodeError
|
||||||
from typing import List, Optional, Dict
|
from typing import List, Optional, Dict
|
||||||
|
|
||||||
@ -9,7 +10,6 @@ from genshin.utility.ds import generate_dynamic_secret
|
|||||||
from genshin.utility.uid import recognize_genshin_server
|
from genshin.utility.uid import recognize_genshin_server
|
||||||
from httpx import AsyncClient
|
from httpx import AsyncClient
|
||||||
from pydantic import BaseModel, validator
|
from pydantic import BaseModel, validator
|
||||||
from datetime import datetime
|
|
||||||
|
|
||||||
from modules.apihelper.base import ArtworkImage, PostInfo
|
from modules.apihelper.base import ArtworkImage, PostInfo
|
||||||
from modules.apihelper.helpers import get_device_id
|
from modules.apihelper.helpers import get_device_id
|
||||||
@ -190,6 +190,9 @@ class GachaInfo:
|
|||||||
self.cache[gacha_id] = req
|
self.cache[gacha_id] = req
|
||||||
return req
|
return req
|
||||||
|
|
||||||
|
async def close(self):
|
||||||
|
await self.client.shutdown()
|
||||||
|
|
||||||
|
|
||||||
class SignIn:
|
class SignIn:
|
||||||
LOGIN_URL = "https://webapi.account.mihoyo.com/Api/login_by_mobilecaptcha"
|
LOGIN_URL = "https://webapi.account.mihoyo.com/Api/login_by_mobilecaptcha"
|
||||||
|
@ -30,6 +30,9 @@ class GachaBanner:
|
|||||||
15304,
|
15304,
|
||||||
]
|
]
|
||||||
# 硬编码三星武器
|
# 硬编码三星武器
|
||||||
|
title: str = ""
|
||||||
|
html_title: str = ""
|
||||||
|
banner_id: str = ""
|
||||||
banner_type: BannerType = BannerType.STANDARD
|
banner_type: BannerType = BannerType.STANDARD
|
||||||
wish_max_progress: int = 0
|
wish_max_progress: int = 0
|
||||||
pool_balance_weights4: Tuple[int] = ((1, 255), (17, 255), (21, 10455))
|
pool_balance_weights4: Tuple[int] = ((1, 255), (17, 255), (21, 10455))
|
||||||
|
@ -1,19 +1,25 @@
|
|||||||
|
import asyncio
|
||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from typing import Dict
|
from typing import Optional, Union, Any, List
|
||||||
|
|
||||||
|
import ujson as json
|
||||||
from bs4 import BeautifulSoup
|
from bs4 import BeautifulSoup
|
||||||
from pyppeteer import launch
|
|
||||||
from telegram import Update
|
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
|
||||||
|
|
||||||
|
from core.base.redisdb import RedisDB
|
||||||
from core.baseplugin import BasePlugin
|
from core.baseplugin import BasePlugin
|
||||||
from core.plugin import Plugin, handler
|
from core.plugin import Plugin, handler
|
||||||
from core.template import TemplateService
|
from core.template import TemplateService
|
||||||
from modules.apihelper.hyperion import GachaInfo
|
from metadata.genshin import weapon_to_game_id, avatar_to_game_id, WEAPON_DATA, AVATAR_DATA
|
||||||
from plugins.genshin.gacha.wish import WishCountInfo, get_one
|
from metadata.shortname import weaponToName
|
||||||
|
from modules.apihelper.hyperion import GachaInfo, GachaInfoObject
|
||||||
|
from modules.gacha.banner import BannerType, GachaBanner
|
||||||
|
from modules.gacha.player.info import PlayerGachaInfo
|
||||||
|
from modules.gacha.system import BannerSystem
|
||||||
from utils.bot import get_all_args
|
from utils.bot import get_all_args
|
||||||
from utils.decorators.error import error_callable
|
from utils.decorators.error import error_callable
|
||||||
from utils.decorators.restricts import restricts
|
from utils.decorators.restricts import restricts
|
||||||
@ -23,37 +29,126 @@ from utils.log import logger
|
|||||||
class GachaNotFound(Exception):
|
class GachaNotFound(Exception):
|
||||||
"""卡池未找到"""
|
"""卡池未找到"""
|
||||||
|
|
||||||
def __init__(self, gacha_name):
|
def __init__(self, gacha_name: str):
|
||||||
|
self.gacha_name = gacha_name
|
||||||
super().__init__(f"{gacha_name} gacha not found")
|
super().__init__(f"{gacha_name} gacha not found")
|
||||||
|
|
||||||
|
|
||||||
|
class GachaRedis:
|
||||||
|
def __init__(self, redis: RedisDB):
|
||||||
|
self.client = redis.client
|
||||||
|
self.qname = "plugin:gacha:"
|
||||||
|
|
||||||
|
async def get(self, user_id: int) -> PlayerGachaInfo:
|
||||||
|
data = await self.client.get(f"{self.qname}{user_id}")
|
||||||
|
if data is None:
|
||||||
|
return PlayerGachaInfo()
|
||||||
|
return PlayerGachaInfo(**json.loads(data))
|
||||||
|
|
||||||
|
async def set(self, user_id: int, player_gacha_info: PlayerGachaInfo):
|
||||||
|
value = player_gacha_info.json()
|
||||||
|
await self.client.set(f"{self.qname}{user_id}", value)
|
||||||
|
|
||||||
|
|
||||||
|
class GachaHandle:
|
||||||
|
def __init__(self, hyperion: Optional[GachaInfo] = None):
|
||||||
|
if hyperion is None:
|
||||||
|
self.hyperion = GachaInfo()
|
||||||
|
else:
|
||||||
|
self.hyperion = hyperion
|
||||||
|
|
||||||
|
async def de_banner(self, gacha_id: str, gacha_type: int) -> Optional[GachaBanner]:
|
||||||
|
gacha_info = await self.hyperion.get_gacha_info(gacha_id)
|
||||||
|
banner = GachaBanner()
|
||||||
|
banner.title, banner.html_title = self.de_title(gacha_info["title"])
|
||||||
|
for r5_up_items in gacha_info["r5_up_items"]:
|
||||||
|
if r5_up_items["item_type"] == "角色":
|
||||||
|
banner.rate_up_items5.append(avatar_to_game_id(r5_up_items["item_name"]))
|
||||||
|
elif r5_up_items["item_type"] == "武器":
|
||||||
|
banner.rate_up_items5.append(weapon_to_game_id(r5_up_items["item_name"]))
|
||||||
|
for r5_prob_list in gacha_info["r5_prob_list"]:
|
||||||
|
if r5_prob_list["item_type"] == "角色":
|
||||||
|
banner.fallback_items5_pool1.append(avatar_to_game_id(r5_prob_list["item_name"]))
|
||||||
|
elif r5_prob_list["item_type"] == "武器":
|
||||||
|
banner.fallback_items5_pool1.append(weapon_to_game_id(r5_prob_list["item_name"]))
|
||||||
|
for r4_up_items in gacha_info["r4_up_items"]:
|
||||||
|
if r4_up_items["item_type"] == "角色":
|
||||||
|
banner.rate_up_items4.append(avatar_to_game_id(r4_up_items["item_name"]))
|
||||||
|
elif r4_up_items["item_type"] == "武器":
|
||||||
|
banner.rate_up_items4.append(weapon_to_game_id(r4_up_items["item_name"]))
|
||||||
|
for r4_prob_list in gacha_info["r4_prob_list"]:
|
||||||
|
if r4_prob_list["item_type"] == "角色":
|
||||||
|
banner.fallback_items4_pool1.append(avatar_to_game_id(r4_prob_list["item_name"]))
|
||||||
|
elif r4_prob_list["item_type"] == "武器":
|
||||||
|
banner.fallback_items4_pool1.append(weapon_to_game_id(r4_prob_list["item_name"]))
|
||||||
|
if gacha_type in (310, 400):
|
||||||
|
banner.wish_max_progress = 1
|
||||||
|
banner.banner_type = BannerType.EVENT
|
||||||
|
banner.weight4 = ((1, 510), (8, 510), (10, 10000))
|
||||||
|
banner.weight5 = ((1, 60), (73, 60), (90, 10000))
|
||||||
|
elif gacha_type == 302:
|
||||||
|
banner.wish_max_progress = 3
|
||||||
|
banner.banner_type = BannerType.WEAPON
|
||||||
|
banner.weight4 = ((1, 600), (7, 600), (10, 10000))
|
||||||
|
banner.weight5 = ((1, 70), (62, 70), (90, 10000))
|
||||||
|
else:
|
||||||
|
banner.banner_type = BannerType.STANDARD
|
||||||
|
return banner
|
||||||
|
|
||||||
|
async def gacha_base_info(self, gacha_name: str = "角色活动", default: bool = False) -> GachaInfoObject:
|
||||||
|
gacha_list_info = await self.hyperion.get_gacha_list_info()
|
||||||
|
now = datetime.now()
|
||||||
|
for gacha in gacha_list_info:
|
||||||
|
if gacha.gacha_name == gacha_name and gacha.begin_time <= now <= gacha.end_time:
|
||||||
|
return gacha
|
||||||
|
else: # pylint: disable=W0120
|
||||||
|
if default and len(gacha_list_info) > 0:
|
||||||
|
return gacha_list_info[0]
|
||||||
|
else:
|
||||||
|
raise GachaNotFound(gacha_name)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
async def de_item_list(item_list: List[int]) -> List[dict]:
|
||||||
|
gacha_item: List[dict] = []
|
||||||
|
for item_id in item_list:
|
||||||
|
if 10000 <= item_id <= 100000:
|
||||||
|
gacha_item.append(WEAPON_DATA.get(str(item_id)))
|
||||||
|
if 10000000 <= item_id <= 19999999:
|
||||||
|
gacha_item.append(AVATAR_DATA.get(str(item_id)))
|
||||||
|
return gacha_item
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def de_title(title: str) -> Union[tuple[str, None], tuple[str, Any]]:
|
||||||
|
title_html = BeautifulSoup(title, "lxml")
|
||||||
|
re_color = re.search(r"<color=#(.*?)>", title, flags=0)
|
||||||
|
if re_color is None:
|
||||||
|
return title_html.text, None
|
||||||
|
color = re_color.group(1)
|
||||||
|
title_html.color.name = "span"
|
||||||
|
title_html.span["style"] = f"color:#{color};"
|
||||||
|
return title_html.text, title_html.p
|
||||||
|
|
||||||
|
|
||||||
class Gacha(Plugin, BasePlugin):
|
class Gacha(Plugin, BasePlugin):
|
||||||
"""抽卡模拟器(非首模拟器/减寿模拟器)"""
|
"""抽卡模拟器(非首模拟器/减寿模拟器)"""
|
||||||
|
|
||||||
def __init__(self, template_service: TemplateService = None):
|
def __init__(self, template_service: TemplateService = None, redis: RedisDB = None):
|
||||||
self.gacha = GachaInfo()
|
self.gacha_db = GachaRedis(redis)
|
||||||
|
self.handle = GachaHandle()
|
||||||
|
self.banner_system = BannerSystem()
|
||||||
self.template_service = template_service
|
self.template_service = template_service
|
||||||
self.browser: launch = None
|
|
||||||
self.current_dir = os.getcwd()
|
self.current_dir = os.getcwd()
|
||||||
self.resources_dir = os.path.join(self.current_dir, "resources")
|
self.resources_dir = os.path.join(self.current_dir, "resources")
|
||||||
self.character_gacha_card = {}
|
self.banner_cache = {}
|
||||||
self.user_time = {}
|
self._look = asyncio.Lock()
|
||||||
|
|
||||||
async def gacha_info(self, gacha_name: str = "角色活动", default: bool = False):
|
async def get_banner(self, gacha_base_info: GachaInfoObject):
|
||||||
gacha_list_info = await self.gacha.get_gacha_list_info()
|
async with self._look:
|
||||||
gacha_id = ""
|
banner = self.banner_cache.get(gacha_base_info.gacha_id)
|
||||||
now = datetime.now()
|
if banner is None:
|
||||||
for gacha in gacha_list_info:
|
banner = await self.handle.de_banner(gacha_base_info.gacha_id, gacha_base_info.gacha_type)
|
||||||
if gacha.gacha_name == gacha_name and gacha.begin_time <= now <= gacha.end_time:
|
self.banner_cache.setdefault(gacha_base_info.gacha_id, banner)
|
||||||
gacha_id = gacha.gacha_id
|
return banner
|
||||||
if gacha_id == "":
|
|
||||||
if default and len(gacha_list_info) > 0:
|
|
||||||
gacha_id = gacha_list_info[0].gacha_id
|
|
||||||
else:
|
|
||||||
raise GachaNotFound(gacha_name)
|
|
||||||
gacha_info = await self.gacha.get_gacha_info(gacha_id)
|
|
||||||
gacha_info["gacha_id"] = gacha_id
|
|
||||||
return gacha_info
|
|
||||||
|
|
||||||
@handler(CommandHandler, command="gacha", block=False)
|
@handler(CommandHandler, command="gacha", block=False)
|
||||||
@handler(MessageHandler, filters=filters.Regex("^非首模拟器(.*)"), block=False)
|
@handler(MessageHandler, filters=filters.Regex("^非首模拟器(.*)"), block=False)
|
||||||
@ -72,66 +167,92 @@ class Gacha(Plugin, BasePlugin):
|
|||||||
gacha_name = value
|
gacha_name = value
|
||||||
break
|
break
|
||||||
try:
|
try:
|
||||||
gacha_info = await self.gacha_info(gacha_name)
|
gacha_base_info = await self.handle.gacha_base_info(gacha_name)
|
||||||
except GachaNotFound:
|
except GachaNotFound as exc:
|
||||||
await message.reply_text(f"没有找到名为 {gacha_name} 的卡池")
|
await message.reply_text(f"没有找到名为 {exc.gacha_name} 的卡池")
|
||||||
return
|
return
|
||||||
else:
|
else:
|
||||||
gacha_info = await self.gacha_info(default=True)
|
gacha_base_info = await self.handle.gacha_base_info(default=True)
|
||||||
logger.info(f"用户 {user.full_name}[{user.id}] 抽卡模拟器命令请求 || 参数 {gacha_name}")
|
logger.info(f"用户 {user.full_name}[{user.id}] 抽卡模拟器命令请求 || 参数 {gacha_name}")
|
||||||
# 用户数据储存和处理
|
# 用户数据储存和处理
|
||||||
gacha_id: str = gacha_info["gacha_id"]
|
|
||||||
user_gacha: Dict[str, WishCountInfo] = context.user_data.get("gacha")
|
|
||||||
if user_gacha is None:
|
|
||||||
user_gacha = context.user_data["gacha"] = {}
|
|
||||||
user_gacha_count: WishCountInfo = user_gacha.get(gacha_id)
|
|
||||||
if user_gacha_count is None:
|
|
||||||
user_gacha_count = user_gacha[gacha_id] = WishCountInfo(user_id=user.id)
|
|
||||||
# 用户数据储存和处理
|
|
||||||
title = gacha_info["title"]
|
|
||||||
re_color = re.search(r"<color=#(.*?)>", title, flags=0)
|
|
||||||
if re_color is None:
|
|
||||||
title_html = BeautifulSoup(title, "lxml")
|
|
||||||
pool_name = title_html.text
|
|
||||||
logger.warning(f"卡池信息 title 提取 color 失败 title[{title}]")
|
|
||||||
else:
|
|
||||||
color = re_color.group(1)
|
|
||||||
title_html = BeautifulSoup(title, "lxml")
|
|
||||||
title_html.color.name = "span"
|
|
||||||
title_html.span["style"] = f"color:#{color};"
|
|
||||||
pool_name = title_html.p
|
|
||||||
await message.reply_chat_action(ChatAction.TYPING)
|
await message.reply_chat_action(ChatAction.TYPING)
|
||||||
data = {
|
banner = await self.get_banner(gacha_base_info)
|
||||||
|
player_gacha_info = await self.gacha_db.get(user.id)
|
||||||
|
# 检查 wish_item_id
|
||||||
|
if banner.banner_type == BannerType.WEAPON:
|
||||||
|
if player_gacha_info.event_weapon_banner.wish_item_id not in banner.rate_up_items5:
|
||||||
|
player_gacha_info.event_weapon_banner.wish_item_id = 0
|
||||||
|
# 执行抽卡
|
||||||
|
item_list = self.banner_system.do_pulls(player_gacha_info, banner, 10)
|
||||||
|
data = await self.handle.de_item_list(item_list)
|
||||||
|
player_gacha_banner_info = player_gacha_info.get_banner_info(banner)
|
||||||
|
template_data = {
|
||||||
"_res_path": f"file://{self.resources_dir}",
|
"_res_path": f"file://{self.resources_dir}",
|
||||||
"name": f"{user.full_name}",
|
"name": f"{user.full_name}",
|
||||||
"info": gacha_name,
|
"info": gacha_name,
|
||||||
"poolName": pool_name,
|
"banner_name": banner.html_title,
|
||||||
|
"banner_type": banner.banner_type.name,
|
||||||
|
"player_gacha_banner_info": player_gacha_banner_info,
|
||||||
"items": [],
|
"items": [],
|
||||||
|
"wish_name": "",
|
||||||
}
|
}
|
||||||
for _ in range(10):
|
if player_gacha_banner_info.wish_item_id != 0:
|
||||||
item = get_one(user_gacha_count, gacha_info)
|
weapon = WEAPON_DATA.get(str(player_gacha_banner_info.wish_item_id))
|
||||||
# 下面为忽略的代码,因为metadata未完善,具体武器和角色类型无法显示
|
if weapon is not None:
|
||||||
# item_name = item["item_name"]
|
template_data["wish_name"] = weapon["name"]
|
||||||
# item_type = item["item_type"]
|
await self.gacha_db.set(user.id, player_gacha_info)
|
||||||
# if item_type == "角色":
|
|
||||||
# gacha_card = self.character_gacha_card.get(item_name)
|
|
||||||
# if gacha_card is None:
|
|
||||||
# await message.reply_text(f"获取角色 {item_name} GachaCard信息失败")
|
|
||||||
# return
|
|
||||||
# item["item_character_img"] = await url_to_file(gacha_card)
|
|
||||||
data["items"].append(item)
|
|
||||||
|
|
||||||
def take_rang(elem: dict):
|
def take_rang(elem: dict):
|
||||||
return elem["rank"]
|
return elem["rank"]
|
||||||
|
|
||||||
data["items"].sort(key=take_rang, reverse=True)
|
data.sort(key=take_rang, reverse=True)
|
||||||
|
template_data["items"] = data
|
||||||
await message.reply_chat_action(ChatAction.UPLOAD_PHOTO)
|
await message.reply_chat_action(ChatAction.UPLOAD_PHOTO)
|
||||||
# 因为 gacha_info["title"] 返回的是 HTML 标签 尝试关闭自动转义
|
|
||||||
png_data = await self.template_service.render(
|
png_data = await self.template_service.render(
|
||||||
"genshin/gacha/gacha.html", data, {"width": 1157, "height": 603}, False
|
"genshin/gacha/gacha.html", template_data, {"width": 1157, "height": 603}, False
|
||||||
)
|
)
|
||||||
|
|
||||||
reply_message = await message.reply_photo(png_data)
|
reply_message = await message.reply_photo(png_data)
|
||||||
if filters.ChatType.GROUPS.filter(message):
|
if filters.ChatType.GROUPS.filter(message):
|
||||||
self._add_delete_message_job(context, reply_message.chat_id, reply_message.message_id, 300)
|
self._add_delete_message_job(context, reply_message.chat_id, reply_message.message_id, 300)
|
||||||
self._add_delete_message_job(context, message.chat_id, message.message_id, 300)
|
self._add_delete_message_job(context, message.chat_id, message.message_id, 300)
|
||||||
|
|
||||||
|
@handler(CommandHandler, command="set_wish", block=False)
|
||||||
|
@handler(MessageHandler, filters=filters.Regex("^非首模拟器定轨(.*)"), block=False)
|
||||||
|
@restricts(restricts_time=3, restricts_time_of_groups=20)
|
||||||
|
@error_callable
|
||||||
|
async def set_wish(self, update: Update, context: CallbackContext) -> None:
|
||||||
|
message = update.effective_message
|
||||||
|
user = update.effective_user
|
||||||
|
args = get_all_args(context)
|
||||||
|
gacha_base_info = await self.handle.gacha_base_info("武器活动")
|
||||||
|
banner = await self.get_banner(gacha_base_info)
|
||||||
|
if len(args) >= 1:
|
||||||
|
weapon_name = args[0]
|
||||||
|
else:
|
||||||
|
reply_message = await message.reply_text("参数错误")
|
||||||
|
if filters.ChatType.GROUPS.filter(reply_message):
|
||||||
|
self._add_delete_message_job(context, message.chat_id, message.message_id, 10)
|
||||||
|
self._add_delete_message_job(context, reply_message.chat_id, reply_message.message_id, 10)
|
||||||
|
return
|
||||||
|
weapon_name = weaponToName(weapon_name)
|
||||||
|
player_gacha_info = await self.gacha_db.get(user.id)
|
||||||
|
for rate_up_items5 in banner.rate_up_items5:
|
||||||
|
weapon = WEAPON_DATA.get(str(rate_up_items5))
|
||||||
|
if weapon is None:
|
||||||
|
continue
|
||||||
|
if weapon["name"] == weapon_name:
|
||||||
|
player_gacha_info.event_weapon_banner.wish_item_id = rate_up_items5
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
reply_message = await message.reply_text(f"没有找到 {weapon_name} 武器或该武器不存在UP卡池中")
|
||||||
|
if filters.ChatType.GROUPS.filter(reply_message):
|
||||||
|
self._add_delete_message_job(context, message.chat_id, message.message_id, 10)
|
||||||
|
self._add_delete_message_job(context, reply_message.chat_id, reply_message.message_id, 10)
|
||||||
|
return
|
||||||
|
await self.gacha_db.set(user.id, player_gacha_info)
|
||||||
|
reply_message = await message.reply_text(f"抽卡模拟器定轨 {weapon_name} 武器成功")
|
||||||
|
if filters.ChatType.GROUPS.filter(reply_message):
|
||||||
|
self._add_delete_message_job(context, message.chat_id, message.message_id, 10)
|
||||||
|
self._add_delete_message_job(context, reply_message.chat_id, reply_message.message_id, 10)
|
||||||
|
return
|
||||||
|
@ -1,131 +0,0 @@
|
|||||||
import random
|
|
||||||
from enum import Enum
|
|
||||||
|
|
||||||
|
|
||||||
class GachaType(Enum):
|
|
||||||
activity = 301 # 限定卡池
|
|
||||||
activity2 = 400 # 限定卡池
|
|
||||||
weapon = 302 # 武器卡池
|
|
||||||
permanent = 200 # 常驻卡池
|
|
||||||
|
|
||||||
|
|
||||||
class WishCountInfo:
|
|
||||||
def __init__(self, user_id: int):
|
|
||||||
self.user_id = user_id
|
|
||||||
self.five_stars_count: int = 1
|
|
||||||
self.four_stars_count: int = 1
|
|
||||||
self.is_up: bool = False
|
|
||||||
self.maximum_fate_points: int = 0
|
|
||||||
|
|
||||||
|
|
||||||
def character_probability(rank, count):
|
|
||||||
ret = 0
|
|
||||||
if rank == 5 and count <= 73:
|
|
||||||
ret = 60
|
|
||||||
elif rank == 5 and count >= 74:
|
|
||||||
ret = 60 + 600 * (count - 73)
|
|
||||||
elif rank == 4 and count <= 8:
|
|
||||||
ret = 510
|
|
||||||
elif rank == 4 and count >= 9:
|
|
||||||
ret = 510 + 5100 * (count - 8)
|
|
||||||
return ret
|
|
||||||
|
|
||||||
|
|
||||||
def weapon_probability(rank, count):
|
|
||||||
ret = 0
|
|
||||||
if rank == 5 and count <= 62:
|
|
||||||
ret = 70
|
|
||||||
elif rank == 5 and count <= 73:
|
|
||||||
ret = 70 + 700 * (count - 62)
|
|
||||||
elif rank == 5 and count >= 74:
|
|
||||||
ret = 7770 + 350 * (count - 73)
|
|
||||||
elif rank == 4 and count <= 7:
|
|
||||||
ret = 600
|
|
||||||
elif rank == 4 and count == 8:
|
|
||||||
ret = 6600
|
|
||||||
elif rank == 4 and count >= 9:
|
|
||||||
ret = 6600 + 3000 * (count - 8)
|
|
||||||
return ret
|
|
||||||
|
|
||||||
|
|
||||||
def is_character_gacha(gacha_type: GachaType) -> bool:
|
|
||||||
return gacha_type in (GachaType.activity, GachaType.activity2, GachaType.permanent)
|
|
||||||
|
|
||||||
|
|
||||||
def random_int():
|
|
||||||
return random.randint(0, 10000)
|
|
||||||
|
|
||||||
|
|
||||||
def get_is_up(rank: int, count: WishCountInfo, gacha_type: GachaType):
|
|
||||||
if gacha_type == GachaType.permanent:
|
|
||||||
return False
|
|
||||||
elif gacha_type == GachaType.weapon:
|
|
||||||
return random_int() <= 7500
|
|
||||||
else:
|
|
||||||
return random_int() <= 5000 or (rank == 5 and count.is_up)
|
|
||||||
|
|
||||||
|
|
||||||
def get_rank(count: WishCountInfo, gacha_type: GachaType):
|
|
||||||
value = random_int()
|
|
||||||
probability_fn = character_probability if is_character_gacha(gacha_type) else weapon_probability
|
|
||||||
index_5 = probability_fn(5, count.five_stars_count)
|
|
||||||
index_4 = probability_fn(4, count.four_stars_count) + index_5
|
|
||||||
if value <= index_5:
|
|
||||||
return 5
|
|
||||||
elif value <= index_4:
|
|
||||||
return 4
|
|
||||||
else:
|
|
||||||
return 3
|
|
||||||
|
|
||||||
|
|
||||||
def get_one(count: WishCountInfo, gacha_info: dict, weapon_name: str = "") -> dict:
|
|
||||||
gacha_type = GachaType(gacha_info["gacha_type"])
|
|
||||||
rank = get_rank(count, gacha_type)
|
|
||||||
is_up = get_is_up(rank, count, gacha_type)
|
|
||||||
if rank == 5:
|
|
||||||
count.five_stars_count = 1
|
|
||||||
if is_up:
|
|
||||||
data = random.choice(gacha_info["r5_up_items"])
|
|
||||||
else:
|
|
||||||
data = random.choice(gacha_info["r5_prob_list"])
|
|
||||||
if gacha_type == GachaType.weapon:
|
|
||||||
if data["item_name"] == weapon_name:
|
|
||||||
count.maximum_fate_points = 0
|
|
||||||
elif count.maximum_fate_points == 2:
|
|
||||||
count.maximum_fate_points = 0
|
|
||||||
for temp_item in gacha_info["r5_up_items"]:
|
|
||||||
if temp_item["item_name"] == weapon_name:
|
|
||||||
data = temp_item
|
|
||||||
break
|
|
||||||
else:
|
|
||||||
count.maximum_fate_points += 1
|
|
||||||
if gacha_type in (GachaType.activity, GachaType.activity2, GachaType.weapon):
|
|
||||||
count.is_up = not is_up
|
|
||||||
return {
|
|
||||||
"item_type": data["item_type"],
|
|
||||||
"item_name": data["item_name"],
|
|
||||||
"rank": 5,
|
|
||||||
}
|
|
||||||
elif rank == 4:
|
|
||||||
count.five_stars_count += 1
|
|
||||||
count.four_stars_count = 1
|
|
||||||
if is_up:
|
|
||||||
data = random.choice(gacha_info["r4_up_items"])
|
|
||||||
else:
|
|
||||||
data = random.choice(gacha_info["r4_prob_list"])
|
|
||||||
return {
|
|
||||||
"item_type": data["item_type"],
|
|
||||||
"item_name": data["item_name"],
|
|
||||||
"rank": 4,
|
|
||||||
}
|
|
||||||
elif rank == 3:
|
|
||||||
count.five_stars_count += 1
|
|
||||||
count.four_stars_count += 1
|
|
||||||
data = random.choice(gacha_info["r3_prob_list"])
|
|
||||||
return {
|
|
||||||
"item_type": data["item_type"],
|
|
||||||
"item_name": data["item_name"],
|
|
||||||
"rank": 3,
|
|
||||||
}
|
|
||||||
else:
|
|
||||||
raise ValueError("rank value error")
|
|
@ -137,6 +137,10 @@
|
|||||||
<div class="command-name">/gacha</div>
|
<div class="command-name">/gacha</div>
|
||||||
<div class="command-description">抽卡模拟器(非洲人模拟器)</div>
|
<div class="command-description">抽卡模拟器(非洲人模拟器)</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="command">
|
||||||
|
<div class="command-name">/set_wish</div>
|
||||||
|
<div class="command-description">抽卡模拟器定轨</div>
|
||||||
|
</div>
|
||||||
<div class="command">
|
<div class="command">
|
||||||
<div class="command-name">/quiz</div>
|
<div class="command-name">/quiz</div>
|
||||||
<div class="command-description">
|
<div class="command-description">
|
||||||
|
@ -54,14 +54,14 @@ body {
|
|||||||
z-index: 9999;
|
z-index: 9999;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.pity5 {
|
||||||
.poor-bing {
|
|
||||||
position: fixed;
|
position: fixed;
|
||||||
top: 85px;
|
top: 85px;
|
||||||
right: 55px;
|
right: 55px;
|
||||||
z-index: 9999;
|
z-index: 9999;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
.list-box {
|
.list-box {
|
||||||
display: flex;
|
display: flex;
|
||||||
padding-top: 130px;
|
padding-top: 130px;
|
||||||
@ -162,6 +162,20 @@ body {
|
|||||||
z-index: 120;
|
z-index: 120;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.poor-wish {
|
||||||
|
position: fixed;
|
||||||
|
top: 500px;
|
||||||
|
right: 55px;
|
||||||
|
z-index: 9999;
|
||||||
|
}
|
||||||
|
|
||||||
|
.poor-bing {
|
||||||
|
position: fixed;
|
||||||
|
top: 580px;
|
||||||
|
right: 55px;
|
||||||
|
z-index: 9999;
|
||||||
|
}
|
||||||
|
|
||||||
.logo {
|
.logo {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
right: 55px;
|
right: 55px;
|
||||||
|
@ -10,29 +10,43 @@
|
|||||||
<div class="container" id="container">
|
<div class="container" id="container">
|
||||||
<div class="info-bg info-name">{{name}}</div>
|
<div class="info-bg info-name">{{name}}</div>
|
||||||
<div class="info-bg info-count">{{info}}</div>
|
<div class="info-bg info-count">{{info}}</div>
|
||||||
|
{% if banner_type == "WEAPON" %}
|
||||||
|
{% if wish_name %}
|
||||||
|
<div class="info-bg poor-wish">定轨:{{wish_name}}</div>
|
||||||
|
<div class="info-bg poor-bing">命定值:{{player_gacha_banner_info.failed_chosen_item_pulls}}</div>
|
||||||
|
{% else %}
|
||||||
|
<div class="info-bg poor-bing">当前卡池未定轨</div>
|
||||||
|
{% endif %}
|
||||||
|
{% endif %}
|
||||||
<div class="info-bg poor-info">
|
<div class="info-bg poor-info">
|
||||||
{% autoescape off %}
|
{% autoescape off %}
|
||||||
{{poolName}}
|
{{banner_name}}
|
||||||
{% endautoescape %}
|
{% endautoescape %}
|
||||||
</div>
|
</div>
|
||||||
|
<div class="info-bg pity5">距离上一个五星{{player_gacha_banner_info.pity5}}抽</div>
|
||||||
<div class="list-box">
|
<div class="list-box">
|
||||||
{% for item in items %}
|
{% for item in items %}
|
||||||
<div class="item">
|
<div class="item">
|
||||||
<div class="item-bg-box">
|
<div class="item-bg-box">
|
||||||
<img class="item-bg" src="{{_res_path}}/genshin/gacha/items/bg.png"/>
|
<img class="item-bg" src="{{_res_path}}/genshin/gacha/items/bg.png" alt=""/>
|
||||||
</div>
|
</div>
|
||||||
<img class="item-shadow" src="{{_res_path}}/genshin/gacha/items/shadow-{{item.rank}}.png"/>
|
<img class="item-shadow" src="{{_res_path}}/genshin/gacha/items/shadow-{{item.rank}}.png" alt=""/>
|
||||||
<img class="item-shadow2" src="{{_res_path}}/genshin/gacha/items/bg2.png"/>
|
<img class="item-shadow2" src="{{_res_path}}/genshin/gacha/items/bg2.png" alt=""/>
|
||||||
{% if item.item_type=='武器' %}
|
{% if item.id <= 100000 %}
|
||||||
<div class="item-weapon-box">
|
<div class="item-weapon-box">
|
||||||
<img class="item-weapon-img" src="{{_res_path}}/genshin/gacha/weapon/{{item.item_name}}.png"/>
|
<img class="item-weapon-img" src="{{_res_path}}/genshin/gacha/weapon/{{item.name}}.png" alt=""/>
|
||||||
</div>
|
</div>
|
||||||
{% else %}
|
{% else %}
|
||||||
<div class="item-img-box">
|
<div class="item-img-box">
|
||||||
<img class="item-character-img" src="{{_res_path}}/genshin/gacha/character/{{item.item_name}}.png"/>
|
<img class="item-character-img" src="{{_res_path}}/genshin/gacha/character/{{item.name}}.png" alt=""/>
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<img class="item-star" src="{{_res_path}}/genshin/gacha/items/s-{{item.rank}}.png"/>
|
{% if item.id <= 100000 %}
|
||||||
|
<img class="item-element" src="{{_res_path}}/genshin/gacha/items/{{item.type}}.png" alt=""/>
|
||||||
|
{% else %}
|
||||||
|
<img class="item-element" src="{{_res_path}}/genshin/gacha/items/{{item.element}}.png" alt=""/>
|
||||||
|
{% endif %}
|
||||||
|
<img class="item-star" src="{{_res_path}}/genshin/gacha/items/s-{{item.rank}}.png" alt=""/>
|
||||||
</div>
|
</div>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</div>
|
</div>
|
||||||
|
Before Width: | Height: | Size: 6.5 KiB After Width: | Height: | Size: 6.5 KiB |
Before Width: | Height: | Size: 7.6 KiB After Width: | Height: | Size: 7.6 KiB |
Before Width: | Height: | Size: 8.0 KiB After Width: | Height: | Size: 8.0 KiB |
Before Width: | Height: | Size: 8.2 KiB After Width: | Height: | Size: 8.2 KiB |
Before Width: | Height: | Size: 7.4 KiB After Width: | Height: | Size: 7.4 KiB |
Before Width: | Height: | Size: 2.8 KiB After Width: | Height: | Size: 2.8 KiB |
Before Width: | Height: | Size: 2.8 KiB After Width: | Height: | Size: 2.8 KiB |
Before Width: | Height: | Size: 3.0 KiB After Width: | Height: | Size: 3.0 KiB |
Before Width: | Height: | Size: 3.0 KiB After Width: | Height: | Size: 3.0 KiB |
Before Width: | Height: | Size: 3.0 KiB After Width: | Height: | Size: 3.0 KiB |
Before Width: | Height: | Size: 6.7 KiB After Width: | Height: | Size: 6.7 KiB |
Before Width: | Height: | Size: 8.4 KiB After Width: | Height: | Size: 8.4 KiB |