diff --git a/LOCALES.md b/LOCALES.md new file mode 100644 index 00000000..c4e21514 --- /dev/null +++ b/LOCALES.md @@ -0,0 +1,194 @@ +# 目录 +- [简介](#简介) +- [文件位置](#文件位置) +- [语言文件格式](#语言文件格式) +- [排版](#排版) + - [空格](#空格) + - [中英文之间需要增加空格](#中英文之间需要增加空格) + - [中文与数字之间需要增加空格](#中文与数字之间需要增加空格) + - [数字与单位之间不加空格](#数字与单位之间不加空格) + - [全角标点与其他字符之间不加空格](#全角标点与其他字符之间不加空格) + - [变量与中文之间需要增加空格](#变量与中文之间需要增加空格) + - [标点符号](#标点符号) + - [不要重复使用标点符号](#不要重复使用标点符号) + - [简体中文不要使用直角引号](#简体中文不要使用直角引号) + - [英文不要使用弯引号](#英文不要使用弯引号) + - [英文省略号使用三个点](#英文省略号使用三个点) + - [全角和半角](#全角和半角) + - [使用全角中文标点](#使用全角中文标点) + - [数字和英文使用半角字符](#数字和英文使用半角字符) + - [遇到完整的英文成句内容中使用半角标点](#遇到完整的英文成句内容中使用半角标点) + - [名词](#名词) + - [专有名词使用正确的大小写](#专有名词使用正确的大小写) + - [不要使用非正式的缩写](#不要使用非正式的缩写) + - [不同地区的中文使用对应的地区词](#不同地区的中文使用对应的地区词) +- [报告问题](#报告问题) + +# 简介 +本规范文件旨在确保项目中的多语言文本一致性和格式规范。请遵循以下内容以保持代码库中的多语言资源的统一性。 + +# 文件位置 +请将语言文件放置在 `/locales` 目录中,并以语言代码命名为 JSON 文件。 + +根路径下的目录用于放置全局字符串,模块下的目录用于放置对应专用的模块字符串。 + +原则上,模块字符串和对应模块须对应,若模块之间存在关联则可以例外,否则请考虑转为全局字符串。 + +# 语言文件格式 +请确保键名与字符串一一对应,不得嵌套。 + +**全局字符串的命名方式**:`字符串类别.用途` + +**模块字符串的命名方式**:`模块名称.字符串类别.命令名称.用途` + +使用 `${变量名}` 表示变量,变量名必须使用英文,不能使用空格和特殊符号,如需分隔则使用下划线代替。 + +# 排版 +> 本文部分参照[中文文案排版指北](https://github.com/sparanoid/chinese-copywriting-guidelines),内容可能有出入。 + +## 空格 +### 中英文之间需要增加空格 +正确: +> 在 IBM 的研究中,他们利用 AI 技术开发了一种先进的语音识别系统。 + +错误: +> 在IBM的研究中,他们利用AI技术开发了一种先进的语音识别系统。 + +例外:专有名词、商品名等词语,按照约定俗成的格式书写。 + +### 中文与数字之间需要增加空格 +正确: + > 今年的全球汽车销售量达到了 8000 万辆。 + +错误: + > 今年的全球汽车销售量达到了8000万辆。 + +### 数字与单位之间不加空格 +正确: +> 我家的光纤入户宽带有 10Gbps,SSD 一共有 10TB。 + +> 这个城市每年平均降雨量为 1200mm。 +> +> 角度为 90° 的角,就是直角。 + +错误: +>我家的光纤入户宽带有 10 Gbps,SSD 一共有 20 TB。 + +> 这个城市每年平均降雨量为 1200 mm。 +> +> 角度为 90 ° 的角,就是直角。 + +### 全角标点与其他字符之间不加空格 +正确: +> 刚刚买了一部 iPhone,好开心! + +错误: +> 刚刚买了一部 iPhone ,好开心! + +### 变量与中文之间需要增加空格 +一般情况下,变量的输入为英文或数字,故变量与中文之间加入空格。 + +正确: +> 你扔了一块石头,漂了 ${count} 下。 + +错误: +> 你扔了一块石头,漂了${count}下。 + +例外:如果变量的输入一定为中文,则不加空格。 + +## 标点符号 +### 不要重复使用标点符号 +虽然重复使用标点符号是被允许的行为,但是请别这样做。 + +正确: +> 德国队竟然战胜了巴西队! + +错误: +> 德国队竟然战胜了巴西队!!! + +### 简体中文不要使用直角引号 +直角引号并不符合简体中文使用者的使用习惯。 + +正确: +> “老师,‘有条不紊’的‘紊’是什么意思?” + +错误: +> 「老师,『有条不紊』的『紊』是什么意思?」 + +### 英文不要使用弯引号 +中文弯引号和英文弯引号属于同一个字符,如果使用弯引号反而会造成阅读问题,请使用直引号 `"`。 + +正确: +> "Success is not final, failure is not fatal: It is the courage to continue that counts." + +错误: +> “Success is not final, failure is not fatal: It is the courage to continue that counts.” + +### 英文省略号使用三个点 +同上,请使用三个点 `...`。 + +正确: +> In the serene moonlit night, whispers of ancient tales lingered, echoing through the stillness of time... + +错误: +> In the serene moonlit night, whispers of ancient tales lingered, echoing through the stillness of time… + +## 全角和半角 +### 使用全角中文标点 +正确: +> 嗨!你知道嘛?今天前台的小妹跟我说“喵”了哎! + +错误: +> 嗨! 你知道嘛? 今天前台的小妹跟我说 "喵" 了哎! +> +> 嗨!你知道嘛?今天前台的小妹跟我说"喵"了哎! + +### 数字和英文使用半角字符 +正确: +> 这件蛋糕只卖 200 元。 + +错误: +> 这件蛋糕只卖 200 元。 + +### 遇到完整的英文成句内容中使用半角标点 +正确: +> 乔布斯那句话是怎么说的?“Stay hungry, stay foolish.” + +错误: +> 乔布斯那句话是怎么说的?“Stay hungry, stay foolish。” + +## 名词 +### 专有名词使用正确的大小写 +正确: +> 使用 GitHub 登录 + +错误: +> 使用 Github 登录 +> +> 使用 gitHub 登录 +> +> 使用 github 登录 +> +> 使用 GITHUB 登录 + +### 不要使用非正式的缩写 +正确: +> 我们需要一位熟悉 JavaScript、HTML5,至少理解一种框架(如 Backbone.js、AngularJS、React 等)的前端开发者。 + +错误: +> 我们需要一位熟悉 Js、h5,至少理解一种框架(如 backbone、angular、RJS 等)的 FED。 + + +### 不同地区的中文使用对应的地区词 +不要在繁体中文中使用“视频”等错误的地区词,认真的。 + +正确: +> 輸入影片編號取得對應資訊。 + +错误: +> 輸入視頻編號獲得相應信息。 + +# 报告问题 +与简体中文翻译有关的问题可直接使用 [Issue](https://github.com/Teahouse-Studios/akari-bot/issues/new) 报告。 + +简体中文以外的所有语言由机器人 @Hldbot 维护,相关问题请移步 [Crowdin](https://crowdin.com/project/akari-bot) 报告。 diff --git a/README.md b/README.md index c6cd93cf..efaff9de 100644 --- a/README.md +++ b/README.md @@ -44,7 +44,7 @@ #### QQ -~~目前 QQ 上稳定运行的小可实例为小可三号机。由于腾讯风控问题,请[在此提交](https://github.com/Teahouse-Studios/bot/issues/new?assignees=OasisAkari&labels=New&template=add_new_group.yaml&title=%5BNEW%5D%3A+)入群申请。~~ +~~目前 QQ 上稳定运行的小可实例为小可三号机。由于腾讯风控问题,请[在此提交](https://github.com/Teahouse-Studios/akari-bot/issues/new?assignees=OasisAkari&labels=New&template=add_new_group.yaml&title=%5BNEW%5D%3A+)入群申请。~~ ~~QQ 频道的测试版支持也请在上方链接申请。~~ 我们正在进行 QQ 频道官方机器人入驻,未来可能会成为官方认证的机器人。 @@ -56,7 +56,7 @@ 你可以[参考这里](./DEPLOY.md)来进行尝试搭建。 -若遇到问题,可以通过 [Issue](https://github.com/Teahouse-Studios/bot/issues/new) 或其他方式咨询开发者。 +若遇到问题,可以通过 [Issue](https://github.com/Teahouse-Studios/akari-bot/issues/new) 或其他方式咨询开发者。 ### 多语言 diff --git a/bots/aiocqhttp/message.py b/bots/aiocqhttp/message.py index d724f8ba..e50561b2 100644 --- a/bots/aiocqhttp/message.py +++ b/bots/aiocqhttp/message.py @@ -14,7 +14,7 @@ from aiocqhttp import MessageSegment from bots.aiocqhttp.client import bot from bots.aiocqhttp.info import client_name from config import Config -from core.builtins import Bot, base_superuser_list, command_prefix, ErrorMessage, Image, Plain, Temp, Voice +from core.builtins import Bot, base_superuser_list, command_prefix, ErrorMessage, Image, Plain, Temp, Voice, MessageTaskManager from core.builtins.message import MessageSession as MessageSessionT from core.builtins.message.chain import MessageChain from core.exceptions import SendMessageFailed @@ -88,26 +88,30 @@ class MessageSession(MessageSessionT): quote = True async def send_message(self, message_chain, quote=True, disable_secret_check=False, - allow_split_image=True) -> FinishedSession: + allow_split_image=True, + callback=None) -> FinishedSession: message_chain = MessageChain(message_chain) message_chain_assendable = message_chain.as_sendable(self, embed=False) + if (self.target.target_from == 'QQ|Group' and Temp.data.get('lagrange_status', False) and not - self.tmp.get('enforce_send_by_master_client', False)): + self.tmp.get('enforce_send_by_master_client', False) and not callback): lagrange_available_groups = Temp.data.get('lagrange_available_groups', []) if int(self.session.target) in lagrange_available_groups: choose = random.randint(0, 1) Logger.debug(f'choose: {choose}') if choose: - can_sends = [] + cant_sends = [] for x in message_chain_assendable: - if isinstance(x, (Plain, Image)): - can_sends.append(x) + if isinstance(x, Voice): + cant_sends.append(x) message_chain_assendable.remove(x) - if can_sends: + if message_chain_assendable: await JobQueue.send_message('Lagrange', self.target.target_id, - MessageChain(can_sends).to_list()) - if not message_chain_assendable: + MessageChain(message_chain_assendable).to_list()) + if cant_sends: + self.tmp['enforce_send_by_master_client'] = True + await self.send_message(MessageChain(cant_sends)) return FinishedSession(self, 0, [{}]) else: Logger.debug(f'Do not use lagrange since some conditions are not met.\n{self.target.target_from} ' @@ -170,6 +174,8 @@ class MessageSession(MessageSessionT): return FinishedSession(self, 0, [{}]) else: raise e + if callback: + MessageTaskManager.add_callback(send['message_id'], callback) return FinishedSession(self, send['message_id'], [send]) async def check_native_permission(self): diff --git a/bots/aiogram/message.py b/bots/aiogram/message.py index acbdc8d1..417e9fb2 100644 --- a/bots/aiogram/message.py +++ b/bots/aiogram/message.py @@ -5,7 +5,7 @@ from typing import List, Union from bots.aiogram.client import dp, bot, token from bots.aiogram.info import client_name from config import Config -from core.builtins import Bot, Plain, Image, Voice, MessageSession as MessageSessionT, ErrorMessage +from core.builtins import Bot, Plain, Image, Voice, MessageSession as MessageSessionT, ErrorMessage, MessageTaskManager from core.builtins.message.chain import MessageChain from core.logger import Logger from core.types import FetchTarget as FetchTargetT, \ @@ -39,7 +39,7 @@ class MessageSession(MessageSessionT): wait = True async def send_message(self, message_chain, quote=True, disable_secret_check=False, - allow_split_image=True) -> FinishedSession: + allow_split_image=True, callback=None) -> FinishedSession: message_chain = MessageChain(message_chain) if not message_chain.is_safe and not disable_secret_check: return await self.send_message(Plain(ErrorMessage(self.locale.t("error.message.chain.unsafe")))) @@ -89,6 +89,8 @@ class MessageSession(MessageSessionT): msg_ids = [] for x in send: msg_ids.append(x.message_id) + if callback: + MessageTaskManager.add_callback(x.message_id, callback) return FinishedSession(self, msg_ids, send) async def check_native_permission(self): diff --git a/bots/discord/client.py b/bots/discord/client.py index e1ae5130..ededf6d3 100644 --- a/bots/discord/client.py +++ b/bots/discord/client.py @@ -1,5 +1,7 @@ import discord +from config import Config + intents = discord.Intents.default() intents.message_content = True -client = discord.Bot(intents=intents) +client = discord.Bot(intents=intents, proxy=Config('proxy')) diff --git a/bots/discord/message.py b/bots/discord/message.py index cfd5643f..f15e5187 100644 --- a/bots/discord/message.py +++ b/bots/discord/message.py @@ -9,7 +9,7 @@ import filetype from bots.discord.client import client from bots.discord.info import client_name from config import Config -from core.builtins import Bot, Plain, Image, MessageSession as MessageSessionT +from core.builtins import Bot, Plain, Image, MessageSession as MessageSessionT, MessageTaskManager from core.builtins.message.chain import MessageChain from core.builtins.message.internal import Embed, ErrorMessage, Voice from core.logger import Logger @@ -69,7 +69,8 @@ class MessageSession(MessageSessionT): quote = True wait = True - async def send_message(self, message_chain, quote=True, disable_secret_check=False, allow_split_image=True + async def send_message(self, message_chain, quote=True, disable_secret_check=False, allow_split_image=True, + callback=None ) -> FinishedSession: message_chain = MessageChain(message_chain) if not message_chain.is_safe and not disable_secret_check: @@ -102,13 +103,15 @@ class MessageSession(MessageSessionT): files=files) Logger.info(f'[Bot] -> [{self.target.target_id}]: Embed: {str(x.__dict__)}') else: - send_ = False + send_ = None if send_: send.append(send_) count += 1 msg_ids = [] for x in send: msg_ids.append(x.id) + if callback: + MessageTaskManager.add_callback(x.id, callback) return FinishedSession(self, msg_ids, send) diff --git a/bots/kook/message.py b/bots/kook/message.py index 74b7cadf..162c405c 100644 --- a/bots/kook/message.py +++ b/bots/kook/message.py @@ -9,7 +9,7 @@ from khl import MessageTypes, Message from bots.kook.client import bot from bots.kook.info import client_name from config import Config -from core.builtins import Bot, Plain, Image, Voice, MessageSession as MessageSessionT, ErrorMessage +from core.builtins import Bot, Plain, Image, Voice, MessageSession as MessageSessionT, ErrorMessage, MessageTaskManager from core.builtins.message.chain import MessageChain from core.logger import Logger from core.types import FetchTarget as FetchTargetT, \ @@ -68,7 +68,7 @@ class MessageSession(MessageSessionT): wait = True async def send_message(self, message_chain, quote=True, disable_secret_check=False, - allow_split_image=True) -> FinishedSession: + allow_split_image=True, callback=None) -> FinishedSession: self.session.message: Message message_chain = MessageChain(message_chain) if not message_chain.is_safe and not disable_secret_check: @@ -102,6 +102,8 @@ class MessageSession(MessageSessionT): msg_ids = [] for x in send: msg_ids.append(x['msg_id']) + if callback: + MessageTaskManager.add_callback(x['msg_id'], callback) return FinishedSession(self, msg_ids, {self.session.message.channel_type.name: send}) async def check_native_permission(self): diff --git a/bots/lagrange/message.py b/bots/lagrange/message.py index 63fdca39..bfc6185c 100644 --- a/bots/lagrange/message.py +++ b/bots/lagrange/message.py @@ -14,7 +14,8 @@ from aiocqhttp import MessageSegment from bots.lagrange.client import bot from bots.lagrange.info import client_name from config import Config -from core.builtins import Bot, base_superuser_list, command_prefix, ErrorMessage, Image, Plain, Temp, Voice +from core.builtins import Bot, base_superuser_list, command_prefix, ErrorMessage, Image, Plain, Temp, Voice, \ + MessageTaskManager from core.builtins.message import MessageSession as MessageSessionT from core.builtins.message.chain import MessageChain from core.exceptions import SendMessageFailed @@ -86,7 +87,8 @@ class MessageSession(MessageSessionT): quote = False async def send_message(self, message_chain, quote=True, disable_secret_check=False, - allow_split_image=True) -> FinishedSession: + allow_split_image=True, + callback=None) -> FinishedSession: msg = [] """ if quote and self.target.target_from == 'QQ|Group' and self.session.message: @@ -102,7 +104,7 @@ class MessageSession(MessageSessionT): msg.append({ "type": "text", "data": { - "text": x.text + "text": ('\n' if count != 0 else '') + x.text } }) elif isinstance(x, Image): @@ -122,7 +124,7 @@ class MessageSession(MessageSessionT): self.locale.t("error.message.timeout"))) except aiocqhttp.exceptions.ActionFailed: message_chain.insert(0, Plain(self.locale.t("error.message.limited.msg2img"))) - msg2img = MessageSegment.image(Path(await msgchain2image(self, message_chain)).as_uri()) + msg2img = MessageSegment.image(Path(await msgchain2image(message_chain, self)).as_uri()) try: send = await bot.send_group_msg(group_id=int(self.session.target), message=msg2img) except aiocqhttp.exceptions.ActionFailed as e: @@ -143,6 +145,8 @@ class MessageSession(MessageSessionT): return FinishedSession(self, 0, [{}]) else: raise e + if callback: + MessageTaskManager.add_callback(send['message_id'], callback) return FinishedSession(self, send['message_id'], [send]) async def check_native_permission(self): diff --git a/bots/matrix/message.py b/bots/matrix/message.py index 0de53184..f8aa6c72 100644 --- a/bots/matrix/message.py +++ b/bots/matrix/message.py @@ -9,7 +9,7 @@ import nio from bots.matrix.client import bot, homeserver_host from bots.matrix.info import client_name from config import Config -from core.builtins import Bot, Plain, Image, Voice, MessageSession as MessageSessionT, ErrorMessage +from core.builtins import Bot, Plain, Image, Voice, MessageSession as MessageSessionT, ErrorMessage, MessageTaskManager from core.builtins.message.chain import MessageChain from core.logger import Logger from core.types import FetchTarget as FetchedTargetT, \ @@ -43,7 +43,8 @@ class MessageSession(MessageSessionT): wait = True async def send_message(self, message_chain, quote=True, disable_secret_check=False, - allow_split_image=True) -> FinishedSession: + allow_split_image=True, + callback=None) -> FinishedSession: message_chain = MessageChain(message_chain) if not message_chain.is_safe and not disable_secret_check: return await self.send_message(Plain(ErrorMessage(self.locale.t("error.message.chain.unsafe")))) @@ -154,7 +155,9 @@ class MessageSession(MessageSessionT): Logger.error(f"Error in sending message: {str(resp)}") else: send.append(resp) - + if callback: + for x in send: + MessageTaskManager.add_callback(x.event_id, callback) return FinishedSession(self, [resp.event_id for resp in send], self.session.target) async def check_native_permission(self): diff --git a/core/builtins/message/__init__.py b/core/builtins/message/__init__.py index 6f1a5f20..bc67a081 100644 --- a/core/builtins/message/__init__.py +++ b/core/builtins/message/__init__.py @@ -144,10 +144,13 @@ class MessageSession(MessageSessionT): checkPermission = check_permission checkSuperUser = check_super_user - def ts2strftime(self, timestamp: float, date=True, time=True, seconds=True, timezone=True): + def ts2strftime(self, timestamp: float, date=True, iso=False, time=True, seconds=True, timezone=True): ftime_template = [] if date: - ftime_template.append(self.locale.t("time.date.format")) + if iso: + ftime_template.append(self.locale.t("time.date.iso.format")) + else: + ftime_template.append(self.locale.t("time.date.format")) if time: if seconds: ftime_template.append(self.locale.t("time.time.format")) @@ -155,7 +158,7 @@ class MessageSession(MessageSessionT): ftime_template.append(self.locale.t("time.time.nosec.format")) if timezone: if self._tz_offset == "+0": - ftime_template.append(f"(UTC)") + ftime_template.append("(UTC)") else: ftime_template.append(f"(UTC{self._tz_offset})") return (datetime.utcfromtimestamp(timestamp) + self.timezone_offset).strftime(' '.join(ftime_template)) diff --git a/core/builtins/message/chain.py b/core/builtins/message/chain.py index f685ff58..2350ecdd 100644 --- a/core/builtins/message/chain.py +++ b/core/builtins/message/chain.py @@ -5,7 +5,7 @@ from urllib.parse import urlparse import ujson as json -from core.builtins.message.internal import Plain, Image, Voice, Embed, Url, ErrorMessage, FormattedTime +from core.builtins.message.internal import Plain, Image, Voice, Embed, Url, ErrorMessage, FormattedTime, I18NText from core.builtins.utils import Secret from core.logger import Logger from core.types.message import MessageChain as MessageChainT, MessageSession @@ -51,6 +51,13 @@ class MessageChain(MessageChainT): e['data']['timestamp'], e['data']['color'], Image(e['data']['image']), Image(e['data']['thumbnail']), e['data']['author'], e['data']['footer'], e['data']['fields'])) + elif e['type'] == 'url': + self.value.append(Url(e['data']['url'])) + elif e['type'] == 'formatted_time': + self.value.append(FormattedTime(e['data']['time'], e['data']['date'], e['data']['seconds'], + e['data']['timezone'])) + elif e['type'] == 'i18n': + self.value.append(I18NText(e['data']['key'], **e['data']['kwargs'])) elif isinstance(e, str): if e != '': self.value += match_kecode(e) @@ -124,6 +131,8 @@ class MessageChain(MessageChainT): value.append(Plain(ErrorMessage('{error.message.chain.plain.empty}', locale=locale))) elif isinstance(x, FormattedTime): value.append(Plain(x.to_str(msg=msg))) + elif isinstance(x, I18NText): + value.append(Plain(msg.locale.t(x.key, **x.kwargs))) else: value.append(x) if not value: diff --git a/core/builtins/message/internal.py b/core/builtins/message/internal.py index f0f4abee..1f102cd0 100644 --- a/core/builtins/message/internal.py +++ b/core/builtins/message/internal.py @@ -93,7 +93,23 @@ class FormattedTime: return f'FormattedTime(time={self.timestamp})' def to_dict(self): - return {'type': 'time', 'data': {'time': self.timestamp}} + return {'type': 'formatted_time', 'data': {'time': self.timestamp, 'date': self.date, 'seconds': self.seconds, + 'timezone': self.timezone}} + + +class I18NText: + def __init__(self, key, **kwargs): + self.key = key + self.kwargs = kwargs + + def __str__(self): + return self.key + + def __repr__(self): + return f'I18NText(key="{self.key}", kwargs={self.kwargs})' + + def to_dict(self): + return {'type': 'i18n', 'data': {'key': self.key, 'kwargs': self.kwargs}} class ErrorMessage(EMsg): @@ -269,4 +285,4 @@ class Embed(EmbedT): 'fields': self.fields}} -__all__ = ["Plain", "Image", "Voice", "Embed", "EmbedField", "Url", "ErrorMessage", "FormattedTime"] +__all__ = ["Plain", "Image", "Voice", "Embed", "EmbedField", "Url", "ErrorMessage", "FormattedTime", "I18NText"] diff --git a/core/builtins/tasks.py b/core/builtins/tasks.py index 57cac442..75313a2e 100644 --- a/core/builtins/tasks.py +++ b/core/builtins/tasks.py @@ -6,6 +6,7 @@ from core.types import MessageSession class MessageTaskManager: _list = {} + _callback_list = {} @classmethod def add_task(cls, session: MessageSession, flag, all_=False, reply=None): @@ -22,6 +23,10 @@ class MessageTaskManager: 'flag': flag, 'active': True, 'type': task_type, 'reply': reply, 'ts': datetime.now().timestamp()} Logger.debug(cls._list) + @classmethod + def add_callback(cls, message_id, callback): + cls._callback_list[message_id] = {'callback': callback, 'ts': datetime.now().timestamp()} + @classmethod def get_result(cls, session: MessageSession): if 'result' in cls._list[session.target.target_id][session.target.sender_id][session]: @@ -42,6 +47,9 @@ class MessageTaskManager: if datetime.now().timestamp() - cls._list[target][sender][session]['ts'] > 3600: cls._list[target][sender][session]['active'] = False cls._list[target][sender][session]['flag'].set() # no result = cancel + for message_id in cls._callback_list: + if datetime.now().timestamp() - cls._callback_list[message_id]['ts'] > 3600: + del cls._callback_list[message_id] @classmethod async def check(cls, session: MessageSession): @@ -72,6 +80,8 @@ class MessageTaskManager: get_['result'] = session get_['active'] = False get_['flag'].set() + if session.target.reply_id in cls._callback_list: + await cls._callback_list[session.target.reply_id]['callback'](session) __all__ = ['MessageTaskManager'] diff --git a/core/console/message.py b/core/console/message.py index 2781ba40..6c937dd2 100644 --- a/core/console/message.py +++ b/core/console/message.py @@ -30,7 +30,7 @@ class MessageSession(MessageSessionT): wait = True async def send_message(self, message_chain, quote=True, disable_secret_check=False, - allow_split_image=True) -> FinishedSession: + allow_split_image=True, callback=None) -> FinishedSession: message_chain = MessageChain(message_chain) self.sent.append(message_chain) msg_list = [] @@ -44,7 +44,7 @@ class MessageSession(MessageSessionT): img = Image.open(image_path) img.show() Logger.info(f'[Bot] -> [{self.target.target_id}]: Image: {image_path}') - return FinishedSession(self, [0], ['There should be a callable here... hmm...']) + return FinishedSession(self, [0], ['Should be a callable here... hmm...']) async def wait_confirm(self, message_chain=None, quote=True, delete=True): send = None diff --git a/core/types/message/__init__.py b/core/types/message/__init__.py index 2a107346..703c9e0b 100644 --- a/core/types/message/__init__.py +++ b/core/types/message/__init__.py @@ -1,5 +1,5 @@ import asyncio -from typing import List, Union, Dict +from typing import List, Union, Dict, Coroutine from core.exceptions import FinishedException from .chain import MessageChain @@ -89,13 +89,15 @@ class MessageSession: message_chain, quote=True, disable_secret_check=False, - allow_split_image=True) -> FinishedSession: + allow_split_image=True, + callback: Coroutine = None) -> FinishedSession: """ 用于向消息发送者回复消息。 :param message_chain: 消息链,若传入str则自动创建一条带有Plain元素的消息链 :param quote: 是否引用传入dict中的消息(默认为True) :param disable_secret_check: 是否禁用消息检查(默认为False) :param allow_split_image: 是否允许拆分图片发送(此参数作接口兼容用,仅telegram平台使用了切割) + :param callback: 回调函数,用于在消息发送完成后回复本消息执行的函数 :return: 被发送的消息链 """ raise NotImplementedError @@ -104,32 +106,36 @@ class MessageSession: message_chain=None, quote=True, disable_secret_check=False, - allow_split_image=True): + allow_split_image=True, + callback: Coroutine = None): """ 用于向消息发送者回复消息并终结会话(模块后续代码不再执行)。 :param message_chain: 消息链,若传入str则自动创建一条带有Plain元素的消息链 :param quote: 是否引用传入dict中的消息(默认为True) :param disable_secret_check: 是否禁用消息检查(默认为False) :param allow_split_image: 是否允许拆分图片发送(此参数作接口兼容用,仅telegram平台使用了切割) + :param callback: 回调函数,用于在消息发送完成后回复本消息执行的函数 :return: 被发送的消息链 """ ... f = None if message_chain is not None: f = await self.send_message(message_chain, disable_secret_check=disable_secret_check, quote=quote, - allow_split_image=allow_split_image) + allow_split_image=allow_split_image, callback=callback) raise FinishedException(f) - async def send_direct_message(self, message_chain, disable_secret_check=False, allow_split_image=True): + async def send_direct_message(self, message_chain, disable_secret_check=False, allow_split_image=True, + callback: Coroutine = None): """ 用于向消息发送者直接发送消息。 :param message_chain: 消息链,若传入str则自动创建一条带有Plain元素的消息链 :param disable_secret_check: 是否禁用消息检查(默认为False) :param allow_split_image: 是否允许拆分图片发送(此参数作接口兼容用,仅Telegram平台使用了切割) + :param callback: 回调函数,用于在消息发送完成后回复本消息执行的函数 :return: 被发送的消息链 """ await self.send_message(message_chain, disable_secret_check=disable_secret_check, quote=False, - allow_split_image=allow_split_image) + allow_split_image=allow_split_image, callback=callback) async def wait_confirm(self, message_chain=None, quote=True, delete=True, timeout=120, append_instruction=True): """ @@ -142,7 +148,8 @@ class MessageSession: """ raise NotImplementedError - async def wait_next_message(self, message_chain=None, quote=True, delete=False, timeout=120, append_instruction=True): + async def wait_next_message(self, message_chain=None, quote=True, delete=False, timeout=120, + append_instruction=True): """ 一次性模板,用于等待对象的下一条消息。 :param message_chain: 需要发送的确认消息,可不填 @@ -153,7 +160,8 @@ class MessageSession: """ raise NotImplementedError - async def wait_reply(self, message_chain, quote=True, delete=False, timeout=120, all_=False, append_instruction=True): + async def wait_reply(self, message_chain, quote=True, delete=False, timeout=120, all_=False, + append_instruction=True): """ 一次性模板,用于等待触发对象回复消息。 :param message_chain: 需要发送的确认消息,可不填 diff --git a/locales/en_us.json b/locales/en_us.json index 4c1de8d4..6f748942 100644 --- a/locales/en_us.json +++ b/locales/en_us.json @@ -35,6 +35,7 @@ "game.message.stop.none": "This game is not in progress.", "i18n.prompt.fallback.failed": "(If you're seeing this strange string, it means something we've messed up!\nPlease send the issue back to\n${url}\nso that we can fix it in time.)", "language": "English", + "message.brackets": " (${msg})", "message.collapse": "...show only the first ${amount} items.", "message.cooldown": "${time} second(s) have passed since the last execution. The cooldown time of this command are ${cd_time} second(s).", "message.delimiter": ", ", @@ -60,6 +61,7 @@ "petal.message.lost.success": "You lost ${amount} petal(s).", "success": "Success.", "time.date.format": "%B %d, %Y", + "time.date.iso.format": "%Y-%m-%d", "time.time.format": "%H:%M:%S", "time.time.nosec.format": "%H:%M", "tos.reason": "Reason: ", diff --git a/locales/zh_cn.json b/locales/zh_cn.json index 60e879ee..42525ea2 100644 --- a/locales/zh_cn.json +++ b/locales/zh_cn.json @@ -35,13 +35,14 @@ "game.message.stop.none": "当前此游戏未在进行。", "i18n.prompt.fallback.failed": "(如果你看到了这条奇怪的字符串,说明我们又搞错了什么东西!\n请将问题反馈至\n${url}\n以便我们快速解决此问题。)", "language": "简体中文", + "message.brackets": "(${msg})", + "message.collapse": "……仅显示前 ${amount} 条内容。", "message.cooldown": "距离上次执行已过去 ${time} 秒,本命令的冷却时间为 ${cd_time} 秒。", "message.delimiter": "、", "message.end": "。", "message.reply.prompt": "(请使用指定的词语回复本条消息)", "message.wait.confirm.prompt.type1": "(发送“是”或符合确认条件的词语来确认)", "message.wait.confirm.prompt.type2": "(发送符合条件的词语来确认)", - "message.collapse": "……仅显示前 ${amount} 条内容。", "no": "否", "none": "无", "parser.admin.module.permission.denied": "“${module}”模块下的命令仅能被该群组的管理员所使用,请联系管理员执行此命令。", @@ -60,6 +61,7 @@ "petal.message.lost.success": "失去了 ${amount} 片花瓣。", "success": "成功。", "time.date.format": "%Y 年 %m 月 %d 日", + "time.date.iso.format": "%Y-%m-%d", "time.time.format": "%H:%M:%S", "time.time.nosec.format": "%H:%M", "tos.reason": "具体原因:", @@ -74,4 +76,4 @@ "tos.warning.last": "这是最后一次警告。", "unknown": "未知", "yes": "是" -} +} \ No newline at end of file diff --git a/locales/zh_tw.json b/locales/zh_tw.json index f8f23b80..e07e1fe0 100644 --- a/locales/zh_tw.json +++ b/locales/zh_tw.json @@ -35,6 +35,7 @@ "game.message.stop.none": "目前此遊戲沒有在進行。", "i18n.prompt.fallback.failed": "(如果你看到了這條奇怪的字串,說明我們又搞錯了什麼!\n請將問題回報至\n${url}\n以便我們快速解決此問題。)", "language": "繁體中文", + "message.brackets": "(${msg})", "message.collapse": "……僅顯示前 ${amount} 條內容。", "message.cooldown": "距上次執行已過去 ${time} 秒,此指令的冷卻時間為 ${cd_time} 秒。", "message.delimiter": "、", @@ -59,7 +60,8 @@ "petal.message.lost.limit": "(本日失去花瓣已達到每日上限。)", "petal.message.lost.success": "失去了 ${amount} 片花瓣。", "success": "成功。", - "time.date.format": "%Y年%m月%d日", + "time.date.format": "%Y 年 %m 月 %d 日", + "time.date.iso.format": "%Y-%m-%d", "time.time.format": "%H:%M:%S", "time.time.nosec.format": "%H:%M", "tos.reason": "原因:", diff --git a/modules/bilibili/bilibili.py b/modules/bilibili/bilibili.py index 92f72d88..c03d42e9 100644 --- a/modules/bilibili/bilibili.py +++ b/modules/bilibili/bilibili.py @@ -1,6 +1,6 @@ from datetime import datetime -from core.builtins import Bot, Image, Plain +from core.builtins import Bot, Image, Plain, Url from core.utils.http import get_url @@ -16,16 +16,13 @@ async def get_info(msg: Bot.MessageSession, url, get_detail): stat = view['stat'] pic = view['pic'] - video_url = f"https://www.bilibili.com/video/{view['bvid']}\n" + video_url = f"https://www.bilibili.com/video/{view['bvid']}" title = view['title'] tname = view['tname'] - - timestamp = view['ctime'] - timeobject = datetime.fromtimestamp(timestamp) - time = timeobject.strftime('%Y-%m-%d %H:%M:%S') + time = msg.ts2strftime(view['ctime'], iso=True, timezone=False) if len(view['pages']) > 1: - pages = f" ({len(view['pages'])}P)" + pages = msg.locale.t("message.brackets", msg=f"{len(view['pages'])}P") else: pages = '' @@ -41,15 +38,15 @@ async def get_info(msg: Bot.MessageSession, url, get_detail): fans = format_num(res['data']['Card']['card']['fans']) if not get_detail: - output = video_url + msg.locale.t("bilibili.message", title=title, tname=tname, owner=owner, time=time) + output = msg.locale.t("bilibili.message", title=title, tname=tname, owner=owner, time=time) else: - output = video_url + msg.locale.t("bilibili.message.detail", title=title, pages=pages, tname=tname, + output = msg.locale.t("bilibili.message.detail", title=title, pages=pages, tname=tname, owner=owner, fans=fans, view=stat_view, danmaku=stat_danmaku, reply=stat_reply, like=stat_like, coin=stat_coin, favorite=stat_favorite, share=stat_share, time=time) - await msg.finish([Image(pic), Plain(output)]) + await msg.finish([Image(pic), Url(video_url), Plain(output)]) def format_num(number): diff --git a/modules/bilibili/locales/zh_cn.json b/modules/bilibili/locales/zh_cn.json index f1c33a09..5e92eb17 100644 --- a/modules/bilibili/locales/zh_cn.json +++ b/modules/bilibili/locales/zh_cn.json @@ -6,8 +6,8 @@ "bilibili.help.regex.bv": "发送 BV 号获取视频信息。", "bilibili.help.regex.shorturl": "发送 b23.tv 短连接获取视频信息。", "bilibili.help.regex.url": "发送 URL 获取视频信息。", - "bilibili.message": "标题:${title}\n类型:${tname}\nUP 主:${owner}\n日期:${time}", - "bilibili.message.detail": "标题:${title}${pages} | 类型:${tname}\nUP 主:${owner} | 粉丝:${fans}\n观看:${view} | 弹幕:${danmaku} | 评论:${reply}\n喜欢:${like} | 投币:${coin} | 收藏:${favorite} | 分享:${share}\n日期:${time}", + "bilibili.message": "标题:${title}\n类型:${tname}\nUP主:${owner}\n日期:${time}", + "bilibili.message.detail": "标题:${title}${pages} | 类型:${tname}\nUP主:${owner} | 粉丝:${fans}\n观看:${view} | 弹幕:${danmaku} | 评论:${reply}\n喜欢:${like} | 投币:${coin} | 收藏:${favorite} | 分享:${share}\n日期:${time}", "bilibili.message.error.invalid": "发生错误:视频编号无效,请检查输入。", "bilibili.message.not_found": "未找到对应的视频。" } diff --git a/modules/bilibili/locales/zh_tw.json b/modules/bilibili/locales/zh_tw.json index a7d562aa..1ffab802 100644 --- a/modules/bilibili/locales/zh_tw.json +++ b/modules/bilibili/locales/zh_tw.json @@ -6,7 +6,7 @@ "bilibili.help.regex.bv": "傳送 BV 號取得影片資訊。", "bilibili.help.regex.shorturl": "傳送 b23.tv 短網址取得影片資訊。", "bilibili.help.regex.url": "傳送 URL 取得影片資訊。", - "bilibili.message": "標題:${title}\n類型:${tname}\nUP 主:${owner}\n日期:${time}", + "bilibili.message": "標題:${title}\n類型:${tname}\nUP主:${owner}\n日期:${time}", "bilibili.message.detail": "標題:${title}${pages} | 類型:${tname}\nUP 主:${owner} | 追隨者:${fans}\n觀看:${view} | 彈幕:${danmaku} | 留言:${reply}\n喜歡:${like} | 投幣:${coin} | 收藏:${favorite} | 分享:${share}\n日期:${time}", "bilibili.message.error.invalid": "發生錯誤:影片編號無效,請校對輸入。", "bilibili.message.not_found": "未找到對應的影片。" diff --git a/modules/calc/locales/en_us.json b/modules/calc/locales/en_us.json index cc69ad11..d66eb01d 100644 --- a/modules/calc/locales/en_us.json +++ b/modules/calc/locales/en_us.json @@ -26,6 +26,6 @@ "calc.help.option.str": "转换为字符串:str(1) -> \"1\"", "calc.help.option.xor": "Bitwise xor: 1 ^ 1 -> 0", "calc.message.invalid": "Invalid expression: ${expr}", - "calc.message.running_time": "Calculating time: ${time} ms / 10000 ms. ", + "calc.message.running_time": "Calculating time: ${time}ms / 10000ms. ", "calc.message.time_out": "Calculation timeout." } \ No newline at end of file diff --git a/modules/calc/locales/zh_cn.json b/modules/calc/locales/zh_cn.json index 577849a8..523707bf 100644 --- a/modules/calc/locales/zh_cn.json +++ b/modules/calc/locales/zh_cn.json @@ -26,6 +26,6 @@ "calc.help.option.str": "转换为字符串:str(1) -> \"1\"", "calc.help.option.xor": "按位异或:1 ^ 1 -> 0", "calc.message.invalid": "表达式无效:${expr}", - "calc.message.running_time": "计算花费时间:${time} ms / 10000 ms。", + "calc.message.running_time": "计算花费时间:${time}ms / 10000ms。", "calc.message.time_out": "计算超时。" } \ No newline at end of file diff --git a/modules/calc/locales/zh_tw.json b/modules/calc/locales/zh_tw.json index 9eeec64f..11571818 100644 --- a/modules/calc/locales/zh_tw.json +++ b/modules/calc/locales/zh_tw.json @@ -26,6 +26,6 @@ "calc.help.option.str": "轉換為字串:str(1) -> \"1\"", "calc.help.option.xor": "按位元互斥或:1 ^ 1 -> 0", "calc.message.invalid": "運算式無效:${expr}", - "calc.message.running_time": "計算耗費時間:${time} ms / 10000 ms。", + "calc.message.running_time": "計算耗費時間:${time}ms / 10000ms。", "calc.message.time_out": "計算逾時。" } \ No newline at end of file diff --git a/modules/cytoid/__init__.py b/modules/cytoid/__init__.py index fce4a541..94b03bbf 100644 --- a/modules/cytoid/__init__.py +++ b/modules/cytoid/__init__.py @@ -24,7 +24,7 @@ async def _(msg: Bot.MessageSession): elif 'r30' in msg.parsed_msg: query = 'r30' else: - raise + return pat = msg.parsed_msg.get('', False) if pat: query_id = pat @@ -39,7 +39,7 @@ async def _(msg: Bot.MessageSession): qc = CoolDown('cytoid_rank', msg) c = qc.check(150) if c == 0: - img = await get_rating(query_id, query, msg) + img = await get_rating(msg, query_id, query) if 'path' in img: await msg.send_message([Image(path=img['path'])], allow_split_image=False) if 'text' in img: diff --git a/modules/cytoid/locales/en_us.json b/modules/cytoid/locales/en_us.json index 94c1b910..461bdc85 100644 --- a/modules/cytoid/locales/en_us.json +++ b/modules/cytoid/locales/en_us.json @@ -6,6 +6,7 @@ "cytoid.help.r30": "查询 Cytoid 用户的 R30 列表。", "cytoid.help.unbind": "解绑用户。", "cytoid.message.b30.cooldown": "(据官方人员所述,此 API 的调用十分昂贵,故手动做出此限制,请谅解。)", + "cytoid.message.b30.level": "等级", "cytoid.message.bind.failed": "绑定失败,请检查输入。", "cytoid.message.bind.success": "绑定成功:", "cytoid.message.unbind.success": "解绑成功。", diff --git a/modules/cytoid/rating.py b/modules/cytoid/rating.py index 7ce91cd2..8c9c8d54 100644 --- a/modules/cytoid/rating.py +++ b/modules/cytoid/rating.py @@ -20,7 +20,7 @@ from core.utils.http import get_url from core.utils.html2text import html2text -async def get_rating(uid, query_type, msg: Bot.MessageSession): +async def get_rating(msg: Bot.MessageSession, uid, query_type): try: if query_type == 'b30': query_type = 'bestRecords' @@ -88,7 +88,7 @@ async def get_rating(uid, query_type, msg: Bot.MessageSession): resources = [] songcards = [] - async def mkresources(x, rank): + async def mkresources(msg: Bot.MessageSession, x, rank): thumbpath = await download_cover_thumb(x['chart']['level']['uid']) chart_type = x['chart']['type'] difficulty = x['chart']['difficulty'] @@ -98,7 +98,7 @@ async def get_rating(uid, query_type, msg: Bot.MessageSession): rt = x['rating'] details = x['details'] _date = datetime.strptime(x['date'], "%Y-%m-%dT%H:%M:%S.%fZ") - local_time = _date + timedelta(hours=int(Config("timezone_offset", 8))) + local_time = _date + timedelta(hours=int(msg.options.get('timezone_offset', Config('timezone_offset', '+8')))) playtime = local_time.timestamp() nowtime = time.time() playtime = playtime - nowtime @@ -125,7 +125,7 @@ async def get_rating(uid, query_type, msg: Bot.MessageSession): for x in best_records: rank += 1 - resources.append(mkresources(x, rank)) + resources.append(mkresources(msg, x, rank)) await asyncio.gather(*resources) cards_ = await asyncio.gather(*songcards) diff --git a/modules/langconv/locales/en_us.json b/modules/langconv/locales/en_us.json index 79fccb26..57bad537 100644 --- a/modules/langconv/locales/en_us.json +++ b/modules/langconv/locales/en_us.json @@ -1,5 +1,5 @@ { "langconv.help": "简繁转换。", "langconv.message.unsupported_language": "不支持此语言。支持的语言:zh_cn、zh_tw、zh_hk", - "langconv.message.running_time": "花费时间:${time} ms。" + "langconv.message.running_time": "花费时间:${time}ms。" } diff --git a/modules/langconv/locales/zh_cn.json b/modules/langconv/locales/zh_cn.json index 79fccb26..57bad537 100644 --- a/modules/langconv/locales/zh_cn.json +++ b/modules/langconv/locales/zh_cn.json @@ -1,5 +1,5 @@ { "langconv.help": "简繁转换。", "langconv.message.unsupported_language": "不支持此语言。支持的语言:zh_cn、zh_tw、zh_hk", - "langconv.message.running_time": "花费时间:${time} ms。" + "langconv.message.running_time": "花费时间:${time}ms。" } diff --git a/modules/langconv/locales/zh_tw.json b/modules/langconv/locales/zh_tw.json index d25aaa96..0dba8448 100644 --- a/modules/langconv/locales/zh_tw.json +++ b/modules/langconv/locales/zh_tw.json @@ -1,5 +1,5 @@ { "langconv.help": "繁簡轉換。", "langconv.message.unsupported_language": "不支援此語言。已支援的語言:zh_cn、zh_tw、zh_hk", - "langconv.message.running_time": "花費時間:${time} ms。" + "langconv.message.running_time": "花費時間:${time}ms。" } diff --git a/modules/mcv/locales/en_us.json b/modules/mcv/locales/en_us.json index a2a89361..f4a38fd7 100644 --- a/modules/mcv/locales/en_us.json +++ b/modules/mcv/locales/en_us.json @@ -14,6 +14,6 @@ "mcv.message.mclgv": "The latest version: ${version}\n(The data from Mojira may be earlier than the official release. Information is for reference purposes only).", "mcv.message.mcv": "The latest version in the launcher is: \n${launcher_ver}\nThe latest version recorded on Mojira is: \n${jira_ver}\n(The latest version in the launcher shall prevail, Mojira is only for previewing the version)", "mcv.message.mcv.jira.failed": "Failed to get Mojira information.", - "mcv.message.mcv.launcher": "The latest release: ${release}, the latest snapshot: ${snapshot},", + "mcv.message.mcv.launcher": "The latest release: ${release}\nRelease time: ${release_time}\nThe latest snapshot: ${snapshot}\nRelease time: ${snapshot_time}", "mcv.message.mcv.launcher.failed": "Failed to get manifest.json." -} \ No newline at end of file +} diff --git a/modules/mcv/locales/zh_tw.json b/modules/mcv/locales/zh_tw.json index 28e2ecb2..1bc6f7f8 100644 --- a/modules/mcv/locales/zh_tw.json +++ b/modules/mcv/locales/zh_tw.json @@ -14,6 +14,6 @@ "mcv.message.mclgv": "最新版:${version}\n(資訊來源於 Mojira,可能會比官方發布要早一段時間。資訊僅供參考。)", "mcv.message.mcv": "目前啟動器內最新版本為:\n${launcher_ver}\nMojira 上所記錄最新版本為:\n${jira_ver}\n(以啟動器內最新版本為準,Mojira 僅作版本號預覽用)", "mcv.message.mcv.jira.failed": "取得 Mojira 資訊失敗。", - "mcv.message.mcv.launcher": "最新版:${release},最新快照:${snapshot},", + "mcv.message.mcv.launcher": "最新版:${release}\n發佈於:${release_time}\n最新快照:${snapshot}\n發佈於:${snapshot_time}\n", "mcv.message.mcv.launcher.failed": "取得 manifest.json 失敗。" -} \ No newline at end of file +} diff --git a/modules/mcv_rss/locales/en_us.json b/modules/mcv_rss/locales/en_us.json index 52d1e3b1..d4bb6045 100644 --- a/modules/mcv_rss/locales/en_us.json +++ b/modules/mcv_rss/locales/en_us.json @@ -13,4 +13,4 @@ "mcv_rss.message.mcv_jira_rss.future": "Java Edition ${version} is added to Mojira.\n(“Future Version” only indicates that a new version is planned; you will have to wait for Mojang to release it in the Launcher.)", "mcv_rss.message.mcv_rss.release": "${version} is now released in Minecraft Launcher.", "mcv_rss.message.mcv_rss.snapshot": "Snapshot ${version} is now released in Minecraft Launcher." -} \ No newline at end of file +} diff --git a/modules/mcv_rss/locales/zh_cn.json b/modules/mcv_rss/locales/zh_cn.json index d720c3dc..211ec484 100644 --- a/modules/mcv_rss/locales/zh_cn.json +++ b/modules/mcv_rss/locales/zh_cn.json @@ -11,6 +11,6 @@ "mcv_rss.message.mclgv_jira_rss": "Mojira 已更新 Minecraft Legends ${version}。\n(Mojira 上的信息仅作版本号预览用,不代表启动器/商店已更新此版本)", "mcv_rss.message.mcv_jira_rss": "Mojira 已更新 Java 版 ${version}。\n(Mojira 上的信息仅作版本号预览用,不代表启动器已更新此版本)", "mcv_rss.message.mcv_jira_rss.future": "Mojira 已新增 Java 版 ${version}。\n(Future Version 仅代表与此相关的版本正在规划中,不代表启动器已更新此版本)", - "mcv_rss.message.mcv_rss.release": "启动器已更新 ${version} 正式版。", - "mcv_rss.message.mcv_rss.snapshot": "启动器已更新 ${version} 快照。" -} \ No newline at end of file + "mcv_rss.message.mcv_rss.release": "启动器已更新 ${version} 正式版。\n更新时间:", + "mcv_rss.message.mcv_rss.snapshot": "启动器已更新 ${version} 快照。\n更新时间:" +} diff --git a/modules/mcv_rss/locales/zh_tw.json b/modules/mcv_rss/locales/zh_tw.json index 972bf852..b5a8982f 100644 --- a/modules/mcv_rss/locales/zh_tw.json +++ b/modules/mcv_rss/locales/zh_tw.json @@ -2,7 +2,7 @@ "mcv_rss.help.mcbv_jira_rss.desc": "啟用後 Mojira 更新基岩版時會自動推播訊息。", "mcv_rss.help.mcbv_rss.desc": "啟用後商店更新 Minecraft 基岩版時會自動推播訊息。", "mcv_rss.help.mcdv_jira_rss.desc": "啟用後 Mojira 更新 Minecraft Dungeons 時會自動推播訊息。", - "mcv_rss.help.mclgv_jira_rss.desc": "开启后 Mojira 更新 Minecraft Legends 时会自动推送消息。", + "mcv_rss.help.mclgv_jira_rss.desc": "啟用後 Mojira 更新 Minecraft Legends 時會自動推播訊息。", "mcv_rss.help.mcv_jira_rss.desc": "啟用後 Mojira 更新 Java 版時會自動推播訊息。", "mcv_rss.help.mcv_rss.desc": "啟用後 Minecraft 啟動器更新 Minecraft:Java 版時會自動推播訊息。", "mcv_rss.message.mcbv_jira_rss": "Mojira 已更新基岩版 ${version}。 \n(Mojira 上的資訊僅作版本號預覽用,不代表啟動器已更新此版本)", @@ -11,6 +11,6 @@ "mcv_rss.message.mclgv_jira_rss": "Mojira 已更新 Minecraft Legends ${version}。 \n(Mojira 上的資訊僅作版本號預覽用,不代表啟動器/商店已更新此版本)", "mcv_rss.message.mcv_jira_rss": "Mojira 已更新 Java 版 ${version}。 \n(Mojira 上的資訊僅作版本號預覽用,不代表啟動器已更新此版本)", "mcv_rss.message.mcv_jira_rss.future": "Mojira 已新增 Java 版 ${version}。 \n(Future Version 僅代表與此相關的版本正在規劃中,不代表啟動器已更新此版本)", - "mcv_rss.message.mcv_rss.release": "啟動器已更新 ${version} 正式版。", - "mcv_rss.message.mcv_rss.snapshot": "啟動器已更新 ${version} 快照。" -} \ No newline at end of file + "mcv_rss.message.mcv_rss.release": "啟動器已更新 ${version} 正式版。\n更新時間:", + "mcv_rss.message.mcv_rss.snapshot": "啟動器已更新 ${version} 快照。\n更新時間:" +} diff --git a/modules/ncmusic/locales/en_us.json b/modules/ncmusic/locales/en_us.json index 8f70882a..316a5cc6 100644 --- a/modules/ncmusic/locales/en_us.json +++ b/modules/ncmusic/locales/en_us.json @@ -1,15 +1,18 @@ { + "ncmusic.help.desc": "网易云音乐相关工具。", + "ncmusic.help.info": "获取音乐详细信息。", "ncmusic.help.search": "搜索网易云音乐。", "ncmusic.help.search.legacy": "搜索网易云音乐。(旧版)", - "ncmusic.help.info": "获取音乐详细信息。", - "ncmusic.message.info": "歌名:${name}(${id})\n歌手:${artists}\n专辑名:${album}(${album_id})", + "ncmusic.message.info": "歌名:${name}(${id})\n专辑名:${album}(${album_id})\n歌手:${artists}", + "ncmusic.message.info.not_found": "找不到对应的音乐。", + "ncmusic.message.search.confirm": "是否查看音乐详细信息?", "ncmusic.message.search.invalid.non_digital": "无效的编号,必须为数字。", "ncmusic.message.search.invalid.out_of_range": "编号超出范围。", "ncmusic.message.search.not_found": "未找到结果。", "ncmusic.message.search.prompt": "输入对应的序号以查看歌曲信息。", + "ncmusic.message.search.result": "搜索结果:", "ncmusic.message.search.table.header.album": "专辑名", "ncmusic.message.search.table.header.artists": "歌手", "ncmusic.message.search.table.header.id": "序号", - "ncmusic.message.search.table.header.name": "歌名", - "ncmusic.message.search.result": "搜索结果:" -} + "ncmusic.message.search.table.header.name": "歌名" +} \ No newline at end of file diff --git a/modules/ncmusic/locales/zh_tw.json b/modules/ncmusic/locales/zh_tw.json index f76f04c5..c6d9b821 100644 --- a/modules/ncmusic/locales/zh_tw.json +++ b/modules/ncmusic/locales/zh_tw.json @@ -1,8 +1,10 @@ { + "ncmusic.help.desc": "网易云音乐相关工具。", "ncmusic.help.info": "取得音樂詳細資訊。", "ncmusic.help.search": "搜尋網易雲音樂。", "ncmusic.help.search.legacy": "搜尋網易雲音樂。(舊版)", "ncmusic.message.info": "歌名:${name}(${id})\n歌手:${artists}\n專輯名:${album}(${album_id})", + "ncmusic.message.info.not_found": "找不到对应的音乐。", "ncmusic.message.search.confirm": "是否檢視音樂詳細資訊?", "ncmusic.message.search.invalid.non_digital": "無效的編號,必須為數字。", "ncmusic.message.search.invalid.out_of_range": "編號超出範圍。", diff --git a/modules/wiki/audit.py b/modules/wiki/audit.py index 49a73946..f8e5b1ee 100644 --- a/modules/wiki/audit.py +++ b/modules/wiki/audit.py @@ -8,109 +8,112 @@ from modules.wiki.utils.dbutils import Audit from modules.wiki.utils.wikilib import WikiLib -if Config('enable_urlmanager'): - aud = module('wiki_audit', required_superuser=True, - alias='wau') +aud = module('wiki_audit', required_superuser=True, + alias='wau') - @aud.command(['trust ', 'block ']) - async def _(msg: Bot.MessageSession): - date = msg.ts2strftime(datetime.now().timestamp(), timezone=False) - api = msg.parsed_msg[''] - check = await WikiLib(api).check_wiki_available() - if check.available: - api = check.value.api - if msg.parsed_msg.get('trust', False): - res = Audit(api).add_to_AllowList(date) - list_name = msg.locale.t('wiki.message.wiki_audit.list_name.allowlist') - else: - res = Audit(api).add_to_BlockList(date) - list_name = msg.locale.t('wiki.message.wiki_audit.list_name.blocklist') - if not res: - await msg.finish(msg.locale.t('wiki.message.wiki_audit.add.failed', list_name=list_name) + api) - else: - await msg.finish(msg.locale.t('wiki.message.wiki_audit.add.success', list_name=list_name) + api) - else: - result = msg.locale.t('wiki.message.error.add') + \ - ('\n' + msg.locale.t('wiki.message.error.info') + check.message if check.message != '' else '') - await msg.finish(result) - @aud.command(['distrust ', 'unblock ']) - async def _(msg: Bot.MessageSession): - api = msg.parsed_msg[''] # 已关闭的站点无法验证有效性 - if msg.parsed_msg.get('distrust', False): - res = Audit(api).remove_from_AllowList() - if res is None: - await msg.finish(msg.locale.t('wiki.message.wiki_audit.remove.failed.other') + api) +@aud.command(['trust ', 'block ']) +async def _(msg: Bot.MessageSession): + date = msg.ts2strftime(datetime.now().timestamp(), timezone=False) + api = msg.parsed_msg[''] + check = await WikiLib(api).check_wiki_available() + if check.available: + api = check.value.api + if msg.parsed_msg.get('trust', False): + res = Audit(api).add_to_AllowList(date) list_name = msg.locale.t('wiki.message.wiki_audit.list_name.allowlist') else: - res = Audit(api).remove_from_BlockList() + res = Audit(api).add_to_BlockList(date) list_name = msg.locale.t('wiki.message.wiki_audit.list_name.blocklist') if not res: - await msg.finish(msg.locale.t('wiki.message.wiki_audit.remove.failed', list_name=list_name) + api) + await msg.finish(msg.locale.t('wiki.message.wiki_audit.add.failed', list_name=list_name) + api) else: - await msg.finish(msg.locale.t('wiki.message.wiki_audit.remove.success', list_name=list_name) + api) + await msg.finish(msg.locale.t('wiki.message.wiki_audit.add.success', list_name=list_name) + api) + else: + result = msg.locale.t('wiki.message.error.add') + \ + ('\n' + msg.locale.t('wiki.message.error.info') + check.message if check.message != '' else '') + await msg.finish(result) - @aud.command('query ') - async def _(msg: Bot.MessageSession): - api = msg.parsed_msg[''] - check = await WikiLib(api).check_wiki_available() - if check.available: - api = check.value.api - audit = Audit(api) - allow = audit.inAllowList - block = audit.inBlockList - msg_list = [] - if allow: - msg_list.append(api + msg.locale.t('wiki.message.wiki_audit.query.allowlist')) - if block: - msg_list.append(api + msg.locale.t('wiki.message.wiki_audit.query.blocklist')) - if msg_list: - msg_list.append(msg.locale.t('wiki.message.wiki_audit.query.conflict')) - await msg.finish('\n'.join(msg_list)) - else: - await msg.finish(api + msg.locale.t('wiki.message.wiki_audit.query.none')) + +@aud.command(['distrust ', 'unblock ']) +async def _(msg: Bot.MessageSession): + api = msg.parsed_msg[''] # 已关闭的站点无法验证有效性 + if msg.parsed_msg.get('distrust', False): + res = Audit(api).remove_from_AllowList() + if res is None: + await msg.finish(msg.locale.t('wiki.message.wiki_audit.remove.failed.other') + api) + list_name = msg.locale.t('wiki.message.wiki_audit.list_name.allowlist') + else: + res = Audit(api).remove_from_BlockList() + list_name = msg.locale.t('wiki.message.wiki_audit.list_name.blocklist') + if not res: + await msg.finish(msg.locale.t('wiki.message.wiki_audit.remove.failed', list_name=list_name) + api) + else: + await msg.finish(msg.locale.t('wiki.message.wiki_audit.remove.success', list_name=list_name) + api) + + +@aud.command('query ') +async def _(msg: Bot.MessageSession): + api = msg.parsed_msg[''] + check = await WikiLib(api).check_wiki_available() + if check.available: + api = check.value.api + audit = Audit(api) + allow = audit.inAllowList + block = audit.inBlockList + msg_list = [] + if allow: + msg_list.append(api + msg.locale.t('wiki.message.wiki_audit.query.allowlist')) + if block: + msg_list.append(api + msg.locale.t('wiki.message.wiki_audit.query.blocklist')) + if msg_list: + msg_list.append(msg.locale.t('wiki.message.wiki_audit.query.conflict')) + await msg.finish('\n'.join(msg_list)) else: - result = msg.locale.t('wiki.message.error.query') + \ - ('\n' + msg.locale.t('wiki.message.error.info') + check.message if check.message != '' else '') - await msg.finish(result) + await msg.finish(api + msg.locale.t('wiki.message.wiki_audit.query.none')) + else: + result = msg.locale.t('wiki.message.error.query') + \ + ('\n' + msg.locale.t('wiki.message.error.info') + check.message if check.message != '' else '') + await msg.finish(result) - @aud.command(['list', 'list legacy']) - async def _(msg: Bot.MessageSession): - allow_list = Audit.get_allow_list() - block_list = Audit.get_block_list() - legacy = True - if not msg.parsed_msg.get('legacy', False) and msg.Feature.image: - send_msgs = [] - allow_columns = [[x[0], x[1]] for x in allow_list] - if allow_columns: - allow_table = ImageTable(data=allow_columns, headers=[ - msg.locale.t('wiki.message.wiki_audit.list.table.header.apilink'), - msg.locale.t('wiki.message.wiki_audit.list.table.header.date') - ]) - if allow_table: - allow_image = await image_table_render(allow_table) - if allow_image: - send_msgs.append(Plain(msg.locale.t('wiki.message.wiki_audit.list.allowlist'))) - send_msgs.append(Image(allow_image)) - block_columns = [[x[0], x[1]] for x in block_list] - if block_columns: - block_table = ImageTable(data=block_columns, headers=[ - msg.locale.t('wiki.message.wiki_audit.list.table.header.apilink'), - msg.locale.t('wiki.message.wiki_audit.list.table.header.date') - ]) - if block_table: - block_image = await image_table_render(block_table) - if block_image: - send_msgs.append(Plain(msg.locale.t('wiki.message.wiki_audit.list.blocklist'))) - send_msgs.append(Image(block_image)) - if send_msgs: - await msg.finish(send_msgs) - legacy = False - if legacy: - wikis = [msg.locale.t('wiki.message.wiki_audit.list.allowlist')] - for al in allow_list: - wikis.append(f'{al[0]} ({al[1]})') - wikis.append(msg.locale.t('wiki.message.wiki_audit.list.blocklist')) - for bl in block_list: - wikis.append(f'{bl[0]} ({bl[1]})') - await msg.finish('\n'.join(wikis)) + +@aud.command(['list', 'list legacy']) +async def _(msg: Bot.MessageSession): + allow_list = Audit.get_allow_list() + block_list = Audit.get_block_list() + legacy = True + if not msg.parsed_msg.get('legacy', False) and msg.Feature.image: + send_msgs = [] + allow_columns = [[x[0], x[1]] for x in allow_list] + if allow_columns: + allow_table = ImageTable(data=allow_columns, headers=[ + msg.locale.t('wiki.message.wiki_audit.list.table.header.apilink'), + msg.locale.t('wiki.message.wiki_audit.list.table.header.date') + ]) + if allow_table: + allow_image = await image_table_render(allow_table) + if allow_image: + send_msgs.append(Plain(msg.locale.t('wiki.message.wiki_audit.list.allowlist'))) + send_msgs.append(Image(allow_image)) + block_columns = [[x[0], x[1]] for x in block_list] + if block_columns: + block_table = ImageTable(data=block_columns, headers=[ + msg.locale.t('wiki.message.wiki_audit.list.table.header.apilink'), + msg.locale.t('wiki.message.wiki_audit.list.table.header.date') + ]) + if block_table: + block_image = await image_table_render(block_table) + if block_image: + send_msgs.append(Plain(msg.locale.t('wiki.message.wiki_audit.list.blocklist'))) + send_msgs.append(Image(block_image)) + if send_msgs: + await msg.finish(send_msgs) + legacy = False + if legacy: + wikis = [msg.locale.t('wiki.message.wiki_audit.list.allowlist')] + for al in allow_list: + wikis.append(f'{al[0]} ({al[1]})') + wikis.append(msg.locale.t('wiki.message.wiki_audit.list.blocklist')) + for bl in block_list: + wikis.append(f'{bl[0]} ({bl[1]})') + await msg.finish('\n'.join(wikis)) diff --git a/modules/wiki/inline.py b/modules/wiki/inline.py index cf126de6..d726d9a4 100644 --- a/modules/wiki/inline.py +++ b/modules/wiki/inline.py @@ -9,6 +9,7 @@ from core.component import module from core.dirty_check import check from core.logger import Logger from core.utils.http import download_to_cache +from core.utils.image_table import image_table_render, ImageTable from modules.wiki.utils.dbutils import WikiTargetInfo from modules.wiki.utils.screenshot_image import generate_screenshot_v1, generate_screenshot_v2 from modules.wiki.utils.wikilib import WikiLib @@ -20,7 +21,7 @@ wiki_inline = module('wiki_inline', @wiki_inline.regex(re.compile(r'\[\[(.*?)]]', flags=re.I), mode='A', - desc="{wiki.help.wiki_inline.page}") + desc="{wiki.help.wiki_inline.page}") async def _(msg: Bot.MessageSession): query_list = [] for x in msg.matched_msg: @@ -31,7 +32,7 @@ async def _(msg: Bot.MessageSession): @wiki_inline.regex(re.compile(r'\{\{(.*?)}}', flags=re.I), mode='A', - desc='{wiki.help.wiki_inline.template}') + desc='{wiki.help.wiki_inline.template}') async def _(msg: Bot.MessageSession): query_list = [] for x in msg.matched_msg: @@ -42,7 +43,7 @@ async def _(msg: Bot.MessageSession): @wiki_inline.regex(re.compile(r'≺(.*?)≻|⧼(.*?)⧽', flags=re.I), mode='A', show_typing=False, - desc='{wiki.help.wiki_inline.mediawiki}') + desc='{wiki.help.wiki_inline.mediawiki}') async def _(msg: Bot.MessageSession): query_list = [] for x in msg.matched_msg: @@ -130,6 +131,27 @@ async def _(msg: Bot.MessageSession): get_infobox = await generate_screenshot_v1(q[qq].realurl, qq, headers) if get_infobox: await msg.send_message(Image(get_infobox), quote=False) + if get_page.invalid_section and wiki_.wiki_info.in_allowlist: + i_msg_lst = [] + session_data = [[str(i + 1), get_page.sections[i]] for i in + range(len(get_page.sections))] + i_msg_lst.append(Plain(msg.locale.t('wiki.message.invalid_section'))) + i_msg_lst.append(Image(await + image_table_render( + ImageTable(session_data, + ['ID', + msg.locale.t('wiki.message.section')])))) + + async def _callback(msg: Bot.MessageSession): + display = msg.as_display(text_only=True) + if display.isdigit(): + display = int(display) + if display <= len(get_page.sections): + get_page.selected_section = display - 1 + await query_pages(msg, title=get_page.title + '#' + + get_page.sections[display - 1]) + + await msg.send_message(i_msg_lst, callback=_callback) if len(query_list) == 1 and img_send: return if msg.Feature.image: diff --git a/modules/wiki/locales/en_us.json b/modules/wiki/locales/en_us.json index 3d669a78..70386d56 100644 --- a/modules/wiki/locales/en_us.json +++ b/modules/wiki/locales/en_us.json @@ -29,6 +29,8 @@ "wiki.help.wiki_inline.page": "Enter a title within [[ ]] to automatically query the page.", "wiki.help.wiki_inline.template": "Enter a title within {{ }} to automatically query the template.", "wiki.help.wiki_inline.url": "Send the URL of the wiki page to generate the Infobox image.", + "wiki.message.section": "章节", + "wiki.message.section.rendering": "(章节渲染中)", "wiki.message.ab.qq.title": "滥用过滤器日志", "wiki.message.ab.qq.title.address": "滥用过滤器日志地址", "wiki.message.ab.slice": "•${title} - ${user} at ${time}\n Filter description: ${filter_name}\n Actions taken: ${result}", @@ -37,6 +39,7 @@ "wiki.message.error.info": "Detail: ", "wiki.message.error.query": "An error occurred: Unable to query this wiki.", "wiki.message.error.set": "An error occurred: Unable to set up this wiki.", + "wiki.message.error.unable_to_render_section": "(章节渲染失败,请联系开发者检查原因)", "wiki.message.fandom.disable": "Disabled to use Fandom global Interwiki queries.", "wiki.message.fandom.enable": "Enabled to use Fandom global Interwiki queries.", "wiki.message.flies": "This page includes the following files:", @@ -47,6 +50,8 @@ "wiki.message.id.error": "An error occurred: Page ID must be numeric.", "wiki.message.id.not_found": "Page with page ID ${id} not found.", "wiki.message.invalid_namespace": "There is no namespace named ${namespace} on this wiki. Please check your input.", + "wiki.message.invalid_section": "此章节不存在,以下是可能的章节(回复对应序号以选中):", + "wiki.message.invalid_section.prompt": "(此章节不存在)", "wiki.message.iw.add.success": "Custom Interwiki added:\n ${iw} -> ${name}", "wiki.message.iw.get.not_found": "Interwiki not found: ${iw}", "wiki.message.iw.list": "Use \"${prefix}wiki iw get \" to get the corresponding link to Interwiki.", diff --git a/modules/wiki/locales/zh_cn.json b/modules/wiki/locales/zh_cn.json index aeb3dc09..b94c3228 100644 --- a/modules/wiki/locales/zh_cn.json +++ b/modules/wiki/locales/zh_cn.json @@ -29,6 +29,8 @@ "wiki.help.wiki_inline.page": "在 [[ ]] 内输入标题以自动查询页面。", "wiki.help.wiki_inline.template": "在 {{ }} 内输入标题以自动查询模板。", "wiki.help.wiki_inline.url": "发送 Wiki 页面的 URL 以生成 Infobox 图片。", + "wiki.message.section": "章节", + "wiki.message.section.rendering": "(章节渲染中)", "wiki.message.ab.qq.title": "滥用过滤器日志", "wiki.message.ab.qq.title.address": "滥用过滤器日志地址", "wiki.message.ab.slice": "•${title} - ${user} 于 ${time}\n 过滤器描述:${filter_name}\n 采取的行动:${result}", @@ -37,6 +39,7 @@ "wiki.message.error.info": "详细信息:", "wiki.message.error.query": "发生错误:无法查询此 Wiki。", "wiki.message.error.set": "发生错误:无法设置此 Wiki。", + "wiki.message.error.unable_to_render_section": "(章节渲染失败,请联系开发者检查原因)", "wiki.message.fandom.disable": "已关闭 Fandom 全局 Interwiki 查询。", "wiki.message.fandom.enable": "已开启 Fandom 全局 Interwiki 查询。", "wiki.message.flies": "此页面包括以下文件:\n", @@ -47,6 +50,8 @@ "wiki.message.id.error": "发生错误:页面 ID 必须为数字。", "wiki.message.id.not_found": "未找到页面 ID 为 ${id} 的页面。", "wiki.message.invalid_namespace": "此 Wiki 上没有名为 ${namespace} 的命名空间,请检查输入。", + "wiki.message.invalid_section": "此章节不存在,以下是可能的章节(回复对应序号以选中):", + "wiki.message.invalid_section.prompt": "(此章节不存在)", "wiki.message.iw.add.success": "已添加自定义 Interwiki:\n${iw} -> ${name}", "wiki.message.iw.get.not_found": "未找到 Interwiki:${iw}", "wiki.message.iw.list": "使用“${prefix}wiki iw get ”可以获取 Interwiki 对应的链接。", diff --git a/modules/wiki/locales/zh_tw.json b/modules/wiki/locales/zh_tw.json index 91ec0751..d3e155ad 100644 --- a/modules/wiki/locales/zh_tw.json +++ b/modules/wiki/locales/zh_tw.json @@ -29,6 +29,8 @@ "wiki.help.wiki_inline.page": "在 [[ ]] 內輸入標題以自動查詢頁面。", "wiki.help.wiki_inline.template": "在 {{ }} 內輸入標題以自動查詢模板。", "wiki.help.wiki_inline.url": "傳送 Wiki 頁面的 URL 以生成 Infobox 圖片。", + "wiki.message.section": "章節", + "wiki.message.section.rendering": "(章節成像中)", "wiki.message.ab.qq.title": "防濫用過濾器日誌", "wiki.message.ab.qq.title.address": "防濫用過濾器位址", "wiki.message.ab.slice": "•${title} - ${user} 於 ${time}\n 過濾器描述:${filter_name}\n 採取的動作:${result}", @@ -37,6 +39,7 @@ "wiki.message.error.info": "詳細訊息:", "wiki.message.error.query": "發生錯誤:無法查詢此 Wiki。", "wiki.message.error.set": "發生錯誤:無法設定此 Wiki。", + "wiki.message.error.unable_to_render_section": "(章節成像失敗,請聯絡開發人員檢查原因)", "wiki.message.fandom.disable": "已停用 Fandom 全域 Interwiki 查詢。", "wiki.message.fandom.enable": "已啟用 Fandom 全域 Interwiki 查詢。", "wiki.message.flies": "此頁面含有以下檔案:", @@ -47,6 +50,8 @@ "wiki.message.id.error": "發生錯誤:頁面 ID 必須為數字。", "wiki.message.id.not_found": "未找到頁面 ID 為 ${id} 的頁面。", "wiki.message.invalid_namespace": "此 Wiki 上沒有名為 ${namespace} 的命名空間,请校對輸入。", + "wiki.message.invalid_section": "此章節不存在,以下是可能的章節(回覆對應序號以選中):", + "wiki.message.invalid_section.prompt": "(此章節不存在)", "wiki.message.iw.add.success": "已新增自訂 Interwiki:\n${iw} -> ${name}", "wiki.message.iw.get.not_found": "未找到 Interwiki:${iw}", "wiki.message.iw.list": "使用 「${prefix}wiki iw get 」可以取得 Interwiki 對應的連結。", diff --git a/modules/wiki/utils/wikilib.py b/modules/wiki/utils/wikilib.py index ca805be1..0172283f 100644 --- a/modules/wiki/utils/wikilib.py +++ b/modules/wiki/utils/wikilib.py @@ -114,7 +114,8 @@ class PageInfo: file: str = None, desc: str = None, args: str = None, - section: str = None, + selected_section: str = None, + sections: List[str] = None, interwiki_prefix: str = '', status: bool = True, templates: List[str] = None, @@ -122,7 +123,7 @@ class PageInfo: page_property: str = 'page', has_template_doc: bool = False, invalid_namespace: Union[str, bool] = False, - possible_research_title: List[str] = None + possible_research_title: List[str] = None, ): self.info = info self.id = id @@ -133,7 +134,8 @@ class PageInfo: self.file = file self.desc = desc self.args = args - self.section = section + self.selected_section = selected_section + self.sections = sections self.interwiki_prefix = interwiki_prefix self.templates = templates self.status = status @@ -142,6 +144,7 @@ class PageInfo: self.has_template_doc = has_template_doc self.invalid_namespace = invalid_namespace self.possible_research_title = possible_research_title + self.invalid_section = False class WikiLib: @@ -437,7 +440,7 @@ class WikiLib: if _tried > 5: if Config('enable_tos'): raise WhatAreUDoingError - section = None + selected_section = None if title is not None: if title == '': return PageInfo(title='', link=self.wiki_info.articlepath.replace("$1", ""), info=self.wiki_info, @@ -450,11 +453,13 @@ class WikiLib: arg_list = [] _arg_list = [] section_list = [] + used_quote = False quote_code = False for a in split_name[1:]: if len(a) > 0: if a[0] == '#': quote_code = True + used_quote = True if a[0] == '?': quote_code = False if quote_code: @@ -471,9 +476,11 @@ class WikiLib: else: title += _arg if len(section_list) > 1: - section = ''.join(section_list)[1:] + selected_section = ''.join(section_list)[1:] page_info = PageInfo(info=self.wiki_info, title=title, args=''.join(arg_list), interwiki_prefix=_prefix) - page_info.section = section + page_info.selected_section = selected_section + if not selected_section and used_quote: + page_info.invalid_section = True query_string = {'action': 'query', 'prop': 'info|imageinfo|langlinks|templates', 'llprop': 'url', 'inprop': 'url', 'iiprop': 'url', 'redirects': 'True', 'titles': title} @@ -484,7 +491,7 @@ class WikiLib: else: raise ValueError('title and pageid cannot be both None') use_textextracts = True if 'TextExtracts' in self.wiki_info.extensions else False - if use_textextracts and section is None: + if use_textextracts and selected_section is None: query_string.update({'prop': 'info|imageinfo|langlinks|templates|extracts|pageprops', 'ppprop': 'description|displaytitle|disambiguation|infoboxes', 'explaintext': 'true', 'exsectionformat': 'plain', 'exchars': '200'}) @@ -493,6 +500,7 @@ class WikiLib: if query is None: return PageInfo(title=title, link=None, desc=self.locale.t("wiki.message.utils.wikilib.error.empty"), info=self.wiki_info) + redirects_: List[Dict[str, str]] = query.get('redirects') if redirects_ is not None: for r in redirects_: @@ -598,6 +606,17 @@ class WikiLib: else: page_info.status = True templates = page_info.templates = [t['title'] for t in page_raw.get('templates', [])] + if selected_section or page_info.invalid_section: + parse_section_string = {'action': 'parse', 'page': title, 'prop': 'sections'} + parse_section = await self.get_json(**parse_section_string) + section_list = [] + sections = parse_section['parse']['sections'] + for s in sections: + section_list.append(s['anchor']) + page_info.sections = section_list + if selected_section: + if urllib.parse.unquote(selected_section) not in section_list: + page_info.invalid_section = True if 'special' in page_raw: full_url = re.sub(r'\$1', urllib.parse.quote(title.encode('UTF-8')), @@ -675,12 +694,12 @@ class WikiLib: page_info.has_template_doc = True page_info.before_page_property = page_info.page_property = 'template' if get_desc: - if use_textextracts and section is None: + if use_textextracts and (selected_section is None or page_info.invalid_section): raw_desc = page_raw.get('extract') if raw_desc is not None: page_desc = self.parse_text(raw_desc) else: - page_desc = self.parse_text(await self.get_html_to_text(title, section)) + page_desc = self.parse_text(await self.get_html_to_text(title, selected_section)) full_url = page_raw['fullurl'] + page_info.args file = None if 'imageinfo' in page_raw: @@ -736,8 +755,8 @@ class WikiLib: for possible_title in page_info.possible_research_title] - if before_page_info.section is not None: - page_info.section = before_page_info.section + if before_page_info.selected_section is not None: + page_info.selected_section = before_page_info.selected_section if not self.wiki_info.in_allowlist: checklist = [] if page_info.title is not None: diff --git a/modules/wiki/wiki.py b/modules/wiki/wiki.py index 9e314b87..15c956db 100644 --- a/modules/wiki/wiki.py +++ b/modules/wiki/wiki.py @@ -5,6 +5,7 @@ from typing import Union import filetype from core.builtins import Bot, Plain, Image, Voice, Url, confirm_command +from core.utils.image_table import image_table_render, ImageTable from core.component import module from core.exceptions import AbuseWarning from core.logger import Logger @@ -25,7 +26,7 @@ wiki = module('wiki', @wiki.command(' [-l ] {{wiki.help}}', - options_desc={'-l': '{wiki.help.option.l}'}) + options_desc={'-l': '{wiki.help.option.l}'}) async def _(msg: Bot.MessageSession): get_lang = msg.parsed_msg.get('-l', False) if get_lang: @@ -201,8 +202,16 @@ async def query_pages(session: Union[Bot.MessageSession, QueryInfo], title: Unio else: plain_slice.append(session.locale.t('wiki.message.redirect', title=display_before_title, redirected_title=display_title)) - if r.desc is not None and r.desc != '': - plain_slice.append(r.desc) + if (r.link is not None and r.selected_section is not None and r.info.in_allowlist and + not r.invalid_section): + render_section_list.append( + {r.link: {'url': r.info.realurl, 'section': r.selected_section, + 'in_allowlist': r.info.in_allowlist}}) + plain_slice.append(session.locale.t("wiki.message.section.rendering")) + else: + if r.desc is not None and r.desc != '': + plain_slice.append(r.desc) + if r.link is not None: plain_slice.append( str(Url(r.link, use_mm=not r.info.in_allowlist))) @@ -211,19 +220,39 @@ async def query_pages(session: Union[Bot.MessageSession, QueryInfo], title: Unio dl_list.append(r.file) plain_slice.append(session.locale.t('wiki.message.flies') + r.file) else: - if r.link is not None and r.section is None: + if r.link is not None and r.selected_section is None: render_infobox_list.append( {r.link: {'url': r.info.realurl, 'in_allowlist': r.info.in_allowlist, 'content_mode': r.has_template_doc or r.title.split(':')[0] in ['User'] or (r.templates is not None and ('Template:Disambiguation' in r.templates or 'Template:Version disambiguation' in r.templates))}}) - elif r.link is not None and r.section is not None and r.info.in_allowlist: - render_section_list.append( - {r.link: {'url': r.info.realurl, 'section': r.section, - 'in_allowlist': r.info.in_allowlist}}) if plain_slice: msg_list.append(Plain('\n'.join(plain_slice))) + if r.invalid_section and r.info.in_allowlist: + if isinstance(session, Bot.MessageSession): + + if session.Feature.image: + i_msg_lst = [] + session_data = [[str(i + 1), r.sections[i]] for i in range(len(r.sections))] + i_msg_lst.append(Plain(session.locale.t('wiki.message.invalid_section'))) + i_msg_lst.append(Image(await + image_table_render( + ImageTable(session_data, + ['ID', + session.locale.t('wiki.message.section')])))) + + async def _callback(msg: Bot.MessageSession): + display = msg.as_display(text_only=True) + if display.isdigit(): + display = int(display) + if display <= len(r.sections): + r.selected_section = display - 1 + await query_pages(session, title=r.title + '#' + r.sections[display - 1]) + + await session.send_message(i_msg_lst, callback=_callback) + else: + msg_list.append(Plain(session.locale.t('wiki.message.invalid_section.prompt'))) else: plain_slice = [] wait_plain_slice = [] @@ -323,11 +352,17 @@ async def query_pages(session: Union[Bot.MessageSession, QueryInfo], title: Unio get_section = await generate_screenshot_v2(ii, section=i[ii]['section']) if get_section: section_msg_list.append(Image(get_section)) + else: + section_msg_list.append(Plain( + session.locale.t("wiki.message.error.unable_to_render_section"))) else: get_section = await generate_screenshot_v1(i[ii]['url'], ii, headers, section=i[ii]['section']) if get_section: section_msg_list.append(Image(get_section)) + else: + section_msg_list.append(Plain( + session.locale.t("wiki.message.error.unable_to_render_section"))) if section_msg_list: await session.send_message(section_msg_list, quote=False) diff --git a/schedulers/mcv_rss.py b/schedulers/mcv_rss.py index 9b32ee6c..9237b06f 100644 --- a/schedulers/mcv_rss.py +++ b/schedulers/mcv_rss.py @@ -1,3 +1,4 @@ +import datetime import re import traceback from urllib.parse import quote @@ -7,6 +8,7 @@ from bs4 import BeautifulSoup from google_play_scraper import app as google_play_scraper from config import CFG, Config +from core.builtins import I18NText, FormattedTime from core.logger import Logger from core.queue import JobQueue from core.scheduler import Scheduler, IntervalTrigger @@ -74,32 +76,47 @@ async def mcv_rss(): file = json.loads(await get_url(url, attempt=1)) release = file['latest']['release'] snapshot = file['latest']['snapshot'] + time_release = 0 + time_snapshot = 0 + for v in file['versions']: + if v['id'] == release: + time_release = datetime.datetime.fromisoformat(v['releaseTime']).timestamp() + if v['id'] == snapshot: + time_snapshot = datetime.datetime.fromisoformat(v['releaseTime']).timestamp() + if release not in verlist: Logger.info(f'huh, we find {release}.') - await JobQueue.trigger_hook_all('mcv_rss', message='mcv_rss.message.mcv_rss.release', - i18n=True, version=file['latest']['release']) + + await JobQueue.trigger_hook_all('mcv_rss', + message=[I18NText('mcv_rss.message.mcv_rss.release', + version=release).to_dict(), + FormattedTime(time_release).to_dict() + ]) verlist.append(release) update_stored_list('scheduler', 'mcv_rss', verlist) article = await get_article(release) if article[0] != '': get_stored_news_title = get_stored_list('scheduler', 'mcnews') if article[1] not in get_stored_news_title: - await JobQueue.trigger_hook_all('minecraft_news', message='minecraft_news.message.update_log', - i18n=True, version=release, article=article[0]) + await JobQueue.trigger_hook_all('minecraft_news', + message=[I18NText('minecraft_news.message.update_log', + version=release, article=article[0]).to_dict()]) get_stored_news_title.append(article[1]) update_stored_list('scheduler', 'mcnews', get_stored_news_title) if snapshot not in verlist: Logger.info(f'huh, we find {snapshot}.') - await JobQueue.trigger_hook_all('mcv_rss', message='mcv_rss.message.mcv_rss.snapshot', i18n=True, - version=file['latest']['snapshot']) + await JobQueue.trigger_hook_all('mcv_rss', message=[I18NText('mcv_rss.message.mcv_rss.snapshot', + version=file['latest']['snapshot']).to_dict(), + FormattedTime(time_snapshot).to_dict()]) verlist.append(snapshot) update_stored_list('scheduler', 'mcv_rss', verlist) article = await get_article(snapshot) if article[0] != '': get_stored_news_title = get_stored_list('scheduler', 'mcnews') if article[1] not in get_stored_news_title: - await JobQueue.trigger_hook_all('minecraft_news', message='minecraft_news.message.update_log', - i18n=True, version=snapshot, article=article[0]) + await JobQueue.trigger_hook_all('minecraft_news', + message=[I18NText('minecraft_news.message.update_log', + version=snapshot, article=article[0]).to_dict()]) get_stored_news_title.append(article[1]) update_stored_list('scheduler', 'mcnews', get_stored_news_title) except Exception: