Exemplo n.º 1
0
    def prefix_match(self, target_chain: MessageChain):
        target_chain = target_chain.asMerged()

        chain_frames: List[MessageChain] = target_chain.split(" ",
                                                              raw_string=True)

        # 前缀匹配
        if len(self.prefixs) > len(chain_frames):
            return
        for index, current_prefix in enumerate(self.prefixs):
            current_frame = chain_frames[index]
            if (not current_frame.__root__
                    or type(current_frame.__root__[0]) is not Plain):
                return
            if current_frame.__root__[0].text != current_prefix:
                return

        chain_frames = chain_frames[len(self.prefixs):]
        return MessageChain.create(
            list(
                itertools.chain(
                    *[i.__root__ + [Plain(" ")]
                      for i in chain_frames]))[:-1]).asMerged()
Exemplo n.º 2
0
    def detect_index(
        signature_chain: Tuple[Union[NormalMatch, PatternReceiver]],
        message_chain: MessageChain,
    ) -> Optional[Dict[str, Tuple[MessageIndex, MessageIndex]]]:
        merged_chain = merge_signature_chain(signature_chain)
        message_chain = message_chain.asMerged()
        element_num = len(message_chain.__root__)
        end_index: MessageIndex = (
            element_num - 1,
            len(message_chain.__root__[-1].text) if element_num != 0
            and message_chain.__root__[-1].__class__ is Plain else None,
        )

        reached_message_index: MessageIndex = (0, None)
        # [0] => real_index
        # [1] => text_index(optional)

        start_index: MessageIndex = (0, None)

        match_result: Dict[Arguments, Tuple[
            MessageIndex, MessageIndex],  # start(include)  # stop(exclude)
                           ] = {}

        signature_iterable = InsertGenerator(enumerate(merged_chain))
        latest_index = None
        matching_recevier: Optional[Arguments] = None

        for signature_index, signature in signature_iterable:
            if isinstance(signature, (Arguments, PatternReceiver)):
                if matching_recevier:  # 已经选中了一个...
                    if isinstance(signature, Arguments):
                        if latest_index == signature_index:
                            matching_recevier.content.extend(signature.content)
                            continue
                        else:
                            raise TypeError(
                                "a unexpected case: match conflict")
                    if isinstance(signature, PatternReceiver):
                        matching_recevier.content.append(signature)
                        continue
                else:
                    if isinstance(signature, PatternReceiver):
                        signature = Arguments([signature])
                matching_recevier = signature
                start_index = reached_message_index
            elif isinstance(signature, NormalMatch):
                if not matching_recevier:
                    # 如果不要求匹配参数, 从当前位置(reached_message_index)开始匹配FullMatch.
                    current_chain = message_chain.subchain(
                        slice(reached_message_index, None, None))
                    if not current_chain.__root__:  # index 越界
                        return
                    if not isinstance(current_chain.__root__[0], Plain):
                        # 切片后第一个 **不是** Plain.
                        return
                    re_match_result = re.match(signature.operator(),
                                               current_chain.__root__[0].text)
                    if not re_match_result:
                        # 不匹配的
                        return
                    # 推进当前进度.
                    plain_text_length = len(current_chain.__root__[0].text)
                    pattern_length = re_match_result.end(
                    ) - re_match_result.start()
                    if (pattern_length + 1) > plain_text_length:  # 推进后可能造成错误
                        # 不推进 text_index 进度, 转而推进 element_index 进度
                        reached_message_index = (reached_message_index[0] + 1,
                                                 None)
                    else:
                        # 推进 element_index 进度至已匹配到的地方后.
                        reached_message_index = (
                            reached_message_index[0],
                            origin_or_zero(reached_message_index[1]) +
                            re_match_result.start() + pattern_length,
                        )
                else:
                    # 需要匹配参数(是否贪婪模式查找, 即是否从后向前)
                    greed = matching_recevier.isGreed
                    for element_index, element in enumerate(
                            message_chain.subchain(
                                slice(reached_message_index, None,
                                      None)).__root__):
                        if isinstance(element, Plain):
                            current_text: str = element.text
                            # 完成贪婪判断
                            text_find_result_list = list(
                                re.finditer(signature.operator(),
                                            current_text))
                            if not text_find_result_list:
                                continue
                            text_find_result = text_find_result_list[-int(greed
                                                                          )]
                            if not text_find_result:
                                continue
                            text_find_index = text_find_result.start()

                            # 找到了! 这里不仅要推进进度, 还要把当前匹配的参数记录结束位置并清理.
                            stop_index = (
                                reached_message_index[0] + element_index +
                                int(element_index == 0),
                                origin_or_zero(reached_message_index[1]) +
                                text_find_index,
                            )
                            match_result[matching_recevier] = (
                                copy.copy(start_index),
                                stop_index,
                            )

                            start_index = (0, None)
                            matching_recevier = None

                            pattern_length = (text_find_result.end() -
                                              text_find_result.start())
                            if (current_text == text_find_result.string[slice(
                                    *text_find_result.span())]):
                                # 此处是如果推进 text_index 就会被爆破....
                                # 推进 element_index 而不是 text_index
                                reached_message_index = (
                                    reached_message_index[0] + element_index +
                                    int(element_index != 0),
                                    None,
                                )
                            else:
                                reached_message_index = (
                                    reached_message_index[0] + element_index,
                                    origin_or_zero(reached_message_index[1]) +
                                    text_find_index + pattern_length,
                                )
                            break
                    else:
                        # 找遍了都没匹配到.
                        return
            latest_index = signature_index
        else:
            if matching_recevier:  # 到达了终点, 却仍然还要做点事的.
                # 计算终点坐标.
                text_index = None

                latest_element = message_chain.__root__[-1]
                if isinstance(latest_element, Plain):
                    text_index = len(latest_element.text)

                stop_index = (len(message_chain.__root__), text_index)
                match_result[matching_recevier] = (start_index, stop_index)
            else:  # 如果不需要继续捕获消息作为参数, 但 Signature 已经无法指示 Message 的样式时, 判定本次匹配非法.
                if reached_message_index < end_index:
                    return

        return match_result
