예제 #1
0
    async def handle(app: Ariadne, message: MessageChain, group: Group,
                     member: Member, image: ElementResult,
                     resize: RegexResult) -> Optional[MessageItem]:
        @Waiter.create_using_function(listening_events=[GroupMessage])
        async def image_waiter(waiter_group: Group, waiter_member: Member,
                               waiter_message: MessageChain):
            if waiter_group.id == group.id and waiter_member.id == member.id:
                if waiter_message.has(Image):
                    return await waiter_message.getFirst(Image).get_bytes()
                else:
                    return False

        if not enable:
            return MessageItem(MessageChain.create([Plain(text="功能未开启!")]),
                               QuoteSource())
        if image.matched:
            image_data = await image.result.get_bytes()
        else:
            try:
                await app.sendMessage(group,
                                      MessageChain.create("请在30s内发送要处理的图片"),
                                      quote=message[Source][0])
                image_data = await asyncio.wait_for(inc.wait(image_waiter), 30)
                if not image_data:
                    return MessageItem(
                        MessageChain.create([Plain(text="未检测到图片,请重新发送,进程退出")]),
                        QuoteSource())
            except asyncio.TimeoutError:
                return MessageItem(
                    MessageChain.create([Plain(text="图片等待超时,进程退出")]),
                    QuoteSource())
        if SuperResolution.processing:
            return MessageItem(
                MessageChain.create([Plain(text="有任务正在处理中,请稍后重试")]),
                QuoteSource())
        await mutex.acquire()
        SuperResolution.processing = True
        mutex.release()
        await app.sendMessage(group,
                              MessageChain.create([Plain(text="已收到图片,启动处理进程")
                                                   ]),
                              quote=message[Source][0])
        try:
            return await SuperResolution.super_resolution(
                image_data, resize.matched)
        except RuntimeError as e:
            await mutex.acquire()
            SuperResolution.processing = False
            mutex.release()
            return MessageItem(MessageChain.create([Plain(text=str(e))]),
                               QuoteSource())
예제 #2
0
    async def swallowed(image: Union[int, str]) -> MessageItem:
        avatar = await AvatarFunPic.get_pil_avatar(image)
        frame_locs = [(180, 60, 100, 100), (184, 75, 100, 100),
                      (183, 98, 100, 100), (179, 118, 110, 100),
                      (156, 194, 150, 48), (178, 136, 122, 69),
                      (175, 66, 122, 85), (170, 42, 130, 96),
                      (175, 34, 118, 95), (179, 35, 110, 93),
                      (180, 54, 102, 93), (183, 58, 97, 92),
                      (174, 35, 120, 94), (179, 35, 109, 93),
                      (181, 54, 101, 92), (182, 59, 98, 92),
                      (183, 71, 90, 96), (180, 131, 92, 101)]
        raw_frames = [f"{os.getcwd()}/statics/SwallowedFrames/frame{i}.png" for i in range(23)]
        raw_frames = [IMG.open(i).convert('RGBA') for i in raw_frames]

        avatar_frames = []
        for i in range(len(frame_locs)):
            frame = IMG.new('RGBA', (480, 400), (255, 255, 255, 0))
            x, y, l, w = frame_locs[i]
            avatar_resized = avatar.resize((l, w), IMG.ANTIALIAS)
            frame.paste(avatar_resized, (x, y))
            img = raw_frames[i]
            frame.paste(img, mask=img)
            avatar_frames.append(frame)

        frames = []
        for i in range(2):
            frames.extend(avatar_frames[0:12])
        frames.extend(avatar_frames[0:8])
        frames.extend(avatar_frames[12:18])
        frames.extend(raw_frames[18:23])

        output = BytesIO()
        imageio.mimsave(output, frames, format='gif', duration=0.06)
        return MessageItem(MessageChain.create([Image(data_bytes=output.getvalue())]), Normal())
 async def handle(app: Ariadne, message: MessageChain, group: Group,
                  member: Member, content: RegexResult,
                  image: ElementResult):
     msg = content.result.asDisplay()
     img = await image.result.get_bytes()
     msg = await get_translate(msg)
     w2b = BuildImage(0, 0, background=BytesIO(img))
     w2b.convert("L")
     msg_sp = msg.split("<|>")
     w, h = w2b.size
     add_h, font_size = init_h_font_size(h)
     bg = BuildImage(w, h + add_h, color="black", font_size=font_size)
     bg.paste(w2b)
     chinese_msg = formalization_msg(msg)
     if not bg.check_font_size(chinese_msg):
         if len(msg_sp) == 1:
             centered_text(bg, chinese_msg, add_h)
         else:
             centered_text(bg, chinese_msg + "<|>" + msg_sp[1], add_h)
     elif not bg.check_font_size(msg_sp[0]):
         centered_text(bg, msg, add_h)
     else:
         ratio = (bg.getsize(msg_sp[0])[0] + 20) / bg.w
         add_h = add_h * ratio
         bg.resize(ratio)
         centered_text(bg, msg, add_h)
     return MessageItem(
         MessageChain.create([Image(data_bytes=bg.pic2bytes())]),
         QuoteSource())
예제 #4
0
 async def super_resolution(image_data: bytes,
                            resize: bool = False) -> MessageItem:
     start = time.time()
     image = IMG.open(BytesIO(image_data))
     image_size = image.size[0] * image.size[1]
     if image_size > max_size:
         if not resize:
             await mutex.acquire()
             SuperResolution.processing = False
             mutex.release()
             return MessageItem(
                 MessageChain.create([
                     Plain(
                         text=
                         "图片尺寸过大!请发送1080p以内即像素数小于 1920×1080=2073600的照片!\n"),
                     Plain(
                         text=
                         f"此图片尺寸为:{image.size[0]}×{image.size[1]}={image_size}!"
                     )
                 ]), QuoteSource())
         length = 1
         for b in str(max_size / image_size).split('.')[1]:
             if b == '0':
                 length += 1
             else:
                 break
         magnification = round(max_size / image_size, length + 1)
         image = image.resize((round(image.size[0] * magnification),
                               round(image.size[1] * magnification)))
     image_array: np.ndarray = image.__array__()
     output, _ = await loop.run_in_executor(None, upsampler.enhance,
                                            image_array, 2)
     result = BytesIO()
     img = IMG.fromarray(output)
     img.save(result, format='PNG')  # format: PNG / JPEG
     end = time.time()
     use_time = round(end - start, 2)
     await mutex.acquire()
     SuperResolution.processing = False
     mutex.release()
     return MessageItem(
         MessageChain.create([
             Plain(text=f"超分完成!处理用时:{use_time}s\n"),
             Plain(text=f"由于像素过大,图片已进行缩放,结果可能不如原图片清晰\n" if resize else ""),
             Image(data_bytes=result.getvalue())
         ]), QuoteSource())
예제 #5
0
 async def handle(app: Ariadne, message: MessageChain, group: Group,
                  member: Member):
     if message.asDisplay().startswith("/fake "):
         content = "".join(i.text for i in message.get(Plain))[6:]
         if not message.has(At):
             return MessageItem(MessageChain.create([Plain(text="未指定目标!")]),
                                Normal())
         sender = message.get(At)[0]
         forward_nodes = [
             ForwardNode(
                 senderId=sender.target,
                 time=datetime.now(),
                 senderName=(await app.getMember(group,
                                                 sender.target)).name,
                 messageChain=MessageChain.create(Plain(text=content)),
             )
         ]
         return MessageItem(
             MessageChain.create(Forward(nodeList=forward_nodes)), Normal())
예제 #6
0
 async def wrapper(*args, **kwargs):
     try:
         if asyncio.iscoroutinefunction(func):
             result = await func(*args, **kwargs)
         else:
             result = func(*args, **kwargs)
         return result
     except:
         return MessageItem(
             MessageChain.create([Plain(text=traceback.format_exc())]),
             QuoteSource())
