def char(_, ctx_msg, internal=False, argv=None): if len(argv) == 0: core.echo('请传入正确的参数哦~', ctx_msg, internal) return argv random.shuffle(argv) core.echo(', '.join(argv), ctx_msg, internal) return argv
def number(_, ctx_msg, internal=False, argv=None): if len(argv) == 0 or not re.match('x\d+', argv[-1]): n = 1 else: n = max(1, int(argv[-1][1:])) argv = argv[:-1] if len(argv) > 2 or any((not re.match('-?\d+', num) for num in argv)): core.echo('参数有错误哦~\n正确的使用方法(主要看参数,你的命令是对的~):\n\n' '/random.number\n' '/random.number x5\n' '/random.number 100\n' '/random.number 100 x10\n' '/random.number 50 100\n' '/random.number 50 100 x3', ctx_msg, internal) return if len(argv) == 1: argv.append(1) start, end = (int(argv[0]), int(argv[1])) if len(argv) == 2 else (None, None) start, end = (min(start, end), max(start, end)) if start is not None else (start, end) result = [] for _ in range(n): result.append(random.randint(start, end) if start is not None else random.random()) core.echo(', '.join(str(num) for num in result), ctx_msg, internal) return result
def baike(args_text, ctx_msg): query = args_text.strip() if not query: core.echo('请在命令后加上要查的关键词哦~(命令和关键词用空格或逗号隔开)', ctx_msg) return data = ai.tuling123('百科 ' + query, ctx_msg, internal=True) core.echo(data.get('text', ''), ctx_msg)
def suggestion(args_text, ctx_msg, argv: list = None, allow_interactive=True): source = get_source(ctx_msg) if allow_interactive and (len(argv) < 1 or not argv[0].startswith('CN') or has_session(source, _cmd_suggestion)): # Be interactive return _do_interactively(_cmd_suggestion, suggestion, args_text.strip(), ctx_msg, source) city_id = argv[0] text = '' data = _get_weather(city_id) if data: data = data['suggestion'] text += '生活指数:\n\n' \ '舒适度:%s\n\n' \ '洗车指数:%s\n\n' \ '穿衣指数:%s\n\n' \ '感冒指数:%s\n\n' \ '运动指数:%s\n\n' \ '旅游指数:%s\n\n' \ '紫外线指数:%s' \ % tuple([data[k]['txt'] for k in ('comf', 'cw', 'drsg', 'flu', 'sport', 'trav', 'uv')]) if text: core.echo(text, ctx_msg) else: core.echo('查询失败了,请稍后再试哦~', ctx_msg)
def ask_for_city(s, a, c): if a: if search_city(s, a, c): return True else: core.echo('你要查询哪个城市呢?', c) s.state += 1
def unblock(_, ctx_msg, argv=None): def _send_error_msg(): core.echo('参数不正确。\n\n正确使用方法:\nsudo.unblock <account-to-unblock>', ctx_msg) if len(argv) != 1: _send_error_msg() return account = argv[0] # Get a target using a fake context message target = get_target({ 'via': 'default', 'msg_type': 'private', 'sender_id': account }) if not target: _send_error_msg() return conn = _open_db_conn() conn.execute('DELETE FROM blocked_target_list WHERE target = ?', (target, )) conn.commit() conn.close() core.echo('成功取消屏蔽用户 ' + account, ctx_msg)
def weather(args_text, ctx_msg): city = args_text.strip() if not city: core.echo('请在命令后加上要查的城市哦~(命令和城市用空格或逗号隔开)', ctx_msg) return data = ai.tuling123(city + '天气', ctx_msg, internal=True) core.echo(data.get('text', ''), ctx_msg)
def wait_for_time(s, a, c): if s.data['need_confirm']: if a.strip() != '1': # Cancel core.echo('已放弃更改~', c) return True core.echo('请发送想要获取推送的时间(格式如 20:05):', c) s.state += 1
def execute_job(args_text, ctx_msg, internal=False): job = get_job(args_text, ctx_msg, internal=True) if not job: core.echo('没有找到该计划任务,请指定正确的计划任务 ID', ctx_msg, internal) return job_id_suffix = _job_id_suffix_start + get_target(ctx_msg) job_id = job.id[:-len(job_id_suffix)] _call_commands(job_id, job.kwargs['command_list'], job.kwargs['ctx_msg'], internal)
def clear(_, ctx_msg): conn = _open_db_conn() target = get_target(ctx_msg) cursor = conn.cursor() cursor.execute('DELETE FROM cmd_note WHERE target = ?', (target, )) if cursor.rowcount > 0: core.echo('成功删除了所有的笔记,共 %s 条~' % cursor.rowcount, ctx_msg) else: core.echo('本来就没有笔记哦~', ctx_msg) conn.commit() conn.close()
def block_list(_, ctx_msg, internal=False): conn = _open_db_conn() cursor = conn.execute('SELECT target FROM blocked_target_list') blocked_targets = list(set([x[0] for x in list(cursor) ])) # Get targets and remove duplications conn.close() if internal: return blocked_targets if blocked_targets: core.echo('已屏蔽的用户:\n' + ', '.join(blocked_targets), ctx_msg) else: core.echo('还没有屏蔽过用户', ctx_msg)
def translate(args_text, ctx_msg): query = args_text.strip() if not query: core.echo('请在命令后加上要翻译的内容哦~(命令和要翻译的内容用空格或逗号隔开)', ctx_msg) return cmd = ctx_msg.get('command') if cmd == 'translate': return translate_to('英语 ' + args_text, ctx_msg) elif cmd == '翻訳': return translate_to('日语 ' + args_text, ctx_msg) else: return translate_to('简体中文 ' + args_text, ctx_msg)
def confirm_override(s, a, c): job = scheduler.get_job(_scheduler_job_id, c, internal=True) if job: core.echo('先前已经订阅过了哦~\n' + '下次推送时间:\n' + job.next_run_time.strftime('%Y-%m-%d %H:%M') + '\n' + '要更改推送时间吗?\n' + '回复 1 继续,回复 0 放弃', c) s.data['need_confirm'] = True else: s.data['need_confirm'] = False wait_for_time(s, a, c) s.state += 1
def take(args_text, ctx_msg, allow_interactive=True): source = get_source(ctx_msg) if allow_interactive and (not args_text or has_session(source, _cmd_take)): # Be interactive return _take_interactively(args_text, ctx_msg, source) conn = _open_db_conn() dt_unix = int(datetime.now(tz=pytz.utc).timestamp()) target = get_target(ctx_msg) conn.execute('INSERT INTO cmd_note (content, dt, target) VALUES (?, ?, ?)', (args_text, dt_unix, target)) conn.commit() conn.close() core.echo('好的,记下了~', ctx_msg)
def translate_to(args_text, ctx_msg): args = args_text.strip().split(' ', 1) if len(args) < 2 or (args[0] not in _lang_map and args[0] not in _lang_alias_map): core.echo( '请指定目标语言和要翻译的内容哦~(命令、目标语言、要翻译的内容之间用空格或逗号隔开\n目前支持的语言:' + '、'.join(_lang_map.keys()), ctx_msg ) return core.echo('正在翻译,请稍等……', ctx_msg) to_lang = _lang_map.get(args[0]) or _lang_alias_map.get(args[0]) query = args[1] api_url = 'https://fanyi-api.baidu.com/api/trans/vip/translate' salt = str(int(datetime.now().timestamp())) sign = hashlib.md5((_app_id + query + salt + _api_key).encode('utf-8')).hexdigest() resp = requests.post(api_url, data={ 'q': query, 'from': 'auto', 'to': to_lang, 'appid': _app_id, 'salt': salt, 'sign': sign }) if resp.status_code == 200: data = resp.json() if 'trans_result' in data: core.echo('翻译结果(百度翻译):\n' + '\n'.join([x['dst'] for x in data['trans_result']]), ctx_msg) return core.echo('翻译失败,可能因为后台接口的频率限制或服务器连接不上', ctx_msg)
def xiaoice(args_text, ctx_msg, internal=False): msg_sources = get_message_sources() for src in msg_sources: if src['via'] == 'mojo_weixin': # Only MojoWeixin support this function adapter = get_adapter('mojo_weixin', src['login_id']) if adapter: json = adapter.consult(account='xiaoice-ms', content=args_text) if json and json.get('reply'): reply = json['reply'] core.echo(reply, ctx_msg, internal) return reply core.echo('小冰现在无法回复,请稍后再试', ctx_msg, internal) return None
def short_url(args_text, ctx_msg): raw_url = args_text.strip() if not raw_url: core.echo('请在命令后加上要转换成短链接的网址哦~(命令和网址用空格或逗号隔开)', ctx_msg) return core.echo('正在生成,请稍等……', ctx_msg) session = requests.Session() short_urls = [] resp = session.get( 'http://dwz.wailian.work/', headers={ 'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.95 Safari/537.36', 'Referer': 'http://dwz.wailian.work/' }) if resp.status_code == 200: api_url = 'http://dwz.wailian.work/api.php?url=%s&site=%s' encoded_url = base64.b64encode(bytes(raw_url, 'utf-8')).decode('utf-8') for site in ('sina', 'googl'): resp = session.get(api_url % (encoded_url, site)) data = resp.json() if resp.status_code == 200 and data.get('result') == 'ok': short_urls.append(data['data']['short_url']) if short_urls: core.echo('\n'.join(short_urls), ctx_msg) else: core.echo('生成失败,可能因为链接格式错误或服务器连接不上', ctx_msg)
def choose_city(s, a, c): if not a or not a.isdigit(): core.echo('你输入的序号不正确哦,请重新发送命令~', c) return True choice = int(a) - 1 # Should be from 0 to len(city_list) - 1 city_list = s.data['city_list'] if choice < 0 or choice >= len(city_list): core.echo('你输入的序号超出范围了,请重新发送命令~', c) return True city_id = city_list[choice]['id'] # sess.data['func']([city_id], c, allow_interactive=False) func([city_id], c, allow_interactive=False) return True
def subscribe(args_text, ctx_msg, argv=None, internal=False, allow_interactive=True): source = get_source(ctx_msg) if not internal and allow_interactive and has_session( source, _cmd_subscribe): # Already in a session, no need to pass in data, # because the interactive version of this command will take care of it return _subscribe_interactively(args_text, ctx_msg, source, None) data = {} if argv: m = re.match('([0-1]\d|[2][0-3])(?::|:)?([0-5]\d)', argv[0]) if not m: # Got command but no time data['command'] = args_text else: # Got time data['hour'], data['minute'] = m.group(1), m.group(2) if len(argv) == 2: # Got command data['command'] = argv[1] if not internal and allow_interactive: if data.keys() != {'command', 'hour', 'minute'}: # First visit and data is not enough return _subscribe_interactively(args_text, ctx_msg, source, data) # Got both time and command, do the job! hour, minute = data['hour'], data['minute'] command = data['command'] job = scheduler.add_job('-H %s -M %s --multi %s %s' % (hour, minute, _scheduler_job_id_prefix + str(int(datetime.now().timestamp())), command), ctx_msg, internal=True) if internal: return job if job: # Succeeded to add a job reply = '订阅成功,我会在每天 %s 推送哦~' % ':'.join((hour, minute)) else: reply = '订阅失败,可能后台出了点小问题~' core.echo(reply, ctx_msg, internal)
def weather(args_text, ctx_msg, argv: list = None, allow_interactive=True): source = get_source(ctx_msg) if allow_interactive and (not argv or not argv[0].startswith('CN') or has_session(source, _cmd_weather)): # Be interactive return _do_interactively(_cmd_weather, weather, args_text.strip(), ctx_msg, source) city_id = argv[0] text = '' data = _get_weather(city_id) if data: text += '%s天气\n更新时间:%s' % (data['basic']['city'], data['basic']['update']['loc']) now = data['now'] aqi = data['aqi']['city'] text += '\n\n实时:\n\n%s,气温%s°C,体感温度%s°C,%s%s级,' \ '能见度%skm,空气质量指数:%s,%s,PM2.5:%s,PM10:%s' \ % (now['cond']['txt'], now['tmp'], now['fl'], now['wind']['dir'], now['wind']['sc'], now['vis'], aqi['aqi'], aqi['qlty'], aqi['pm25'], aqi['pm10']) daily_forecast = data['daily_forecast'] text += '\n\n预报:\n\n' for forecast in daily_forecast: d = datetime.strptime(forecast['date'], '%Y-%m-%d') text += '%d月%d日%s,' % (d.month, d.day, _weekday_string[d.weekday()]) cond_d = forecast['cond']['txt_d'] cond_n = forecast['cond']['txt_n'] text += cond_d + ('转' + cond_n if cond_d != cond_n else '') + ',' text += forecast['tmp']['min'] + '~' + forecast['tmp'][ 'max'] + '°C,' text += forecast['wind']['dir'] + forecast['wind']['sc'] + '级,' text += '降雨概率%s%%' % forecast['pop'] text += '\n\n' text = text.rstrip() if text: core.echo(text, ctx_msg) else: core.echo('查询失败了,请稍后再试哦~', ctx_msg)
def money_zh(args_text, ctx_msg): query = args_text.strip() try: _ = float(query) except ValueError: query = None if not query: core.echo('请在命令后加上要转换成大写的人民币金额哦~(命令和数字用空格或逗号隔开)', ctx_msg) return resp = requests.get('http://tool.lu/daxie/ajax.html?number=%s' % query) if resp.status_code == 200: data = resp.json() if data.get('status') and 'text' in data: reply = query + ' 的汉字大写是:' + data['text'].strip() core.echo(reply, ctx_msg) return
def _subscribe_interactively(args_text, ctx_msg, source, data): sess = get_session(source, _cmd_subscribe) if data: sess.data.update(data) state_command = 1 state_time = 2 state_finish = -1 if sess.state == state_command: if not args_text.strip(): core.echo('你输入的命令不正确,请重新发送订阅命令哦~', ctx_msg) sess.state = state_finish else: sess.data['command'] = args_text elif sess.state == state_time: m = re.match('([0-1]\d|[2][0-3])(?::|:)?([0-5]\d)', args_text.strip()) if not m: core.echo('你输入的时间格式不正确,请重新发送订阅命令哦~', ctx_msg) sess.state = state_finish else: sess.data['hour'], sess.data['minute'] = m.group(1), m.group(2) if sess.state == state_finish: remove_session(source, _cmd_subscribe) return if 'command' not in sess.data: # Ask for command core.echo( '请输入你需要订阅的命令(包括所需的参数),每行一条,不需要加开头的斜杠哦~\n\n' '例如(序号后的):\n' '(1) 天气 南京\n' '(2) 知乎日报\n' '(3) 历史上的今天', ctx_msg) sess.state = state_command return if 'hour' not in sess.data or 'minute' not in sess.data: # Ask for time core.echo('请输入你需要推送的时间,格式如 22:00', ctx_msg) sess.state = state_time return subscribe('', ctx_msg, argv=[ ':'.join((sess.data['hour'], sess.data['minute'])), sess.data['command'] ], allow_interactive=False) remove_session(source, _cmd_subscribe)
def subscribe(args_text, ctx_msg, allow_interactive=True): arg = args_text.strip() source = get_source(ctx_msg) if allow_interactive and (not arg or has_session(source, _cmd_subscribe)): # Be interactive return _subscribe_interactively(args_text, ctx_msg, source) force = False if arg.startswith('-f '): force = True arg = arg.split(' ', 1)[1].strip() reply = None try: m = re.match('([0-1]\d|[2][0-3])(?::|:)?([0-5]\d)', arg) if m: job = scheduler.get_job(_scheduler_job_id, ctx_msg, internal=True) if job and not force: reply = '已经订阅过了哦~\n' \ + '下次推送时间:\n' \ + job.next_run_time.strftime('%Y-%m-%d %H:%M') + '\n' \ + '如果需要更改推送时间,请先取消订阅再重新订阅,' \ + '或在订阅命令的时间参数前面加 -f 来强制更新推送时间' raise SkipException job = scheduler.add_job( '-M %s -H %s %s zhihu.zhihu-daily' % (m.group(2), m.group(1), _scheduler_job_id), ctx_msg, internal=True ) if job: # Succeeded to add a job reply = '订阅成功,我会在每天 %s 推送哦~' % ':'.join((m.group(1), m.group(2))) else: reply = '订阅失败,可能后台出了点问题呢~' else: reply = '命令格式错误,正确的命令格式:\n' \ '/zhihu.subscribe\n' \ '或\n' \ '/zhihu.subscribe [-f] 20:30\n' except SkipException: reply = reply if reply else '发生了未知错误……' core.echo(reply, ctx_msg)
def char(_, ctx_msg, internal=False, argv=None): if len(argv) > 2 or (len(argv) == 2 and not re.match('x\d+', argv[-1])): core.echo('参数有错误哦~\n正确的使用方法(主要看参数,你的命令是对的~):\n\n' '/random.char\n' '/random.char x5\n' '/random.char ABCDEFG\n' '/random.char ABCDEFG x10\n', ctx_msg, internal) return chars = string.ascii_letters + string.digits size = 1 if len(argv) and re.match('x\d+', argv[-1]): size = max(1, int(argv[-1][1:])) argv = argv[:-1] if len(argv): chars = argv[0] result = ''.join(random.choice(chars) for _ in range(size)) core.echo(result, ctx_msg, internal) return result
def get_job(args_text, ctx_msg, internal=False): job_id_without_suffix = args_text.strip() if not job_id_without_suffix: _send_text('请指定计划任务的 ID', ctx_msg, internal) return None job_id = job_id_without_suffix + _job_id_suffix_start + get_target(ctx_msg) job = _scheduler.get_job(job_id, 'default') if internal: if job: job.id = job_id_without_suffix return job if not job: core.echo('没有找到该计划任务,请指定正确的计划任务 ID', ctx_msg, internal) return reply = '找到计划任务如下:\n' reply += 'ID:' + job_id_without_suffix + '\n' reply += '下次触发时间:\n%s\n' % job.next_run_time.strftime('%Y-%m-%d %H:%M') reply += '命令:\n' command_list = job.kwargs['command_list'] reply += convert_command_list_to_str(command_list) _send_text(reply, ctx_msg, internal)
def process(args_text, ctx_msg): sentence = args_text.strip() if not sentence: core.echo('你什么都没说哦~', ctx_msg) return potential_commands = parse_potential_commands(sentence) potential_commands = sorted(filter(lambda x: x[0] > 60, potential_commands), key=lambda x: x[0], reverse=True) # If it's a fallback and with empty start flag, then don't send verbose information hide_verbose = ctx_msg.get('is_fallback') and ctx_msg.get('start_flag') == '' if len(potential_commands) > 0: most_possible_cmd = potential_commands[0] core.echo( '识别出最可能的等价命令:\n' + ' '.join((most_possible_cmd[1], most_possible_cmd[2])), ctx_msg, internal=hide_verbose ) ctx_msg['parsed_data'] = most_possible_cmd[3] cmdhub.call(most_possible_cmd[1], most_possible_cmd[2], ctx_msg) else: if _fallback_command: core.echo('暂时无法理解你的意思,下面将使用备用命令 ' + _fallback_command + '……', ctx_msg, internal=hide_verbose) cmdhub.call(_fallback_command, sentence, ctx_msg) return
def search_city(s, a, c): if not a: core.echo('你输入的城市不正确哦,请重新发送命令~', c) return True city_list = _get_city_list(a) if not city_list: core.echo('没有找到你输入的城市哦,请重新发送命令~', c) return True s.data['city_list'] = city_list if len(city_list) == 1: # Directly choose the first one choose_city(s, '1', c) return True # Here comes more than one city with the same name core.echo( '找到 %d 个重名城市,请选择你要查询的那个,发送它的序号:\n\n' % len(city_list) + '\n'.join([ str(i + 1) + '. ' + c['prov'] + c['city'] for i, c in enumerate(city_list) ]), c) s.state += 1
def _filter(ctx_msg): if ctx_msg.get('format') == 'media' and ctx_msg['raw_ctx'].get( 'media_type') == 'voice': m = re.match('\[语音\]\(([/_A-Za-z0-9]+\.mp3)\)', ctx_msg.get('content')) if m: core.echo('正在识别语音内容,请稍等……', ctx_msg) mp3_path = m.group(1) wav_path = os.path.splitext(mp3_path)[0] + '.wav' voice = AudioSegment.from_mp3(mp3_path) voice.export(wav_path, format='wav') service = os.environ.get('SPEECH_RECOGNITION_SERVICE', '').lower() text = None service_full_name = None if service == 'baidu': service_full_name = '百度语音识别' text = _recognize_baidu( wav_path, get_source(ctx_msg), os.environ.get('BAIDU_SPEECH_API_KEY'), os.environ.get('BAIDU_SPEECH_SECRET_KEY'), language='zh') elif service == 'bing': service_full_name = '必应语音识别' text = _recognize_bing(wav_path, os.environ.get('BING_SPEECH_API_KEY'), language='zh-CN') else: print('Unknown speech recognition service name.', file=sys.stderr) if text: reply = '识别结果(' + service_full_name + '):\n%s\n\n下面将把识别到的内容作为文字消息处理……' % text ctx_msg['text'] = text ctx_msg['from_voice'] = True else: reply = '抱歉哦,没有识别出你说的是什么' core.echo(reply, ctx_msg) os.remove(wav_path)
def tuling123(args_text, ctx_msg, internal=False): url = 'http://www.tuling123.com/openapi/api' data = { 'key': os.environ.get('TURING123_API_KEY'), 'info': args_text, 'userid': get_source(ctx_msg) } resp = requests.post(url, data=data) if resp.status_code == 200: json = resp.json() if internal: return json if int(json.get('code', 0)) == 100000: reply = json.get('text', '') else: # Is not text type reply = '腊鸡图灵机器人返回了一堆奇怪的东西,就不发出来了' else: if internal: return None reply = '腊鸡图灵机器人出问题了,先不管他,过会儿再玩他' core.echo(reply, ctx_msg)
def anime_index(_, ctx_msg, argv=None): now = datetime.now(pytz.timezone('Asia/Shanghai')) year = now.year month = now.month if len(argv) == 2 and re.fullmatch('(?:20)?\d{2}', argv[0]) and re.fullmatch('\d{1,2}', argv[1]): year = int(argv[0]) if len(argv[0]) > 2 else 2000 + int(argv[0]) month = int(argv[1]) elif len(argv) == 1 and re.fullmatch('\d{1,2}', argv[0]): month = int(argv[0]) elif len(argv) == 1 and re.fullmatch('(?:20)?\d{2}-\d{1,2}', argv[0]): year, month = [int(x) for x in argv[0].split('-')] year = 2000 + year if year < 100 else year elif len(argv): core.echo('抱歉无法识别的输入的参数,下面将给出本季度的番剧~', ctx_msg) quarter = math.ceil(month / 3) json = requests.get('http://bangumi.bilibili.com/web_api/season/index' '?page=1&page_size=20&version=0&is_finish=0' '&start_year=%d&quarter=%d&tag_id=&index_type=1&index_sort=0' % (year, quarter)).json() if json and json.get('result') and int(json['result'].get('count', 0)) > 0: anime_list = json['result'].get('list', []) reply = '%d年%d月番剧\n按追番人数排序,前20部如下:\n\n' % (year, 1 + (quarter - 1) * 3) reply += '\n'.join([anime.get('title', '未知动画') + ' ' + ('未开播' if anime.get('total_count', -1) < 0 else ('全%d话' % anime['total_count'] if anime['newest_ep_index'] == str(anime['total_count']) else '更新至%s' % anime['newest_ep_index'] + ('话' if anime['newest_ep_index'].isdigit() else ''))) for anime in anime_list]) reply += '\n\n更多详细资料见 bilibili 官网 ' \ 'http://bangumi.bilibili.com/anime/index' \ '#p=1&v=0&area=&stat=0&y=%d&q=%d&tag=&t=1&sort=0' % (year, quarter) else: reply = '没有查询到%d年%d月开播的番剧……' % (year, 1 + (quarter - 1) * 3) core.echo(reply, ctx_msg)