Пример #1
0
def localize(url: str, filename: str, failed_times: int = 0) -> Optional[str]:
    """本地化图片存储在语料库图片文件夹

    Args:
        url (str): 要下载的url
        filename (str): 下载后存储的文件名称
        failed_times (int, optional): 初始失败次数. Defaults to 0.

    Returns:
        Optional[str]: 成功下载会返回下载后的文件储存路径,否则返回None
    """

    searchfile = CORPUS_IMAGES_PATH.glob(filename.split('.')[0] + ".*")
    for f in searchfile:
        fp = f
        logger.debug(f'File [{filename}] has localized with {fp}')
        return fp.name
    fp = CORPUS_IMAGES_PATH / filename
    try:
        urlretrieve(url, fp)
        realpath = fp.with_suffix('.' + what(fp))  # 修复文件为真正的后缀
        fp.rename(realpath)
        logger.info(f'Localize image [{filename}] with path: {realpath.name}')
        return realpath.name
    except Exception as err:
        failed_times += 1
        logger.warning(
            f'Download file [{url}] error {failed_times} times: {err}')
        if failed_times < 6:
            return localize(url, filename, failed_times=failed_times)
        else:
            logger.error(
                f'Can not download file [{url}] with filename[{fp}]: {err}')
            return None
Пример #2
0
async def msg2str(message: Message,
                  *,
                  localize_: bool = False,
                  bot: Optional[Bot] = None) -> str:
    """把Message转换成可供数据库插入和查询用的字符串

    对纯文本对emoji去转义,对image去除data中的url参数

    Args:
        message (Message): 可由event.message或event.get_message()获得
        localize_ (bool): 是否要本地化,本地化后file字段加入的是file:///...形式的本地文件格式,一般用在插入数据库中answer中
        bot (Optional[Bot]): 本地化处理时需要传入bot对象调用获得图片信息api

    Returns:
        str: 转换后的字符串
    """

    strcq = ''
    for seg in message:
        logger.debug('Handle segment: ', str(seg), 'type: ', seg.type)
        if seg.type == 'text':
            strcq += demojize(str(seg))
        elif seg.type == 'image':
            if not localize_:
                strcq += f'[CQ:image,file={seg.data["file"]}]'
            else:
                imginfo = await bot.get_image(file=seg.data["file"])
                realname = localize(imginfo["url"], seg.data["file"])
                if not realname:
                    return None
                strcq += f'[CQ:image,file=file:///{{res_path}}/{realname}]'
        else:
            strcq += str(seg)
    return strcq
Пример #3
0
async def batch_get_q(bot: Bot, event: MessageEvent, state: T_State):
    state["question"] = await msg2str(Message(event.raw_message))
    logger.debug(f'Current question is [{state["question"]}]')
    qs = event.raw_message.split("|")
    qs_i = [f'{i + 1}.{q}' for i, q in enumerate(qs)]
    msg = Message('当前要记录的问句为:\n' + '\n'.join(qs_i) +
                  '\n请确认以上问句无误,输入回答内容,使用“|”分隔,否则请发送[取消]结束对话重新输入')
    await batch_learn.send(msg)
Пример #4
0
def plus_one(sid: int, gid: int, plus_num: int = 1):
    """指定ID的记录call_times加一"""

    with QbotDB() as qb:
        cmd = 'UPDATE corpus SET call_times=call_times+%s WHERE ID=%s;'
        param = (plus_num, sid)
        qb.update(cmd, param)
    if gid:
        his = Called_Reply[gid]
        his.called.add(sid)
        his.check_expired()
    logger.debug(f'SID {sid}: call_times + {plus_num}')
