async def show_progress(session: CommandSession): """progress: 查询本周刷题进度(周日18点为界线) 用法: progress """ group_id = session.ctx['group_id'] qq_id = session.ctx['user_id'] dm = DataManager(group_id) accounts = dm.query_binded_accounts(qq_id) starttime = last_sunday() endtime = dm.get_latest_csttime_by_qq(qq_id) vs, ve = dm.report_by_qq(qq_id, starttime, endtime) tf = "%Y/%m/%d %H:%M" msg = f"""Progress this week: [{starttime.strftime(tf)}] Accepted: {vs} [{endtime.strftime(tf)}] Accepted: {ve} (+{ve-vs})""" await session.send(msg)
async def report_total(session: CommandSession): group_id = session.ctx['group_id'] group_info = await session.bot.get_group_info(group_id=group_id) group_name = group_info['group_name'] member_count = group_info['member_count'] dm = DataManager(group_id) starttime = long_long_ago() endtime = cstnow() data = dm.report(starttime, endtime) data = [[alias, ac_now] for alias, _, ac_now in data if ac_now > 0] data.sort(reverse=True, key=itemgetter(1)) lines = autoalign(data, formatter=lambda x: "| {} | {:<5} |".format(*x)) entries = min(member_count, REPORT_TOTAL_MAX_ENTRIES, len(data)) header = f"{group_name} Top {entries} / {member_count}\n" header += "# | Name | Accepted |" await session.send(header) for msg in multiline_msg_generator(lines[:entries], lineno=True): await session.bot.send_msg_rate_limited(group_id=group_id, message=msg)
async def handle_accounts(session: CommandSession): """accounts: 查询当前已绑定账号 用法: accounts """ group_id = session.ctx['group_id'] qq_id = session.ctx['user_id'] argv = session.args['argv'] at_someone = None if argv and '-u' in argv and qq_id in SUPERUSERS: at_someone = argv[-1] qq_id = parse_cq_at(at_someone) dm = DataManager(group_id) accounts = dm.query_binded_accounts(qq_id) if accounts: msg = '已绑定账号:\n' + '\n'.join([u+'@'+p for u, p in accounts]) else: msg = '并没有绑定账号。' if at_someone: await session.send(f"{at_someone} {msg}") else: await session.send(msg, at_sender=True)
async def test_db_refactor(session: CommandSession): group_id = 1234 dm = DataManager(group_id) ok, snapshot = await dm.get_and_save_user_summary(QQ_ID, 'nuullll', 'leetcode') await session.send(str(snapshot.accepted))
async def handle_init_db(session: CommandSession): argv = session.args['argv'] cleanup = True if '-c' in argv else False msg = '正在初始化数据库...' if cleanup: msg += '(重置)' await session.send(msg) group_id = session.ctx['group_id'] members = await session.bot.get_group_member_list(group_id=group_id) dm = DataManager(group_id) success = dm.init(members, cleanup) await session.send('成功导入{}位成员信息'.format(success))
async def daily_update(): bot = nb.get_bot() checkpoint = set() for group_id in AUTO_UPDATES: await debug(f"Updating for group: {group_id}") dm = DataManager(group_id) members = await bot.get_group_member_list(group_id=group_id) dm.init(members) fails, successes = await dm.get_and_save_all_user_summary(checkpoint) await debug(f"Group [{group_id}] update failures: {fails}") retry = 0 _fails = fails[:] while fails: fails = [] for qq_id, user_id, platform in _fails: ok, snapshot = await dm.get_and_save_user_summary( qq_id, user_id, platform) if not ok: fails.append((qq_id, user_id, platform)) else: successes.append((qq_id, user_id, platform)) retry += 1 if retry >= AUTO_UPDATE_MAX_RETRIES: await debug( f"Failures after {AUTO_UPDATE_MAX_RETRIES} retries: {fails}" ) break _fails = fails[:] checkpoint.update(successes) for group_id, mode in AUTO_DAILY_REPORT.items(): event = get_fake_cqevent(group_id=group_id) if mode == 'week_delta': await call_command(bot, event, 'report') else: await call_command(bot, event, 'report_total')
async def handle_accounts(session: CommandSession): """accounts: 查询当前已绑定账号 用法: accounts """ group_id = session.ctx['group_id'] qq_id = session.ctx['user_id'] dm = DataManager(group_id) accounts = dm.query_binded_accounts(qq_id) if accounts: msg = '已绑定账号:\n' + '\n'.join([u + '@' + p for u, p in accounts]) else: msg = '并没有绑定账号。' await session.send(msg, at_sender=True)
async def report(session: CommandSession): group_id = session.ctx['group_id'] dm = DataManager(group_id) starttime = last_sunday() endtime = cstnow() data = dm.report(starttime, endtime) data = [[alias, ac_now, ac_now - ac_before] for alias, ac_before, ac_now in data if ac_now > 0] data.sort(reverse=True, key=itemgetter(2, 1)) lines = autoalign(data, formatter=lambda x: "| {} | {:<5} (+{}) |".format(*x)) header = f"{starttime} - {endtime}\n" header += "| Name | Accepted (+delta) |" await session.send(header) for msg in multiline_msg_generator(lines, lineno=True): await session.bot.send_msg_rate_limited(group_id=group_id, message=msg)
async def test_blog_date_parser(session: CommandSession): lines = [] url_map = DataManager.query_blog() for qq_id, blog_urls in url_map.items(): for url in blog_urls: if 'juejin' not in url: continue # if qq_id not in card_dict: # line = f"{render_cq_at(qq_id)} {url}" # else: # line = f"{card_dict[qq_id]} {url}" # lines.append(line) guess_blog_update_time(url)
async def query_registered(session: CommandSession): argv = session.args['argv'] group_id = session.ctx['group_id'] dm = DataManager(group_id) members = await session.bot.get_group_member_list(group_id=group_id) lines = [] for member in members: alias = member['card'] if member['card'] else member['nickname'] qq_id = member['user_id'] accounts = dm.query_binded_accounts(qq_id) msg = f"{alias}: " if accounts: msg += ", ".join([f"{u}@{p}" for u, p in accounts]) else: msg += "NaN" lines.append(msg) for msg in multiline_msg_generator(lines=lines, lineno=True): await session.bot.send_msg_rate_limited(group_id=group_id, message=msg)
async def update_all(session: CommandSession): group_id = session.ctx['group_id'] dm = DataManager(group_id) members = await session.bot.get_group_member_list(group_id=group_id) await session.send("开始强制更新...") for member in members: print(member) ctx = {'debug': True, 'anonymous': None, 'font': 1623440, 'group_id': group_id, 'message': [{'type': 'text', 'data': {'text': 'report'}}], 'message_id': 20804, 'message_type': 'group', 'post_type': 'message', 'raw_message': 'report', 'self_id': 2210705648, 'sender': {'age': 24, 'area': '北京', 'card': '', 'level': '冒泡', 'nickname': 'Nuullll', 'role': 'owner', 'sex': 'unknown', 'title': '', 'user_id': 724463877}, 'sub_type': 'normal', 'time': 1584248424, 'user_id': 724463877, 'to_me': True} ctx['sender'].update(member) ctx['user_id'] = member['user_id'] await call_command(nonebot.get_bot(), ctx, 'update') await session.finish("手动更新成功!")
async def update_database(session: CommandSession): """update: 立刻更新账号数据(冷却时间10分钟) 除此之外,爬虫会在每日18点左右自动更新账号数据(如有bug请 @Nuullll) 用法: update """ group_id = session.ctx['group_id'] qq_id = session.ctx['user_id'] debug = session.ctx.get('debug', False) if not debug: dm = DataManager(group_id) accounts = dm.query_binded_accounts(qq_id) await session.send("正在更新数据", at_sender=True) if not accounts: await session.finish("请先绑定账号!命令:register") latest = dm.get_latest_csttime_by_qq(qq_id) now = cstnow() delta = int((now - latest).total_seconds()) print(latest, now, delta) await session.send(f"最近更新: {latest}") if delta < USER_UPDATE_COOLDOWN: await session.finish(f"技能冷却中:剩余{USER_UPDATE_COOLDOWN-delta}秒") for user_id, platform in accounts: ok, snapshot = await dm.get_and_save_user_summary( qq_id, user_id, platform) if not ok: await session.send("ID错误或网络错误!请检查后重试。") for msg in multiline_msg_generator(snapshot.lines): await session.bot.send_msg_rate_limited(group_id=group_id, message=msg) else: dm = DataManager(group_id) accounts = dm.query_binded_accounts(qq_id) for user_id, platform in accounts: ok, snapshot = await dm.get_and_save_user_summary( qq_id, user_id, platform) for msg in multiline_msg_generator(snapshot.lines): await session.bot.send_msg_rate_limited(user_id=724463877, message=msg)
async def change_field(session: CommandSession): _ids = pymongo.MongoClient()[OJID_DB]['all'] docs = _ids.find({'platform': 'leetcodecn'}) for doc in docs: qq_id = doc['qq_id'] snapshots = DataManager.get_snapshots(qq_id) ss = snapshots.find_and_modify( {'platform': 'leetcodecn'}, {"$rename": { "data.Global Ranking": "data.AC Ranking" }}) print(snapshots.find_one({'platform': 'leetcodecn'}))
async def handle_unregister(session: CommandSession): """unregister: 解绑账号 用法: unregister [-a] [platform] [user_id] 可选参数: -a 解绑本人所有OJ平台账号 示例: unregister -a unregister leetcodecn nuullll """ USAGE = handle_unregister.__doc__ # args argv = session.args['argv'] if not argv: await session.finish(USAGE) return rm_all = True if '-a' in argv else False group_id = session.ctx['group_id'] qq_id = session.ctx['user_id'] if '-u' in argv and qq_id in SUPERUSERS: qq_id = parse_cq_at(argv[-1]) dm = DataManager(group_id) if rm_all: candidates = dm.query_binded_accounts(qq_id) else: try: platform = argv[0] user_id = argv[1] except: await session.send("参数有误!") await session.finish(USAGE) return binded, bind_qq = dm.is_account_binded(user_id, platform) if not binded or bind_qq != qq_id: await session.finish("账号不存在,解绑失败。@我 accounts 查询已绑定账号。") return candidates = [(user_id, platform)] for u, p in candidates: dm.remove_account(qq_id, u, p) await session.finish("解绑成功。")
async def get_latest_blogs(): bot = nb.get_bot() # members = await session.bot.get_group_member_list(group_id=group_id) # # build qq_id -> card dict # card_dict = {member['user_id']: (member['card'] if member['card'] else member['nickname']) for member in members} # query all recent_updated = [] now = datetime.now() url_map = DataManager.query_blog() for qq_id, blog_urls in url_map.items(): for url in blog_urls: # if qq_id not in card_dict: # line = f"{render_cq_at(qq_id)} {url}" # else: # line = f"{card_dict[qq_id]} {url}" # lines.append(line) text, dt = guess_blog_update_time(url) if text is None: continue if (now - dt).days <= 2: recent_updated.append((qq_id, url, dt.strftime("%Y-%m-%d"))) if not recent_updated: return lines = [f"{render_cq_at('all')} 以下博客近2天内有更新,大家快去学习吧"] for qq_id, url, dt_str in recent_updated: line = f"{render_cq_at(qq_id)} {url} {dt_str}" lines.append(line) for group_id in AUTO_BLOG_PUSHES: for msg in multiline_msg_generator(lines=lines, lineno=False): await bot.send_msg_rate_limited(group_id=group_id, message=msg)
async def daily_update(): bot = nb.get_bot() for group_id in AUTO_UPDATES: await debug(f"Updating for group: {group_id}") dm = DataManager(group_id) members = await bot.get_group_member_list(group_id=group_id) dm.init(members) fails = await dm.get_and_save_all_user_summary() await debug(f"Group [{group_id}] update failures: {fails}") retry = 0 while fails: fails = [] for qq_id, user_id, platform in fails: ok, snapshot = await dm.get_and_save_user_summary( qq_id, user_id, platform) if not ok: fails.append((qq_id, user_id, platform)) retry += 1 if retry >= AUTO_UPDATE_MAX_RETRIES: await debug( f"Failures after {AUTO_UPDATE_MAX_RETRIES} retries: {fails}" ) break for group_id, mode in AUTO_DAILY_REPORT.items(): ctx = { 'anonymous': None, 'font': 1623440, 'group_id': group_id, 'message': [{ 'type': 'text', 'data': { 'text': 'report' } }], 'message_id': 20804, 'message_type': 'group', 'post_type': 'message', 'raw_message': 'report', 'self_id': 2210705648, 'sender': { 'age': 24, 'area': '北京', 'card': '', 'level': '冒泡', 'nickname': 'Nuullll', 'role': 'owner', 'sex': 'unknown', 'title': '', 'user_id': 724463877 }, 'sub_type': 'normal', 'time': 1584248424, 'user_id': 724463877, 'to_me': True } if mode == 'week_delta': await call_command(bot, ctx, 'report') else: await call_command(bot, ctx, 'report_total') # @nb.scheduler.scheduled_job('cron', hour='12') # async def report_hns(): # print("Waiting for coingecko...") # ok, html = await Spider.render_html_with_splash('https://www.coingecko.com/en/coins/handshake') # if not ok: # return # def get_content(xpath): # try: # return html.xpath(xpath + "/text()")[0] # except: # return "" # usd = get_content("/html/body/div[2]/div[3]/div[4]/div[1]/div[2]/div[1]/span[1]") # usd_d = get_content("/html/body/div[2]/div[3]/div[4]/div[1]/div[2]/div[1]/span[2]/span") # btc = get_content("/html/body/div[2]/div[3]/div[4]/div[1]/div[2]/div[3]") # btc_d = get_content("/html/body/div[2]/div[3]/div[4]/div[1]/div[2]/div[3]/span/span") # l_24h = get_content("/html/body/div[2]/div[3]/div[6]/div/div/div[2]/div/div[1]/div/div[1]/div[1]/div[2]/div[1]/table/tbody/tr[6]/td/span[1]") # h_24h = get_content("/html/body/div[2]/div[3]/div[6]/div/div/div[2]/div/div[1]/div/div[1]/div[1]/div[2]/div[1]/table/tbody/tr[6]/td/span[2]") # l_7d = get_content("/html/body/div[2]/div[3]/div[6]/div/div/div[2]/div/div[1]/div/div[1]/div[1]/div[2]/div[1]/table/tbody/tr[7]/td/span[1]") # h_7d = get_content("/html/body/div[2]/div[3]/div[6]/div/div/div[2]/div/div[1]/div/div[1]/div[1]/div[2]/div[1]/table/tbody/tr[7]/td/span[2]") # message = f"""{datetime.now(pytz.timezone('Asia/Shanghai'))} # HNS Hourly Report # USD: {usd} {usd_d} # BTC: {btc} {btc_d} # 24h Low/High: {l_24h}/{h_24h} # 7d Low/High: {l_7d}/{h_7d} # """ # bot = nb.get_bot() # await bot.send_msg(user_id=724463877, message=message)
async def handle_blog(session: CommandSession): """blog: 查询本群公开博客列表/绑定博客/解绑博客 用法: blog // 查询博客列表 blog add <your blog url> blog remove <your blog url> [-a] 示例: blog add https://blog.nuullll.com blog remove https://blog.nuullll.com blog remove -a // 解绑本人所有博客 """ argv = session.args['argv'] group_id = session.event.group_id qq_id = session.event.user_id dm = DataManager(group_id) try: op = argv[0].lower() url = argv[1] if op == "add": if not is_valid_url(url): await session.send("闹闹无法获取博客内容,请检查url或网站可达性,或稍后再试") return dm.bind_blog(qq_id, url) await session.send("绑定成功!") elif op == "remove": remove_all = url == "-a" valid = is_valid_url(url) if not valid and not remove_all: await session.send("闹闹无法获取博客内容,请检查url或网站可达性,或稍后再试") return if valid: if not dm.unbind_blog(qq_id, url): await session.send("该博客未绑定,请检查url") return if remove_all: url_map = dm.query_blog(qq_id=qq_id) for url in url_map[qq_id]: dm.unbind_blog(qq_id, url) await session.send("解绑成功!") else: raise ValueError("Usage error.") return except: pass # query all lines = [get_random_header()] url_map = dm.query_blog() for qq_id, blog_urls in url_map.items(): for url in blog_urls: line = f"{render_cq_at(qq_id)} {url}" lines.append(line) for msg in multiline_msg_generator(lines=lines, lineno=False): await session.bot.send_msg_rate_limited(group_id=group_id, message=msg)
async def handle_reset_db(session: CommandSession): group_id = session.ctx['group_id'] DataManager(group_id).reset() await session.send('数据库已重置')
async def register_helper(session, for_other=False): examples = [f"{oj}: {url.format('<id>')}" for oj, url in PLATFORM_URLS.items()] shuffle(examples) USAGE = """ 用法: register <your OJ profile url> 目前支持的OJ平台: """ + "\n".join(examples) + """ 示例: register https://leetcode.com/nuullll """ group_id = session.ctx['group_id'] if not for_other: url = session.current_arg_text.strip() if not url: await session.finish("未检测到url\n" + USAGE) return url = url.split()[0] qq_id = session.ctx['user_id'] else: argv = session.args['argv'] qq_id, url = argv try: if qq_id.isnumeric(): qq_id = int(qq_id) else: # infer CQ code qq_id = parse_cq_at(qq_id) except: await session.finish('参数错误!') return platform = '' user_id = '' for oj, template in PLATFORM_URLS.items(): # escape special character in template template = template.replace('?', '\?') m = re.search(template.format('([a-zA-Z0-9_-]+)'), url) if m: platform = oj user_id = m.group(1) break if not user_id: await session.finish("请检查URL格式\n" + USAGE) return dm = DataManager(group_id) binded, bind_qq = dm.is_account_binded(user_id, platform) if binded: if bind_qq == qq_id: await session.finish(f"您已绑定{user_id}@{platform},请勿重复操作。") else: await session.finish(f"绑定失败,{user_id}@{platform}已被用户[CQ:at,qq={bind_qq}]绑定!") return ok, snapshot = await dm.get_and_save_user_summary(qq_id, user_id, platform) if not ok: await session.send("ID错误或网络错误!请检查后重试。") return if not dm.bind_account(qq_id, user_id, platform): await session.finish("绑定失败,代码线程不安全。") return await session.send(f"{user_id}@{platform}绑定成功!") for msg in multiline_msg_generator(snapshot.lines): await session.send(msg)
async def handle_blog(session: CommandSession): """blog: 查询本群公开博客列表/绑定博客/解绑博客 用法: blog // 查询博客列表 blog add <your blog url> blog remove <your blog url> [-a] 示例: blog add https://blog.nuullll.com blog remove https://blog.nuullll.com blog remove -a // 解绑本人所有博客 """ argv = session.args['argv'] group_id = session.event.group_id qq_id = session.event.user_id dm = DataManager(group_id) try: op = argv[0].lower() url = argv[1] if op == "add": if not is_valid_url(url): await session.send("闹闹无法获取博客内容,请检查url或网站可达性,或稍后再试") return if dm.bind_blog(qq_id, url): await session.send("绑定成功!") else: await session.send("该博客你已经绑定过啦!") elif op == "remove": remove_all = url == "-a" if remove_all: url_map = dm.query_blog(qq_id=qq_id) for url in url_map[qq_id]: dm.unbind_blog(qq_id, url) else: if not dm.unbind_blog(qq_id, url): await session.send("该博客未绑定,请检查url") return await session.send("解绑成功!") else: raise ValueError("Usage error.") return except: pass members = await session.bot.get_group_member_list(group_id=group_id) # build qq_id -> card dict name_dict = { member['user_id']: (member['card'], member['nickname']) for member in members } # query all lines = [] url_map = dm.query_blog() for qq_id, blog_urls in url_map.items(): for url in blog_urls: if qq_id not in name_dict: line = f"{render_cq_at(qq_id)} {url}" else: card, nickname = name_dict[qq_id] card = f"({card})" if card else "" line = f"{nickname}{card} {url}" lines.append(line) shuffle(lines) lines = [get_random_header()] + lines for msg in multiline_msg_generator(lines=lines, lineno=False): await session.bot.send_msg_rate_limited(group_id=group_id, message=msg)