From eb32c1af9a9fcc1683ba42b00032b142cfaa9b3c Mon Sep 17 00:00:00 2001 From: MixBadGun <1059129006@qq.com> Date: Fri, 24 Oct 2025 19:39:06 +0800 Subject: [PATCH] new --- assets/img/dog/haha_dog.jpg | Bin 0 -> 14224 bytes konabot/common/web_render/__init__.py | 86 ++++++++ konabot/plugins/hanzi/__init__.py | 217 +++++++++++++++++++++ konabot/plugins/idiomgame/__init__.py | 71 ++++--- konabot/plugins/xibao_generate/__init__.py | 80 ++++++++ poetry.lock | 173 ++++++++++++++-- pyproject.toml | 2 + 7 files changed, 584 insertions(+), 45 deletions(-) create mode 100644 assets/img/dog/haha_dog.jpg create mode 100644 konabot/common/web_render/__init__.py create mode 100644 konabot/plugins/hanzi/__init__.py create mode 100644 konabot/plugins/xibao_generate/__init__.py diff --git a/assets/img/dog/haha_dog.jpg b/assets/img/dog/haha_dog.jpg new file mode 100644 index 0000000000000000000000000000000000000000..2a4cc2ed0325444a5964c3ca827f2193ed0c3b30 GIT binary patch literal 14224 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 literal 0 HcmV?d00001 diff --git a/konabot/common/web_render/__init__.py b/konabot/common/web_render/__init__.py new file mode 100644 index 0000000..77f000e --- /dev/null +++ b/konabot/common/web_render/__init__.py @@ -0,0 +1,86 @@ +import asyncio +import queue +from loguru import logger +from playwright.async_api import async_playwright, Browser + +class WebRenderer: + browser_pool: queue.Queue["WebRendererInstance"] = queue.Queue() + + @classmethod + async def render(cls, url: str, target: str, params: dict = {}, other_function: callable = None, timeout: int = 30) -> bytes: + ''' + 访问指定URL并返回截图 + + :param url: 目标URL + :param target: 渲染目标,如 ".box"、"#main" 等CSS选择器 + :param timeout: 页面加载超时时间,单位秒 + :param params: URL键值对参数 + :param other_function: 其他自定义操作函数,接受page参数 + :return: 截图的字节数据 + + ''' + logger.debug(f"Requesting render for {url} targeting {target} with timeout {timeout}") + if cls.browser_pool.empty(): + instance = await WebRendererInstance.create() + cls.browser_pool.put(instance) + instance = cls.browser_pool.get() + cls.browser_pool.put(instance) + logger.debug(f"Using WebRendererInstance {id(instance)} to render {url} targeting {target}") + return await instance.render(url, target, params=params, other_function=other_function, timeout=timeout) + +class WebRendererInstance: + def __init__(self): + self.playwright = None + self.browser: Browser = None + self.lock: asyncio.Lock = None + + @classmethod + async def create(cls) -> "WebRendererInstance": + instance = cls() + instance.playwright = await async_playwright().start() + instance.browser = await instance.playwright.chromium.launch(headless=True) + instance.lock = asyncio.Lock() + return instance + + async def render(self, url: str, target: str, index: int = 0, params: dict = {}, other_function: callable = None, timeout: int = 30) -> bytes: + ''' + 访问指定URL并返回截图 + + :param url: 目标URL + :param target: 渲染目标,如 ".box"、"#main" 等CSS选择器 + :param timeout: 页面加载超时时间,单位秒 + :param index: 如果目标是一个列表,指定要截图的元素索引 + :param params: URL键值对参数 + :param other_function: 其他自定义操作函数,接受page参数 + :return: 截图的字节数据 + + ''' + async with self.lock: + context = await self.browser.new_context() + page = await context.new_page() + logger.debug(f"Navigating to {url} with timeout {timeout}") + try: + url_with_params = url + ("?" + "&".join(f"{k}={v}" for k, v in params.items()) if params else "") + await page.goto(url_with_params, timeout=timeout * 1000, wait_until="load") + logger.debug(f"Page loaded successfully") + # 等待目标元素出现 + await page.wait_for_selector(target, timeout=timeout * 1000) + logger.debug(f"Target element '{target}' found, taking screenshot") + if other_function: + await other_function(page) + elements = await page.query_selector_all(target) + if not elements: + raise Exception(f"Target element '{target}' not found on the page.") + if index >= len(elements): + raise Exception(f"Index {index} out of range for elements matching '{target}'.") + element = elements[index] + screenshot = await element.screenshot() + logger.debug(f"Screenshot taken successfully") + return screenshot + finally: + await page.close() + await context.close() + + async def close(self): + await self.browser.close() + await self.playwright.stop() diff --git a/konabot/plugins/hanzi/__init__.py b/konabot/plugins/hanzi/__init__.py new file mode 100644 index 0000000..e165327 --- /dev/null +++ b/konabot/plugins/hanzi/__init__.py @@ -0,0 +1,217 @@ +import random +from typing import Optional +import opencc + +from nonebot import on_message +from nonebot.adapters import Event as BaseEvent +from nonebot.adapters.discord.event import MessageEvent as DiscordMessageEvent +from nonebot_plugin_alconna import ( + Alconna, + Args, + UniMessage, + UniMsg, + on_alconna, +) + +convert_type = ["简","簡","繁","正","港","日"] + +compiled_str = "|".join([f"{a}{mid}{b}" for mid in ["转","轉","転"] for a in convert_type for b in convert_type if a != b]) + +def hanzi_to_abbr(hanzi: str) -> str: + mapping = { + "简": "s", + "簡": "s", + "繁": "t", + "正": "t", + "港": "hk", + "日": "jp", + } + return mapping.get(hanzi, "") + +def check_valid_convert_type(convert_type: str) -> bool: + avaliable_set = ["s2t","t2s","s2tw","tw2s","s2hk","hk2s","s2twp","tw2sp","t2tw","hk2t","t2hk","t2jp","jp2t","tw2t"] + if convert_type in avaliable_set: + return True + return False + +def convert(source, src_abbr, dst_abbr): + convert_type_key = f"{src_abbr}2{dst_abbr}" + if not check_valid_convert_type(convert_type_key): + # 先转为繁体,再转为目标 + converter = opencc.OpenCC(f"{src_abbr}2t.json") + source = converter.convert(source) + src_abbr = "t" + converter = opencc.OpenCC(f"{src_abbr}2{dst_abbr}.json") + converted = converter.convert(source) + return converted + +evt = on_alconna( + Alconna( + f"re:({compiled_str})", + Args["source?", str], + ), + use_cmd_start=True, + use_cmd_sep=False, + skip_for_unmatch=True, +) + +@evt.handle() +async def _(msg: UniMsg, event: BaseEvent, source: Optional[str] = None): + if isinstance(event, DiscordMessageEvent): + content = event.get_message().extract_plain_text() + else: + content = event.get_message().extract_plain_text() + + prefix = content.split()[0] + to_convert = "" + # 如果回复了消息,则转换回复的内容 + if(source is None): + if event.reply: + to_convert = event.reply.message.extract_plain_text() + if not to_convert: + return + else: + return + else: + to_convert = source + parts = [] + if "转" in prefix: + parts = prefix.split("转") + elif "轉" in prefix: + parts = prefix.split("轉") + elif "転" in prefix: + parts = prefix.split("転") + if len(parts) != 2: + notice = "转换格式错误,请使用“简转繁”、“繁转简”等格式。" + await evt.send(await UniMessage().text(notice).export()) + return + src, dst = parts + src_abbr = hanzi_to_abbr(src) + dst_abbr = hanzi_to_abbr(dst) + if not src_abbr or not dst_abbr: + notice = "不支持的转换类型,请使用“简”、“繁”、“正”、“港”、“日”等。" + if src_abbr: + notice = convert(notice, "s", src_abbr) + await evt.send(await UniMessage().text(notice).export()) + return + + converted = convert(to_convert, src_abbr, dst_abbr) + + converted_prefix = convert("转换结果", "s", dst_abbr) + + await evt.send(await UniMessage().text(f"{converted_prefix}:{converted}").export()) + +shuo = ["说","說"] + +full_name_type = ["简体","簡體","繁體","繁体","正體","正体","港話","港话","日文"] + +combined_list = [f"{a}{b}" for a in shuo for b in full_name_type] + +compiled_str_2 = "|".join(combined_list) + +evt = on_alconna( + Alconna( + f"re:({compiled_str_2})", + Args["source?", str] + ), + use_cmd_start=True, + use_cmd_sep=False, + skip_for_unmatch=True, +) + +@evt.handle() +async def _(msg: UniMsg, event: BaseEvent, source: Optional[str] = None): + if isinstance(event, DiscordMessageEvent): + content = event.get_message().extract_plain_text() + else: + content = event.get_message().extract_plain_text() + + prefix = content.split()[0] + to_convert = "" + # 如果回复了消息,则转换回复的内容 + if(source is None): + if event.reply: + to_convert = event.reply.message.extract_plain_text() + if not to_convert: + return + else: + return + else: + to_convert = source + # 获取目标转换类型 + dst = "" + match prefix: + case "说简体" | "說簡體" | "说簡體" | "說简体": + dst = "简" + case "說繁體" | "说繁体" | "說繁体" | "说繁體": + dst = "繁" + case "說正體" | "说正体" | "說正体" | "说正體": + dst = "正" + case "說港話" | "说港话" | "說港话" | "说港話": + dst = "港" + case "說日文" | "说日文": + dst = "日" + dst_abbr = hanzi_to_abbr(dst) + if not dst_abbr: + notice = "不支持的转换类型,请使用“简体”、“繁體”、“正體”、“港話”、“日文”等。" + await evt.send(await UniMessage().text(notice).export()) + return + # 循环,将源语言一次次转换为目标语言 + current_text = to_convert + for src_abbr in ["s","hk","jp","tw","t"]: + if src_abbr != dst_abbr: + current_text = convert(current_text, src_abbr, dst_abbr) + + converted_prefix = convert("转换结果", "s", dst_abbr) + + await evt.send(await UniMessage().text(f"{converted_prefix}:{current_text}").export()) + +def random_char(char: str) -> str: + dst_abbr = random.choice(["s","t","hk","jp","tw"]) + for src_abbr in ["s","hk","jp","tw","t"]: + if src_abbr != dst_abbr: + char = convert(char, src_abbr, dst_abbr) + return char + +def random_string(text: str) -> str: + final_text = "" + for char in text: + final_text += random_char(char) + return final_text + +random_match = ["混乱字形","混亂字形","乱数字形","亂數字形","ランダム字形"] + +evt = on_alconna( + Alconna( + f"re:({'|'.join(random_match)})", + Args["source?", str] + ), + use_cmd_start=True, + use_cmd_sep=False, + skip_for_unmatch=True, +) +@evt.handle() +async def _(msg: UniMsg, event: BaseEvent, source: Optional[str] = None): + if isinstance(event, DiscordMessageEvent): + content = event.get_message().extract_plain_text() + else: + content = event.get_message().extract_plain_text() + + prefix = content.split()[0] + to_convert = "" + # 如果回复了消息,则转换回复的内容 + if(source is None): + if event.reply: + to_convert = event.reply.message.extract_plain_text() + if not to_convert: + return + else: + return + else: + to_convert = source + + final_text = "" + final_text = random_string(to_convert) + converted_prefix = convert(random_string("转换结果"), "s", "s") + + await evt.send(await UniMessage().text(f"{converted_prefix}:{final_text}").export()) \ No newline at end of file diff --git a/konabot/plugins/idiomgame/__init__.py b/konabot/plugins/idiomgame/__init__.py index 0ef893f..79fb508 100644 --- a/konabot/plugins/idiomgame/__init__.py +++ b/konabot/plugins/idiomgame/__init__.py @@ -71,8 +71,8 @@ class TryVerifyState(Enum): VERIFIED_AND_REAL = 1 NOT_IDIOM = 2 WRONG_FIRST_CHAR = 3 - VERIFIED_BUT_NO_NEXT = 4 - VERIFIED_GAME_END = 5 + BUT_NO_NEXT = 4 + GAME_END = 5 class IdiomGame: @@ -185,29 +185,33 @@ class IdiomGame: """ return last_char in IdiomGame.AVALIABLE_IDIOM_FIRST_CHAR - def _verify_idiom(self, idiom: str, user_id: str) -> TryVerifyState: + def _verify_idiom(self, idiom: str, user_id: str) -> list[TryVerifyState]: + state = [] # 新成语的首字应与上一条成语的尾字相同 if idiom[0] != self.last_char: - return TryVerifyState.WRONG_FIRST_CHAR + state.append(TryVerifyState.WRONG_FIRST_CHAR) + return state if idiom not in IdiomGame.ALL_IDIOMS and idiom not in IdiomGame.ALL_WORDS: self.add_score(user_id, -0.1) - return TryVerifyState.NOT_IDIOM + state.append(TryVerifyState.NOT_IDIOM) + return state + # 成语合法,更新状态 + state.append(TryVerifyState.VERIFIED) self.last_idiom = idiom self.last_char = idiom[-1] self.add_score(user_id, 1) if idiom in IdiomGame.ALL_IDIOMS: + state.append(TryVerifyState.VERIFIED_AND_REAL) self.add_score(user_id, 4) # 再加 4 分 self.remain_rounds -= 1 if self.remain_rounds <= 0: self.now_playing = False - return TryVerifyState.VERIFIED_GAME_END + state.append(TryVerifyState.GAME_END) if not self.is_nextable(self.last_char): # 没有成语可以接了,自动跳过 self._skip_idiom_async() - return TryVerifyState.VERIFIED_BUT_NO_NEXT - if idiom in IdiomGame.ALL_IDIOMS: - return TryVerifyState.VERIFIED_AND_REAL # 真实成语 - return TryVerifyState.VERIFIED + state.append(TryVerifyState.BUT_NO_NEXT) + return state def get_user_score(self, user_id: str) -> float: if user_id not in self.score_board: @@ -233,9 +237,9 @@ class IdiomGame: @classmethod def random_idiom_starting_with(cls, first_char: str) -> Optional[str]: cls.init_lexicon() - if first_char not in cls.IDIOM_FIRST_CHAR: + if first_char not in cls.AVALIABLE_IDIOM_FIRST_CHAR: return None - return secrets.choice(cls.IDIOM_FIRST_CHAR[first_char]) + return secrets.choice(cls.AVALIABLE_IDIOM_FIRST_CHAR[first_char]) @classmethod def init_lexicon(cls): @@ -249,7 +253,10 @@ class IdiomGame: # 词语大表 with open(ASSETS_PATH / "lexicon" / "ci.json", "r", encoding="utf-8") as f: - cls.ALL_WORDS = json.load(f) + jsonData = json.load(f) + cls.ALL_WORDS = [item["ci"] for item in jsonData] + logger.debug(f"Loaded {len(cls.ALL_WORDS)} words from ci.json") + logger.debug(f"Sample words: {cls.ALL_WORDS[:5]}") COMMON_WORDS = [] # 读取 COMMON 词语大表 @@ -258,6 +265,8 @@ class IdiomGame: word = line.strip() if len(word) == 4: COMMON_WORDS.append(word) + logger.debug(f"Loaded {len(COMMON_WORDS)} common words from common.txt") + logger.debug(f"Sample common words: {COMMON_WORDS[:5]}") # 读取 THUOCL 成语库 with open( @@ -265,7 +274,9 @@ class IdiomGame: "r", encoding="utf-8", ) as f: - THUOCL_IDIOMS = [line.split(" ")[0].strip() for line in f] + THUOCL_IDIOMS = [line.split(" ")[0].split("\t")[0].strip() for line in f] + logger.debug(f"Loaded {len(THUOCL_IDIOMS)} idioms from THUOCL_chengyu.txt") + logger.debug(f"Sample idioms: {THUOCL_IDIOMS[:5]}") # 读取 THUOCL 剩下的所有 txt 文件,只保留四字词 THUOCL_WORDS = [] @@ -279,9 +290,11 @@ class IdiomGame: encoding="utf-8", ) as f: for line in f: - word = line.lstrip().split(" ")[0].strip() + word = line.lstrip().split(" ")[0].split("\t")[0].strip() if len(word) == 4: THUOCL_WORDS.append(word) + logger.debug(f"Loaded {len(THUOCL_WORDS)} words from THUOCL txt files") + logger.debug(f"Sample words: {THUOCL_WORDS[:5]}") # 只有成语的大表 cls.ALL_IDIOMS = [idiom["word"] for idiom in ALL_IDIOMS_INFOS] + THUOCL_IDIOMS @@ -441,6 +454,10 @@ async def _(target: DepLongTaskTarget): if not instance or not instance.get_playing_state(): return avaliable_idiom = IdiomGame.random_idiom_starting_with(instance.get_last_char()) + # 发送哈哈狗图片 + with open(ASSETS_PATH / "img" / "dog" / "haha_dog.jpg", "rb") as f: + img_data = f.read() + await evt.send(await UniMessage().image(raw=img_data).export()) await evt.send(await UniMessage().text(f"你们太菜了,全部扣100分!明明还可以接「{avaliable_idiom}」的!").export()) idiom = await instance.skip_idiom(-100) await evt.send( @@ -472,9 +489,9 @@ async def _(event: BaseEvent, msg: UniMsg, target: DepLongTaskTarget): user_idiom = msg.extract_plain_text().strip() user_id, user_name = get_user_info(event) state = await instance.try_verify_idiom(user_idiom, user_id) - if state == TryVerifyState.WRONG_FIRST_CHAR: + if TryVerifyState.WRONG_FIRST_CHAR in state: return - if state == TryVerifyState.NOT_IDIOM: + if TryVerifyState.NOT_IDIOM in state: await evt.send( await UniMessage() .at(user_id) @@ -482,25 +499,25 @@ async def _(event: BaseEvent, msg: UniMsg, target: DepLongTaskTarget): .export() ) return - if state == TryVerifyState.VERIFIED: - await evt.send( - await UniMessage() - .at(user_id) - .text(f" 接上了,喜提 1 分!你有 {instance.get_user_score(user_id)} 分!") - .export() - ) - elif state == TryVerifyState.VERIFIED_AND_REAL: + if TryVerifyState.VERIFIED_AND_REAL in state: await evt.send( await UniMessage() .at(user_id) .text(f" 接上了,这是个真实成语,喜提 5 分!你有 {instance.get_user_score(user_id)} 分!") .export() ) - if state == TryVerifyState.VERIFIED_GAME_END: + elif TryVerifyState.VERIFIED in state: + await evt.send( + await UniMessage() + .at(user_id) + .text(f" 接上了,喜提 1 分!你有 {instance.get_user_score(user_id)} 分!") + .export() + ) + if TryVerifyState.GAME_END in state: await evt.send(await UniMessage().text("全部回合结束!").export()) await end_game(event, group_id) return - if state == TryVerifyState.VERIFIED_BUT_NO_NEXT: + if TryVerifyState.BUT_NO_NEXT in state: await evt.send( await UniMessage() .text("但是,这是条死路!你们全部都要扣 100 分!") diff --git a/konabot/plugins/xibao_generate/__init__.py b/konabot/plugins/xibao_generate/__init__.py new file mode 100644 index 0000000..f574a5e --- /dev/null +++ b/konabot/plugins/xibao_generate/__init__.py @@ -0,0 +1,80 @@ +from typing import Optional +from nonebot_plugin_alconna import Alconna, Args, UniMessage, UniMsg, on_alconna +from konabot.common.web_render import WebRenderer +from nonebot.adapters import Event as BaseEvent +from playwright.async_api import Page + +async def continue_handle(page: Page, content: str) -> None: + # 这里可以添加一些预处理逻辑 + # 找到 id 为 input 的 textarea 元素 + textarea = await page.query_selector("#input") + if textarea: + # 在 textarea 中输入内容 + await textarea.fill(content) + # 找到 id 为 submit-btn 的按钮元素 + submit_button = await page.query_selector("#submit-btn") + if submit_button: + # 点击按钮提交 + await submit_button.click() + +evt = on_alconna( + Alconna( + f"生成喜报", + Args["content?", str] + ), + use_cmd_start=True, + use_cmd_sep=False, + skip_for_unmatch=True, +) +@evt.handle() +async def _(msg: UniMsg, event: BaseEvent, content: Optional[str] = ""): + + screenshot = await WebRenderer.render( + url="https://witnessbot.mxowl.com/services/congratulations/", + target="#main-canvas", + other_function=lambda page: continue_handle(page, content), + timeout=30 + ) + await evt.send( + await UniMessage().image(raw=screenshot).export() + ) + +async def beibao_continue_handle(page: Page, content: str) -> None: + # 这里可以添加一些预处理逻辑 + # 找到 id 为 input 的 textarea 元素 + textarea = await page.query_selector("#input") + if textarea: + # 在 textarea 中输入内容 + await textarea.fill(content) + # 找到 class 为 btn btn-outline-primary,for属性为 mode-2 的标签元素 + mode_radio = await page.query_selector("label.btn.btn-outline-primary[for='mode-2']") + if mode_radio: + # 点击选择悲报模式 + await mode_radio.click() + # 找到 id 为 submit-btn 的按钮元素 + submit_button = await page.query_selector("#submit-btn") + if submit_button: + # 点击按钮提交 + await submit_button.click() + +evt = on_alconna( + Alconna( + f"生成悲报", + Args["content?", str] + ), + use_cmd_start=True, + use_cmd_sep=False, + skip_for_unmatch=True, +) +@evt.handle() +async def _(msg: UniMsg, event: BaseEvent, content: Optional[str] = ""): + + screenshot = await WebRenderer.render( + url="https://witnessbot.mxowl.com/services/congratulations/", + target="#main-canvas", + other_function=lambda page: beibao_continue_handle(page, content), + timeout=30 + ) + await evt.send( + await UniMessage().image(raw=screenshot).export() + ) \ No newline at end of file diff --git a/poetry.lock b/poetry.lock index 6924f11..1e1d599 100644 --- a/poetry.lock +++ b/poetry.lock @@ -989,6 +989,79 @@ type = "legacy" url = "https://pypi.tuna.tsinghua.edu.cn/simple" reference = "mirrors" +[[package]] +name = "greenlet" +version = "3.2.4" +description = "Lightweight in-process concurrent programming" +optional = false +python-versions = ">=3.9" +groups = ["main"] +files = [ + {file = "greenlet-3.2.4-cp310-cp310-macosx_11_0_universal2.whl", hash = "sha256:8c68325b0d0acf8d91dde4e6f930967dd52a5302cd4062932a6b2e7c2969f47c"}, + {file = "greenlet-3.2.4-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:94385f101946790ae13da500603491f04a76b6e4c059dab271b3ce2e283b2590"}, + {file = "greenlet-3.2.4-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:f10fd42b5ee276335863712fa3da6608e93f70629c631bf77145021600abc23c"}, + {file = "greenlet-3.2.4-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:c8c9e331e58180d0d83c5b7999255721b725913ff6bc6cf39fa2a45841a4fd4b"}, + {file = "greenlet-3.2.4-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:58b97143c9cc7b86fc458f215bd0932f1757ce649e05b640fea2e79b54cedb31"}, + {file = "greenlet-3.2.4-cp310-cp310-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c2ca18a03a8cfb5b25bc1cbe20f3d9a4c80d8c3b13ba3df49ac3961af0b1018d"}, + {file = "greenlet-3.2.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:9fe0a28a7b952a21e2c062cd5756d34354117796c6d9215a87f55e38d15402c5"}, + {file = "greenlet-3.2.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:8854167e06950ca75b898b104b63cc646573aa5fef1353d4508ecdd1ee76254f"}, + {file = "greenlet-3.2.4-cp310-cp310-win_amd64.whl", hash = "sha256:73f49b5368b5359d04e18d15828eecc1806033db5233397748f4ca813ff1056c"}, + {file = "greenlet-3.2.4-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:96378df1de302bc38e99c3a9aa311967b7dc80ced1dcc6f171e99842987882a2"}, + {file = "greenlet-3.2.4-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:1ee8fae0519a337f2329cb78bd7a8e128ec0f881073d43f023c7b8d4831d5246"}, + {file = "greenlet-3.2.4-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:94abf90142c2a18151632371140b3dba4dee031633fe614cb592dbb6c9e17bc3"}, + {file = "greenlet-3.2.4-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:4d1378601b85e2e5171b99be8d2dc85f594c79967599328f95c1dc1a40f1c633"}, + {file = "greenlet-3.2.4-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:0db5594dce18db94f7d1650d7489909b57afde4c580806b8d9203b6e79cdc079"}, + {file = "greenlet-3.2.4-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2523e5246274f54fdadbce8494458a2ebdcdbc7b802318466ac5606d3cded1f8"}, + {file = "greenlet-3.2.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:1987de92fec508535687fb807a5cea1560f6196285a4cde35c100b8cd632cc52"}, + {file = "greenlet-3.2.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:55e9c5affaa6775e2c6b67659f3a71684de4c549b3dd9afca3bc773533d284fa"}, + {file = "greenlet-3.2.4-cp311-cp311-win_amd64.whl", hash = "sha256:9c40adce87eaa9ddb593ccb0fa6a07caf34015a29bf8d344811665b573138db9"}, + {file = "greenlet-3.2.4-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:3b67ca49f54cede0186854a008109d6ee71f66bd57bb36abd6d0a0267b540cdd"}, + {file = "greenlet-3.2.4-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:ddf9164e7a5b08e9d22511526865780a576f19ddd00d62f8a665949327fde8bb"}, + {file = "greenlet-3.2.4-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:f28588772bb5fb869a8eb331374ec06f24a83a9c25bfa1f38b6993afe9c1e968"}, + {file = "greenlet-3.2.4-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:5c9320971821a7cb77cfab8d956fa8e39cd07ca44b6070db358ceb7f8797c8c9"}, + {file = "greenlet-3.2.4-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:c60a6d84229b271d44b70fb6e5fa23781abb5d742af7b808ae3f6efd7c9c60f6"}, + {file = "greenlet-3.2.4-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3b3812d8d0c9579967815af437d96623f45c0f2ae5f04e366de62a12d83a8fb0"}, + {file = "greenlet-3.2.4-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:abbf57b5a870d30c4675928c37278493044d7c14378350b3aa5d484fa65575f0"}, + {file = "greenlet-3.2.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:20fb936b4652b6e307b8f347665e2c615540d4b42b3b4c8a321d8286da7e520f"}, + {file = "greenlet-3.2.4-cp312-cp312-win_amd64.whl", hash = "sha256:a7d4e128405eea3814a12cc2605e0e6aedb4035bf32697f72deca74de4105e02"}, + {file = "greenlet-3.2.4-cp313-cp313-macosx_11_0_universal2.whl", hash = "sha256:1a921e542453fe531144e91e1feedf12e07351b1cf6c9e8a3325ea600a715a31"}, + {file = "greenlet-3.2.4-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:cd3c8e693bff0fff6ba55f140bf390fa92c994083f838fece0f63be121334945"}, + {file = "greenlet-3.2.4-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:710638eb93b1fa52823aa91bf75326f9ecdfd5e0466f00789246a5280f4ba0fc"}, + {file = "greenlet-3.2.4-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:c5111ccdc9c88f423426df3fd1811bfc40ed66264d35aa373420a34377efc98a"}, + {file = "greenlet-3.2.4-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:d76383238584e9711e20ebe14db6c88ddcedc1829a9ad31a584389463b5aa504"}, + {file = "greenlet-3.2.4-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:23768528f2911bcd7e475210822ffb5254ed10d71f4028387e5a99b4c6699671"}, + {file = "greenlet-3.2.4-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:00fadb3fedccc447f517ee0d3fd8fe49eae949e1cd0f6a611818f4f6fb7dc83b"}, + {file = "greenlet-3.2.4-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:d25c5091190f2dc0eaa3f950252122edbbadbb682aa7b1ef2f8af0f8c0afefae"}, + {file = "greenlet-3.2.4-cp313-cp313-win_amd64.whl", hash = "sha256:554b03b6e73aaabec3745364d6239e9e012d64c68ccd0b8430c64ccc14939a8b"}, + {file = "greenlet-3.2.4-cp314-cp314-macosx_11_0_universal2.whl", hash = "sha256:49a30d5fda2507ae77be16479bdb62a660fa51b1eb4928b524975b3bde77b3c0"}, + {file = "greenlet-3.2.4-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:299fd615cd8fc86267b47597123e3f43ad79c9d8a22bebdce535e53550763e2f"}, + {file = "greenlet-3.2.4-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:c17b6b34111ea72fc5a4e4beec9711d2226285f0386ea83477cbb97c30a3f3a5"}, + {file = "greenlet-3.2.4-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:b4a1870c51720687af7fa3e7cda6d08d801dae660f75a76f3845b642b4da6ee1"}, + {file = "greenlet-3.2.4-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:061dc4cf2c34852b052a8620d40f36324554bc192be474b9e9770e8c042fd735"}, + {file = "greenlet-3.2.4-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:44358b9bf66c8576a9f57a590d5f5d6e72fa4228b763d0e43fee6d3b06d3a337"}, + {file = "greenlet-3.2.4-cp314-cp314-win_amd64.whl", hash = "sha256:e37ab26028f12dbb0ff65f29a8d3d44a765c61e729647bf2ddfbbed621726f01"}, + {file = "greenlet-3.2.4-cp39-cp39-macosx_11_0_universal2.whl", hash = "sha256:b6a7c19cf0d2742d0809a4c05975db036fdff50cd294a93632d6a310bf9ac02c"}, + {file = "greenlet-3.2.4-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:27890167f55d2387576d1f41d9487ef171849ea0359ce1510ca6e06c8bece11d"}, + {file = "greenlet-3.2.4-cp39-cp39-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:18d9260df2b5fbf41ae5139e1be4e796d99655f023a636cd0e11e6406cca7d58"}, + {file = "greenlet-3.2.4-cp39-cp39-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:671df96c1f23c4a0d4077a325483c1503c96a1b7d9db26592ae770daa41233d4"}, + {file = "greenlet-3.2.4-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:16458c245a38991aa19676900d48bd1a6f2ce3e16595051a4db9d012154e8433"}, + {file = "greenlet-3.2.4-cp39-cp39-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c9913f1a30e4526f432991f89ae263459b1c64d1608c0d22a5c79c287b3c70df"}, + {file = "greenlet-3.2.4-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:b90654e092f928f110e0007f572007c9727b5265f7632c2fa7415b4689351594"}, + {file = "greenlet-3.2.4-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:81701fd84f26330f0d5f4944d4e92e61afe6319dcd9775e39396e39d7c3e5f98"}, + {file = "greenlet-3.2.4-cp39-cp39-win32.whl", hash = "sha256:65458b409c1ed459ea899e939f0e1cdb14f58dbc803f2f93c5eab5694d32671b"}, + {file = "greenlet-3.2.4-cp39-cp39-win_amd64.whl", hash = "sha256:d2e685ade4dafd447ede19c31277a224a239a0a1a4eca4e6390efedf20260cfb"}, + {file = "greenlet-3.2.4.tar.gz", hash = "sha256:0dca0d95ff849f9a364385f36ab49f50065d76964944638be9691e1832e9f86d"}, +] + +[package.extras] +docs = ["Sphinx", "furo"] +test = ["objgraph", "psutil", "setuptools"] + +[package.source] +type = "legacy" +url = "https://pypi.tuna.tsinghua.edu.cn/simple" +reference = "mirrors" + [[package]] name = "h11" version = "0.16.0" @@ -2127,6 +2200,37 @@ type = "legacy" url = "https://pypi.tuna.tsinghua.edu.cn/simple" reference = "mirrors" +[[package]] +name = "opencc" +version = "1.1.9" +description = "Conversion between Traditional and Simplified Chinese" +optional = false +python-versions = "*" +groups = ["main"] +files = [ + {file = "OpenCC-1.1.9-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:a33941dd4cb67457e6f44dfe36dddc30a602363a4f6a29b41d79b062b332c094"}, + {file = "OpenCC-1.1.9-cp310-cp310-manylinux2014_x86_64.whl", hash = "sha256:92769f9a60301574c73096f9ab8a9060fe0d13a9f8266735d82a2a3a92adbd26"}, + {file = "OpenCC-1.1.9-cp310-cp310-win_amd64.whl", hash = "sha256:84e35e5ecfad445a64c0dcd6567d9e9f3a6aed9a6ffd89cdbc071f36cb9e089e"}, + {file = "OpenCC-1.1.9-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3fb7c84f7c182cb5208e7bc1c104b817a3ca1a8fe111d4d19816be0d6e1ab396"}, + {file = "OpenCC-1.1.9-cp311-cp311-manylinux2014_x86_64.whl", hash = "sha256:64994c68796d93cdba42f37e0c073fb8ed6f9d6707232be0ba84f24dc5a36bbb"}, + {file = "OpenCC-1.1.9-cp311-cp311-win_amd64.whl", hash = "sha256:9f6a1413ca2ff490e65a55822e4cae8c3f104bfab46355288de4893a14470fbb"}, + {file = "OpenCC-1.1.9-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:48bc3e37942b91a9cf51f525631792f79378e5332bdba9e10c05f6e7fe9036ca"}, + {file = "OpenCC-1.1.9-cp312-cp312-manylinux2014_x86_64.whl", hash = "sha256:1c5d1489bdaf9dc2865f0ea30eb565093253e73c1868d9c19554c8a044b545d4"}, + {file = "OpenCC-1.1.9-cp312-cp312-win_amd64.whl", hash = "sha256:64f8d22c8505b65e8ee2d6e73241cbc92785d38b3c93885b423d7c4fcd31c679"}, + {file = "OpenCC-1.1.9-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:f4267b66ed6e656b5d8199f94e9673950ac39d49ebaf0e7927330801f06f038f"}, + {file = "OpenCC-1.1.9-cp38-cp38-manylinux2014_x86_64.whl", hash = "sha256:c6d5f9756ed08e67de36c53dc4d8f0bdc72889d6f57a8fc4d8b073d99c58d4dc"}, + {file = "OpenCC-1.1.9-cp38-cp38-win_amd64.whl", hash = "sha256:a6c2650bd3d6a9e3c31fc2057e0f36122c9507af1661627542f618c97d420293"}, + {file = "OpenCC-1.1.9-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:4d66473405c2e360ef346fe1625f201f3f3c4adbb16d5c1c7749a150ae42d875"}, + {file = "OpenCC-1.1.9-cp39-cp39-manylinux2014_x86_64.whl", hash = "sha256:436c43e0855b4f9c9e4fd1191e8ac638e9d9f2c7e2d5753952e6e31aa231d36c"}, + {file = "OpenCC-1.1.9-cp39-cp39-win_amd64.whl", hash = "sha256:b4c36d6974afd94b444ad5ad17364f40d228092ce89b86e46653f7ff38075201"}, + {file = "opencc-1.1.9.tar.gz", hash = "sha256:8ad72283732951303390fae33a1ceda98ac9b03368a8f2912edc934d74077e4a"}, +] + +[package.source] +type = "legacy" +url = "https://pypi.tuna.tsinghua.edu.cn/simple" +reference = "mirrors" + [[package]] name = "opencv-python-headless" version = "4.12.0.88" @@ -2304,6 +2408,33 @@ type = "legacy" url = "https://pypi.tuna.tsinghua.edu.cn/simple" reference = "mirrors" +[[package]] +name = "playwright" +version = "1.55.0" +description = "A high-level API to automate web browsers" +optional = false +python-versions = ">=3.9" +groups = ["main"] +files = [ + {file = "playwright-1.55.0-py3-none-macosx_10_13_x86_64.whl", hash = "sha256:d7da108a95001e412effca4f7610de79da1637ccdf670b1ae3fdc08b9694c034"}, + {file = "playwright-1.55.0-py3-none-macosx_11_0_arm64.whl", hash = "sha256:8290cf27a5d542e2682ac274da423941f879d07b001f6575a5a3a257b1d4ba1c"}, + {file = "playwright-1.55.0-py3-none-macosx_11_0_universal2.whl", hash = "sha256:25b0d6b3fd991c315cca33c802cf617d52980108ab8431e3e1d37b5de755c10e"}, + {file = "playwright-1.55.0-py3-none-manylinux1_x86_64.whl", hash = "sha256:c6d4d8f6f8c66c483b0835569c7f0caa03230820af8e500c181c93509c92d831"}, + {file = "playwright-1.55.0-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:29a0777c4ce1273acf90c87e4ae2fe0130182100d99bcd2ae5bf486093044838"}, + {file = "playwright-1.55.0-py3-none-win32.whl", hash = "sha256:29e6d1558ad9d5b5c19cbec0a72f6a2e35e6353cd9f262e22148685b86759f90"}, + {file = "playwright-1.55.0-py3-none-win_amd64.whl", hash = "sha256:7eb5956473ca1951abb51537e6a0da55257bb2e25fc37c2b75af094a5c93736c"}, + {file = "playwright-1.55.0-py3-none-win_arm64.whl", hash = "sha256:012dc89ccdcbd774cdde8aeee14c08e0dd52ddb9135bf10e9db040527386bd76"}, +] + +[package.dependencies] +greenlet = ">=3.1.1,<4.0.0" +pyee = ">=13,<14" + +[package.source] +type = "legacy" +url = "https://pypi.tuna.tsinghua.edu.cn/simple" +reference = "mirrors" + [[package]] name = "propcache" version = "0.3.2" @@ -2417,23 +2548,6 @@ type = "legacy" url = "https://pypi.tuna.tsinghua.edu.cn/simple" reference = "mirrors" -[[package]] -name = "ptimeparse" -version = "0.2.1" -description = "一个用于解析中文的时间表达的库" -optional = false -python-versions = ">=3.9" -groups = ["main"] -files = [ - {file = "ptimeparse-0.2.1-py3-none-any.whl", hash = "sha256:cf1115784d5d983da2d5b7af327108bf04c218c795d63291e71f76d7c6ffd2d4"}, - {file = "ptimeparse-0.2.1.tar.gz", hash = "sha256:9b640e0a315d19b1e3821a290d236a051d8320348970ce3a835ed675bd2d832f"}, -] - -[package.source] -type = "legacy" -url = "https://gitea.service.jazzwhom.top/api/packages/Passthem/pypi/simple" -reference = "pt-gitea-pypi" - [[package]] name = "pybind11" version = "3.0.1" @@ -2732,6 +2846,29 @@ type = "legacy" url = "https://pypi.tuna.tsinghua.edu.cn/simple" reference = "mirrors" +[[package]] +name = "pyee" +version = "13.0.0" +description = "A rough port of Node.js's EventEmitter to Python with a few tricks of its own" +optional = false +python-versions = ">=3.8" +groups = ["main"] +files = [ + {file = "pyee-13.0.0-py3-none-any.whl", hash = "sha256:48195a3cddb3b1515ce0695ed76036b5ccc2ef3a9f963ff9f77aec0139845498"}, + {file = "pyee-13.0.0.tar.gz", hash = "sha256:b391e3c5a434d1f5118a25615001dbc8f669cf410ab67d04c4d4e07c55481c37"}, +] + +[package.dependencies] +typing-extensions = "*" + +[package.extras] +dev = ["black", "build", "flake8", "flake8-black", "isort", "jupyter-console", "mkdocs", "mkdocs-include-markdown-plugin", "mkdocstrings[python]", "mypy", "pytest", "pytest-asyncio ; python_version >= \"3.4\"", "pytest-trio ; python_version >= \"3.7\"", "sphinx", "toml", "tox", "trio", "trio ; python_version > \"3.6\"", "trio-typing ; python_version > \"3.6\"", "twine", "twisted", "validate-pyproject[all]"] + +[package.source] +type = "legacy" +url = "https://pypi.tuna.tsinghua.edu.cn/simple" +reference = "mirrors" + [[package]] name = "pygments" version = "2.19.2" @@ -3824,4 +3961,4 @@ reference = "mirrors" [metadata] lock-version = "2.1" python-versions = ">=3.12,<4.0" -content-hash = "96080ea588b3ac52b19909379585cd647646faf3dce291f8d2b5801a3111c838" +content-hash = "7626e042aad856cc91a6b474b3068aee6eb0ceb11a44b4f2c8dc9ece51c9cd5f" diff --git a/pyproject.toml b/pyproject.toml index 8be0415..5e8f7a3 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -25,6 +25,8 @@ dependencies = [ "qrcode (>=8.2,<9.0)", "ptimeparse (>=0.2.1,<0.3.0)", "nanoid (>=2.0.0,<3.0.0)", + "opencc (>=1.1.9,<2.0.0)", + "playwright (>=1.55.0,<2.0.0)", ] [build-system]