예제 #7
0
 async def support(image: Union[int, str]) -> MessageItem:
     avatar = await AvatarFunPic.get_pil_avatar(image)
     support = IMG.open(f'{os.getcwd()}/statics/support.png')
     frame = IMG.new('RGBA', (1293, 1164), (255, 255, 255, 0))
     avatar = avatar.resize((815, 815), IMG.ANTIALIAS).rotate(23, expand=True)
     frame.paste(avatar, (-172, -17))
     frame.paste(support, mask=support)
     frame = frame.convert('RGB')
     output = BytesIO()
     frame.save(output, format='jpeg')
     return MessageItem(MessageChain.create([Image(data_bytes=output.getvalue())]), Normal())
예제 #8
0
    async def handle(app: Ariadne, message: MessageChain, group: Group, member: Member):
        if message.asDisplay() == "搜番":
            await update_user_call_count_plus(group, member, UserCalledCount.search, "search")
            if not await group_setting.get_setting(group.id, Setting.bangumi_search):
                return MessageItem(MessageChain.create([Plain(text="搜番功能未开启呐~请联系管理员哦~")]), Normal())
            try:
                await app.sendGroupMessage(group, MessageChain.create([
                    At(member.id), Plain("请在30秒内发送要搜索的图片呐~")
                ]))
            except AccountMuted:
                logger.error(f"Bot 在群 <{group.name}> 被禁言,无法发送!")
                return None

            image_get = None
            message_received = None

            @Waiter.create_using_function([GroupMessage])
            def waiter(
                    event: GroupMessage, waiter_group: Group,
                    waiter_member: Member, waiter_message: MessageChain
            ):
                nonlocal image_get
                nonlocal message_received
                if time.time() - start_time < 30:
                    if all([
                        waiter_group.id == group.id,
                        waiter_member.id == member.id,
                        len(waiter_message[Image]) == len(waiter_message.__root__) - 1
                    ]):
                        image_get = True
                        message_received = waiter_message
                        return event
                else:
                    logger.warning("等待用户超时!BangumiSearchHandler进程推出!")
                    return event

            inc = InterruptControl(bcc)
            start_time = time.time()
            await inc.wait(waiter)
            if image_get:
                logger.success("收到用户图片,启动搜索进程!")
                try:
                    await app.sendGroupMessage(
                        group,
                        await BangumiSearcher.search_bangumi(message_received[Image][0]),
                        quote=message_received[Source][0]
                    )
                except AccountMuted:
                    logger.error(f"Bot 在群 <{group.name}> 被禁言,无法发送!")
                    pass
            return None
        else:
            return None
예제 #9
0
 async def ripped(image: Union[int, str]) -> MessageItem:
     ripped = IMG.open(f"{os.getcwd()}/statics/ripped.png")
     frame = IMG.new('RGBA', (1080, 804), (255, 255, 255, 0))
     avatar = await AvatarFunPic.get_pil_avatar(image)
     left = avatar.resize((385, 385)).rotate(24, expand=True)
     right = avatar.resize((385, 385)).rotate(-11, expand=True)
     frame.paste(left, (-5, 355))
     frame.paste(right, (649, 310))
     frame.paste(ripped, mask=ripped)
     frame = frame.convert('RGB')
     output = BytesIO()
     frame.save(output, format='jpeg')
     return MessageItem(MessageChain.create([Image(data_bytes=output.getvalue())]), Normal())