Пример #5
0
async def get_a(bot: Bot, event: MessageEvent, state: T_State):
    question = state["question"]
    if "answer" in state:
        answer = state["answer"]
    else:
        answer = Message(event.raw_message)
        # 不可以at自己
        for seg in answer:
            if seg.type == 'at' and seg.data["qq"] == str(event.self_id):
                await learn.finish('我为什么要at我自己?不要这样啦,会有bug的::>_<::')
        answer = await msg2str(answer, localize_=True, bot=bot)

    if len(question) > 3000 or len(answer) > 3000:
        await learn.finish(f'内容太长的对话{BOTNAME}记不住的说>﹏<')
    if answer:
        logger.debug(f'Current answer is [{answer}]')
        source = event.group_id if event.message_type == "group" and 'selflearn' not in state else 0
        public = 0 if state["force_priv"] else state["public"]
        creator = event.user_id if 'selflearn' not in state else event.self_id
        result = insertone(question, answer, 70, creator, source, public)
        if isinstance(result, tuple):
            await learn.finish(f'记录已被用户{result[0]}在{result[1]}时创建')
        else:
            logger.info(
                f'Insert record to corpus :\nquestion:[{question}]\nanswer:[{answer}]\npublic:{public}\ncreator:{creator}\nsource:{source}'
            )

            dlmt = DailyNumberLimiter(uid=event.user_id,
                                      func_name='学习',
                                      max_num=6)
            if dlmt.check(close_conn=False):
                if dlmt.count <= 3:
                    exp_mu, fund_mu = 5, 10
                else:
                    exp_mu, fund_mu = 2, 3
                exp = cgauss(exp_mu, 1, 1)
                fund = cgauss(fund_mu, 1, 1)
                user = UserLevel(event.user_id)
                await user.expup(exp, bot, event)
                user.turnover(fund)
                msg = f'对话已记录, 赠送您{exp}exp 和 {fund}金币作为谢礼~'
                dlmt.increase()
            else:
                dlmt.conn.close()
                msg = '对话已记录'
            if state["force_priv"]:
                msg += "\n(消息中含at信息,将强制设置公开性为群内限定)"
            msg += "\n﹟ 当前对话相对出现率默认设置为70,如需设置出现率可直接输入0-100范围内数字,否则可忽视本条说明"
            preprob[event.user_id] = result
            await learn.finish(msg)
    else:
        await learn.finish(reply_header(event, '这条词语好像记不住耶,要不联系主人试试?'))
Пример #6
0
    def __init__(self, uid: int, func_name: str, max_num: int):
        """

        Args:
            uid (int): 用户ID
            func_name (str): 服务名
            max_num (int): 最大调用次数
        """
        self.conn = QbotDB()  # 注意没有使用上下文管理,要手动commit()

        # 如果没有func_name列增加三个相关列
        if func_name not in self.__class__.func_name_ls:
            logger.debug(
                f'A new func {func_name} will be add in table calltimes')
            self.__class__.func_name_ls.append(func_name)
            self.conn.update(
                f"ALTER TABLE calltimes  ADD {func_name}_day DATE, ADD {func_name}_count INT DEFAULT 0, ADD {func_name}_total INT DEFAULT 0;"
            )
            self.conn.update(
                f"UPDATE calltimes SET {func_name}_day = CURDATE();")
            logger.info(f'Add func_name: {func_name} to table calltimes')
            self.conn.commit()

        result = self.conn.queryone(
            f'select {func_name}_day, {func_name}_count, {func_name}_total from calltimes where qq_number=%s;',
            (uid, ))  # 暂时没发现列可以通过传参方式替换的方法,只能动态拼装

        if result:
            self.last_call, self.count, self.total = result
            # 可能之前是调用其他功能时自动创建的记录所以当前功能的最后调用时间是null
            if self.last_call is None:
                self.conn.update(
                    f"UPDATE calltimes SET {func_name}_day = CURDATE() WHERE qq_number=%s;",
                    (uid, ))
                self.conn.commit()
                self.last_call = date.today()
        else:
            # 如果没有用户记录在相关列上增加用户记录并设置为初始值
            self.conn.insert(
                f"INSERT INTO calltimes (qq_number, {func_name}_day, {func_name}_count, {func_name}_total) "
                "VALUES(%s, CURDATE(), 0, 0)", (uid, ))
            self.conn.commit()
            self.last_call, self.count, self.total = date.today(), 0, 0

        self.uid = uid
        self.func_name = func_name
        self.max_num = max_num
