From f1fdb34fba6233f6e7d555e311c8587594785137 Mon Sep 17 00:00:00 2001 From: Richard Chien Date: Thu, 23 Feb 2017 17:32:24 +0800 Subject: [PATCH] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E4=BA=86=E6=9F=A5=E8=AF=A2?= =?UTF-8?q?=20B=20=E7=AB=99=E5=8A=A8=E6=BC=AB=E7=9A=84=E5=91=BD=E4=BB=A4?= =?UTF-8?q?=EF=BC=8C=E4=BF=AE=E5=A4=8D=E4=B8=80=E4=BA=9B=E7=BB=86=E8=8A=82?= =?UTF-8?q?=E7=9A=84=20bug?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- command.py | 25 ++- commands/bilibili.py | 147 ++++++++++++++++++ commands/subscribe.py | 5 +- filters/{how_to_use_1.py => _how_to_use_1.py} | 0 filters/command_dispatcher_0.py | 25 ++- nl_processors/bilibili.py | 48 ++++++ nl_processors/translate.py | 1 - 7 files changed, 239 insertions(+), 12 deletions(-) create mode 100644 commands/bilibili.py rename filters/{how_to_use_1.py => _how_to_use_1.py} (100%) create mode 100644 nl_processors/bilibili.py diff --git a/command.py b/command.py index bf53107f..0144a58d 100644 --- a/command.py +++ b/command.py @@ -302,7 +302,30 @@ def split_arguments(maxsplit=0): elif isinstance(argument, (list, tuple)): argv = list(argument) else: - argv = list(filter(lambda arg: arg, re.split('|'.join(_command_args_seps), argument, maxsplit))) + regexp = re.compile('|'.join(_command_args_seps)) + if maxsplit == 0: + argv = list(filter(lambda arg: arg, regexp.split(argument))) + else: + cur = 0 + tmp_argument = argument + argv = [] + while cur <= maxsplit: + sl = regexp.split(tmp_argument, 1) + if len(sl) == 1: + if sl[0]: + argv.append(sl[0]) + break + # Here len(sl) is > 1 (== 2) + if not sl[0]: + tmp_argument = sl[1] + continue + if cur < maxsplit: + argv.append(sl[0]) + tmp_argument = sl[1] + else: + # Last time + argv.append(tmp_argument) + cur += 1 return func(argument, argv=argv, *args, **kwargs) return wrapper diff --git a/commands/bilibili.py b/commands/bilibili.py new file mode 100644 index 00000000..cca55922 --- /dev/null +++ b/commands/bilibili.py @@ -0,0 +1,147 @@ +import re +import math +from datetime import datetime, timedelta + +import requests +import pytz + +from command import CommandRegistry, split_arguments +from commands import core + +__registry__ = cr = CommandRegistry() + + +@cr.register('anime_index', 'anime-index', '番剧索引', '番剧', '新番') +@split_arguments() +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) + + +@cr.register('anime_timeline', 'anime-timeline', '番剧时间表', '新番时间表') +@split_arguments(maxsplit=1) +def anime_timeline(args_text, ctx_msg, internal=False, argv=None): + if len(argv) == 0: + core.echo('请指定要查询的日期或番剧名称,例如下面(主要看参数,你的命令是对的~):\n\n' + '/新番时间表 02-21\n' + '/新番时间表 0\n' + '/新番时间表 小林家的龙女仆\n' + '/新番时间表 02-21 小林家的龙女仆\n\n' + '上面第二个例子的「0」代表和今天相差的天数,0表示今天,1表示明天,-1表示昨天,以此类推\n' + '参数中间记得用用空格隔开哦~', ctx_msg, internal) + return None + + json = requests.get('http://bangumi.bilibili.com/web_api/timeline_v4').json() + if not json or 'result' not in json: + return None + + timeline_list = json['result'] or [] + + date_str = None + anime_name = None + + if re.fullmatch('\d{1,2}-\d{1,2}', argv[0]): + # month-day + date_str = argv[0] + argv = argv[1:] + elif re.fullmatch('-?\d', argv[0]): + # timedelta (days) + delt = timedelta(days=int(argv[0])) + dt = datetime.now() + delt + date_str = dt.strftime('%m-%d') + argv = argv[1:] + + if len(argv) > 1: + anime_name = args_text.strip() + elif len(argv) == 1: + anime_name = argv[0].rstrip() + + if date_str: + timeline_list = list(filter(lambda item: item.get('pub_date', '').endswith(date_str), timeline_list)) + if anime_name: + timeline_list = list(filter( + lambda item: anime_name.lower() in item.get('title', '').lower() + and len(anime_name) > len(item.get('title', '')) / 4, + timeline_list + )) + + if internal: + return timeline_list + + if date_str and anime_name: + if not timeline_list: + reply = '没更新' + else: + reply = '' + for item in timeline_list: + reply += '\n' + ('更新了' if item['is_published'] else '将在%s更新' % item['ontime']) \ + + '第%s话' % item['ep_index'] if item['ep_index'].isdigit() else item['ep_index'] + reply = reply.lstrip() + + core.echo(reply, ctx_msg, internal) + return + + if not timeline_list: + core.echo('没有找到符合条件的时间表……', ctx_msg, internal) + return + + if date_str and not anime_name: + month, day = [int(x) for x in date_str.split('-')] + reply = '在%d月%d日更新的番剧有:\n\n' % (month, day) + reply += '\n'.join([item.get('title', '未知动画') + ' ' + + item.get('ontime', '未知时间') + ' ' + + ('第%s话' % item.get('ep_index') + if item.get('ep_index', '').isdigit() + else item.get('ep_index', '')) + for item in timeline_list]) + core.echo(reply, ctx_msg, internal) + elif anime_name and not date_str: + anime_dict = {} + for item in timeline_list: + k = item.get('title', '未知动画') + if k not in anime_dict: + anime_dict[k] = [] + anime_dict[k].append(item) + + for name, items in anime_dict.items(): + reply = name + '\n' + for item in items: + _, month, day = [int(x) for x in item['pub_date'].split('-')] + reply += '\n' + ('已' if item['is_published'] else '将') \ + + '在%d月%d日%s更新' % (month, day, item['ontime']) \ + + '第%s话' % item['ep_index'] if item['ep_index'].isdigit() else item['ep_index'] + core.echo(reply, ctx_msg, internal) diff --git a/commands/subscribe.py b/commands/subscribe.py index fdb38214..2326db5a 100644 --- a/commands/subscribe.py +++ b/commands/subscribe.py @@ -45,7 +45,8 @@ def subscribe(args_text, ctx_msg, argv=None, internal=False, allow_interactive=T hour, minute = data['hour'], data['minute'] command = data['command'] job = scheduler.add_job( - '-H %s -M %s %s %s' % (hour, minute, _scheduler_job_id_prefix + str(int(datetime.now().timestamp())), command), + '-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: @@ -138,7 +139,7 @@ def _subscribe_interactively(args_text, ctx_msg, source, data): if 'command' not in sess.data: # Ask for command core.echo( - '请输入你需要订阅的命令(包括所需的参数),不需要加开头的斜杠哦~\n\n' + '请输入你需要订阅的命令(包括所需的参数),每行一条,不需要加开头的斜杠哦~\n\n' '例如(序号后的):\n' '(1) 天气 南京\n' '(2) 知乎日报\n' diff --git a/filters/how_to_use_1.py b/filters/_how_to_use_1.py similarity index 100% rename from filters/how_to_use_1.py rename to filters/_how_to_use_1.py diff --git a/filters/command_dispatcher_0.py b/filters/command_dispatcher_0.py index 01f4525e..c017bb89 100644 --- a/filters/command_dispatcher_0.py +++ b/filters/command_dispatcher_0.py @@ -13,6 +13,7 @@ _command_start_flags = get_command_start_flags() _command_args_start_flags = get_command_args_start_flags() +# noinspection PyBroadException @as_filter(priority=0) def _dispatch_command(ctx_msg): text = ctx_msg.get('text', '').lstrip() @@ -41,14 +42,18 @@ def _dispatch_command(ctx_msg): # No fallback raise SkipException else: - # Split command and arguments - command = re.split('|'.join(_command_args_start_flags), - text[len(start_flag):], 1) - if len(command) == 1: - # Add an empty argument - command.append('') - # Starting a new command, so remove previous command session, if any - interactive.remove_session(source) + if start_flag == '' and interactive.has_session(source): + # Start flag is empty, so we don't override any sessions + command = [interactive.get_session(source).cmd, text] + else: + # Split command and arguments + command = re.split('|'.join(_command_args_start_flags), + text[len(start_flag):], 1) + if len(command) == 1: + # Add an empty argument + command.append('') + # Starting a new command, so remove previous command session, if any + interactive.remove_session(source) command[0] = command[0].lower() ctx_msg['command'] = command[0] @@ -70,6 +75,10 @@ def _dispatch_command(ctx_msg): core.echo('你没有权限使用这个命令哦~', ctx_msg) except CommandScopeError as se: core.echo('这个命令不支持' + se.msg_type + '哦~', ctx_msg) + except Exception as e: + # Ignore all exceptions raised during command running + print(e) + core.echo('程序执行命令时发生了一点错误,可能没法回复啦~', ctx_msg) def _add_registry_mod_cb(mod): diff --git a/nl_processors/bilibili.py b/nl_processors/bilibili.py new file mode 100644 index 00000000..56219c80 --- /dev/null +++ b/nl_processors/bilibili.py @@ -0,0 +1,48 @@ +import re + +from nl_processor import as_processor + + +@as_processor(keywords=('番', '动漫', '动画')) +def _processor_anime_index(sentence, segmentation): + m = re.search('(?:(?P\d{2})\s*年\s*)?(?P\d{1,2})\s*月', sentence) + year, month = None, None + if m: + year = m.group('year') + month = m.group('month') + + args_text = month if month else '' + args_text = (str(year) + ' ' + args_text) if year else args_text + + possibility = 80 + if '哪些' in sentence or '什么' in sentence: + possibility += 3 + if not re.search('b\s*站', sentence.lower()): + possibility -= 10 + + return possibility, 'bilibili.anime_index', args_text, None + + +@as_processor(keywords=('更新',)) +def _processor_anime_timeline(sentence, segmentation): + m = re.match('(?P(?:前|昨|今|明|大?后)天)?(?P.+?)' + '(?P(?:前|昨|今|明|大?后)天)?(?:会|有)?更(?:不更)?新', + sentence) + day_str, name = None, None + if m: + day_str = m.group('day_str') or m.group('day_str2') + name = m.group('name') + + if not name: + return None + + possibility = 80 + if not day_str: + possibility -= 5 + if not re.search('b\s*站', sentence.lower()): + possibility -= 10 + + delta_day_dict = {'前天': -2, '昨天': -1, '今天': 0, '明天': 1, '后天': 2, '大后天': 3} + delta_day = delta_day_dict.get(day_str, 0) + + return possibility, 'bilibili.anime_timeline', str(delta_day) + ' ' + name, None diff --git a/nl_processors/translate.py b/nl_processors/translate.py index fbf27717..4b06fa0e 100644 --- a/nl_processors/translate.py +++ b/nl_processors/translate.py @@ -24,6 +24,5 @@ def _processor(sentence, segmentation): lang, query = m.group('lang'), m.group('query') break if lang and query: - print('翻译: 目标语言:', lang, ', 待翻译文本:', query) return 90, 'translate.translate_to', ' '.join((lang.strip(), query.strip(' ,,'))), None return None