diff --git a/assets/img/dog/haha_dog.jpg b/assets/img/dog/haha_dog.jpg new file mode 100644 index 0000000..2a4cc2e Binary files /dev/null and b/assets/img/dog/haha_dog.jpg differ 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]