Пример #7
0
async def batch_get_a(bot: Bot, event: MessageEvent, state: T_State):
    answer = Message(event.raw_message)
    # 不可以at自己
    for seg in answer:
        if seg.type == 'at' and seg.data["qq"] == str(event.self_id):
            await learn.finish('我为什么要at我自己?不要这样啦,会有bug的::>_<::')
    answer = await msg2str(answer, localize_=True, bot=bot)
    if answer:
        state["answer"] = answer
    else:
        await batch_learn.finish(reply_header(event, '含有学习失败的信息,要不联系主人试试?'))
    logger.debug(f'Current answer is [{answer}]')
    ans = event.raw_message.split("|")
    as_i = [f'{i + 1}.{a}' for i, a in enumerate(ans)]
    msg = Message('当前要记录的回答为:\n' + '\n'.join(as_i) +
                  '\n请确认以上回答无误,输入相对出现率[0-100],否则请发送[取消]结束对话重新输入')
    await batch_learn.send(msg)
    state['wrong_times'] = 0  # 输入错误次数,用于获取出现率时计算
Пример #8
0
async def parse_batch_qa(bot: Bot, event: MessageEvent, state: T_State):
    # 退出指令
    if str(event.message) in CANCEL_EXPRESSION:
        await learn.finish('已退出当前对话')
    for seg in Message(event.raw_message):
        if seg.type == "at":
            # 不可以at自己
            if seg.data["qq"] == str(event.self_id):
                await learn.finish('我为什么要at我自己?不要这样啦,会有bug的::>_<::')
                logger.debug(f'type{type(seg.data["qq"])}')  # 检测一下type
            # 强制非公开
            if state["public"]:
                state["force_priv"] == True
        # 不能存入消息的格式
        if seg.type not in ALLOW_SEGMENT:
            if seg.type == 'reply':
                await learn.finish('请不要学习带有回复上文消息的内容,会引发定位错误')
            else:
                await learn.finish('接收的消息不在可学习范围内')
Пример #9
0
async def reply_checker(bot: Bot, event: MessageEvent, state: T_State) -> bool:
    """问答对话触发规则"""
    q = await msg2str(Message(event.raw_message)
                      )  # 仅仅使用message会去掉呼唤bot昵称的原文本,造成问句中有bot昵称时逻辑混乱
    logger.debug(f'Search question <{q}> in corpus...')
    gid = event.group_id if event.message_type == 'group' else 0
    result = query(q, gid)
    logger.debug(f'当前获得了可触发的对话:{result}')
    if not result:
        return False

    alter = [(s, a) for s, a, p in result
             if randint(0, 100) < p]  # 过滤出通过随机概率的对话
    logger.debug(f'当前被过滤出的对话:{alter}')
    if not alter:
        return False

    sid, answer = choice(alter)  # 从可触发的对话中随机选择一个
    if event.message_type == 'group':
        name = event.sender.card or event.sender.nickname or event.get_user_id(
        )
    else:
        name = event.sender.nickname or event.get_user_id()
    state['answer'] = msglize(answer, name)
    state['sid'] = sid
    return True
Пример #10
0
async def send_others(bot: Bot, event: MessageEvent, state: T_State):
    # msg = MessageSegment.reply(id_=event.message_id) if event.message_type == 'group' else MessageSegment.text('')
    if state['img_type'] == 'meizi':
        call = get_nmb(False)
    elif state['img_type'] == 'photo':
        call = get_pw(False)
    elif state['img_type'] == 'bg':
        if state['lx'] in ('acg', '小姐姐', '风景', '随机'):
            lx = state['lx'].replace('acg', 'dongman').replace('小姐姐', 'meizi').replace('风景', 'fengjing').replace('随机', 'suiji')
        elif state['lx'] is not None:
            msg = MessageSegment.text( f'没有{state["lx"]}类型的壁纸')
            await rand_img.finish(reply_header(event, msg))
        else:
            lx = 'suiji'
        if state['pc'] is not None and state['pc'] == '手机':
            state['pc'] = 'mobile'
        call = get_sjbz(state['pc'], lx)
    elif state['img_type'] == 'acg':
        call = choice((get_asmdh(), get_nmb(True), get_pw(True)))

    logger.debug(f'调用杂图API: {call.__name__}')
    try:
        result = await call
    except httpx.HTTPError as e:
        logger.exception(e)
        msg = MessageSegment.text('图片丢掉了,要不你再试试?')
        await rand_img.finish(reply_header(event, msg))

    if isinstance(result, str):
        img = MessageSegment.image(result)
    elif isinstance(result, int):
        logger.error(f'{call.__name__} 失效,状态码: {result}')
        await rand_img.finish(reply_header(event, '这个API可能挂掉了,如果一直不好使就只好停用这个功能了'))
    else:
        img = imgseg(result)
    
    await rand_img.send(reply_header(event, img))
    return 'completed'
