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)
async def show_remind(bot: Bot, event: GroupMessageEvent, state: T_State): gid = str(event.group_id) if gid not in exitremind_settings: en_setting = DEFAULT_REMIND else: en_setting = exitremind_settings[gid] lv_reminds = '\n'.join([f'{i+1}.{speech}' for i, speech in enumerate(en_setting['leave'])]) kk_reminds = '\n'.join([f'{i+1+len(en_setting["leave"])}.{speech}' for i, speech in enumerate(en_setting['kick'])]) status = '已锁定' if en_setting['locked'] else '未锁定' msg = '当前群内主动退群提醒语句为:\n' + lv_reminds + '\n────────────\n被管理踢出群聊提醒语句为:\n' + kk_reminds + '\n────────────\n可修改状态:' + status if en_setting['locked'] and event.sender.role == 'member': await remind_editor.finish(Message(msg)) else: msg += '\n────────────\n使用以下命令修改通知语句(不带中括号):\n[添加主动退群] 添加一个主动退群提醒语句(使用{name}字段可自动替换退群者的昵称,参考默认邀请语句)\n[添加管理踢人] 添加一个管理踢人提醒语句(除{name}之外可使用{admin}字段可自动替换执行的管理的昵称,参考默认踢人提醒语句)\n[删除+序号] 删除指定的语句\n[切换锁定] 更改锁定状态,锁定状态下群员不可修改退群提醒语句' await remind_editor.send(Message(msg))
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
async def show_speech(bot: Bot, event: GroupMessageEvent, state: T_State): gid = str(event.group_id) if gid not in welcome_settings: wl_setting = DEFAULT_SPEECH else: wl_setting = welcome_settings[gid] ap_speeches = '\n'.join([f'{i+1}.{speech}' for i, speech in enumerate(wl_setting['approve'])]) # in_speeches = '\n'.join([f'{i+1+len(wl_setting["approve"])}.{speech}' for i, speech in enumerate(wl_setting['invite'])]) status = '已锁定' if wl_setting['locked'] else '未锁定' msg = '当前新人入群欢迎语句为:\n' + ap_speeches + '\n────────────\n可修改状态:' + status if wl_setting['locked'] and event.sender.role == 'member': await speech_editor.finish(Message(msg)) else: # msg += '\n────────────\n使用以下命令修改通知语句(不带中括号):\n[添加] 添加一个迎新语句(使用{name}字段可自动替换新人的昵称,参考默认欢迎语句)\n[添加邀请入群] 添加一个被邀请入群欢迎语句(除{name}之外可使用{admin}字段可自动替换邀请人的昵称,参考默认邀请语句)\n[删除+序号] 删除指定的语句\n[切换锁定] 更改锁定状态,锁定状态下群员不可修改欢迎语句' msg += '\n────────────\n使用以下命令修改通知语句(不带中括号):\n[添加] 添加一个迎新语句(使用{name}字段可自动替换新人的昵称,参考默认欢迎语句)\n[删除+序号] 删除指定的语句\n[切换锁定] 更改锁定状态,锁定状态下群员不可修改欢迎语句' await speech_editor.send(Message(msg))
async def show_record(bot: Bot, event: GroupMessageEvent): arg = str(event.message).split('号记录')[0] if not arg.isdigit(): await recorder.finish(reply_header(event, '哪有这种代号的记录啊?!')) fake_id = int(arg) if fake_id not in recalled[event.group_id]: await recorder.finish(reply_header(event, '这条记录不存在或者因为太久所以被消除了~')) msg_id, passive, timestamp = recalled[event.group_id][fake_id] try: msginfo = await bot.get_msg(message_id=msg_id) logger.debug( f"Got recalled message({type(msginfo['message'])}): {str(msginfo['message'])}" ) except ActionFailed: await recorder.finish(reply_header(event, '这条记录不存在或者因为太久所以被消除了~')) for seg in msginfo["message"]: logger.debug(f'Check type of segment: {seg}\n{seg["type"]}') if seg['type'] not in ('text', 'face', 'image', 'at', 'reply'): # 可以夹在普通消息框里的片段 can_append = False break else: can_append = True time = datetime.fromtimestamp(timestamp).strftime("%Y-%m-%d %H:%M:%S") try: litigant_info = await bot.get_group_member_info( group_id=event.group_id, user_id=msginfo["sender"]["user_id"]) name = litigant_info["card"] or litigant_info["nickname"] or str( litigant_info["user_id"]) except ActionFailed: name = msginfo["sender"]["user_id"] # 没获取到的话可能群员退群了 if passive: header = f"{name}在{time}被撤回了:\n" else: header = f"{name}在{time}撤回了:\n" if can_append: await recorder.finish(Message(header) + (Message(msginfo["message"]))) else: await recorder.send(Message(header)) await asleep(1) # await recorder.finish(msginfo["message"]) await recorder.finish(Message(msginfo["raw_message"]))
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 # 输入错误次数,用于获取出现率时计算
async def welcome_newcomers(bot: Bot, event: GroupIncreaseNoticeEvent): gid = str(event.group_id) if gid not in welcome_settings: welcome_settings[gid] = DEFAULT_SPEECH settings = welcome_settings[gid] userinfo = await bot.get_group_member_info(group_id=event.group_id, user_id=event.user_id) name = userinfo['card'] or userinfo['nickname'] or str(event.user_id) # admininfo = await bot.get_group_member_info(group_id=event.group_id, user_id=event.operator_id) # admin = admininfo['card'] or admininfo['nickname'] or str(event.user_id) # msg = Message(choice(settings[event.sub_type]).format(name=name, admin=admin)) msg = Message(choice(settings['approve']).format(name=name)) await entry_welcome.finish(msg, at_sender=True)
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, '这条词语好像记不住耶,要不联系主人试试?'))
async def first_receive(bot: Bot, event: MessageEvent, state: T_State): # 过滤妖精的早苗 if event.user_id in SANAE_BOTS: await learn.finish('两只AI成功握手,但被主人阻止掉了(;∀;)') command = state["_prefix"]["raw_command"] if command == '群内学习': if isinstance(event, GroupMessageEvent): state["public"] = 0 else: await learn.finish( '[群内学习]只适用于在群中对话哦,公开性对话学习请使用[学习对话],私聊内保密对话学习命令为[私聊学习]') elif command in ('私聊学习', '偷偷学习'): if isinstance(event, PrivateMessageEvent): state["public"] = 0 else: await learn.finish( f'[{command}]只适用于在私聊中对话哦,公开性对话学习请使用[学习对话],群内保密对话学习命令为[群内学习]') else: if command == '自学': state["selflearn"] = True state["public"] = 1 state[ "force_priv"] = False # 强制不公开,输入q或a中有at信息且没有用私有学习命令时改为true并在最后将public强制设置为1 arg = str(event.get_message()) if arg: if ' 回答' not in arg: state['question'] = arg else: # 快速学习,但插入记录仍放到对话最后处理 question, answer = arg.split(' 回答', maxsplit=1) state["question"] = await msg2str(Message(question)) answer = await msg2str(Message(answer), localize_=True, bot=bot) if not answer: await learn.finish(reply_header(event, '这条词语好像记不住耶,要不联系主人试试?')) else: state["answer"] = answer.lstrip( ) # 删除行左的空格,因为不确定用户是否会输入多个空格做回答分隔符,如果有需求可能要改逻辑
async def member_exit_remind(bot: Bot, event: GroupDecreaseNoticeEvent): gid = str(event.group_id) if gid not in exitremind_settings: exitremind_settings[gid] = DEFAULT_REMIND settings = exitremind_settings[gid] userinfo = await bot.get_stranger_info(user_id=event.user_id) name = userinfo['nickname'] or str(event.user_id) if event.user_id != event.operator_id: admininfo = await bot.get_group_member_info(group_id=event.group_id, user_id=event.operator_id) admin = admininfo['card'] or admininfo['nickname'] or str(event.user_id) else: admin = name msg = Message(choice(settings[event.sub_type]).format(name=name, admin=admin)) await entry_exitremind.finish(msg)
async def get_setu(bot: Bot, event: MessageEvent, state: T_State): msg: Message = Message(state["setu"]) try: for seg in msg: if seg.type == "image": url = seg.data["url"] # 图片链接 break else: await setu.finish(reply_header(event, "这也不是图啊!")) await bot.send(event=event, message="让我搜一搜...") result = MessageSegment.text('————>SauceNao<————') async for msg in get_des(url, 'sau'): if not msg: await setu.send('未从saucenao检索到高相似度图片,将运行ascii2d检索') break result += msg + '────────────\n' else: result = Message(str(result).rstrip('────────────\n')) await setu.finish(result) result = MessageSegment.text('————>ascii2d<————') async for msg in get_des(url, 'ascii2d'): if not msg: await setu.finish('未从ascii2d检索到高相似度图片,请等待加入更多检索方式') break result += msg + '────────────\n' else: result = Message(str(result).rstrip('────────────\n')) await setu.finish(result) except (IndexError, ClientError): logger.exception(traceback.format_exc()) await setu.finish("遇到未知打击,中断了搜索") except ActionFailed as e: logger.error(f'Send result failed: {e}') await setu.finish('虽然搜到了,但是发送结果途中遭遇拦截,可稍后再试一试')
async def parse_qa(bot: Bot, event: MessageEvent, state: T_State): # 退出指令 if str(event.message) in CANCEL_EXPRESSION: await learn.finish('已退出当前对话') # if f'[CQ:at,qq={event.self_id}]' in event.raw_message: # await learn.finish('我为什么要at我自己?不要这样啦,会有bug的::>_<::') for seg in Message(event.raw_message): if seg.type == "at" and seg.data["qq"] != str(event.self_id): # 强制非公开 if state["public"]: state["force_priv"] = True logger.info('Got at info, force set public to 0') # 不能存入消息的格式 if seg.type not in ALLOW_SEGMENT: if seg.type == 'reply': await learn.finish('请不要学习带有回复上文消息的内容,会引发定位错误') else: await learn.finish('接收的消息不在可学习范围内')
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('接收的消息不在可学习范围内')
def msglize(msg: str, name: str = "{name}", prestr: bool = False) -> Union[Message, str]: """解析数据库answer时调用,把返回消息中的{res_path}替换为真实资源路径, 把{name}换成昵称并去转义emoji Args: msg (str): 数据库中的answer name (str, optional): 要替换{name}字段的字符,通常为event.sender.card|nickname. Defaults to "{name}". prestr (bool): 是否要保持字符串,使用此选项不会把消息转为Message而是会保持为字符串返回. Defaults to False. Returns: Union[Message, str]: 解析后自动转换Message或保持str """ if '[CQ:image,' in msg or "{name}" in msg: msg = msg.format(res_path=str(CORPUS_IMAGES_PATH), name=name) if prestr: return emojize(msg) else: return Message( emojize(msg)) # 由于nb2使用array上报数据所以要实例化为Message可直接转化旧版字符串数据
async def look_over(bot: Bot, event: MessageEvent, state: T_State): op = str(event.message.extract_plain_text()) if not op: await query_record.reject() if op in CANCEL_EXPRESSION: await query_record.finish('已退出查询页面') if op.strip().startswith('修改出现率'): await handle_event(bot, event) # 如果不能一次输入参数的话两个对话会同时进行产生冲突,参数数量不符合时会直接结束查询对话 if len(op.strip('-')) == 3: await query_record.reject() else: await query_record.finish() bar: Pagination = state["record_bar"] addend = '\n发送[上一页][下一页]翻页查看列表,发送<页面序号>跳转到指定页\n使用 [修改出现率] <对话id> <出现率> 来修改指定对话的相对出现率\n发送[退出]退出当前查询' if op == "上一页": if bar.crupg == 1: msg = '当前已经是首页了哦~' state['left_wrong_times'] -= 1 else: msg = bar.pgup() + addend elif op == '下一页': if bar.crupg == len(bar.rcd_ls): msg = '已经是最后页了~' state['left_wrong_times'] -= 1 else: msg = bar.pgdn() + addend elif op.isdigit(): pgnum = int(op) if pgnum > 0 and pgnum <= len(bar.rcd_ls): msg = bar.turnpage(pgnum) + addend else: msg = '超出当前已有的页面范围了~' state['left_wrong_times'] -= 1 elif state['left_wrong_times'] > 0: msg = f"未期望的输入,{state['left_wrong_times']}次输入错误将退出查询对话,发送[退出]退出当前查询" state['left_wrong_times'] -= 1 else: await query_record.finish('未期望的输入,已退出当前查询对话') await query_record.reject(Message(msg))
async def fake_del_handle(bot: Bot, event: MessageEvent, state: T_State): # 退出指令 if str(event.message) in CANCEL_EXPRESSION: await delete_record.finish('已退出当前对话') sid = state["sid"] if "sid" in state else event.message.extract_plain_text( ).strip() if event.user_id in SUPERUSERS: # 真实的删除 try: sid = int(sid) except ValueError: await delete_record.finish(reply_header(event, '非数字参数')) exsit = query_exists(sid) if not exsit: await delete_record.finish(reply_header(event, '不存在的对话')) del_record(sid) await delete_record.finish(reply_header(event, f'已删除对话{sid}')) else: # 虚假的删除 event.message = Message(f'修改出现率 -{sid} -0') await handle_event(bot, event) # TODO: 查询自己设置过的,举报,修改随机算法,先预计算所有会出现的对话再choice
async def standby(bot: Bot, event: GroupMessageEvent, state: T_State): await repeater.finish(Message(state['raw_msg']))
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发送[上一页][下一页]翻页查看列表,发送<序号>跳转到指定页,发送[退出]退出当前查询')))
async def recieve_query(bot: Bot, event: MessageEvent, state: T_State): arg = await msg2str(Message(event.message)) if arg: state["question"] = arg
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"]}]')