Exemplo n.º 3
0
    def detect_index(self, target_chain: MessageChain):
        target_chain = target_chain.asMerged()
        detect_result: Dict[str, List[Tuple[MessageIndex, MessageIndex]]] = {
            "_": []  # 相当于 *args.
        }

        chain_frames: List[MessageChain] = target_chain.split(self.delimiter,
                                                              raw_string=True)

        # 前缀匹配
        if len(self.prefixs) > len(chain_frames):
            return
        for index in range(len(self.prefixs)):
            current_prefix = self.prefixs[index]
            current_frame = chain_frames[index]
            if (not current_frame.__root__
                    or type(current_frame.__root__[0]) is not Plain):
                return
            if current_frame.__root__[0].text != current_prefix:
                return

        chain_frames = chain_frames[len(self.prefixs):]  # 清除无关数据, 开始执行.

        collections: List[Element] = detect_result["_"]

        detecting_param = None
        local_iter = MultiUsageGenerator(enumerate(chain_frames))
        # print(list(local_iter))
        for iter_item in local_iter:
            print(92, iter_item)
            index, current_frame = iter_item
            current_frame: MessageChain
            if not current_frame.__root__:
                collections.append(current_frame)
                continue

            if detecting_param and isinstance(
                    self.param_settings[detecting_param], BoxParameter):
                detect_result[detecting_param] = current_frame
                detecting_param = None
                continue

            splited = current_frame.split("=", raw_string=True)
            origin_data = self.param_settings_index.get(splited[0].asDisplay())
            if not origin_data:
                if not detecting_param:
                    if current_frame.startswith(
                            '"'):  # TODO: 我现在还需要一个更加合理的引号出现判断.
                        # debug("ocur", index, chain_frames, current_frame)
                        afters_root = MessageChain.create(
                            sum(
                                [[*i.__root__,
                                  Plain(self.delimiter)]
                                 for i in chain_frames[max(0, index - 1):]],
                                [],
                            )[:-1]).asMerged()
                        print("aftersRoot", index, afters_root)

                        break_flag_root = False
                        param_content_root = []
                        for elem in afters_root.subchain(
                                slice((0, 1), None,
                                      None), ignore_text_index=True):
                            print(elem)
                            if break_flag_root:
                                break_flag_root = False
                                break
                            if isinstance(elem, Plain):
                                continue_flag = False
                                for text_index, text_i in enumerate(elem.text):
                                    if continue_flag:
                                        continue_flag = False
                                        continue

                                    if text_i == "\\":
                                        continue_flag = True
                                    if text_i == '"':
                                        param_content_root.append(
                                            Plain(elem.text[:text_index]))
                                        break_flag_root = True
                                        break
                                else:
                                    # 没找到
                                    param_content_root.append(elem)
                                    break
                            else:
                                param_content_root.append(elem)
                                break
                        else:
                            if not break_flag_root:
                                raise ValueError("no closing quotes")
                        param_content_chain_root = MessageChain.create(
                            param_content_root)
                        local_iter.continue_count += len(
                            param_content_chain_root.split(self.delimiter,
                                                           raw_string=True))
                        collections.append(param_content_chain_root)
                    else:
                        collections.append(current_frame)
                continue
            param_name, setting = origin_data
            detecting_param = param_name
            if param_name in detect_result:
                continue  # 用户重复输入了参数

            if isinstance(setting, SwitchParameter):  # 这里是已经被 catch 到了.
                if setting.auto_reverse:
                    detect_result[param_name] = not setting.default
                else:
                    detect_result[param_name] = True
            elif isinstance(setting, BoxParameter):
                afters = MessageChain.create(
                    sum(
                        ([i.__root__
                          for i in splited[1:]] + [[Plain(self.delimiter)]]
                         if len(splited) > 1 else []) +
                        [[*i.__root__, Plain(self.delimiter)]
                         for i in chain_frames[index + 1:]],
                        [],
                    )[:-1]).asMerged()
                break_flag = False
                if afters.startswith('"'):
                    param_content = []
                    for elem in afters.subchain(slice((0, 1), None, None),
                                                ignore_text_index=True):
                        if break_flag:
                            break_flag = False
                            break
                        if isinstance(elem, Plain):
                            continue_flag = False
                            for text_index, text_i in enumerate(elem.text):
                                if continue_flag:
                                    continue_flag = False
                                    continue

                                if text_i == "\\":
                                    continue_flag = True
                                if text_i == '"':
                                    param_content.append(
                                        Plain(elem.text[:text_index]))
                                    break_flag = True
                                    break
                            else:
                                # 没找到
                                param_content.append(elem)
                        else:
                            param_content.append(elem)
                    else:
                        if not break_flag:
                            raise ValueError("no closing quotes")
                    param_content_chain = MessageChain.create(param_content)
                    print(
                        param_content,
                        chain_frames[len(
                            param_content_chain.
                            split(self.delimiter, raw_string=True)) + 1:],
                    )
                    local_iter.continue_count += (len(
                        param_content_chain.split(self.delimiter,
                                                  raw_string=True)) - 1)
                    detect_result[param_name] = [
                        MessageChain.create(param_content)
                    ]
                else:
                    detecting_param = param_name

                if len(splited) > 1 and not break_flag:
                    local_iter.insert_items.extend([
                        (index + l_index, value)
                        for l_index, value in enumerate(splited[1:], 1)
                    ])
                detecting_param = None
        else:
            if detecting_param and detecting_param not in detect_result:
                if self.param_settings[detecting_param].default is None:
                    raise ValueError("require for " + detecting_param)

        # 后处理
        for k, v in self.param_settings.items():
            if isinstance(v, SwitchParameter) and k not in detect_result:
                detect_result[k] = v.default
            elif isinstance(v, BoxParameter) and k not in detect_result:
                detect_result[k] = Force(None)
        return detect_result