예제 #10
0
 async def wrapper(*args, **kwargs):
     member_id = -1
     group_id = -1
     for i in args:
         if isinstance(i, Member):
             member_id = i.id
         if isinstance(i, Group):
             group_id = i.id
     if member_id == -1 or group_id == -1 or not await group_setting.get_setting(
             group_id, Setting.frequency_limit):
         if asyncio.iscoroutinefunction(func):
             return await func(*args, **kwargs)
         return func(*args, **kwargs)
     frequency_limit_instance = GlobalFrequencyLimitDict()
     await frequency_limit_instance.add_record(group_id, member_id,
                                               weight)
     if frequency_limit_instance.blacklist_judge(group_id, member_id):
         if not frequency_limit_instance.announce_judge(
                 group_id, member_id):
             await frequency_limit_instance.blacklist_announced(
                 group_id, member_id)
             return MessageItem(
                 MessageChain.create([Plain(text="检测到大量请求,加入黑名单一小时!")]),
                 QuoteSource())
         else:
             return MessageItem(MessageChain.create([Plain("")]),
                                DoNothing())
     if frequency_limit_instance.get(group_id, member_id,
                                     func.__name__) + weight >= 10:
         return MessageItem(
             MessageChain.create([Plain(text="超过频率调用限制!")]),
             QuoteSource())
     else:
         await frequency_limit_instance.update(group_id, weight)
         if asyncio.iscoroutinefunction(func):
             return await func(*args, **kwargs)
         return func(*args, **kwargs)
예제 #11
0
 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)
예제 #12
0
 async def throw(image: Union[int, str]) -> MessageItem:
     avatar = await AvatarFunPic.get_pil_avatar(image)
     mask = IMG.new('L', avatar.size, 0)
     draw = ImageDraw.Draw(mask)
     offset = 1
     draw.ellipse((offset, offset, avatar.size[0] - offset, avatar.size[1] - offset),
                  fill=255)
     mask = mask.filter(ImageFilter.GaussianBlur(0))
     avatar.putalpha(mask)
     avatar = avatar.rotate(random.randint(1, 360), IMG.BICUBIC)
     avatar = avatar.resize((143, 143), IMG.ANTIALIAS)
     throw = IMG.open(f"{os.getcwd()}/statics/throw.png")
     throw.paste(avatar, (15, 178), mask=avatar)
     throw = throw.convert('RGB')
     output = BytesIO()
     throw.save(output, format='jpeg')
     return MessageItem(MessageChain.create([Image(data_bytes=output.getvalue())]), Normal())
예제 #13
0
async def wolfram_alpha(app: Ariadne, message: MessageChain, group: Group,
                        content: RegexResult):
    question = content.result.asDisplay()
    if not api_key or api_key == "wolfram_alpha_key":
        return MessageItem(
            MessageChain.create([Plain(text="尚未配置wolfram_alpha_key!")]),
            QuoteSource())
    url = f"https://api.wolframalpha.com/v1/simple?i={question.replace('+', '%2B')}&appid={api_key}"
    async with get_running(Adapter).session.get(url=url) as resp:
        if resp.status == 200:
            res = await resp.read()
            await app.sendGroupMessage(group,
                                       MessageChain([Image(data_bytes=res)]),
                                       quote=message.getFirst(Source))
        else:
            await app.sendGroupMessage(group,
                                       MessageChain(await resp.text()),
                                       quote=message.getFirst(Source))
예제 #14
0
 async def crawl(image: Union[int, str]) -> MessageItem:
     avatar = await AvatarFunPic.get_pil_avatar(image)
     mask = IMG.new('L', avatar.size, 0)
     draw = ImageDraw.Draw(mask)
     offset = 1
     draw.ellipse((offset, offset, avatar.size[0] - offset, avatar.size[1] - offset),
                  fill=255)
     mask = mask.filter(ImageFilter.GaussianBlur(0))
     avatar.putalpha(mask)
     images = [i for i in os.listdir(f"{os.getcwd()}/statics/crawl")]
     crawl = IMG.open(f"{os.getcwd()}/statics/crawl/{random.choice(images)}").resize(
         (500, 500), IMG.ANTIALIAS)
     avatar = avatar.resize((100, 100), IMG.ANTIALIAS)
     crawl.paste(avatar, (0, 400), mask=avatar)
     crawl = crawl.convert('RGB')
     output = BytesIO()
     crawl.save(output, format='jpeg')
     return MessageItem(MessageChain.create([Image(data_bytes=output.getvalue())]), Normal())
예제 #15
0
 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())
예제 #16
0
    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())
예제 #17
0
    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())
예제 #18
0
 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())
예제 #19
0
 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())
예제 #20
0
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
예제 #21
0
                            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
예제 #22
0
    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())