Пример #11
0
def reply_header(
        event: MessageEvent,
        text: Optional[Union[str, MessageSegment]] = None) -> MessageSegment:
    """快速构建一个带有回复消息头的字段

    由于私聊没太大必要回复特定消息并且当前版本有私聊中的回复消息BUG
    私聊中会返回一个空的文字段

    Args:
        event (MessageEvent): 当前消息
        text (Optional[Union[str, MessageSegment]], optional): 回复内容,文字会自动构建消息段,其他内容需要自定义消息段类型再传入

    Returns:
        MessageSegment: 连接后的消息段
    """
    logger.debug(event)
    msg = MessageSegment.reply(
        event.message_id
    ) if event.message_type == 'group' else MessageSegment.text('')
    if text is not None:
        if isinstance(text, str):
            text = MessageSegment.text(text)
        msg += text
    return msg
Пример #12
0
async def handle_query(bot: Bot, event: MessageEvent, state: T_State):
    question = state["question"] if 'question' in state else await msg2str(
        Message(event.raw_message))
    logger.debug(f'Query question in corpus: [{question}]')
    gid = event.group_id if event.message_type == 'group' else 0
    result = query(question, gid, q=True)

    if not result:
        await query_record.finish(
            Message(f'没找到关于 ') + Message(question) + (Message(' 的对话')))

    Record = namedtuple('Record', [
        'sid', 'answer', 'probability', 'creator', 'source', 'creation_time',
        'public'
    ])
    result = map(lambda x: Record(*x), result)

    # 群里不把私聊中非公开对话列出,私聊中不把非自己创建的私聊非公开对话列出,用作最终显示数据
    result = [
        r for r in result if not (r.public == 0 and r.source == 0 and (
            event.message_type == 'group'
            or event.message_type != 'group' and event.user_id != r.creator))
    ]

    def sort_rule(r: Record) -> int:
        """按照本群限定>本群创建但公开>其它群创建但公开>其它群限定的顺序排列"""

        if r.source == gid:
            priority = 1 if not r.public else 2
        else:
            priority = 3 if r.public else 4
        return priority

    result.sort(key=sort_rule)

    # 可能在当前对话窗口出现的内容,把出现率是0、不在此群或私聊创建的非公开选项排除,用作计算绝对出现率
    # possible = [r for r in result if not (r.probability == 0 or (not r.public and r.source != gid))]
    # possible_count = len(possible)

    result_ls = [
        f'''ID:{sid}
回答:{msglize(answer, prestr=True)}
相对出现率:{probability}%
来自:{creator} 在 {('群' + str(source)) if source else '私聊'} 中创建的{'公开' if public else '群内限定'}对话
创建时间:
{creation_time}
────────────
''' for sid, answer, probability, creator, source, creation_time, public in
        result
    ]
    # TODO: 把回复里的音频和视频分离出来变成'[音频][视频]'
    # TODO:绝对出现率算出来

    record_bar = Pagination(*result_ls)
    if len(record_bar.rcd_ls) == 1:
        msg = ''.join(result_ls)
        await query_record.finish(
            reply_header(
                event,
                Message(
                    msg +
                    '使用"[修改出现率] <对话id> <出现率>"来修改指定对话的相对出现率\n例:修改出现率 -2234 -10')
            ))
    else:
        state["record_bar"] = record_bar
        state['left_wrong_times'] = 3
        await query_record.send(
            reply_header(
                event,
                Message(
                    str(record_bar) +
                    '\n发送[上一页][下一页]翻页查看列表,发送<序号>跳转到指定页,发送[退出]退出当前查询')))
Пример #13
0
async def get_q(bot: Bot, event: MessageEvent, state: T_State):
    if "question" not in state:
        state["question"] = await msg2str(Message(event.raw_message))
    logger.debug(f'Current question is [{state["question"]}]')
