diff --git a/README.md b/README.md index 389f702..0e80222 100644 --- a/README.md +++ b/README.md @@ -16,6 +16,7 @@ - 内建自由且强大的模板语法,适用于各种记账需求 - 允许通过插件扩展记账语法 - 支持定时任务 +- 支持多个用户同时记账,设置不同的标签 ## 安装 diff --git a/beancount_bot/bot.py b/beancount_bot/bot.py index 6be6d79..6a08f19 100644 --- a/beancount_bot/bot.py +++ b/beancount_bot/bot.py @@ -6,10 +6,11 @@ from beancount_bot.config import get_config, load_config from beancount_bot.dispatcher import Dispatcher from beancount_bot.i18n import _ -from beancount_bot.session import get_session, SESS_AUTH, get_session_for, set_session +from beancount_bot.session import get_session, SESS_AUTH, get_session_for, set_session, SESS_TX_TAGS +from beancount_bot.session_config import SESSION_CONFIG from beancount_bot.task import load_task, get_task from beancount_bot.transaction import get_manager -from beancount_bot.util import logger +from beancount_bot.util import logger, stringify_tags apihelper.ENABLE_MIDDLEWARE = True @@ -24,6 +25,7 @@ def session_middleware(bot_instance, message): :param message: :return: """ + bot_instance.session_user_id = message.from_user.id bot_instance.session = get_session_for(message.from_user.id) @@ -112,6 +114,8 @@ def help_handler(message): _("/help - 使用帮助"), _("/reload - 重新加载配置文件"), _("/task - 查看、运行任务"), + _("/set - 设置用户特定配置"), + _("/get - 获取用户特定配置"), ] help_text = \ _("记账 Bot\n\n可用指令列表:\n{command}\n\n交易语句语法帮助请选择对应模块,或使用 /help [模块名] 查看。").format( @@ -185,6 +189,46 @@ def task_handler(message): task.trigger(bot) +################## +# Session 特有配置 # +################## + +@bot.message_handler(commands=['set', 'get']) +def session_config_handler(message): + """ + 设置 session 配置 + :param message: + :return: + """ + if not check_auth(): + bot.reply_to(message, _("请先进行鉴权!")) + return + + # 解析指令 + is_set = message.text.startswith('/set') + cmd: str = message.text[4:] + conf = cmd.split(maxsplit=1) + # 显示指令帮助 + if len(conf) < 1: + desc = '\n'.join(v.make_help(k, is_set=is_set) for k, v in SESSION_CONFIG.items()) + bot.reply_to(message, _("使用方法:\n{desc}").format(desc=desc)) + return + # 获得指令对象 + conf = conf[0] + if conf not in SESSION_CONFIG: + bot.reply_to(message, _("配置不存在!命令使用方法请参考 {cmd}") + .format(cmd='/set' if is_set else '/get')) + return + obj_conf = SESSION_CONFIG[conf] + # 获得参数 + cmd = cmd[len(conf) + 1:] + # 调用 + if is_set: + obj_conf.set(cmd, bot, message) + else: + obj_conf.get(cmd, bot, message) + + ####### # 交易 # ####### @@ -204,7 +248,7 @@ def transaction_query_handler(message: Message): manager = get_manager() try: # 准备交易上下文 - tags = get_config('transaction.tags', []) + tags = get_config('transaction.tags', []) + get_session(message.from_user.id, SESS_TX_TAGS, []) # 处理交易 tx_uuid, tx = manager.create_from_str(message.text, add_tags=tags) # 创建消息按键 @@ -216,7 +260,7 @@ def transaction_query_handler(message: Message): logger.info(f'{message.from_user.id}:无法添加交易', exc_info=e) bot.reply_to(message, e.args[0]) except Exception as e: - logger.error(f'{message.from_user.id}:发生未知错误!添加交易失败。', exe_info=e) + logger.error(f'{message.from_user.id}:发生未知错误!添加交易失败。', exc_info=e) bot.reply_to(message, _("发生未知错误!添加交易失败。")) diff --git a/beancount_bot/session.py b/beancount_bot/session.py index e045d5b..2cf3746 100644 --- a/beancount_bot/session.py +++ b/beancount_bot/session.py @@ -7,6 +7,7 @@ from beancount_bot.util import logger SESS_AUTH = 'auth' +SESS_TX_TAGS = 'tx_tags' _session_cache: Dict[str, dict] = {} diff --git a/beancount_bot/session_config.py b/beancount_bot/session_config.py new file mode 100644 index 0000000..cd88354 --- /dev/null +++ b/beancount_bot/session_config.py @@ -0,0 +1,87 @@ +from typing import Dict + +from telebot import TeleBot + +from beancount_bot.config import get_config +from beancount_bot.i18n import _ +from beancount_bot.session import get_session, SESS_TX_TAGS, set_session +from beancount_bot.util import stringify_tags + + +class SessionSpecificConfig: + """与特定 session 相关的配置""" + + def get(self, cmd, bot: TeleBot, message): + """获取当前配置内容的指令""" + pass + + def set(self, cmd, bot: TeleBot, message): + """设置当前配置的指令""" + pass + + def help(self): + """ + 获取帮助信息 + :return: get 指令参数, get 帮助信息, set 指令参数, set 帮助信息 + """ + return '[param]', 'help', '[param]', 'help' + + def make_help(self, key, is_set=False): + """ + 生成帮助信息 + :param key: 参数 + :param is_set: 是否是 set 指令 + :return: 帮助信息 + """ + prefix, param, info = None, None, None + if is_set: + prefix = '/set' + _, _, param, info = self.help() + else: + prefix = '/get' + param, info, _, _ = self.help() + + if len(param) > 0: + param = f' {param}' + return f'{prefix} {key}{param} - {info}' + + +SESSION_CONFIG: Dict[str, SessionSpecificConfig] = {} + + +def register_session_config(key: str, conf: SessionSpecificConfig): + """注册特定 session 的配置""" + SESSION_CONFIG[key] = conf + + +########## +# 具体配置 # +########## + + +class TagsConfig(SessionSpecificConfig): + """交易标签""" + + def help(self): + return '', _('获取添加于交易的标签'), \ + _('[标签列表]'), _('设置当前用户的标签。多个标签使用空格隔开,为空则表示清空。') + + def get(self, cmd, bot, message): + """显示所有标签""" + global_tags = get_config('transaction.tags', []) + session_tags = get_session(bot.session_user_id, SESS_TX_TAGS, []) + bot.reply_to(message, + _("全局标签:{global_tags}\n" + "当前用户标签:{session_tags}\n" + "可以通过 /set tags [标签列表] 设置当前用户的标签。") + .format(global_tags=stringify_tags(global_tags, human_readable=True), + session_tags=stringify_tags(session_tags, human_readable=True))) + + def set(self, cmd, bot, message): + """设置标签""" + tags = list(set(cmd.split())) + set_session(bot.session_user_id, SESS_TX_TAGS, tags) + bot.reply_to(message, _("成功设置用户标签!")) + + +register_session_config('tags', TagsConfig()) diff --git a/beancount_bot/util.py b/beancount_bot/util.py index 9380012..421282f 100644 --- a/beancount_bot/util.py +++ b/beancount_bot/util.py @@ -28,6 +28,17 @@ def stringify_errors(errors: list) -> str: .format(lineno=err.source["lineno"], message=err.message), errors)) +def stringify_tags(tags, human_readable=False): + """ + 格式化 Beancount 标签列表 + """ + if human_readable: + if len(tags) == 0: + return _('无') + return ', '.join(f'#{t}' for t in tags) + return ' '.join(f'#{t}' for t in tags) + + def indent(text: str, prefix: str = ' ') -> str: """ 文本增加缩进