async def rub(operator_image: Union[int, str], target_image: Union[int, str]) -> MessageItem: user_locs = [(39, 91, 75, 75, 0), (49, 101, 75, 75, 0), (67, 98, 75, 75, 0), (55, 86, 75, 75, 0), (61, 109, 75, 75, 0), (65, 101, 75, 75, 0)] self_locs = [(102, 95, 70, 80, 0), (108, 60, 50, 100, 0), (97, 18, 65, 95, 0), (65, 5, 75, 75, -20), (95, 57, 100, 55, -70), (109, 107, 65, 75, 0)] frames = [] self_img = await AvatarFunPic.get_pil_avatar(operator_image) user_img = await AvatarFunPic.get_pil_avatar(target_image) for i in range(6): frame = IMG.open(f'{os.getcwd()}/statics/RubFrames/frame{i}.png').convert('RGBA') x, y, w, h, angle = user_locs[i] user_img_new = (await AvatarFunPic.resize_img(user_img, w, h, angle)).convert("RGBA") frame.paste(user_img_new, (x, y), mask=user_img_new) x, y, w, h, angle = self_locs[i] self_img_new = (await AvatarFunPic.resize_img(self_img, w, h, angle)).convert("RGBA") frame.paste(self_img_new, (x, y), mask=self_img_new) frames.append(frame) output = BytesIO() imageio.mimsave(output, frames, format='gif', duration=0.05) return MessageItem(MessageChain.create([Image(data_bytes=output.getvalue())]), Normal())
async def send_newspaper(app: Ariadne): image_content = None for i in range(3): try: image_content = await get_image() break except Exception as e: logger.error(f"第 {i + 1} 次日报加载失败\n{e}") await asyncio.sleep(3) if not image_content: return logger.error("日报获取失败!") for group in await app.getGroupList(): if not await group_setting.get_setting(group, Setting.daily_newspaper): continue try: await app.sendMessage( group, MessageChain.create(Image(data_bytes=image_content))) except AccountMuted: continue await asyncio.sleep(random.randint(3, 6))
async def query_resource(resource_name: str) -> Optional[MessageChain]: global CENTER_POINT planning_route: bool = False if resource_name and resource_name[-2:] in ["路径", "路线"]: resource_name = resource_name[:-2].strip() planning_route = True if not resource_name or resource_name not in resource_name_list: return None map_ = Map(resource_name, CENTER_POINT, planning_route=planning_route, ratio=MAP_RATIO) count = map_.get_resource_count() rand = await asyncio.get_event_loop().run_in_executor( None, map_.generate_resource_icon_in_map) return MessageChain.create([ Image(path=str(IMAGE_PATH / "genshin" / "temp" / f"genshin_map_{rand}.png")), Plain(text=f"\n\n※ {resource_name} 一共找到 {count} 个位置点\n※ 数据来源于米游社wiki") ])
def gacha_10(self) -> MessageChain: # 抽10连 if not (self.pool in POOL.keys()): return MessageChain('当前卡池已结束,请使用 原神卡池切换 切换其他卡池') gacha_txt = "" for self.current_times in range(10): new_gacha = self.gacha_one() self.gacha_list.append(new_gacha) gacha_txt += new_gacha gacha_txt += self.is_star(new_gacha) if (self.current_times + 1) % 5 == 0: gacha_txt += '\n' self.add_gacha_all_statistics(new_gacha) # 把所有抽卡结果添加到gacha_all_statistics用于最后统计 self.update_last(new_gacha) # 更新第一次抽到的计数 mes = [ Plain(text='本次祈愿得到以下角色装备:\n'), Image(data_bytes=self.pic2bytes(self.concat_pic())), Plain(text=f'\n{gacha_txt}') ] if self.last_4: # 如果self.last_4为0表示没有抽到,这句话就不写了,下边3个判断标准一样 mes.append(Plain(text=f'第 {self.last_4} 抽首次出现4★!\n')) if self.last_4_up: mes.append(Plain(text=f'第 {self.last_4_up} 抽首次出现4★UP!\n')) if self.last_5: mes.append(Plain(text=f'第 {self.last_5} 抽首次出现5★!\n')) if self.last_5_up: mes.append(Plain(text=f'第 {self.last_5_up} 抽首次出现5★UP!\n')) mes.append(Plain(text=f"\n* 本次抽取卡池为 {self.pool} \n* 发送 原神卡池切换 可切换卡池")) return MessageChain.create(mes)
def gacha_info(pool=DEFAULT_POOL) -> MessageChain: # UP角色信息 info_txt = f'当前卡池为 {pool} ,UP信息如下:\n' up_info = [] for _5_star in POOL[pool]['5_star_UP']: im = IMG.open(Gacha.get_png_path(_5_star)) im = Gacha.pic2bytes(im) up_info.append(Image(data_bytes=im)) up_info.append(Plain(text=f"\n{_5_star} ★★★★★")) for _4_star in POOL[pool]['4_star_UP']: im = IMG.open(Gacha.get_png_path(_4_star)) im = Gacha.pic2bytes(im) up_info.append(Image(data_bytes=im)) up_info.append(Plain(text=f"\n{_4_star} ★★★★")) if not up_info: # 如果up_info是空的,表示当前是常驻池没有UP up_info.append(Plain(text="常驻池没有UP")) return MessageChain.create([Plain(text=info_txt)] + up_info)
async def bot_join_group_event(app: Ariadne, group: Group): logger.info(f"机器人加入群组 <{group.name}>") try: await orm.insert_or_update(Setting, [Setting.group_id == group.id], { "group_id": group.id, "group_name": group.name, "active": True }) await orm.insert_or_update(UserPermission, [ UserPermission.member_id == config.host_qq, UserPermission.group_id == group.id ], { "member_id": config.host_qq, "group_id": group.id, "level": 4 }) GlobalFrequencyLimitDict().add_group(group.id) await app.sendMessage( group, MessageChain.create([Plain(text="欸嘿嘿~我来啦!宇宙无敌小可爱纱雾酱华丽登场!")])) except AccountMuted: pass
async def get_info(event: MessageEvent, app: Ariadne, server_address: str): try: server = mcstatus.MinecraftServer.lookup(server_address) stat = await server.async_status() players: Optional[List[ PingResponse.Players.Player]] = stat.players.sample except Exception as e: reply = MessageChain.create([ Plain(f"{repr(e)}"), ]) else: reply = MessageChain([ Plain(f"{server_address} 状态\n"), Image(base64=stat.favicon.removeprefix("data:image/png;base64,")) if stat.favicon is not None else Plain(""), Plain(f"玩家数:{stat.players.online}/{stat.players.max}\n" f"延迟:{stat.latency}ms\n" "在线玩家:\n"), Plain("\n".join(i.name for i in players) if players is not None else "无"), ]) del server, stat await app.sendMessage(event, reply)
async def kiss(operator_image: Union[int, str], target_image: Union[int, str]) -> MessageItem: """ Author: https://github.com/SuperWaterGod """ gif_frames = [] operator = await AvatarFunPic.get_pil_avatar(operator_image) target = await AvatarFunPic.get_pil_avatar(target_image) operator = operator.resize((40, 40), IMG.ANTIALIAS) size = operator.size r2 = min(size[0], size[1]) circle = IMG.new('L', (r2, r2), 0) draw = ImageDraw.Draw(circle) draw.ellipse((0, 0, r2, r2), fill=255) alpha = IMG.new('L', (r2, r2), 255) alpha.paste(circle, (0, 0)) operator.putalpha(alpha) target = target.resize((50, 50), IMG.ANTIALIAS) size = target.size r2 = min(size[0], size[1]) circle = IMG.new('L', (r2, r2), 0) draw = ImageDraw.Draw(circle) draw.ellipse((0, 0, r2, r2), fill=255) alpha = IMG.new('L', (r2, r2), 255) alpha.paste(circle, (0, 0)) target.putalpha(alpha) md5 = hashlib.md5(str(str(operator_image) + str(target_image)).encode("utf-8")).hexdigest() for i in range(1, 14): gif_frames.append(await AvatarFunPic.kiss_make_frame(operator, target, i)) await AvatarFunPic.save_gif(gif_frames, f"{os.getcwd()}/statics/temp/tempKiss-{md5}.gif", fps=25) with open(f"{os.getcwd()}/statics/temp/tempKiss-{md5}.gif", 'rb') as r: img_content = r.read() os.remove(f"{os.getcwd()}/statics/temp/tempKiss-{md5}.gif") return MessageItem(MessageChain.create([Image(data_bytes=img_content)]), Normal())
async def petpet(image: Union[int, str], flip=False, squish=0, fps=20) -> MessageItem: """生成PetPet 将输入的头像生成为所需的 PetPet 并输出 参数 path: str 为头像路径 flip: bool 为是否横向反转头像 squish: float 为一个 [0, 1] 之间的数,为挤压量 fps: int 为输出 gif 每秒显示的帧数 返回 bool 但是会输出一个符合参数的 gif """ gif_frames = [] avatar = await AvatarFunPic.get_pil_avatar(image) # 生成每一帧 for i in range(5): gif_frames.append(await AvatarFunPic.make_frame(avatar, i, squish=squish, flip=flip)) if not os.path.exists(f"{os.getcwd()}/statics/temp/"): os.mkdir(f"{os.getcwd()}/statics/temp/") md5 = hashlib.md5(str(image).encode("utf-8")).hexdigest() await AvatarFunPic.save_gif(gif_frames, f"{os.getcwd()}/statics/temp/tempPetPet-{md5}.gif", fps=fps) with open(f"{os.getcwd()}/statics/temp/tempPetPet-{md5}.gif", "rb") as r: image_bytes = r.read() os.remove(f"{os.getcwd()}/statics/temp/tempPetPet-{md5}.gif") return MessageItem(MessageChain.create([Image(data_bytes=image_bytes)]), Normal())
async def handle(app: Ariadne, message: MessageChain, group: Group, member: Member): if re.match(pattern, message.asDisplay()): if not await user_permission_require(group, member, 2): return MessageItem( MessageChain.create([Plain(text="你没有权限,爬!")]), Normal()) image_type = re.findall(r"添加(.*?)图片.*(\[图片].*)+", message.asDisplay(), re.S)[0][0] if image_type not in legal_type: return MessageItem( MessageChain.create([ Plain( text=f"非法图片类型!\n合法image_type:{'、'.join(legal_type)}" ) ]), QuoteSource()) if path := image_paths.get(image_type): if os.path.exists(path): try: await ImageAdder.add_image(path, message.get(Image)) except: logger.error(traceback.format_exc()) return MessageItem( MessageChain.create( [Plain(text="出错了呐~请查看日志/控制台输出!")]), Normal()) return MessageItem( MessageChain.create([ Plain( text=f"保存成功!共保存了{len(message.get(Image))}张图片!") ]), Normal()) else: return MessageItem( MessageChain.create([ Image( path= f"{os.getcwd()}/statics/error/path_not_exists.png" ) ]), QuoteSource()) else: return MessageItem( MessageChain.create([Plain(text=f"无{image_type}项!请检查配置!") ]), QuoteSource())
async def nudge_event(app: Ariadne, event: NudgeEvent): if event.group_id and not await group_setting.get_setting( event.group_id, Setting.switch): return None if event.target == config.bot_qq: if event.context_type == "group": if member := await app.getMember(event.group_id, event.supplicant): logger.info( f"机器人被群 <{member.group.name}> 中用户 <{member.name}> 戳了戳。") if member.group.id in nudge_info.keys(): if member.id in nudge_info[member.group.id].keys(): period = nudge_info[member.group.id][ member.id]["time"] + relativedelta(minutes=1) if datetime.now() >= period: nudge_info[member.group.id][member.id] = { "count": 0, "time": datetime.now() } count = nudge_info[member.group.id][ member.id]["count"] + 1 if count == 1: try: await app.sendNudge(member) except: pass nudge_info[member.group.id][member.id] = { "count": count, "time": datetime.now() } elif count == 2: try: await app.sendNudge(member) await app.sendMessage( member.group, MessageChain.create([Plain(text=f"不许戳了!") ])) except: pass nudge_info[member.group.id][member.id] = { "count": count, "time": datetime.now() } elif count == 3: try: await app.sendNudge(member) await app.sendMessage( member.group, MessageChain.create( [Plain(text=f"说了不许再戳了!")])) except: pass nudge_info[member.group.id][member.id] = { "count": count, "time": datetime.now() } elif count == 4: try: await app.sendNudge(member) except: pass nudge_info[member.group.id][member.id] = { "count": count, "time": datetime.now() } elif count == 5: try: await app.sendNudge(member) await app.sendMessage( member.group, MessageChain.create( [Plain(text=f"呜呜呜你欺负我,不理你了!")])) except: pass nudge_info[member.group.id][member.id] = { "count": count, "time": datetime.now() } elif 6 <= count <= 9: nudge_info[member.group.id][member.id] = { "count": count, "time": datetime.now() } elif count == 10: try: await app.sendNudge(member) await app.sendMessage( member.group, MessageChain.create( [Plain(text="你真的很有耐心欸。")])) except: pass else: nudge_info[member.group.id][member.id] = { "count": 1, "time": datetime.now() } await app.sendNudge(member) else: nudge_info[member.group.id] = { member.id: { "count": 1, "time": datetime.now() } } await app.sendNudge(member) else: if friend := await app.getFriend(event.supplicant): logger.info(f"机器人被好友 <{friend.nickname}> 戳了戳。")
async def grass_spammer(app: Ariadne, group: Group, msg: MessageChain): disable_in_groups: List[int] = [qq.littleskin_main, qq.csl_group] if not group.id in disable_in_groups: await app.sendGroupMessage(group, MessageChain.create( [Plain('\u202e草')]))
async def bot_leave_event_kick(app: Ariadne, event: BotLeaveEventKick): logger.warning("bot has been kicked!") await app.sendFriendMessage( config.host_qq, MessageChain.create([Plain(text=f"呜呜呜主人我被踢出{event.group.name}群了")]))
async def wrong_usage_tips(app: Ariadne, group: Group, messagechain: MessageChain): msg_text = messagechain.asDisplay() if msg_text.startswith(('&mute ', '&unmute')) and msg_text.endswith(' '): await app.sendGroupMessage(group, MessageChain.create([Plain('请删除末尾空格后重试')]))
async def real_handle(self, app: Ariadne, message: MessageChain, group: Group = None, member: Member = None, friend: Friend = None) -> MessageItem: commands = { "enable": { "permission": [3, []], "permission_nl": "3 级权限", "manual": "/github-watch enable", "description": "启用 Github 订阅功能", "func": self.enable }, "disable": { "permission": [3, []], "permission_nl": "3 级权限", "manual": "/github-watch disable", "description": "禁用 Github 订阅功能", "func": self.disable }, "add": { "permission": [2, (MemberPerm.Administrator, MemberPerm.Owner)], "permission_nl": "2 级或群管理员及以上权限", "manual": "/github-watch add {repo} [repo]+", "description": "订阅仓库变动,可同时订阅多个仓库", "func": self.add }, "remove": { "permission": [2, (MemberPerm.Administrator, MemberPerm.Owner)], "permission_nl": "2 级或群管理员及以上权限", "manual": "/github-watch remove {repo} [repo]+", "description": "取消订阅仓库变动,可同时取消订阅多个仓库", "func": self.remove }, "check": { "permission": [ 1, (MemberPerm.Member, MemberPerm.Administrator, MemberPerm.Owner) ], "permission_nl": "任何人", "manual": "/github-watch check", "description": "手动查看仓库订阅列表", "func": self.check }, "cache": { "permission": [2, (MemberPerm.Administrator, MemberPerm.Owner)], "permission_nl": "2 级或群管理员及以上权限", "manual": "/github-watch cache {update/store}", "description": "更新/储存缓存", "func": self.cache } } if message.asDisplay().startswith("/github-watch"): if not self.initialize: self.update_cache() for repo in self.__cached.keys(): self.__cached[repo]['enabled'] = True self.store_cache() self.initialize = True args = message.asDisplay().split(" ", maxsplit=1) if len(args) == 1: msg = [Plain(text="缺少参数\n\n")] for func in commands.keys(): msg.append( Plain(text=( f"/github-watch {func}\n" f" 描述:{commands[func]['description']}\n" f" 用法:{commands[func]['manual']}\n" f" 权限:{commands[func]['permission_nl']}\n"))) return MessageItem( await MessageChainUtils.messagechain_to_img( MessageChain.create(msg)), QuoteSource()) _, args = args name = args.split(" ", maxsplit=1)[0] arg = ''.join(args.split(" ", maxsplit=1)[1:]) if name not in commands.keys(): return MessageItem( MessageChain.create([Plain(text=f"未知指令:{arg}")]), QuoteSource()) if member and group: permission = commands[name]['permission'] if not await user_permission_require(group, member, permission[0]) \ and not (member.permission in permission[1]): return MessageItem( MessageChain.create([ Plain( text=f"权限不足,你需要 {permission[0]} 级权限" f"{('或来自 ' + str(permission[1][0]) + ' 的权限') if permission[1] else ''}" ) ]), QuoteSource()) arg = arg.strip() return MessageItem( await commands[name]['func'](app=app, group=group, friend=friend, arg=arg), QuoteSource())
class GithubWatcher(object): __name__ = "GithubWatcher" __description__ = "Github 订阅 Handler" __usage__ = "None" __cached = {} if config.functions['github'][ 'username'] != "username" and config.functions['github'][ 'token'] != 'token': __auth = True __session = aiohttp.ClientSession( auth=BasicAuth(login=config.functions['github']['username'], password=config.functions['github']['token'])) else: __auth = False __session = aiohttp.ClientSession() __first_warned = False __status = True __base_url = "https://api.github.com" __events_url = "/repos/{owner}/{repo}/events" __is_running = False initialize = False @switch() @blacklist() async def real_handle(self, app: Ariadne, message: MessageChain, group: Group = None, member: Member = None, friend: Friend = None) -> MessageItem: commands = { "enable": { "permission": [3, []], "permission_nl": "3 级权限", "manual": "/github-watch enable", "description": "启用 Github 订阅功能", "func": self.enable }, "disable": { "permission": [3, []], "permission_nl": "3 级权限", "manual": "/github-watch disable", "description": "禁用 Github 订阅功能", "func": self.disable }, "add": { "permission": [2, (MemberPerm.Administrator, MemberPerm.Owner)], "permission_nl": "2 级或群管理员及以上权限", "manual": "/github-watch add {repo} [repo]+", "description": "订阅仓库变动,可同时订阅多个仓库", "func": self.add }, "remove": { "permission": [2, (MemberPerm.Administrator, MemberPerm.Owner)], "permission_nl": "2 级或群管理员及以上权限", "manual": "/github-watch remove {repo} [repo]+", "description": "取消订阅仓库变动,可同时取消订阅多个仓库", "func": self.remove }, "check": { "permission": [ 1, (MemberPerm.Member, MemberPerm.Administrator, MemberPerm.Owner) ], "permission_nl": "任何人", "manual": "/github-watch check", "description": "手动查看仓库订阅列表", "func": self.check }, "cache": { "permission": [2, (MemberPerm.Administrator, MemberPerm.Owner)], "permission_nl": "2 级或群管理员及以上权限", "manual": "/github-watch cache {update/store}", "description": "更新/储存缓存", "func": self.cache } } if message.asDisplay().startswith("/github-watch"): if not self.initialize: self.update_cache() for repo in self.__cached.keys(): self.__cached[repo]['enabled'] = True self.store_cache() self.initialize = True args = message.asDisplay().split(" ", maxsplit=1) if len(args) == 1: msg = [Plain(text="缺少参数\n\n")] for func in commands.keys(): msg.append( Plain(text=( f"/github-watch {func}\n" f" 描述:{commands[func]['description']}\n" f" 用法:{commands[func]['manual']}\n" f" 权限:{commands[func]['permission_nl']}\n"))) return MessageItem( await MessageChainUtils.messagechain_to_img( MessageChain.create(msg)), QuoteSource()) _, args = args name = args.split(" ", maxsplit=1)[0] arg = ''.join(args.split(" ", maxsplit=1)[1:]) if name not in commands.keys(): return MessageItem( MessageChain.create([Plain(text=f"未知指令:{arg}")]), QuoteSource()) if member and group: permission = commands[name]['permission'] if not await user_permission_require(group, member, permission[0]) \ and not (member.permission in permission[1]): return MessageItem( MessageChain.create([ Plain( text=f"权限不足,你需要 {permission[0]} 级权限" f"{('或来自 ' + str(permission[1][0]) + ' 的权限') if permission[1] else ''}" ) ]), QuoteSource()) arg = arg.strip() return MessageItem( await commands[name]['func'](app=app, group=group, friend=friend, arg=arg), QuoteSource()) async def enable(self, **kwargs): self.__status = True return MessageChain.create([Plain(text="已开启 Github 仓库订阅")]) async def disable(self, **kwargs): self.__status = False return MessageChain.create([Plain(text="已关闭 Github 仓库订阅")]) async def add(self, **kwargs): if not self.__status: return MessageChain.create([Plain(text="Github 仓库订阅功能已关闭")]) repos = None group = None friend = None app = None for name, arg in kwargs.items(): if name == "arg" and isinstance(arg, str): repos = arg if isinstance(arg, Group): group = arg if isinstance(arg, Friend): friend = arg if isinstance(arg, Ariadne): app = arg err = [] if not group and not friend: err = err.extend([Plain(text="无法获取 Group 或 Friend 实例")]) if not app: err = err.extend([Plain(text="无法获取 Ariadne 实例")]) if not repos: err = err.extend([Plain(text="未填写需要订阅的仓库")]) if err: return MessageChain.create(err) repos = repos.split(" ") failed = [] duplicated = [] success_count = 0 for repo in repos: url = f"https://api.github.com/search/repositories?q={repo}" async with self.__session.get(url=url, proxy=proxy) as resp: try: resp.raise_for_status() except aiohttp.ClientError as e: logger.error(e) logger.error(f"暂时无法取得仓库 {repo} 的更新(状态码 {resp.status})") continue result = (await resp.json())["items"] if not result: failed.append(repo) continue repo = result[0]['full_name'] repo = repo.split("/") repo = (repo[0], repo[1]) if repo not in self.__cached.keys(): self.__cached[repo] = { "group": [], "friend": [], "last_id": -1, "enabled": True } if group: if group.id in self.__cached[repo]['group']: duplicated.append(f"{repo[0]}/{repo[1]}") else: self.__cached[repo][ 'group'] = self.__cached[repo]['group'] + [group.id] if friend: if friend.id in self.__cached[repo]['friend']: duplicated.append(f"{repo[0]}/{repo[1]}") else: self.__cached[repo]['friend'] = self.__cached[repo][ 'friend'] + [friend.id] if self.__cached[repo]['last_id'] == -1: await self.github_schedule(app=app, manuel=True, per_page=1, page=1, repo=repo) success_count += 1 res = [Plain(text=f"{success_count} 个仓库订阅成功")] if failed: res.append( Plain(text=f"\n{len(failed)} 个仓库订阅失败" f"\n失败的仓库有:{' '.join(failed)}")) if duplicated: res.append( Plain(text=f"\n{len(duplicated)} 个仓库已在订阅列表中" f"\n重复的仓库有:{' '.join(duplicated)}")) try: self.store_cache(manual=False) self.update_cache(manual=False) return MessageChain.create(res) except Exception as e: logger.error(e) res.append(Plain(text="\n\n刷新缓存失败")) return MessageChain.create(res) async def remove(self, **kwargs): if not self.__status: return MessageChain.create([Plain(text=f"Github 仓库订阅功能已关闭")]) repos = None group = None friend = None err = [] for name, arg in kwargs.items(): if name == "arg" and isinstance(arg, str): repos = arg if isinstance(arg, Group): group = arg if isinstance(arg, Friend): friend = arg if not group and not friend: err = err.extend([Plain(text=f"无法获取 Group 或 Friend 实例")]) if not repos: err = err.extend([Plain(text="未填写需要取消订阅的仓库")]) if err: return MessageChain.create(err) repos = repos.split(" ") failed = [] success_count = 0 for repo in repos: repo = repo.split("/") if len(repo) != 2: failed.append("/".join(repo)) continue repo = (repo[0], repo[1]) if repo not in self.__cached.keys(): failed.append("/".join(repo)) continue if group: self.__cached[repo]['group'] = [ group_id for group_id in self.__cached[repo]['group'] if group_id != group.id ] if friend: self.__cached[repo]['friend'] = [ friend_id for friend_id in self.__cached[repo]['group'] if friend_id != friend.id ] if not (self.__cached[repo]['group'] and self.__cached[repo]['friend']): self.__cached.pop(repo) success_count += 1 res = [Plain(text=f"{success_count} 个仓库取消订阅成功")] if failed: res.append( Plain(text=f"\n{len(failed)} 个仓库取消订阅失败" f"\n失败的仓库有:{' '.join(failed)}")) try: self.store_cache(manual=False) self.update_cache(manual=False) return MessageChain.create(res) except Exception as e: logger.error(e) res.append(Plain(text="\n\n刷新缓存失败")) return MessageChain.create(res) async def cache(self, **kwargs): accepted = ['update', 'store'] command = None for name, arg in kwargs.items(): if name == "arg" and isinstance(arg, str): command = arg if not command: return MessageChain.create([Plain(text=f"未填写参数")]) if command not in accepted: return MessageChain.create([Plain(text=f"未知参数:{command}")]) if command == 'update': return self.update_cache(manual=True) if command == 'store': return self.store_cache(manual=True) def update_cache(self, manual: bool = False): try: with open(str(Path(__file__).parent.joinpath("watcher_data.json")), "r") as r: data = json.loads(r.read()) cache = {} for key in data.keys(): owner, repo = key.split("/") cache[(owner, repo)] = data[key] self.__cached = cache return MessageChain.create([Plain( text="更新缓存成功")]) if manual else None except (FileNotFoundError, JSONDecodeError): return MessageChain.create( [Plain(text="无法更新缓存,请检查是否删除了缓存文件并重新储存缓存")]) def store_cache(self, manual: bool = False): with open(str(Path(__file__).parent.joinpath("watcher_data.json")), "w") as w: cache = {} for key in self.__cached.keys(): new_key = f"{key[0]}/{key[1]}" cache[new_key] = self.__cached[key] w.write(json.dumps(cache, indent=4)) return MessageChain.create([Plain(text="写入缓存成功")]) if manual else None async def check(self, **kwargs) -> MessageChain: group = None friend = None for name, arg in kwargs.items(): if isinstance(arg, Group): group = arg if isinstance(arg, Friend): friend = arg if not group and not friend: return MessageChain.create([Plain(text=f"无法获取 Group 或 Friend 实例")]) watched = [] target = group if group else friend field = 'group' if group else 'friend' for repo in self.__cached.keys(): if target.id in self.__cached[repo][field]: watched.append(f"{repo[0]}/{repo[1]}") res = [ Plain(text=f"{'本群' if group else '你'}订阅的仓库有:\n" f"{' '.join(watched)}") ] return MessageChain.create(res) async def get_repo_event(self, repo: tuple, per_page: int = 30, page: int = 1): url = self.__base_url \ + self.__events_url.replace('{owner}', repo[0]).replace('{repo}', repo[1]) \ + f'?per_page={per_page}&page={page}' res = None try: res = await self.__session.get(url=url, proxy=proxy) res.raise_for_status() res = await res.json() if isinstance(res, list): return res elif isinstance(res, dict): if "message" in res.keys(): if "API rate limit exceeded" in res["message"]: logger.error("GitHub API 超出速率限制") if not self.__auth: logger.error("请设置 GitHub 用户名和 OAuth Token 以提高限制") self.__first_warned = True elif res["message"] == "Not Found": logger.error(f"无法找到仓库 {repo[0]}/{repo[1]}") self.__cached[repo]['enabled'] = False return res except aiohttp.ClientError as e: logger.error(e) logger.error( f"暂时无法取得仓库 {repo[0]}/{repo[1]} 的更新" f"{'' if not res else '(状态码 ' + str(res.status) + ')'}") return None except Exception as e: logger.error(e) return None async def a_generate_plain(self, event: dict): return await asyncio.get_event_loop().run_in_executor( None, self.generate_plain, event) @staticmethod def generate_plain(event: dict): actor = event['actor']['display_login'] event_time = (datetime.strptime(event['created_at'], '%Y-%m-%dT%H:%M:%SZ') + timedelta(hours=8)) \ .strftime('%Y-%m-%d %H:%M:%S') resp = None if event['type'] == 'IssuesEvent': if event['payload']['action'] == 'opened': title = event['payload']['issue']['title'] number = event['payload']['issue']['number'] body = event['payload']['issue']['body'] if body: if len(body) > 100: body = body[:100] + "......" body = body + "\n" link = event['payload']['issue']['html_url'] resp = Plain(text=f"----------\n" f"[新 Issue]\n" f"#{number} {title}\n" f"{body}\n" f"\n" f"发布人:{actor}\n" f"时间:{event_time}\n" f"链接:{link}\n") elif event['type'] == 'IssueCommentEvent': if event['payload']['action'] == 'created': title = event['payload']['issue']['title'] number = event['payload']['issue']['number'] body = event['payload']['comment']['body'] if body: if len(body) > 100: body = body[:100] + "......" body = body + "\n" link = event['payload']['comment']['html_url'] resp = Plain(text=f"----------\n" f"[新 Comment]\n" f"#{number} {title}\n" f"{body}" f"\n" f"发布人:{actor}\n" f"时间:{event_time}\n" f"链接:{link}\n") elif event['type'] == 'PullRequestEvent': if event['payload']['action'] == 'opened': title = event['payload']['pull_request']['title'] number = event['payload']['pull_request']['number'] body = event['payload']['pull_request']['body'] if body: if len(body) > 100: body = body[:100] + "......" body = body + "\n" head = event['payload']['pull_request']['head']['label'] base = event['payload']['pull_request']['base']['label'] commits = event['payload']['pull_request']['commits'] link = event['payload']['pull_request']['html_url'] resp = Plain(text=f"----------\n" f"[新 PR]\n" f"#{number} {title}\n" f"{body}" f"\n" f"{head} → {base}\n" f"提交数:{commits}\n" f"发布人:{actor}\n" f"时间:{event_time}\n" f"链接:{link}\n") elif event['type'] == 'PushEvent': commits = [] for commit in event['payload']['commits']: commits.append( f"· [{commit['author']['name']}] {commit['message']}") resp = Plain(text=f"----------\n" f"[新 Push]\n" + "\n".join(commits) + f"\n" f"提交数:{len(commits)}\n" f"发布人:{actor}\n" f"时间:{event_time}\n") elif event['type'] == 'CommitCommentEvent': body = event['payload']['comment']['body'] if body: if len(body) > 100: body = body[:100] + "......" body = body + "\n" link = event['payload']['comment']['html_url'] resp = Plain(text=f"----------\n" f"[新 Comment]\n" f"{body}" f"\n" f"发布人:{actor}\n" f"时间:{event_time}\n" f"链接:{link}\n") return resp if resp else None async def github_schedule(self, **kwargs): if not self.initialize: self.update_cache() self.initialize = True try: app = None manual = False repo = None per_page = 30 page = 1 for name, arg in kwargs.items(): if name == "manual" and isinstance(arg, bool): manual = arg if name == "repo" and isinstance(arg, tuple): repo = arg if name == "per_page" and isinstance(arg, int): per_page = arg if name == "page" and isinstance(arg, int): page = arg if isinstance(arg, Ariadne): app = arg if not app: logger.error("无法获得 Ariadne 实例") return None if self.__is_running: if manual: return MessageItem( MessageChain.create( [Plain(text="Github 订阅插件正在进行其他操作,请稍后再试。")]), QuoteSource()) return None if self.__status and repo: res = [] if events := await self.get_repo_event(repo, per_page, page): if isinstance(events, list): self.__cached[repo]['last_id'] = int(events[0]['id']) if resp := await self.a_generate_plain(events[0]): res.append(resp) else: res.append(Plain(text=events["message"])) if not res: return None res.insert(0, Plain(text=f"仓库:{repo[0]}/{repo[1]}\n")) res.append( Plain( text= f"----------\n获取时间:{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}" )) return MessageChain.create(res) if self.__status: self.__is_running = True for repo in self.__cached.keys(): if not self.__cached[repo]['enabled']: continue res = [] if events := await self.get_repo_event( repo, per_page, page): if isinstance(events, list): last_id = self.__cached[repo]['last_id'] new_last_id = last_id for index, event in enumerate(events): if index == 0: new_last_id = int(event['id']) if int(event['id']) <= last_id: break if resp := await self.a_generate_plain(event): res.append(resp) else: continue self.__cached[repo]['last_id'] = new_last_id self.store_cache() else: res.append(Plain(text=events["message"])) if res: if res[0].asDisplay() == "Bad credentials": self.__is_running = False self.__status = False await app.sendFriendMessage( config.host_qq, MessageChain.create([ Plain( text="凭据无效,请检查是否已更改或吊销 Github Token\n" "已自动关闭 Github Watcher") ])) raise Exception("凭据无效,请检查是否已更改或吊销 Github Token") res.insert(0, Plain(text=f"仓库:{repo[0]}/{repo[1]}\n")) res.append( Plain( text= f"----------\n获取时间:{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}" )) res = MessageChain.create(res) # fwd_nodes = [ # ForwardNode( # senderId=config.bot_qq, # time=datetime.now(), # senderName="Github 订阅", # messageChain=MessageChain.create(Plain(text=f"仓库:{repo[0]}/{repo[1]}\n")), # ) # ] # for index, element in enumerate(res): # fwd_nodes.append( # ForwardNode( # senderId=config.bot_qq, # time=datetime.now() + timedelta(minutes=index + 1), # senderName="Github 订阅", # messageChain=MessageChain.create(element), # ) # ) # fwd_nodes.append( # ForwardNode( # senderId=config.bot_qq, # time=datetime.now(), # senderName="Github 订阅", # messageChain=MessageChain.create(Plain( # text=f"获取时间:{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}") # ), # ) # ) # res = MessageChain.create(Forward(nodeList=fwd_nodes)) if manual: self.__is_running = False return MessageItem(res, Normal()) for group in self.__cached[repo]['group']: try: await app.sendGroupMessage(group, res) except (AccountMuted, UnknownTarget): pass for friend in self.__cached[repo]['friend']: try: await app.sendFriendMessage(friend, res) except UnknownTarget: pass
async def add(self, **kwargs): if not self.__status: return MessageChain.create([Plain(text="Github 仓库订阅功能已关闭")]) repos = None group = None friend = None app = None for name, arg in kwargs.items(): if name == "arg" and isinstance(arg, str): repos = arg if isinstance(arg, Group): group = arg if isinstance(arg, Friend): friend = arg if isinstance(arg, Ariadne): app = arg err = [] if not group and not friend: err = err.extend([Plain(text="无法获取 Group 或 Friend 实例")]) if not app: err = err.extend([Plain(text="无法获取 Ariadne 实例")]) if not repos: err = err.extend([Plain(text="未填写需要订阅的仓库")]) if err: return MessageChain.create(err) repos = repos.split(" ") failed = [] duplicated = [] success_count = 0 for repo in repos: url = f"https://api.github.com/search/repositories?q={repo}" async with self.__session.get(url=url, proxy=proxy) as resp: try: resp.raise_for_status() except aiohttp.ClientError as e: logger.error(e) logger.error(f"暂时无法取得仓库 {repo} 的更新(状态码 {resp.status})") continue result = (await resp.json())["items"] if not result: failed.append(repo) continue repo = result[0]['full_name'] repo = repo.split("/") repo = (repo[0], repo[1]) if repo not in self.__cached.keys(): self.__cached[repo] = { "group": [], "friend": [], "last_id": -1, "enabled": True } if group: if group.id in self.__cached[repo]['group']: duplicated.append(f"{repo[0]}/{repo[1]}") else: self.__cached[repo][ 'group'] = self.__cached[repo]['group'] + [group.id] if friend: if friend.id in self.__cached[repo]['friend']: duplicated.append(f"{repo[0]}/{repo[1]}") else: self.__cached[repo]['friend'] = self.__cached[repo][ 'friend'] + [friend.id] if self.__cached[repo]['last_id'] == -1: await self.github_schedule(app=app, manuel=True, per_page=1, page=1, repo=repo) success_count += 1 res = [Plain(text=f"{success_count} 个仓库订阅成功")] if failed: res.append( Plain(text=f"\n{len(failed)} 个仓库订阅失败" f"\n失败的仓库有:{' '.join(failed)}")) if duplicated: res.append( Plain(text=f"\n{len(duplicated)} 个仓库已在订阅列表中" f"\n重复的仓库有:{' '.join(duplicated)}")) try: self.store_cache(manual=False) self.update_cache(manual=False) return MessageChain.create(res) except Exception as e: logger.error(e) res.append(Plain(text="\n\n刷新缓存失败")) return MessageChain.create(res)
async def disable(self, **kwargs): self.__status = False return MessageChain.create([Plain(text="已关闭 Github 仓库订阅")])
async def command_handler(app: Ariadne, group: Group): infos = await apis.AuthlibInjectorLatest.get() _message = f'authlib-injector 最新版本:{infos.version}\n{infos.download_url}' await app.sendGroupMessage(group, MessageChain.create([Plain(_message)]))
async def srr_wrapper(app: Ariadne, group: Group): await app.sendGroupMessage(group, MessageChain.create(reply_content))
async def handle(app: Ariadne, message: MessageChain, group: Group, member: Member) -> MessageItem: # global saya_data if message.asDisplay().strip() == "已加载插件": loaded_channels = SayaManager.get_loaded_channels() keys = list(loaded_channels.keys()) keys.sort() return MessageItem( await MessageChainUtils.messagechain_to_img( MessageChain.create([Plain(text="目前加载插件:\n")] + [ Plain( text=f"{i + 1}. {loaded_channels[keys[i]]._name}\n" ) for i in range(len(keys)) ] + [Plain(text="发送 `插件详情 [编号|名称]` 可查看插件详情")])), QuoteSource()) elif re.match(r"插件详情 .+", message.asDisplay()): target = message.asDisplay()[5:].strip() loaded_channels = SayaManager.get_loaded_channels() keys = list(loaded_channels.keys()) if target.isdigit(): keys.sort() if not 0 <= int(target) - 1 < len(keys): return MessageItem( MessageChain.create([Plain(text="错误的编号!请检查后再发送!")]), QuoteSource()) channel = loaded_channels[keys[int(target) - 1]] channel_path = keys[int(target) - 1] else: for lchannel in loaded_channels.keys(): if loaded_channels[lchannel]._name == target: channel = loaded_channels[lchannel] channel_path = lchannel break else: return MessageItem( MessageChain.create([Plain(text="错误的名称!请检查后再发送!")]), QuoteSource()) return MessageItem( MessageChain.create([ Plain(text=f"插件名称:{channel._name}\n"), Plain(text=f"插件作者:{'、'.join(channel._author)}\n"), Plain(text=f"插件描述:{channel._description}\n"), Plain(text=f"插件包名:{channel_path}") ]), QuoteSource()) elif message.asDisplay() == "未加载插件": if not await user_permission_require(group, member, 3): return MessageItem(MessageChain.create([Plain(text="爬,权限不足")]), QuoteSource()) unloaded_channels = SayaManager.get_unloaded_channels() unloaded_channels.sort() return MessageItem( MessageChain.create([Plain(text="目前未加载插件:\n")] + [ Plain(text=f"{i + 1}. {unloaded_channels[i]}\n") for i in range(len(unloaded_channels)) ] + [Plain(text="发送 `[加载|卸载|重载]插件 [编号|名称]` 可加载/卸载/重载插件\n")]), QuoteSource()) elif re.match(r"加载插件 .+", message.asDisplay()): if not await user_permission_require(group, member, 3): return MessageItem(MessageChain.create([Plain(text="爬,权限不足")]), QuoteSource()) target = message.asDisplay()[5:].strip() unloaded_channels = SayaManager.get_unloaded_channels() if target.isdigit(): unloaded_channels.sort() if not 0 <= int(target) - 1 < len(unloaded_channels): return MessageItem( MessageChain.create([Plain(text="错误的编号!请检查后再发送!")]), QuoteSource()) channel = unloaded_channels[int(target) - 1] else: for ulchannel in unloaded_channels: if ulchannel == target: channel = ulchannel break else: return MessageItem( MessageChain.create([Plain(text="错误的名称!请检查后再发送!")]), QuoteSource()) await app.sendMessage( group, MessageChain.create( [Plain(text=f"你确定要加载插件 `{channel}` 吗?(是/否)")])) @Waiter.create_using_function([GroupMessage]) def confirm_waiter(waiter_group: Group, waiter_member: Member, waiter_message: MessageChain): if all([ waiter_group.id == group.id, waiter_member.id == member.id ]): if re.match(r"[是否]", waiter_message.asDisplay()): return waiter_message.asDisplay() else: return "" result = await inc.wait(confirm_waiter) if not result: return MessageItem( MessageChain.create([Plain(text="非预期回复,进程退出")]), QuoteSource()) elif result == "是": result = SayaManager.load_channel(channel) if result: return MessageItem( MessageChain.create( [Plain(text=f"发生错误:{result[channel]}")]), QuoteSource()) else: return MessageItem( MessageChain.create([Plain(text="加载成功")]), QuoteSource()) else: return MessageItem(MessageChain.create([Plain(text="进程退出")]), QuoteSource()) elif re.match(r"[卸重]载插件 .+", message.asDisplay()): if not await user_permission_require(group, member, 3): return MessageItem(MessageChain.create([Plain(text="爬,权限不足")]), QuoteSource()) load_type = "reload" if message.asDisplay()[0] == "重" else "unload" target = message.asDisplay()[5:].strip() loaded_channels = SayaManager.get_loaded_channels() keys = list(loaded_channels.keys()) keys.sort() if target.isdigit(): if not 0 <= int(target) - 1 < len(keys): return MessageItem( MessageChain.create([Plain(text="错误的编号!请检查后再发送!")]), QuoteSource()) channel = loaded_channels[keys[int(target) - 1]] channel_path = keys[int(target) - 1] else: for lchannel in loaded_channels.keys(): if loaded_channels[lchannel]._name == target: channel = loaded_channels[lchannel] channel_path = lchannel break else: return MessageItem( MessageChain.create([Plain(text="错误的名称!请检查后再发送!")]), QuoteSource()) await app.sendMessage( group, MessageChain.create([ Plain( text= f"你确定要{message.asDisplay()[0]}载插件 `{channel._name}` 吗?(是/否)" ) ])) @Waiter.create_using_function([GroupMessage]) def confirm_waiter(waiter_group: Group, waiter_member: Member, waiter_message: MessageChain): if all([ waiter_group.id == group.id, waiter_member.id == member.id ]): if re.match(r"[是否]", waiter_message.asDisplay()): return waiter_message.asDisplay() else: return "" result = await inc.wait(confirm_waiter) if not result: return MessageItem( MessageChain.create([Plain(text="非预期回复,进程退出")]), QuoteSource()) elif result == "是": result = SayaManager.unload_channel( channel_path ) if load_type == "unload" else SayaManager.reload_channel( channel_path) if result: return MessageItem( MessageChain.create( [Plain(text=f"发生错误:{result[channel_path]}")]), QuoteSource()) else: return MessageItem( MessageChain.create( [Plain(text=f"{message.asDisplay()[0]}载成功")]), QuoteSource()) else: return MessageItem(MessageChain.create([Plain(text="进程退出")]), QuoteSource()) elif re.match(r"(打开|关闭)插件 .+", message.asDisplay()): if not await user_permission_require(group, member, 3): return MessageItem(MessageChain.create([Plain(text="爬,权限不足")]), QuoteSource()) switch_type = "on" if message.asDisplay()[:2] == "打开" else "off" target = message.asDisplay()[5:].strip() loaded_channels = SayaManager.get_loaded_channels() keys = list(loaded_channels.keys()) keys.sort() channel_path = "" if target.isdigit(): if not 0 <= int(target) - 1 < len(keys): return MessageItem( MessageChain.create([Plain(text="错误的编号!请检查后再发送!")]), QuoteSource()) channel_path = keys[int(target) - 1] else: for lchannel in loaded_channels.keys(): if loaded_channels[lchannel]._name == target: channel_path = lchannel break saya_data.switch_on( channel_path, group) if switch_type == "on" else saya_data.switch_off( channel_path, group) return MessageItem( MessageChain.create([ Plain(text=f"插件{channel_path}已{message.asDisplay()[:2]}!") ]), QuoteSource())
async def memberjoinevent_listener(app: Ariadne, event: MemberJoinEvent): member = event.member group = member.group if group.id == qq.littleskin_main: await app.sendGroupMessage(group, MessageChain.create( [At(member.id), Plain(' '), Plain(tF.join_welcome)]))
return MessageItem(res, Normal()) for group in self.__cached[repo]['group']: try: await app.sendGroupMessage(group, res) except (AccountMuted, UnknownTarget): pass for friend in self.__cached[repo]['friend']: try: await app.sendFriendMessage(friend, res) except UnknownTarget: pass self.__is_running = False else: if manual: return MessageItem( MessageChain.create([Plain(text="Github 订阅插件已关闭。")]), QuoteSource()) except Exception as e: logger.error(e) self.__is_running = False gw = GithubWatcher() @channel.use(SchedulerSchema(timer=timers.every_minute())) async def github_schedule(app: Ariadne): try: await gw.github_schedule(app=app, manual=False) except: pass
async def enable(self, **kwargs): self.__status = True return MessageChain.create([Plain(text="已开启 Github 仓库订阅")])
async def messagechain_to_img( message: MessageChain, max_width: int = 1080, font_size: int = 40, spacing: int = 15, padding_x: int = 20, padding_y: int = 15, img_fixed: bool = True, font_path: str = f"{os.getcwd()}/statics/fonts/STKAITI.TTF", ) -> MessageChain: """ 将 MessageChain 转换为图片,仅支持只含有图片/文本的 MessageChain Args: message: 要转换的MessageChain max_width: 最大长度 font_size: 字体尺寸 spacing: 行间距 padding_x: x轴距离边框大小 padding_y: y轴距离边框大小 img_fixed: 图片是否适应大小(仅适用于图片小于最大长度时) font_path: 字体文件路径 Examples: msg = await messagechain_to_img(message=message) Returns: MessageChain (内含图片Image类) """ def get_final_text_lines(text: str, text_width: int, font: ImageFont.FreeTypeFont) -> int: lines = text.split("\n") line_count = 0 for line in lines: if not line: line_count += 1 continue line_count += int( math.ceil( float(font.getsize(line)[0]) / float(text_width))) return line_count + 1 font = ImageFont.truetype(font_path, font_size, encoding="utf-8") message = message.merge(copy=True) elements = message.__root__ plains = message.get(Plain) text_gather = "\n".join([plain.text for plain in plains]) # print(max(font.getsize(text)[0] for text in text_gather.split("\n")) + 2 * padding_x) final_width = min( max(font.getsize(text)[0] for text in text_gather.split("\n")) + 2 * padding_x, max_width) text_width = final_width - 2 * padding_x text_height = (font_size + spacing) * ( get_final_text_lines(text_gather, text_width, font) + 1) img_height_sum = 0 temp_img_list = [] images = [ element for element in message.__root__ if isinstance(element, Image) ] for image in images: temp_img = IMG.open(BytesIO(await image.get_bytes())) img_width, img_height = temp_img.size temp_img_list.append( temp_img := temp_img.resize(( int(final_width - 2 * spacing), int( float(img_height * (final_width - 2 * spacing)) / float(img_width)))) if img_width > final_width - 2 * spacing or (img_fixed and img_width < final_width - 2 * spacing) else temp_img) img_height_sum = img_height_sum + temp_img.size[1] final_height = 2 * padding_y + text_height + img_height_sum picture = IMG.new('RGB', (final_width, final_height), (255, 255, 255)) draw = ImageDraw.Draw(picture) present_x = padding_x present_y = padding_y image_index = 0 for element in elements: if isinstance(element, Image): picture.paste(temp_img_list[image_index], (present_x, present_y)) present_y += (spacing + temp_img_list[image_index].size[1]) image_index += 1 elif isinstance(element, Plain): for char in element.text: if char == "\n": present_y += (font_size + spacing) present_x = padding_x continue if char == "\r": continue if present_x + font.getsize(char)[0] > text_width: present_y += (font_size + spacing) present_x = padding_x draw.text((present_x, present_y), char, font=font, fill=(0, 0, 0)) present_x += font.getsize(char)[0] present_y += (font_size + spacing) present_x = padding_x bytes_io = BytesIO() picture.save(bytes_io, format='PNG') logger.success("消息转图片处理成功!") return MessageChain.create([Image(data_bytes=bytes_io.getvalue())])
if result := result.get("result"): # print(json.dumps(result[0], indent=4)) title_native = result[0]["anilist"]["title"]["native"] title_romaji = result[0]["anilist"]["title"]["romaji"] title_english = result[0]["anilist"]["title"]["english"] file_name = result[0]["filename"] similarity = round(float(result[0]["similarity"]) * 100, 2) time_from = result[0]["from"] time_to = result[0]["to"] thumbnail_url = result[0]["image"] async with aiohttp.ClientSession() as session: async with session.get(url=thumbnail_url) as resp: thumbnail_content = await resp.read() message = await MessageChainUtils.messagechain_to_img( MessageChain.create([ Plain(text="搜索到结果:\n"), Image(data_bytes=thumbnail_content), Plain(text=f"番剧名: {title_native}\n"), Plain(text=f"罗马音名: {title_romaji}\n"), Plain(text=f"英文名: {title_english}\n"), Plain(text=f"文件名: {file_name}\n"), Plain(text=f"时间: {sec_to_str(time_from)} ~ {sec_to_str(time_to)}\n"), Plain(text=f"相似度: {similarity}%"), ]) ) return message else: return MessageChain.create([Plain(text="没有查到结果呐~")])