Пример #14
0
async def send_lolicon(bot: Bot, event: MessageEvent, state: T_State):

    gid = event.group_id if event.message_type=='group' else 0
    if gid:
        if str(gid) not in sl_settings:
            await setu.finish('''先设置本群sl再使用此功能吧
[设置sl 最小sl-最大sl]
例如:设置sl 0-4
────────────
sl说明:
大概可以解释成本群能接收的工口程度,sl越高的图被人看见越会触发社死事件
※ r18权限已改为sl, 当最大sl为5时即为开启r18权限,sl0-4级别仅适用于美图,色图会自动忽略
最低sl0:不含任何ero要素,纯陶冶情操,也有一部分风景图
最高sl5: 就是R18了
中间的等级依次过渡''')

        max_sl = sl_settings[str(gid)]['max_sl']
        min_sl = sl_settings[str(gid)]['min_sl']
        restricted = True if max_sl < 5 else False  # r18是否在本群受限
    else:
        restricted = False

    # 限制条件优先度:r18,5张最大数,等级限制数量,频率,资金,由于要检测参数只好先把个别参数解析混入条款中了
    uid = event.user_id

    # r18限制条款,顺便解析了r18
    r18_call = state["_matched_dict"]['r18_call'] or state["_matched_dict"]['r18_call2']
    if r18_call and restricted:
        await setu.finish(reply_header(event, f'当前群内最大sl为{max_sl},已禁止R18内容'))

    # 5张最大数量限制条款,顺便解析了num
    if state["_matched_dict"]['num']:
        num = cn2an(state["_matched_dict"]['num'].replace('两', '二'), 'smart')
    elif state["_matched_dict"]['num2']:
        num = cn2an(state["_matched_dict"]['num2'].replace('两', '二'), 'smart')
    else:
        num = 1

    if num > 5:
        await setu.finish(reply_header(event, '一次最多只能要5张'))
    elif num == 0:
        await setu.finish(reply_header(event, '你好奇怪的要求'))
    elif num < 0:
        await setu.finish(reply_header(event, f'好的,你现在欠大家{-num}张涩图,快发吧'))  # TODO: 想想办法把负数给提取出来

    # 等级限制数量条款,注册了用户信息
    userinfo = UserLevel(uid)
    if userinfo.level < num:
        if userinfo.level > 0:
            await setu.finish(f'您当前等级为{userinfo.level},最多一次要{userinfo.level}张')
        elif num > 1:
            await setu.finish(reply_header(event, '啊这..0级用户一次只能叫一张哦,使用[签到]或者学习对话可以提升等级~'))

    # 频率限制条款,注册了频率限制器
    flmt = FreqLimiter(uid, 'setu')
    if not flmt.check():
        refuse = f'你冲得太快了,请{ceil(flmt.left_time())}秒后再冲'  # 不用round主要是防止出现'还有0秒'的不科学情况
        if userinfo.level == 0:
            refuse += ',提升等级可以加快装填哦~'
        await setu.finish(reply_header(event, refuse))
    cd = cd_step(userinfo.level, 480)  # 冷却时间
    flmt.start_cd(cd)  # 先进行冷却,防止连续呼叫击穿频率装甲,要是没返回图的话解掉

    # 资金限制条款,注册了每日次数限制器
    cost = num * 3
    dlmt = DailyNumberLimiter(uid, '色图', 3)
    in_free = True if event.message_type == 'private' and event.sub_type == 'friend'\
            else dlmt.check(close_conn=False)  # 来自好友的对话不消耗金币

    if userinfo.fund < cost and not in_free:
        if userinfo.fund > 0:
            refuse = choice((f'你还剩{userinfo.fund}块钱啦,要饭也不至于这么穷吧!', f'你只剩{userinfo.fund}块钱了,要不考虑援交一下赚点钱?'))
        elif userinfo.level == 0 and userinfo.fund == 0:
            refuse = '每天有三次免费次数哦,使用[签到]领取资金来获得更多使用次数吧~'
        else:
            refuse = '你已经穷得裤子都穿不起了,到底是做了什么呀?!'
        dlmt.conn.close()  # 确认直接结束不会增加调用次数了,直接返还链接
        flmt.start_cd(0)
        await setu.finish(reply_header(event, refuse))

    kwd = state["_matched_dict"]['kwd'] or ''

    if r18_call:
        r18 = 1 if r18_call in ('r18', 'R18') else 0      
    else:
        if event.message_type == 'group':
            if max_sl < 5:
                r18 = 0
            elif min_sl == 5:
                r18 = 1
            elif min_sl < 5:
                r18 = 2
        else:
            r18 = 2
    
    msg = MessageSegment.reply(id_=event.message_id) if event.message_type == 'group' else MessageSegment.text('') # 由于当前私聊回复有bug所以只在群里设置信息开始为回复消息

    # 有搜索条件,从本地库中提
    if kwd:
        kwds = tuple(kwdrex.split(kwd))
        success, result = get_setu(gid, kwds, num, r18)
        if not success:
            flmt.start_cd(0)
            dlmt.conn.close()
            await setu.finish(reply_header(event, result))

        count = result['count']  # 返回数量,每次处理过后自减1
        miss_count = 0  # 丢失数量
        for data in result['data']:
            if data is None:
                miss_count += 1
                continue
            img : Path = data['file']
            logger.debug(f'当前处理本地图片{img.name}')
            info = f"{data['title']}\n画师:{data['author']}\nPID:{data['source']}\n"
            try:
                im_b64 = Image_Handler(img).save2b64()
            except OSError as err:
                miss_count += 1
                logger.error(f'File {img} may be damaged: {err}')
                continue
            except UnidentifiedImageError as imgerr:
                miss_count += 1
                logger.error(f'failed to open local file: {img}: {imgerr}')
                continue
            msg += MessageSegment.text(info) + MessageSegment.image(im_b64)
            if count > 1:
                msg += MessageSegment.text('\n=====================\n')
                count -= 1
            elif result['count'] < num:
                msg += MessageSegment.text(f'\n=====================\n没搜到{num}张,只搜到这些了')

    # 无搜索条件,链接API,5次错误退出并反馈错误
    else:
        logger.debug('Start getting lolicon API')
        failed_time = 0
        while failed_time < 5:
            try:
                result = await get_lolicon(kwd, r18, num)
                break
            except BaseException as e:
                failed_time += 1
                logger.exception(f"connect api faild {failed_time} time(s)\n{e}")
        else:
            logger.error(f'多次链接API失败,当前参数: kwd: [{kwd}], num: {num}, r18: {r18}')
            dlmt.conn.close()
            flmt.start_cd(0)
            await setu.finish('链接API失败, 若多次失败请反馈给维护组', at_sender=True)
        logger.debug('Receive lolicon API data!')

        # 处理数据
        if result['code'] == 0:
            count = result['count']  # 返回数量,每次处理过后自减1
            untreated_ls = []  # 未处理数据列表,遇到本地库中没有的数据要加入这个列表做并发下载
            miss_count = 0  # 丢失数量
            for data in result['data']:
                pid = data['pid']
                p = data['p']
                name = f'{pid}_p{p}'
                # 按 色图备份路径->美图原文件路径 顺序查找本地图,遇到没有本地路径的等待并发下载处理
                imgbkup = [f for f in Path(SETUPATH).glob(f'{name}.[jp][pn]*g')]
                if imgbkup:
                    img = imgbkup[0]
                else:
                    imgorg = [f for f in (Path(MEITUPATH)/'origin_info').rglob(f'{name}.[jp][pn]*g')]
                    if imgorg:
                        img = imgorg[0]
                    else:
                        untreated_ls.append(data)
                        continue
                logger.debug(f'当前处理本地图片{name}')
                info = f"{data['title']}\n画师:{data['author']}\nPID:{name}\n"
                try:
                    im_b64 = Image_Handler(img).save2b64()
                except UnidentifiedImageError as imgerr:
                    miss_count += 1
                    logger.error(f'failed to open local file: {img}: {imgerr}')
                    continue
                msg += MessageSegment.text(info) + MessageSegment.image(im_b64)
                if count > 1:
                    msg += MessageSegment.text('\n=====================\n')
                    count -= 1
                elif result['count'] < num:
                    msg += MessageSegment.text(f'\n=====================\n没搜到{num}张,只搜到这些了')
                
            # 对未处理过的数据进行并发下载,只下载1200做临时使用
            async with httpx.AsyncClient() as client:
                task_ls = []
                for imgurl in [d['url'] for d in untreated_ls]:
                    task_ls.append(client.get(get_1200(imgurl), timeout=120))
                imgs = await gather(*task_ls, return_exceptions=True)
                
            for i, data in enumerate(untreated_ls):
                if isinstance(imgs[i], BaseException):
                    miss_count += 1
                    logger.exception(data)
                    continue
                if imgs[i].status_code != httpx.codes.OK:
                    miss_count += 1
                    logger.error(f'Got unsuccessful status_code [{imgs[i].status_code}] when visit url: {imgs[i].url}')
                    continue
                pid = data['pid']
                p = data['p']
                name = f'{pid}_p{p}'
                info = f"{data['title']}\n画师:{data['author']}\nPID:{name}\n"
                logger.debug(f'当前处理网络图片{name}')
                try:
                    im_b64 = Image_Handler(imgs[i].content).save2b64()
                except BaseException as err:
                    logger.error(f"Error with handle {name}, url: [{data['url']}]\n{err}")
                    miss_count += 1
                    continue

                msg += MessageSegment.text(info) + MessageSegment.image(im_b64)
                if count > 1:
                    msg += MessageSegment.text('\n=====================\n')
                    count -= 1
                elif result['count'] < num:
                    msg += MessageSegment.text(f'\n=====================\n没搜到{num}张,只搜到这些了')
            if miss_count > 0 and num > 1:
                msg += MessageSegment.text(f'\n有{miss_count}张图丢掉了,{BOTNAME}也不知道丢到哪里去了T_T')
            elif miss_count == 1:
                msg += MessageSegment.text(f'{BOTNAME}拿来了图片但是弄丢了呜呜T_T')
        else:
            flmt.start_cd(0)
            dlmt.conn.close()
            await setu.finish(msg + MessageSegment.text('获取涩图失败,请稍后再试'))

    try:
        await setu.send(msg)
    except NetworkError as err:
        logger.error(f'Maybe callout error happend: {err}')
    except AdapterException as err:
        logger.error(f"Some Unkown error: {err}")

    if miss_count < result['count']:
        if not in_free:
            cost = (result['count'] - miss_count) * 3  # 返回数量可能少于调用量,并且要减去miss的数量
            userinfo.turnover(-cost)  # 如果超过每天三次的免费次数则扣除相应资金
        dlmt.increase()  # 调用量加一
    else:
        flmt.start_cd(0)  # 一张没得到也刷新CD
        dlmt.conn.close()

    # 下载原始图片做本地备份
    if not kwd:
        async with httpx.AsyncClient() as bakeuper:
            backup_ls = []
            json_ls = []
            for info in untreated_ls:
                url = info['url']
                json_data = {
                    'pid': info['pid'],
                    'p': info['p'],
                    'uid': info['uid'],
                    'title': info['title'],
                    'author': info['author'],
                    'url': url,
                    'r18': info['r18'],
                    'tags': info['tags']
                }
                backup_ls.append(bakeuper.get(url, timeout=500))
                json_ls.append(json_data)
            origims = await gather(*backup_ls, return_exceptions=True)
            for i, im in enumerate(origims):
                if isinstance(im, BaseException):
                    logger.exception(im)
                    continue
                if im.status_code != httpx.codes.OK:
                    logger.error(f'Got unsuccessful status_code [{im.status_code}] when visit url: {im.url}')
                    continue
                imgfp = Path(SETUPATH)/(str(json_ls[i]['pid']) + '_p' + str(json_ls[i]['p']) + '.' + json_ls[i]['url'].split('.')[-1])
                jsonfp = Path(SETUPATH)/(str(json_ls[i]['pid']) + '_p' + str(json_ls[i]['p']) + '.json')
                try:
                    with imgfp.open('wb') as f:
                        f.write(im.content)
                    logger.info(f'Downloaded image {imgfp.absolute()}')
                except BaseException as e:
                    logger.exception(e)
                with jsonfp.open('w', encoding='utf-8') as j:
                    json.dump(json_ls[i], j, ensure_ascii=False, escape_forward_slashes=False, indent=4)
                    logger.info(f'Generated json {jsonfp.absolute()}')
                increase_setu(**json_ls[i])