Exemplo n.º 4
0
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 = False,
    font_path: str = "./simhei.ttf",
    save_path: str = "./statics/temp/tempMessageChainToImg.png"
) -> MessageChain:
    """
    将 MessageChain 转换为图片,仅支持只含有本地图片/文本的 MessageChain

    Args:
        message: 要转换的MessageChain
        max_width: 最大长度
        font_size: 字体尺寸
        spacing: 行间距
        padding_x: x轴距离边框大小
        padding_y: y轴距离边框大小
        img_fixed: 图片是否适应大小(仅适用于图片小于最大长度时)
        font_path: 字体文件路径
        save_path: 图片存储路径

    Examples:
        msg = await messagechain_to_img(message=message)

    Returns:
        MessageChain (内含图片Image类)
    """
    font = ImageFont.truetype(font_path, font_size, encoding="utf-8")
    message = message.asMerged()
    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) * await get_final_text_lines(
        text_gather, text_width, font)
    # text_height = (font_size + spacing) * sum([await get_final_text_lines(plain.text, text_width, font)for plain in plains])

    img_height_sum = 0
    temp_img_list = []
    images = message.get(Image_LocalFile)
    for image in images:
        if isinstance(image, Image_LocalFile):
            # print(img_height_sum)
            temp_img = IMG.open(image.filepath)
            # print(temp_img.size)
            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]
            # print(temp_img.size[1])
            # print(img_height)
        else:
            raise Exception("messagechain_to_img:仅支持本地图片即Image_LocalFile类的处理!")
    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
    # print(temp_img_list)
    for element in elements:
        if isinstance(element, Image_LocalFile):
            print(f"adding img {image_index}")
            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):
            print(f"adding text '{element.text}'")
            # if font.getsize(element.text)[0] <= text_width:
            #     draw.text((present_x, present_y), element.text, font=font, fill=(0, 0, 0))
            # else:
            for char in element.text:
                if char == "\n":
                    present_y += (font_size + spacing)
                    present_x = padding_x
                    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

    # print(f"textHeight: {text_height}\nimgHeight: {img_height}\nfinalHeight: {final_height}")
    # print(f"present_x: {present_x}, present_y: {present_y}")
    picture.save(save_path)
    print(f"process finished! Image saved at {save_path}")
    return MessageChain.create([Image.fromLocalFile(save_path)])
Exemplo n.º 5
0
    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 = False,
        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.asMerged()
        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)

        img_height_sum = 0
        temp_img_list = []
        images = [element for element in message.__root__ if (isinstance(element, Image_LocalFile) or isinstance(element, Image_UnsafeBytes))]
        for image in images:
            if isinstance(image, Image_LocalFile):
                temp_img = IMG.open(image.filepath)
            elif isinstance(image, Image_UnsafeBytes):
                temp_img = IMG.open(BytesIO(image.image_bytes))
            else:
                raise ValueError("messagechain_to_img:仅支持Image_LocalFile和Image_UnsafeBytes类的处理!")
            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) or isinstance(element, Image_UnsafeBytes) or isinstance(element, Image_LocalFile):
                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.fromUnsafeBytes(bytes_io.getvalue())
        ])