def _get_group_uncached(self, name: Union[str, int], referer: Optional["PermissionGroup"], required: bool ) -> Tuple["PermissionGroup", bool]: group_desc = self.config.get(name) if group_desc is None: if required: if referer: logger.error('Permission group {}:{} not found (required from {}:{})', self.name, name, referer.namespace.name, referer.name) else: logger.error('Permission group {}:{} not found', self.name, name) return NullPermissionGroup(), False try: desc = parse_obj_as(GroupDesc, group_desc) except ValueError: logger.exception('Failed to parse {}:{} ({})', self.name, name, self.path) return NullPermissionGroup(), False # 注入插件预设 if self.name == 'global' and name in default_groups: for pn in plugin_namespaces: if name in pn.config: desc.inherits.append(f'{pn.name}:{name}') self.groups[name] = group = PermissionGroup(self, name) group.populate(desc, referer) return group, True
async def _real_run_command(session: CommandSession, ctx_id: str, disable_interaction: bool = False, **kwargs) -> Optional[bool]: if not disable_interaction: # override session only when interaction is not disabled _sessions[ctx_id] = session try: logger.debug(f'Running command {session.cmd.name}') session.running = True future = asyncio.ensure_future(session.cmd.run(session, **kwargs)) timeout_opt = session.run_timeout timeout = timeout_opt.total_seconds() if timeout_opt else None try: await asyncio.wait_for(future, timeout) handled = future.result() except asyncio.TimeoutError: handled = True except CommandInterrupt: raise except Exception as e: logger.error(f'An exception occurred while ' f'running command {session.cmd.name}:') logger.exception(e) handled = True raise _FinishException(handled) except _PauseException: session.running = False if disable_interaction: # if the command needs further interaction, we view it as failed return False logger.debug(f'Further interaction needed for ' f'command {session.cmd.name}') # return True because this step of the session is successful return True except _YieldException: return True # return True because this step of the session is successful except (_FinishException, SwitchException) as e: session.running = False logger.debug(f'Session of command {session.cmd.name} finished') if not disable_interaction and ctx_id in _sessions: # the command is finished, remove the session, # but if interaction is disabled during this command call, # we leave the _sessions untouched. del _sessions[ctx_id] if isinstance(e, _FinishException): return e.result elif isinstance(e, SwitchException): # we are guaranteed that the session is not first run here, # which means interaction is definitely enabled, # so we can safely touch _sessions here. if ctx_id in _sessions: # make sure there is no session waiting del _sessions[ctx_id] logger.debug(f'Session of command {session.cmd.name} switching, ' f'new message: {e.new_message}') raise e # this is intended to be propagated to handle_message()
async def broadcast(self, msgs: Union[str, MessageSegment, Message, Iterable[Any]], TAG: str = '', interval_time: float = 0.5, randomiser=None): bot = list(get_bots().values())[0] if isinstance(msgs, (str, MessageSegment, Message)): msgs = (msgs, ) else: msgs = tuple(msgs) group_list = await self.get_enabled_groups(bot) for gid in group_list: try: for msg in msgs: await asyncio.sleep(interval_time) msg = randomiser(msg) if randomiser else msg await bot.send_group_msg(self_id=bot.self_id, group_id=gid, message=msg) l = len(msgs) if l: logger.info(f"群{gid} 投递{TAG}成功 共{l}条消息") except Exception as e: logger.error(f"群{gid} 投递{TAG}失败:{type(e)}") logger.exception(e)
async def _run_matcher(Matcher: Type[Matcher], bot: Bot, event: Event, state: dict) -> Union[None, NoReturn]: if Matcher.expire_time and datetime.now() > Matcher.expire_time: raise _ExceptionContainer([ExpiredException]) try: if not await Matcher.check_perm( bot, event) or not await Matcher.check_rule(bot, event, state): return except Exception as e: logger.error(f"Rule check failed for matcher {Matcher}. Ignored.") logger.exception(e) return # TODO: log matcher logger.info(f"Event will be handled by {Matcher}") matcher = Matcher() # TODO: BeforeMatcherRun try: logger.debug(f"Running matcher {matcher}") await matcher.run(bot, event, state) except Exception as e: logger.error(f"Running matcher {matcher} failed.") logger.exception(e) exceptions = [] if Matcher.temp: exceptions.append(ExpiredException) if Matcher.block: exceptions.append(StopPropagation) if exceptions: raise _ExceptionContainer(exceptions)
def save_all(): logger.debug('Saving permissions') for k, v in loaded.items(): try: v.save() except Exception as e: _ = e logger.exception('Failed to save namespace {}', k)
async def send_grass(self, bot, group): if group not in self.last_grass or time.time( ) - self.last_grass[group] > config.grass_delay: try: await bot.send_group_msg(group_id=group, message='草') except CQHttpError as e: logger.exception(e.retcode) self.last_grass[group] = time.time()
async def init_mysql(self, host='', port=44444, user='', password='', db='', charset=''): try: self.conn = await aiomysql.connect( host=host, port=port, user=user, password=password, db=db, charset=charset) self.cur = await self.conn.cursor(aiomysql.cursors.DictCursor) except aiomysql.Error as e: logger.error("服务器连接失败!") logger.exception(e)
async def http_query(id_list, user_id, region=1, force=False): # id_list = [ x * 100 + 1 for x in id_list ] t = id_list.copy() t.sort() attack = ",".join(str(v) for v in t) result_list = jijian.get_attack(attack) if result_list is None or force: header = { 'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.87 Safari/537.36', 'authorization': __get_auth_key() } payload = { "_sign": "a", "def": id_list, "nonce": "a", "page": 1, "sort": 1, "ts": int(time.time()), "region": region } logger.debug(f'Arena query {payload=}') try: resp = await aiorequests.post( 'https://api.pcrdfans.com/x/v1/search', headers=header, json=payload, timeout=10) res = await resp.json() logger.debug(f'len(res)={len(res)}') except Exception as e: logger.exception(e) return f'{e}' if res['code']: code = int(res['code']) logger.error( f"Arena query failed.\nResponse={res}\nPayload={payload}") return f'Arena query failed.\nCode={code}' result_list = res['data']['result'] result = ','.join(str(v) for v in result_list) jijian.add_attack(attack, result) logger.info('[do_query INFO]:在线查询') else: try: result_list = str(result_list) result_list = eval(result_list) result_list = eval(result_list[0]) logger.info('[do_query INFO]:本地缓存') except Exception as e: logger.exception(e) return f'{e}' return result_list
def _load_config() -> Dict[str, Any]: """载入外部的配置文件""" try: with open(_save_path, 'r', encoding='utf-8') as f: config = json.load(f) return config except FileNotFoundError: logger.warning(f'未找到配置文件"{_save_path}",将使用默认配置。') return {} except Exception as e: logger.exception(e) return {}
def load_plugin(module_path: str) -> Optional[Plugin]: try: _tmp_matchers.clear() module = importlib.import_module(module_path) plugin = Plugin(module_path, module, _tmp_matchers.copy()) plugins[module_path] = plugin logger.info(f"Succeeded to import \"{module_path}\"") return plugin except Exception as e: logger.error(f"Failed to import \"{module_path}\", error: {e}") logger.exception(e) return None
def load_config(inbuilt_file_var): """ Just use `config = load_config(__file__)`, you can get the config.json as a dict. """ filename = os.path.join(os.path.dirname(inbuilt_file_var), 'config.json') try: with open(filename, encoding='utf8') as f: config = json.load(f) return config except Exception as e: logger.exception(e) return {}
async def select_all(self, query, params=None): ''' 查询数据表的单条数据 :param query:包含%s的sql字符串,当params=None的时候,不包含%s :param params:一个元祖,默认为None :return:如果执行未crash,并以包含dict的列表的方式返回select的结果,否则返回错误代码001 ''' try: await self.cur.execute(query, params) # await self.cur.scroll(0, "absolute") # 光标回到初始位置,感觉这里得这句有点多余 return await self.cur.fetchall() except BaseException as e: logger.info("[sql_query] - %s" % query) logger.info("[sql_params] - %s" % params) logger.exception(e) return None # 错误代码001
async def op_sql(self, query, params=None): ''' 单条数据的操作,insert,update,delete :param query:包含%s的sql字符串,当params=None的时候,不包含%s :param params:一个元祖,默认为None :return:如果执行过程没有crash,返回True,反之返回False ''' try: await self.cur.execute(query, params) await self.conn.commit() return True except BaseException as e: await self.conn.rollback() # 如果这里是执行的执行存储过程的sql命令,那么可能会存在rollback的情况,所以这里应该考虑到 logger.info("[sql_query] - %s" % query) logger.info("[sql_params] - %s" % (params,)) logger.exception(e) return False
async def insert_many(self, query, params): ''' 向数据表中插入多条数据 :param query:包含%s的sql字符串,当params=None的时候,不包含%s :param params:一个内容为元祖的列表 :return:如果执行过程没有crash,返回True,反之返回False ''' try: await self.cur.executemany(query, params) await self.conn.commit() return True except BaseException as e: await self.conn.rollback() logger.info("[sql_query] - %s" % query) logger.info("[sql_params] - %s" % params) logger.exception(e) return False
async def _dispatcher(self): while not self.closed: try: data = await self.receive() except websockets.ConnectionClosedOK: logger.debug(f'Websocket connection {self.websocket} closed') break except websockets.ConnectionClosedError: logger.exception(f'Websocket connection {self.websocket} ' 'connection closed abnormally:') break except json.JSONDecodeError as e: logger.exception(f'Websocket client listened {self.websocket} ' f'failed to decode data: {e}') continue asyncio.gather( *map(lambda f: f(data), self.event_handlers), #type: ignore return_exceptions=True)
async def notify_others(self, event_name: str, **kwargs): """ 主动发出事件 :参数: - `event_name: str`: 发出事件的名称 - `kwargs`: 传递给事件处理器的参数 """ handlers = self._event_handlers.get(event_name, []) if not handlers: logger.info(f'服务事件{event_name}没有处理程序。') return for handler in handlers: try: await handler(**kwargs) logger.info(f'服务事件{event_name}被{handler.__name__}处理。') except Exception as e: logger.error(f'服务事件{event_name}被{handler.__name__}处理时发生错误:{e}') logger.exception(e)
def load_plugins(*plugin_dir: str) -> Set[Plugin]: loaded_plugins = set() for module_info in pkgutil.iter_modules(plugin_dir): _tmp_matchers.clear() name = module_info.name if name.startswith("_"): continue try: spec = module_info.module_finder.find_spec(name) module = module_from_spec(spec) plugin = Plugin(name, module, _tmp_matchers.copy()) plugins[name] = plugin loaded_plugins.add(plugin) logger.info(f"Succeeded to import \"{name}\"") except Exception as e: logger.error(f"Failed to import \"{name}\", error: {e}") logger.exception(e) return loaded_plugins
async def _clanbattle_bus(bot: Bot, event: Event, state: dict): # check prefix start = '' for m in event.message: if m.type == 'text': start = m.data.get('text', '').lstrip() break # if not start or start[0] not in '!!': # return # find cmd plain_text = event.message.extract_plain_text() cmd, *args = plain_text[0:].split() cmd = helper.normalize_str(cmd) if cmd in _registry: func, parser = _registry[cmd] try: logger.info( f'Message {event.message_id} is a clanbattle command, start to process by {func.__name__}.' ) args = parser.parse(args, event.message) await func(bot, event, args) logger.info( f'Message {event.message_id} is a clanbattle command, handled by {func.__name__}.' ) except DatabaseError as e: await bot.send(event, f'DatabaseError: {e.message}\n{SORRY}\n※请及时联系维护组', at_sender=True) except ClanBattleError as e: await bot.send(event, e.message, at_sender=True) except Exception as e: logger.exception(e) logger.error( f'{type(e)} occured when {func.__name__} handling message {event.message_id}.' ) await bot.send(event, f'Error: 机器人出现未预料的错误\n{SORRY}\n※请及时联系维护组', at_sender=True)
def __init__(self, namespace: str, required: bool, path: Optional[Path], modifiable: bool): self.name = namespace self.path = path self.groups: Dict[Union[str, int], PermissionGroup] = {} self.dirty = False self.modifiable = modifiable if not path: self.config = {} self.modifiable = False elif not required and not path.is_file(): self.config = {} else: try: doc = yaml.load(path) except (OSError, YAMLError): logger.exception('Failed to load namespace {} ({})', namespace, path) doc = {} if not isinstance(doc, dict): logger.error('Expect a dict: {} ({})', namespace, path) doc = {} self.config: Dict[Union[str, int], dict] = doc
async def _real_run_command(session: CommandSession, ctx_id: str, disable_interaction: bool = False, **kwargs) -> Optional[bool]: if not disable_interaction: # override session only when interaction is not disabled _sessions[ctx_id] = session try: logger.debug(f'Running command {session.cmd.name}') session.running = True future = asyncio.ensure_future(session.cmd.run(session, **kwargs)) timeout = None if session.bot.config.SESSION_RUN_TIMEOUT: timeout = session.bot.config.SESSION_RUN_TIMEOUT.total_seconds() try: # 设置超时,运行命令。 await asyncio.wait_for(future, timeout) handled = future.result() except asyncio.TimeoutError: handled = True except (PauseException, FinishException, SwitchException) as e: raise e except Exception as e: logger.error(f'An exception occurred while ' f'running command {session.cmd.name}:') logger.exception(e) handled = True # 命令正常执行完成抛出完成异常。 raise FinishException(handled) # 如果命令被暂停,则 running 为 False 但是不删除 Session; except PauseException: session.running = False if disable_interaction: # if the command needs further interaction, we view it as failed return False logger.debug(f'Further interaction needed for ' f'command {session.cmd.name}') # return True because this step of the session is successful return True # 如果命令被完成或者切换,则删除 Session。 except (FinishException, SwitchException) as e: session.running = False logger.debug(f'Session of command {session.cmd.name} finished') if not disable_interaction and ctx_id in _sessions: # the command is finished, remove the session, # but if interaction is disabled during this command call, # we leave the _sessions untouched. del _sessions[ctx_id] if isinstance(e, FinishException): return e.result elif isinstance(e, SwitchException): # we are guaranteed that the session is not first run here, # which means interaction is definitely enabled, # so we can safely touch _sessions here. if ctx_id in _sessions: # make sure there is no session waiting del _sessions[ctx_id] logger.debug(f'Session of command {session.cmd.name} switching, ' f'new message: {e.new_message}') raise e # this is intended to be propagated to handle_message()
async def top(session: CommandSession): if session.ctx['message_type'] == 'group': command = session.get('command', prompt='?') if command == 'today': time_before = time.time() today_query = { '$and': [{ 'time': { '$gte': today_start_time() } }, { 'group_id': session.ctx['group_id'] }] } today_sender = {} today_qqlist = [] today_rank = [] for x in db.watertop.find(today_query, {'_id': 0, 'sender': 1}): # print(x) if x['sender']['user_id'] in today_sender: today_sender[x['sender']['user_id']] += 1 else: today_sender[x['sender']['user_id']] = 1 for k in sorted(today_sender, key=today_sender.__getitem__, reverse=True): today_qqlist.append(k) if len(today_qqlist) >= 10: break for i in range(len(today_qqlist)): try: ranker = await session.bot.get_group_member_info( group_id=session.ctx['group_id'], user_id=today_qqlist[i]) except CQHttpError as eo: logger.exception(eo) try: ranker = await session.bot.get_stranger_info( user_id=today_qqlist[i]) ranker['card'] = ranker['nickname'] + '(已退群)' except CQHttpError as e: logger.exception(e) ranker = {'card': str(today_qqlist[i]) + '(名称获取失败)'} today_rank.append( f'第{cn_number[i]}名:{ranker["card"] if ranker["card"] != "" and "card" in ranker else ranker["nickname"]}---{today_sender[today_qqlist[i]]}条\n' ) water_report = f'你群今日水群排行\n{"".join(today_rank)}本次查询花费{str(time.time() - time_before)}秒' elif command == 'help': water_report = 'top\n从机器人开始记录的第一条消息起开始计算\ntop today\n只显示今天发言排行\ntop myself\n查看自己当前在本群的排名和发言条数(从有记录开始)\ntop BaseOnMe\n以自己被机器人第一次记录的发言开始计算排名\ntop today-myself\n今日你的排名' elif command == 'myself': time_before = time.time() self_query = {'group_id': session.ctx['group_id']} self_sender = {} self_qqlist = [] self_rank = [] for x in db.watertop.find(self_query, {'_id': 0, 'sender': 1}): # print(x) if x['sender']['user_id'] in self_sender: self_sender[x['sender']['user_id']] += 1 else: self_sender[x['sender']['user_id']] = 1 for k in sorted(self_sender, key=self_sender.__getitem__, reverse=True): self_qqlist.append(k) if k == session.ctx['sender']['user_id']: break if self_qqlist[-1] == session.ctx['sender']['user_id']: try: ranker = await session.bot.get_group_member_info( group_id=session.ctx['group_id'], user_id=session.ctx['sender']['user_id']) except CQHttpError: try: ranker = await session.bot.get_stranger_info( user_id=session.ctx['sender']['user_id']) ranker['card'] = ranker['nickname'] except CQHttpError: ranker = {'card': str(self_qqlist[i]) + '(名称获取失败)'} water_report = f"你在本群排第{str(len(self_qqlist))}名:共{self_sender[session.ctx['sender']['user_id']]}条\n本次查询花费{str(time.time() - time_before)}秒" else: water_report = '你已被加入bot忽略名单' elif command == 'BaseOnMe': time_before = time.time() bom_start = 0 for r in db.watertop.find( { 'sender': { 'user_id': session.ctx['sender']['user_id'] } }, { '_id': 0, 'sender': 1 }).limit(1).sort('time'): bom_start = r['time'] break bom_query = { '$and': [{ 'time': { '$gte': bom_start } }, { 'group_id': session.ctx['group_id'] }] } bom_sender = {} bom_qqlist = [] bom_rank = [] for x in db.watertop.find(bom_query, {'_id': 0, 'sender': 1}): # print(x) if x['sender']['user_id'] in bom_sender: bom_sender[x['sender']['user_id']] += 1 else: bom_sender[x['sender']['user_id']] = 1 for k in sorted(bom_sender, key=bom_sender.__getitem__, reverse=True): bom_qqlist.append(k) if len(bom_qqlist) >= 10: break for i in range(len(bom_qqlist)): try: ranker = await session.bot.get_group_member_info( group_id=session.ctx['group_id'], user_id=bom_qqlist[i]) except CQHttpError: try: ranker = await session.bot.get_stranger_info( user_id=bom_qqlist[i]) ranker['card'] = ranker['nickname'] + '(已退群)' except CQHttpError: ranker = {'card': str(bom_qqlist[i]) + '(名称获取失败)'} bom_rank.append( f'第{cn_number[i]}名:{ranker["card"] if ranker["card"] != "" and "card" in ranker else ranker["nickname"]}---{bom_sender[bom_qqlist[i]]}条\n' ) water_report = f'在你第一次(有记录的)发言后本群的水群排名\n{"".join(bom_rank)}本次查询花费{str(time.time() - time_before)}秒' elif command == 'today-myself': time_before = time.time() today_self_query = { '$and': [{ 'time': { '$gte': today_start_time() } }, { 'group_id': session.ctx['group_id'] }] } today_self_sender = {} today_self_qqlist = [] today_self_rank = [] for x in db.watertop.find(today_self_query, { '_id': 0, 'sender': 1 }): # print(x) if x['sender']['user_id'] in today_self_sender: today_self_sender[x['sender']['user_id']] += 1 else: today_self_sender[x['sender']['user_id']] = 1 for k in sorted(today_self_sender, key=today_self_sender.__getitem__, reverse=True): today_self_qqlist.append(k) if k == session.ctx['sender']['user_id']: break try: ranker = await session.bot.get_group_member_info( group_id=session.ctx['group_id'], user_id=session.ctx['sender']['user_id']) except CQHttpError: try: ranker = await session.bot.get_stranger_info( user_id=session.ctx['sender']['user_id']) ranker['card'] = ranker['nickname'] except CQHttpError: ranker = {'card': str(today_self_qqlist[i]) + '(名称获取失败)'} water_report = f"你今天在本群排第{str(len(today_self_qqlist))}名:共{today_self_sender[session.ctx['sender']['user_id']]}条\n本次查询花费{str(time.time() - time_before)}秒" else: # command == 'all' all_query = {'group_id': session.ctx['group_id']} all_sender = {} all_qqlist = [] all_rank = [] time_before = time.time() for x in db.watertop.find(all_query, {'_id': 0, 'sender': 1}): # print(x) if x['sender']['user_id'] in all_sender: all_sender[x['sender']['user_id']] += 1 else: all_sender[x['sender']['user_id']] = 1 for k in sorted(all_sender, key=all_sender.__getitem__, reverse=True): all_qqlist.append(k) if len(all_qqlist) >= 10: break for i in range(len(all_qqlist)): try: ranker = await session.bot.get_group_member_info( group_id=session.ctx['group_id'], user_id=all_qqlist[i]) except CQHttpError: try: ranker = await session.bot.get_stranger_info( user_id=all_qqlist[i]) ranker['card'] = ranker['nickname'] + '(已退群)' except CQHttpError: ranker = {'card': str(all_qqlist[i]) + '(名称获取失败)'} all_rank.append( f'第{cn_number[i]}名:{ranker["card"] if ranker["card"] != "" and "card" in ranker else ranker["nickname"]}---{all_sender[all_qqlist[i]]}条\n' ) water_report = f'你群水群排行\n{"".join(all_rank)}本次查询花费{str(time.time() - time_before)}秒\n使用top help可以获取top命令更多功能' await session.bot.send_group_msg(group_id=session.ctx['group_id'], message=water_report)
async def do_query(id_list, user_id, region=1, force=False): id_list = [x * 100 + 1 for x in id_list] t = id_list.copy() t.sort() attack = ",".join(str(v) for v in t) result_list = jijian.get_attack(attack) if result_list is None or force: header = { 'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.87 Safari/537.36', 'authorization': __get_auth_key() } payload = { "_sign": "a", "def": id_list, "nonce": "a", "page": 1, "sort": 1, "ts": int(time.time()), "region": region } logger.debug(f'Arena query {payload=}') try: resp = await aiorequests.post( 'https://api.pcrdfans.com/x/v1/search', headers=header, json=payload, timeout=10) res = await resp.json() logger.debug(f'len(res)={len(res)}') except Exception as e: logger.exception(e) return 'Arena query failed.' if res['code']: code = int(res['code']) logger.error( f"Arena query failed.\nResponse={res}\nPayload={payload}") return f'Arena query failed.\nCode={code}' result_list = res['data']['result'] # logger.info(f'[do_query INFO]{res}') if not result_list: return [] result = ','.join(str(v) for v in result_list) jijian.add_attack(attack, result) logger.info('[do_query INFO]:在线查询') else: try: # logger.info(f'[do_query INFO]{result_list}') result_list = str(result_list) result_list = eval(result_list) result_list = eval(result_list[0]) logger.info('[do_query INFO]:本地缓存') except Exception as e: logger.exception(e) return 'Arena query failed.' ret = [] index = 0 try: for entry in result_list: eid = entry['id'] likes = get_likes(eid) dislikes = get_dislikes(eid) ret.append({ 'qkey': gen_quick_key(eid, user_id), 'atk': [ chara.fromid(c['id'] // 100, c['star'], c['equip']) for c in entry['atk'] ], 'up': entry['up'], 'down': entry['down'], 'my_up': len(likes), 'my_down': len(dislikes), 'user_like': 1 if user_id in likes else -1 if user_id in dislikes else 0 }) index += 1 return ret except Exception as e: logger.exception(e) return 'Arena query failed.'