Пример #15
0
def get_mitu(gid: int,
             kwd: tuple = (),
             num: int = 1,
             min_sl: int = 0,
             max_sl: int = 1):
    """从数据库中获得美图数据

    Args:
        gid (int): 呼叫美图的群号,相同群内会有两个小时的记录去重
        kwd (tuple, optional): 关键词,元组内的词会做交集查询. Defaults to ().
        num (int, optional): 查询数量. Defaults to 1.
        min_sl (int, optional): 最小的sl值. Defaults to 0.
        max_sl (int, optional): 最大的sl值. Defaults to 1.

    Returns:
        tuple: 返回是否调用成功以及返回的结果,如果没有查询结果会返回无结果还是结果已经用尽,如果有结果会返回数据列表
        数据结构:{
                'title' (str): 作品名,
                'author' (str): 作者,
                'source' (str): 来源, Pixiv pid_p
                'sl' (int): sl级别,
                'file' (int): 文件路径
                }
    """
    if gid not in Called_Data:
        Called_Data[gid] = Group_Called(gid)
    his = Called_Data[gid]
    kwds = "%" + '%'.join(kwd) + "%"  # like查询要在两边加上%占位符
    kwds_demojize = demojize(kwds)  # 查询数据库要用转义为普通字符的emoji
    with GalleryDB() as glrdb:
        # TODO: 优化随机查询的方式取代orded by rand()
        if len(his.called) > 1:
            cmd = f'''select title, author, source, sl, visits, id from gallery
            where sl between %s and %s and id not in {tuple(his.called)}
            and (tags like %s or title like %s or author like %s)
            ORDER BY RAND() LIMIT %s;'''
        else:
            cmd = '''select title, author, source, sl, visits, id from gallery
            where sl between %s and %s and (tags like %s or title like %s or author like %s)
            ORDER BY RAND() LIMIT %s;'''
        params = (min_sl, max_sl, kwds_demojize, kwds_demojize, kwds_demojize,
                  num)
        results = glrdb.queryall(cmd, params)
        # 随机查询不在called列表中的图片
        if not results:
            if kwds in his.called_kwds:
                return False, f'暂时没有更多关于"{kwds.replace("%", "|")[1:-1]}"的美图了'
            else:
                return False, f'没有找到关于"{kwds.replace("%", "|")[1:-1]}"的美图'  # TODO: 换了标签之后再搜索到相同的图用这个标签不太合适,换成"有x张相关图刚才已经发过了"
        mitu_ls = []  # 所有美图列表
        for record in results:
            if record[2].startswith(
                    'Pixiv'):  # 暂时只有pixiv图片,加入非p站图片之后要增加拼装文件名方法
                filestem = record[2][6:]  # 名字是去掉Pixiv 前缀剩下的pid_p
            searchfile = glob.glob(
                str(Path(MEITUPATH) / ('sl' + str(record[3])) / (filestem)) +
                '.[jp][pn]*g')  # 从本地图库中搜索图片,暂时只搜索jpg, png, jpeg,正常情况应该只能搜到一个

            if not searchfile:
                logger.error(f'Not found file {filestem}')
                mitu_ls.append(None)
                continue
            elif len(searchfile) > 1:
                logger.warning(f'查找到多个名称为{filestem}的图片,请检查图库')
            logger.debug(f'Found {len(searchfile)} file(s):' +
                         '\n'.join(searchfile))
            data = {
                'title': emojize(record[0]),
                'author': emojize(record[1]),
                'source': record[2],
                'sl': record[4],
                'file': searchfile[0]
            }
            mitu_ls.append(data)

            his.called.add(record[5])  # 添加id记录

        his.called_kwds.add(kwds)  # 添加关键词记录
        his.check_expired()  # 检查记录是否过期
        if len(results) == 1:
            increase_cmd = "UPDATE gallery SET visits=visits+1 WHERE id=%s;"
            increase_parm = (results[0][5], )
        else:
            id_ls = str(tuple([r[5] for r in results]))  # 如果有多条记录就传入元组同时加1
            increase_cmd = f"UPDATE gallery SET visits=visits+1 WHERE id in {id_ls};"
            increase_parm = ()
        glrdb.update(increase_cmd, increase_parm)  # 调用次数加一

        return True, mitu_ls