🤡我知道您急,您先别急,这是有bug的,我还没改完,我就想换台设备来写
4
.gitignore
vendored
@ -18,6 +18,10 @@ config.yml
|
|||||||
config.example.yml
|
config.example.yml
|
||||||
compile.bat
|
compile.bat
|
||||||
src/resources/templates/latest-debug.html
|
src/resources/templates/latest-debug.html
|
||||||
|
|
||||||
|
src/plugins/trimo_plugin_msctconverter/config
|
||||||
|
src/plugins/trimo_plugin_msctconverter/temp
|
||||||
|
|
||||||
# vuepress
|
# vuepress
|
||||||
.github
|
.github
|
||||||
pyproject.toml
|
pyproject.toml
|
||||||
|
@ -42,7 +42,7 @@
|
|||||||
版权所有 © 2024 SnowyKami & EillesWan
|
版权所有 © 2024 SnowyKami & EillesWan
|
||||||
|
|
||||||
|
|
||||||
轻雪机器人睿乐定制版(LiteyukiBot-TriM)根据 第一版 汉钰律许可协议(“本协议”)授权。\
|
轻雪机器人睿乐定制版(LiteyukiBot-TriMO)根据 第一版 汉钰律许可协议(“本协议”)授权。\
|
||||||
任何人皆可从以下地址获得本协议副本:[汉钰律许可协议 第一版](https://gitee.com/EillesWan/YulvLicenses/raw/master/%E6%B1%89%E9%92%B0%E5%BE%8B%E8%AE%B8%E5%8F%AF%E5%8D%8F%E8%AE%AE/%E6%B1%89%E9%92%B0%E5%BE%8B%E8%AE%B8%E5%8F%AF%E5%8D%8F%E8%AE%AE.MD)。\
|
任何人皆可从以下地址获得本协议副本:[汉钰律许可协议 第一版](https://gitee.com/EillesWan/YulvLicenses/raw/master/%E6%B1%89%E9%92%B0%E5%BE%8B%E8%AE%B8%E5%8F%AF%E5%8D%8F%E8%AE%AE/%E6%B1%89%E9%92%B0%E5%BE%8B%E8%AE%B8%E5%8F%AF%E5%8D%8F%E8%AE%AE.MD)。\
|
||||||
若非因法律要求或经过了特殊准许,此作品在根据本协议“原样”提供的基础上,不予提供任何形式的担保、任何明示、任何暗示或类似承诺。也就是说,用户将自行承担因此作品的质量或性能问题而产生的全部风险。\
|
若非因法律要求或经过了特殊准许,此作品在根据本协议“原样”提供的基础上,不予提供任何形式的担保、任何明示、任何暗示或类似承诺。也就是说,用户将自行承担因此作品的质量或性能问题而产生的全部风险。\
|
||||||
详细的准许和限制条款请见原协议文本。
|
详细的准许和限制条款请见原协议文本。
|
||||||
@ -57,4 +57,4 @@
|
|||||||
[lightyuki-link]: /
|
[lightyuki-link]: /
|
||||||
[python-link]: https://www.python.org/
|
[python-link]: https://www.python.org/
|
||||||
[usage-link]: https://bot.liteyuki.icu/
|
[usage-link]: https://bot.liteyuki.icu/
|
||||||
[banner]: https://socialify.git.ci/TriM-Organization/LiteyukiBot-TriM/image?description=1&forks=1&issues=1&Plus&pulls=1&stargazers=1&theme=Auto&logo=https://gitee.com/TriM-Organization/Linglun-Converter/raw/master/resources/TriMO_LOGO.png
|
[banner]: https://socialify.git.ci/TriM-Organization/LiteyukiBot-TriM/image?description=1&forks=1&issues=1&Plus&pulls=1&stargazers=1&theme=Auto&logo=https://raw.githubusercontent.com/TriM-Organization/Linglun-Converter/master/resources/TriMO_LOGO.png
|
||||||
|
@ -4,8 +4,8 @@ from git import Repo
|
|||||||
from src.utils.base.config import get_config
|
from src.utils.base.config import get_config
|
||||||
|
|
||||||
remote_urls = [
|
remote_urls = [
|
||||||
"https://gitee.com/TriM-Organization/LiteyukiBot-TriM.git",
|
"https://gitee.com/TriM-Organization/LiteyukiBot-TriMO.git",
|
||||||
"https://github.com/TriM-Organization/LiteyukiBot-TriM.git"
|
"https://github.com/TriM-Organization/LiteyukiBot-TriMO.git"
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
@ -23,12 +23,22 @@ from ..utils.base.ly_function import get_function
|
|||||||
|
|
||||||
require("nonebot_plugin_alconna")
|
require("nonebot_plugin_alconna")
|
||||||
require("nonebot_plugin_apscheduler")
|
require("nonebot_plugin_apscheduler")
|
||||||
from nonebot_plugin_alconna import UniMessage, on_alconna, Alconna, Args, Subcommand, Arparma, MultiVar
|
from nonebot_plugin_alconna import (
|
||||||
|
UniMessage,
|
||||||
|
on_alconna,
|
||||||
|
Alconna,
|
||||||
|
Args,
|
||||||
|
Subcommand,
|
||||||
|
Arparma,
|
||||||
|
MultiVar,
|
||||||
|
)
|
||||||
from nonebot_plugin_apscheduler import scheduler
|
from nonebot_plugin_apscheduler import scheduler
|
||||||
|
|
||||||
driver = get_driver()
|
driver = get_driver()
|
||||||
|
|
||||||
markdown_image = common_db.where_one(StoredConfig(), default=StoredConfig()).config.get("markdown_image", False)
|
markdown_image = common_db.where_one(StoredConfig(), default=StoredConfig()).config.get(
|
||||||
|
"markdown_image", False
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@on_alconna(
|
@on_alconna(
|
||||||
@ -36,42 +46,38 @@ markdown_image = common_db.where_one(StoredConfig(), default=StoredConfig()).con
|
|||||||
"ryounecho",
|
"ryounecho",
|
||||||
Args["text", str, ""],
|
Args["text", str, ""],
|
||||||
),
|
),
|
||||||
permission=SUPERUSER
|
permission=SUPERUSER,
|
||||||
).handle()
|
).handle()
|
||||||
# Satori OK
|
# Satori OK
|
||||||
async def _(bot: T_Bot, matcher: Matcher, result: Arparma):
|
async def _(bot: T_Bot, matcher: Matcher, result: Arparma):
|
||||||
if result.main_args.get("text"):
|
if result.main_args.get("text"):
|
||||||
await matcher.finish(Message(unescape(result.main_args.get("text"))))
|
await matcher.finish(Message(unescape(result.main_args.get("text")))) # type: ignore
|
||||||
else:
|
else:
|
||||||
await matcher.finish(f"君安!灵温向你问好~\n此机 {bot.self_id}")
|
await matcher.finish(f"君安!灵温向你问好~\n此机 {bot.self_id}")
|
||||||
|
|
||||||
|
|
||||||
@on_alconna(
|
@on_alconna(
|
||||||
command=Alconna(
|
command=Alconna(
|
||||||
"liteecho",
|
"liteecho",
|
||||||
Args["text", str, ""],
|
# Args["text", str, ""],
|
||||||
),
|
),
|
||||||
permission=SUPERUSER
|
# permission=SUPERUSER
|
||||||
).handle()
|
).handle()
|
||||||
# Satori OK
|
# Satori OK
|
||||||
async def _(bot: T_Bot, matcher: Matcher, result: Arparma):
|
async def _(bot: T_Bot, matcher: Matcher, result: Arparma):
|
||||||
if result.main_args.get("text"):
|
await matcher.finish(f"Hello! TriMO-Liteyuki!\nRyBot {bot.self_id}")
|
||||||
await matcher.finish(Message(unescape(result.main_args.get("text"))))
|
|
||||||
else:
|
|
||||||
await matcher.finish(f"Hello! TriM-Liteyuki!\nRyBot {bot.self_id}")
|
|
||||||
|
|
||||||
|
|
||||||
@on_alconna(
|
@on_alconna(
|
||||||
aliases={"更新灵温"},
|
aliases={"更新灵温"}, command=Alconna("update-ryoun"), permission=SUPERUSER
|
||||||
command=Alconna(
|
|
||||||
"update-ryoun"
|
|
||||||
),
|
|
||||||
permission=SUPERUSER
|
|
||||||
).handle()
|
).handle()
|
||||||
# Satori OK
|
# Satori OK
|
||||||
async def _(bot: T_Bot, event: T_MessageEvent):
|
async def _(bot: T_Bot, event: T_MessageEvent):
|
||||||
# 使用git pull更新
|
# 使用git pull更新
|
||||||
|
|
||||||
ulang = get_user_lang(str(event.user.id if isinstance(event, satori.event.Event) else event.user_id))
|
ulang = get_user_lang(
|
||||||
|
str(event.user.id if isinstance(event, satori.event.Event) else event.user_id)
|
||||||
|
)
|
||||||
success, logs = update_liteyuki()
|
success, logs = update_liteyuki()
|
||||||
reply = "尹灵温 更新完成!\n"
|
reply = "尹灵温 更新完成!\n"
|
||||||
reply += f"```\n{logs}\n```\n"
|
reply += f"```\n{logs}\n```\n"
|
||||||
@ -82,11 +88,11 @@ async def _(bot: T_Bot, event: T_MessageEvent):
|
|||||||
|
|
||||||
|
|
||||||
@on_alconna(
|
@on_alconna(
|
||||||
aliases={"重启灵温","重启尹灵温", "重载灵温"},
|
aliases={"重启灵温", "重启尹灵温", "重载灵温"},
|
||||||
command=Alconna(
|
command=Alconna(
|
||||||
"reload-ryoun",
|
"reload-ryoun",
|
||||||
),
|
),
|
||||||
permission=SUPERUSER
|
permission=SUPERUSER,
|
||||||
).handle()
|
).handle()
|
||||||
# Satori OK
|
# Satori OK
|
||||||
async def _(matcher: Matcher, bot: T_Bot, event: T_MessageEvent):
|
async def _(matcher: Matcher, bot: T_Bot, event: T_MessageEvent):
|
||||||
@ -95,13 +101,16 @@ async def _(matcher: Matcher, bot: T_Bot, event: T_MessageEvent):
|
|||||||
|
|
||||||
temp_data.data.update(
|
temp_data.data.update(
|
||||||
{
|
{
|
||||||
"reload" : True,
|
"reload": True,
|
||||||
"reload_time" : time.time(),
|
"reload_time": time.time(),
|
||||||
"reload_bot_id" : bot.self_id,
|
"reload_bot_id": bot.self_id,
|
||||||
"reload_session_type": event_utils.get_message_type(event),
|
"reload_session_type": event_utils.get_message_type(event),
|
||||||
"reload_session_id" : (event.group_id if event.message_type == "group" else event.user_id) if not isinstance(event,
|
"reload_session_id": (
|
||||||
satori.event.Event) else event.channel.id,
|
(event.group_id if event.message_type == "group" else event.user_id)
|
||||||
"delta_time" : 0
|
if not isinstance(event, satori.event.Event)
|
||||||
|
else event.channel.id
|
||||||
|
),
|
||||||
|
"delta_time": 0,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -117,34 +126,31 @@ async def _(matcher: Matcher, bot: T_Bot, event: T_MessageEvent):
|
|||||||
"set",
|
"set",
|
||||||
Args["key", str]["value", Any],
|
Args["key", str]["value", Any],
|
||||||
alias=["设置"],
|
alias=["设置"],
|
||||||
|
|
||||||
),
|
),
|
||||||
Subcommand(
|
Subcommand("get", Args["key", str, None], alias=["查询", "获取"]),
|
||||||
"get",
|
Subcommand("remove", Args["key", str], alias=["删除"]),
|
||||||
Args["key", str, None],
|
|
||||||
alias=["查询", "获取"]
|
|
||||||
),
|
|
||||||
Subcommand(
|
|
||||||
"remove",
|
|
||||||
Args["key", str],
|
|
||||||
alias=["删除"]
|
|
||||||
)
|
|
||||||
),
|
),
|
||||||
permission=SUPERUSER
|
permission=SUPERUSER,
|
||||||
).handle()
|
).handle()
|
||||||
# Satori OK
|
# Satori OK
|
||||||
async def _(result: Arparma, event: T_MessageEvent, bot: T_Bot, matcher: Matcher):
|
async def _(result: Arparma, event: T_MessageEvent, bot: T_Bot, matcher: Matcher):
|
||||||
ulang = get_user_lang(str(event_utils.get_user_id(event)))
|
ulang = get_user_lang(str(event_utils.get_user_id(event)))
|
||||||
stored_config: StoredConfig = common_db.where_one(StoredConfig(), default=StoredConfig())
|
stored_config: StoredConfig = common_db.where_one(
|
||||||
|
StoredConfig(), default=StoredConfig()
|
||||||
|
)
|
||||||
if result.subcommands.get("set"):
|
if result.subcommands.get("set"):
|
||||||
key, value = result.subcommands.get("set").args.get("key"), result.subcommands.get("set").args.get("value")
|
key, value = result.subcommands.get("set").args.get(
|
||||||
|
"key"
|
||||||
|
), result.subcommands.get("set").args.get("value")
|
||||||
try:
|
try:
|
||||||
value = eval(value)
|
value = eval(value)
|
||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
stored_config.config[key] = value
|
stored_config.config[key] = value
|
||||||
common_db.save(stored_config)
|
common_db.save(stored_config)
|
||||||
await matcher.finish(f"{ulang.get('liteyuki.config_set_success', KEY=key, VAL=value)}")
|
await matcher.finish(
|
||||||
|
f"{ulang.get('liteyuki.config_set_success', KEY=key, VAL=value)}"
|
||||||
|
)
|
||||||
elif result.subcommands.get("get"):
|
elif result.subcommands.get("get"):
|
||||||
key = result.subcommands.get("get").args.get("key")
|
key = result.subcommands.get("get").args.get("key")
|
||||||
file_config = load_from_yaml("config.yml")
|
file_config = load_from_yaml("config.yml")
|
||||||
@ -168,29 +174,36 @@ async def _(result: Arparma, event: T_MessageEvent, bot: T_Bot, matcher: Matcher
|
|||||||
if key in stored_config.config:
|
if key in stored_config.config:
|
||||||
stored_config.config.pop(key)
|
stored_config.config.pop(key)
|
||||||
common_db.save(stored_config)
|
common_db.save(stored_config)
|
||||||
await matcher.finish(f"{ulang.get('liteyuki.config_remove_success', KEY=key)}")
|
await matcher.finish(
|
||||||
|
f"{ulang.get('liteyuki.config_remove_success', KEY=key)}"
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
await matcher.finish(f"{ulang.get('liteyuki.invalid_command', TEXT=key)}")
|
await matcher.finish(f"{ulang.get('liteyuki.invalid_command', TEXT=key)}")
|
||||||
|
|
||||||
|
|
||||||
@on_alconna(
|
@on_alconna(
|
||||||
aliases={"切换图片模式"},
|
aliases={"切换图片模式"}, command=Alconna("switch-image-mode"), permission=SUPERUSER
|
||||||
command=Alconna(
|
|
||||||
"switch-image-mode"
|
|
||||||
),
|
|
||||||
permission=SUPERUSER
|
|
||||||
).handle()
|
).handle()
|
||||||
# Satori OK
|
# Satori OK
|
||||||
async def _(event: T_MessageEvent, matcher: Matcher):
|
async def _(event: T_MessageEvent, matcher: Matcher):
|
||||||
global markdown_image
|
global markdown_image
|
||||||
# 切换图片模式,False以图片形式发送,True以markdown形式发送
|
# 切换图片模式,False以图片形式发送,True以markdown形式发送
|
||||||
ulang = get_user_lang(str(event_utils.get_user_id(event)))
|
ulang = get_user_lang(str(event_utils.get_user_id(event)))
|
||||||
stored_config: StoredConfig = common_db.where_one(StoredConfig(), default=StoredConfig())
|
stored_config: StoredConfig = common_db.where_one(
|
||||||
stored_config.config["markdown_image"] = not stored_config.config.get("markdown_image", False)
|
StoredConfig(), default=StoredConfig()
|
||||||
|
)
|
||||||
|
stored_config.config["markdown_image"] = not stored_config.config.get(
|
||||||
|
"markdown_image", False
|
||||||
|
)
|
||||||
markdown_image = stored_config.config["markdown_image"]
|
markdown_image = stored_config.config["markdown_image"]
|
||||||
common_db.save(stored_config)
|
common_db.save(stored_config)
|
||||||
await matcher.finish(
|
await matcher.finish(
|
||||||
ulang.get("liteyuki.image_mode_on" if stored_config.config["markdown_image"] else "liteyuki.image_mode_off"))
|
ulang.get(
|
||||||
|
"liteyuki.image_mode_on"
|
||||||
|
if stored_config.config["markdown_image"]
|
||||||
|
else "liteyuki.image_mode_off"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
# @on_alconna(
|
# @on_alconna(
|
||||||
@ -209,7 +222,7 @@ async def _(event: T_MessageEvent, matcher: Matcher):
|
|||||||
"/function",
|
"/function",
|
||||||
Args["function", str]["args", MultiVar(str), ()],
|
Args["function", str]["args", MultiVar(str), ()],
|
||||||
),
|
),
|
||||||
permission=SUPERUSER
|
permission=SUPERUSER,
|
||||||
).handle()
|
).handle()
|
||||||
async def _(result: Arparma, bot: T_Bot, event: T_MessageEvent, matcher: Matcher):
|
async def _(result: Arparma, bot: T_Bot, event: T_MessageEvent, matcher: Matcher):
|
||||||
"""
|
"""
|
||||||
@ -226,9 +239,9 @@ async def _(result: Arparma, bot: T_Bot, event: T_MessageEvent, matcher: Matcher
|
|||||||
args: tuple[str] = result.main_args.get("args", ())
|
args: tuple[str] = result.main_args.get("args", ())
|
||||||
_args = []
|
_args = []
|
||||||
_kwargs = {
|
_kwargs = {
|
||||||
"USER_ID" : str(event.user_id),
|
"USER_ID": str(event.user_id),
|
||||||
"GROUP_ID": str(event.group_id) if event.message_type == "group" else "0",
|
"GROUP_ID": str(event.group_id) if event.message_type == "group" else "0",
|
||||||
"BOT_ID" : str(bot.self_id)
|
"BOT_ID": str(bot.self_id),
|
||||||
}
|
}
|
||||||
|
|
||||||
for arg in args:
|
for arg in args:
|
||||||
@ -256,7 +269,7 @@ async def _(result: Arparma, bot: T_Bot, event: T_MessageEvent, matcher: Matcher
|
|||||||
"/api",
|
"/api",
|
||||||
Args["api", str]["args", MultiVar(AnyStr), ()],
|
Args["api", str]["args", MultiVar(AnyStr), ()],
|
||||||
),
|
),
|
||||||
permission=SUPERUSER
|
permission=SUPERUSER,
|
||||||
).handle()
|
).handle()
|
||||||
async def _(result: Arparma, bot: T_Bot, event: T_MessageEvent, matcher: Matcher):
|
async def _(result: Arparma, bot: T_Bot, event: T_MessageEvent, matcher: Matcher):
|
||||||
"""
|
"""
|
||||||
@ -270,7 +283,9 @@ async def _(result: Arparma, bot: T_Bot, event: T_MessageEvent, matcher: Matcher
|
|||||||
|
|
||||||
"""
|
"""
|
||||||
api_name = result.main_args.get("api")
|
api_name = result.main_args.get("api")
|
||||||
args: tuple[str] = result.main_args.get("args", ()) # 类似于url参数,但每个参数间用空格分隔,空格是%20
|
args: tuple[str] = result.main_args.get(
|
||||||
|
"args", ()
|
||||||
|
) # 类似于url参数,但每个参数间用空格分隔,空格是%20
|
||||||
args_dict = {}
|
args_dict = {}
|
||||||
|
|
||||||
for arg in args:
|
for arg in args:
|
||||||
@ -280,7 +295,11 @@ async def _(result: Arparma, bot: T_Bot, event: T_MessageEvent, matcher: Matcher
|
|||||||
|
|
||||||
if api_name in need_user_id and "user_id" not in args_dict:
|
if api_name in need_user_id and "user_id" not in args_dict:
|
||||||
args_dict["user_id"] = str(event.user_id)
|
args_dict["user_id"] = str(event.user_id)
|
||||||
if api_name in need_group_id and "group_id" not in args_dict and event.message_type == "group":
|
if (
|
||||||
|
api_name in need_group_id
|
||||||
|
and "group_id" not in args_dict
|
||||||
|
and event.message_type == "group"
|
||||||
|
):
|
||||||
args_dict["group_id"] = str(event.group_id)
|
args_dict["group_id"] = str(event.group_id)
|
||||||
|
|
||||||
if "message" in args_dict:
|
if "message" in args_dict:
|
||||||
@ -303,29 +322,53 @@ async def _(result: Arparma, bot: T_Bot, event: T_MessageEvent, matcher: Matcher
|
|||||||
@Bot.on_calling_api # 图片模式检测
|
@Bot.on_calling_api # 图片模式检测
|
||||||
async def test_for_md_image(bot: T_Bot, api: str, data: dict):
|
async def test_for_md_image(bot: T_Bot, api: str, data: dict):
|
||||||
# 截获大图发送,转换为markdown发送
|
# 截获大图发送,转换为markdown发送
|
||||||
if api in ["send_msg", "send_private_msg", "send_group_msg"] and markdown_image and data.get(
|
if (
|
||||||
"user_id") != bot.self_id:
|
api in ["send_msg", "send_private_msg", "send_group_msg"]
|
||||||
if api == "send_msg" and data.get("message_type") == "private" or api == "send_private_msg":
|
and markdown_image
|
||||||
|
and data.get("user_id") != bot.self_id
|
||||||
|
):
|
||||||
|
if (
|
||||||
|
api == "send_msg"
|
||||||
|
and data.get("message_type") == "private"
|
||||||
|
or api == "send_private_msg"
|
||||||
|
):
|
||||||
session_type = "private"
|
session_type = "private"
|
||||||
session_id = data.get("user_id")
|
session_id = data.get("user_id")
|
||||||
elif api == "send_msg" and data.get("message_type") == "group" or api == "send_group_msg":
|
elif (
|
||||||
|
api == "send_msg"
|
||||||
|
and data.get("message_type") == "group"
|
||||||
|
or api == "send_group_msg"
|
||||||
|
):
|
||||||
session_type = "group"
|
session_type = "group"
|
||||||
session_id = data.get("group_id")
|
session_id = data.get("group_id")
|
||||||
else:
|
else:
|
||||||
return
|
return
|
||||||
if len(data.get("message", [])) == 1 and data["message"][0].get("type") == "image":
|
if (
|
||||||
|
len(data.get("message", [])) == 1
|
||||||
|
and data["message"][0].get("type") == "image"
|
||||||
|
):
|
||||||
file: str = data["message"][0].data.get("file")
|
file: str = data["message"][0].data.get("file")
|
||||||
# file:// http:// base64://
|
# file:// http:// base64://
|
||||||
if file.startswith("http"):
|
if file.startswith("http"):
|
||||||
result = await md.send_md(await md.image_async(file), bot, message_type=session_type,
|
result = await md.send_md(
|
||||||
session_id=session_id)
|
await md.image_async(file),
|
||||||
|
bot,
|
||||||
|
message_type=session_type,
|
||||||
|
session_id=session_id,
|
||||||
|
)
|
||||||
elif file.startswith("file"):
|
elif file.startswith("file"):
|
||||||
file = file.replace("file://", "")
|
file = file.replace("file://", "")
|
||||||
result = await md.send_image(open(file, "rb").read(), bot, message_type=session_type,
|
result = await md.send_image(
|
||||||
session_id=session_id)
|
open(file, "rb").read(),
|
||||||
|
bot,
|
||||||
|
message_type=session_type,
|
||||||
|
session_id=session_id,
|
||||||
|
)
|
||||||
elif file.startswith("base64"):
|
elif file.startswith("base64"):
|
||||||
file_bytes = base64.b64decode(file.replace("base64://", ""))
|
file_bytes = base64.b64decode(file.replace("base64://", ""))
|
||||||
result = await md.send_image(file_bytes, bot, message_type=session_type, session_id=session_id)
|
result = await md.send_image(
|
||||||
|
file_bytes, bot, message_type=session_type, session_id=session_id
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
return
|
return
|
||||||
raise MockApiException(result=result)
|
raise MockApiException(result=result)
|
||||||
@ -364,7 +407,7 @@ async def _(bot: T_Bot):
|
|||||||
if isinstance(bot, satori.Bot):
|
if isinstance(bot, satori.Bot):
|
||||||
await bot.send_message(
|
await bot.send_message(
|
||||||
channel_id=reload_session_id,
|
channel_id=reload_session_id,
|
||||||
message="灵温 重载耗时 %.2f 秒" % delta_time
|
message="灵温 重载耗时 %.2f 秒" % delta_time,
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
await bot.call_api(
|
await bot.call_api(
|
||||||
@ -372,7 +415,7 @@ async def _(bot: T_Bot):
|
|||||||
message_type=reload_session_type,
|
message_type=reload_session_type,
|
||||||
user_id=reload_session_id,
|
user_id=reload_session_id,
|
||||||
group_id=reload_session_id,
|
group_id=reload_session_id,
|
||||||
message="灵温 重载耗时 %.2f 秒" % delta_time
|
message="灵温 重载耗时 %.2f 秒" % delta_time,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@ -392,22 +435,21 @@ async def every_day_update():
|
|||||||
|
|
||||||
# 需要用户id的api
|
# 需要用户id的api
|
||||||
need_user_id = (
|
need_user_id = (
|
||||||
"send_private_msg",
|
"send_private_msg",
|
||||||
"send_msg",
|
"send_msg",
|
||||||
"set_group_card",
|
"set_group_card",
|
||||||
"set_group_special_title",
|
"set_group_special_title",
|
||||||
"get_stranger_info",
|
"get_stranger_info",
|
||||||
"get_group_member_info"
|
"get_group_member_info",
|
||||||
)
|
)
|
||||||
|
|
||||||
need_group_id = (
|
need_group_id = (
|
||||||
"send_group_msg",
|
"send_group_msg",
|
||||||
"send_msg",
|
"send_msg",
|
||||||
"set_group_card",
|
"set_group_card",
|
||||||
"set_group_name",
|
"set_group_name",
|
||||||
|
"set_group_special_title",
|
||||||
"set_group_special_title",
|
"get_group_member_info",
|
||||||
"get_group_member_info",
|
"get_group_member_list",
|
||||||
"get_group_member_list",
|
"get_group_honor_info",
|
||||||
"get_group_honor_info"
|
)
|
||||||
)
|
|
||||||
|
@ -183,4 +183,7 @@ async def _(bot: T_Bot, event: T_MessageEvent, result: Arparma, matcher: Matcher
|
|||||||
if send_as_md:
|
if send_as_md:
|
||||||
await md.send_md(reply, bot, event=event)
|
await md.send_md(reply, bot, event=event)
|
||||||
else:
|
else:
|
||||||
await matcher.finish(reply)
|
if reply:
|
||||||
|
await matcher.finish(reply)
|
||||||
|
else:
|
||||||
|
await matcher.finish("其实,还没做help\t——神羽")
|
||||||
|
@ -18,6 +18,7 @@ from nonebot_plugin_alconna import (
|
|||||||
# AlconnaQuery,
|
# AlconnaQuery,
|
||||||
# Query,
|
# Query,
|
||||||
Arparma,
|
Arparma,
|
||||||
|
Args,
|
||||||
)
|
)
|
||||||
|
|
||||||
require("nonebot_plugin_apscheduler")
|
require("nonebot_plugin_apscheduler")
|
||||||
@ -59,12 +60,18 @@ yanlun = on_alconna(
|
|||||||
action=store_true,
|
action=store_true,
|
||||||
),
|
),
|
||||||
Option("-c|--count", default=False, alias={"统计"}, action=store_true),
|
Option("-c|--count", default=False, alias={"统计"}, action=store_true),
|
||||||
|
Option("-l|--length", default=1.0, args=Args["length", float | int, 1.0]),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
# 每天4点更新
|
yanlun_path = (
|
||||||
@scheduler.scheduled_job("cron", hour=4)
|
"https://gitee.com/TriM-Organization/LinglunStudio/raw/master/resources/myWords.txt"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
# 每天1点更新
|
||||||
|
@scheduler.scheduled_job("cron", hour=1)
|
||||||
async def every_day_update():
|
async def every_day_update():
|
||||||
ulang = Language(get_default_lang_code(), "zh-WY")
|
ulang = Language(get_default_lang_code(), "zh-WY")
|
||||||
nonebot.logger.success(ulang.get("yanlun.refresh.success", COUNT=update_yanlun()))
|
nonebot.logger.success(ulang.get("yanlun.refresh.success", COUNT=update_yanlun()))
|
||||||
@ -79,15 +86,17 @@ def update_yanlun():
|
|||||||
lunar_date = (lunar_datetime.lunar_month, lunar_datetime.lunar_day)
|
lunar_date = (lunar_datetime.lunar_month, lunar_datetime.lunar_day)
|
||||||
|
|
||||||
if solar_date == (4, 3):
|
if solar_date == (4, 3):
|
||||||
yanlun_texts = ["金羿ELS 生日快乐~!"]
|
yanlun_texts = ["金羿ELS 生日快乐~!", "Happy Birthday, Eilles!"]
|
||||||
elif solar_date == (8, 6):
|
elif solar_date == (8, 6):
|
||||||
yanlun_texts = ["诸葛八卦 生日快乐~!"]
|
yanlun_texts = ["诸葛亮与八卦阵 生日快乐~!", "Happy Birthday, bgArray~!"]
|
||||||
|
elif solar_date == (8, 16):
|
||||||
|
yanlun_texts = ["鱼旧梦 生日快乐~!", "Happy Birthday, ElapsingDreams~!"]
|
||||||
|
|
||||||
else:
|
else:
|
||||||
try:
|
try:
|
||||||
yanlun_texts = (
|
yanlun_texts = (
|
||||||
requests.get(
|
requests.get(
|
||||||
"https://gitee.com/TriM-Organization/LinglunStudio/raw/master/resources/myWords.txt",
|
yanlun_path,
|
||||||
)
|
)
|
||||||
.text.strip("\n")
|
.text.strip("\n")
|
||||||
.split("\n")
|
.split("\n")
|
||||||
@ -165,13 +174,13 @@ async def _(
|
|||||||
# count: Query[bool] = AlconnaQuery("count.value", False),
|
# count: Query[bool] = AlconnaQuery("count.value", False),
|
||||||
):
|
):
|
||||||
# print(result.options)
|
# print(result.options)
|
||||||
|
ulang = get_user_lang(event_utils.get_user_id(event)) # type: ignore
|
||||||
if result.options["refresh"].value:
|
if result.options["refresh"].value:
|
||||||
ulang = get_user_lang(event_utils.get_user_id(event)) # type: ignore
|
|
||||||
global yanlun_texts
|
global yanlun_texts
|
||||||
try:
|
try:
|
||||||
yanlun_texts = (
|
yanlun_texts = (
|
||||||
requests.get(
|
requests.get(
|
||||||
"https://gitee.com/TriM-Organization/LinglunStudio/raw/master/resources/myWords.txt",
|
yanlun_path,
|
||||||
)
|
)
|
||||||
.text.strip("\n")
|
.text.strip("\n")
|
||||||
.split("\n")
|
.split("\n")
|
||||||
@ -205,7 +214,6 @@ async def _(
|
|||||||
)
|
)
|
||||||
yanlun_texts = ["灵光焕发 深艺献心"]
|
yanlun_texts = ["灵光焕发 深艺献心"]
|
||||||
if result.options["count"].value:
|
if result.options["count"].value:
|
||||||
ulang = get_user_lang(event_utils.get_user_id(event)) # type: ignore
|
|
||||||
authors = [
|
authors = [
|
||||||
(
|
(
|
||||||
("B站")
|
("B站")
|
||||||
@ -250,4 +258,27 @@ async def _(
|
|||||||
+ ulang.get("yanlun.count.tail", NUM=total)
|
+ ulang.get("yanlun.count.tail", NUM=total)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
await yanlun.finish(UniMessage.text(random.choice(yanlun_texts)))
|
|
||||||
|
final_length = 0
|
||||||
|
try:
|
||||||
|
final_length += result.options["length"].args["length"]
|
||||||
|
except:
|
||||||
|
final_length = 1
|
||||||
|
|
||||||
|
(
|
||||||
|
(
|
||||||
|
await yanlun.finish(
|
||||||
|
UniMessage.text(
|
||||||
|
"\n".join([random.choice(yanlun_texts) for i in range(iill)])
|
||||||
|
if iill <= 100
|
||||||
|
else ulang.get("yanlun.length.toolong")
|
||||||
|
)
|
||||||
|
)
|
||||||
|
if iill > 0
|
||||||
|
else await yanlun.finish(
|
||||||
|
UniMessage.text(ulang.get("yanlun.length.tooshort"))
|
||||||
|
)
|
||||||
|
)
|
||||||
|
if (iill := int(final_length)) == final_length
|
||||||
|
else await yanlun.finish(UniMessage.text(ulang.get("yanlun.length.float")))
|
||||||
|
)
|
||||||
|
@ -1,42 +0,0 @@
|
|||||||
|
|
||||||
|
|
||||||
import nonebot
|
|
||||||
from nonebot import require
|
|
||||||
|
|
||||||
require("nonebot_plugin_alconna")
|
|
||||||
from nonebot_plugin_alconna import (
|
|
||||||
on_alconna,
|
|
||||||
Alconna,
|
|
||||||
Subcommand,
|
|
||||||
UniMessage,
|
|
||||||
Option,
|
|
||||||
store_true,
|
|
||||||
Arparma,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
musicreater_convert = on_alconna(
|
|
||||||
aliases={"musicreater_convert","音乐转换","midi转换"},
|
|
||||||
command=Alconna(
|
|
||||||
"msctcvt",
|
|
||||||
Option(
|
|
||||||
"-r|--refresh",
|
|
||||||
default=False,
|
|
||||||
alias={"refr", "r", "刷新"},
|
|
||||||
action=store_true,
|
|
||||||
),
|
|
||||||
Subcommand(
|
|
||||||
"memory",
|
|
||||||
alias={"mem", "m", "内存"},
|
|
||||||
),
|
|
||||||
Subcommand(
|
|
||||||
"process",
|
|
||||||
alias={"proc", "p", "进程"},
|
|
||||||
),
|
|
||||||
# Subcommand(
|
|
||||||
# "refresh",
|
|
||||||
# alias={"refr", "r", "刷新"},
|
|
||||||
# ),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
|
|
13
src/plugins/trimo_plugin_handle/LICENSE.md
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
|
||||||
|
## 版权声明
|
||||||
|
|
||||||
|
本插件由 汉钰律许可协议 授权开源,兼容并继承自 MIT 许可协议。
|
||||||
|
|
||||||
|
Copyright (c) 2022 MeetWq
|
||||||
|
|
||||||
|
版权所有 © 2024 EillesWan & MeetWq
|
||||||
|
|
||||||
|
猜成语-睿乐特别版(trimo_plugin_handle)根据 第一版 汉钰律许可协议(“本协议”)授权。\
|
||||||
|
任何人皆可从以下地址获得本协议副本:[汉钰律许可协议 第一版](https://gitee.com/EillesWan/YulvLicenses/raw/master/%E6%B1%89%E9%92%B0%E5%BE%8B%E8%AE%B8%E5%8F%AF%E5%8D%8F%E8%AE%AE/%E6%B1%89%E9%92%B0%E5%BE%8B%E8%AE%B8%E5%8F%AF%E5%8D%8F%E8%AE%AE.MD)。\
|
||||||
|
若非因法律要求或经过了特殊准许,此作品在根据本协议“原样”提供的基础上,不予提供任何形式的担保、任何明示、任何暗示或类似承诺。也就是说,用户将自行承担因此作品的质量或性能问题而产生的全部风险。\
|
||||||
|
详细的准许和限制条款请见原协议文本。
|
268
src/plugins/trimo_plugin_handle/__init__.py
Normal file
@ -0,0 +1,268 @@
|
|||||||
|
import asyncio
|
||||||
|
from asyncio import TimerHandle
|
||||||
|
from typing import Any, Dict
|
||||||
|
|
||||||
|
from nonebot import on_regex, require, on_command
|
||||||
|
from nonebot.matcher import Matcher
|
||||||
|
from nonebot.params import RegexDict
|
||||||
|
from nonebot.plugin import PluginMetadata, inherit_supported_adapters
|
||||||
|
from nonebot.rule import to_me
|
||||||
|
from nonebot.utils import run_sync
|
||||||
|
from nonebot.permission import SUPERUSER
|
||||||
|
from typing_extensions import Annotated
|
||||||
|
|
||||||
|
require("nonebot_plugin_alconna")
|
||||||
|
require("nonebot_plugin_session")
|
||||||
|
|
||||||
|
from nonebot_plugin_alconna import (
|
||||||
|
Alconna,
|
||||||
|
AlconnaQuery,
|
||||||
|
Image,
|
||||||
|
Option,
|
||||||
|
Query,
|
||||||
|
Text,
|
||||||
|
UniMessage,
|
||||||
|
on_alconna,
|
||||||
|
store_true,
|
||||||
|
Args,
|
||||||
|
Arparma,
|
||||||
|
)
|
||||||
|
from nonebot_plugin_session import SessionId, SessionIdType
|
||||||
|
|
||||||
|
from .config import Config, handle_config
|
||||||
|
from .data_source import GuessResult, Handle
|
||||||
|
from .utils import random_idiom
|
||||||
|
|
||||||
|
__plugin_meta__ = PluginMetadata(
|
||||||
|
name="猜成语",
|
||||||
|
description="猜成语-睿乐特别版",
|
||||||
|
usage=(
|
||||||
|
"@我 + “猜成语”开始游戏;\n"
|
||||||
|
"你有十次的机会猜一个四字词语;\n"
|
||||||
|
"每次猜测后,汉字与拼音的颜色将会标识其与正确答案的区别;\n"
|
||||||
|
"青色 表示其出现在答案中且在正确的位置;\n"
|
||||||
|
"橙色 表示其出现在答案中但不在正确的位置;\n"
|
||||||
|
"每个格子的 汉字、声母、韵母、声调 都会独立进行颜色的指示。\n"
|
||||||
|
"当四个格子都为青色时,你便赢得了游戏!\n"
|
||||||
|
"可发送“结束”结束游戏;可发送“提示”查看提示。\n"
|
||||||
|
"使用 --strict 选项开启非默认的成语检查,即猜测的短语必须是成语,\n"
|
||||||
|
"如:@我 猜成语 --strict"
|
||||||
|
),
|
||||||
|
type="application",
|
||||||
|
homepage="https://github.com/noneplugin/nonebot-plugin-handle",
|
||||||
|
config=Config,
|
||||||
|
supported_adapters=inherit_supported_adapters(
|
||||||
|
"nonebot_plugin_alconna", "nonebot_plugin_session"
|
||||||
|
),
|
||||||
|
extra={
|
||||||
|
"example": "@小Q 猜成语",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
games: Dict[str, Handle] = {}
|
||||||
|
timers: Dict[str, TimerHandle] = {}
|
||||||
|
|
||||||
|
UserId = Annotated[str, SessionId(SessionIdType.GROUP)]
|
||||||
|
|
||||||
|
|
||||||
|
def game_is_running(user_id: UserId) -> bool:
|
||||||
|
return user_id in games
|
||||||
|
|
||||||
|
|
||||||
|
def game_not_running(user_id: UserId) -> bool:
|
||||||
|
return user_id not in games
|
||||||
|
|
||||||
|
|
||||||
|
handle = on_alconna(
|
||||||
|
Alconna("handle", Option("-s|--strict", default=False, action=store_true)),
|
||||||
|
aliases=("猜成语",),
|
||||||
|
rule=to_me() & game_not_running,
|
||||||
|
use_cmd_start=True,
|
||||||
|
block=True,
|
||||||
|
priority=13,
|
||||||
|
)
|
||||||
|
handle_hint = on_alconna(
|
||||||
|
"提示",
|
||||||
|
rule=game_is_running,
|
||||||
|
use_cmd_start=True,
|
||||||
|
block=True,
|
||||||
|
priority=13,
|
||||||
|
)
|
||||||
|
handle_stop = on_alconna(
|
||||||
|
"结束",
|
||||||
|
aliases=("结束游戏", "结束猜成语"),
|
||||||
|
rule=game_is_running,
|
||||||
|
use_cmd_start=True,
|
||||||
|
block=True,
|
||||||
|
priority=13,
|
||||||
|
)
|
||||||
|
|
||||||
|
handle_answer = on_alconna(
|
||||||
|
Alconna(
|
||||||
|
"答案",
|
||||||
|
Option(
|
||||||
|
"-g|--group",
|
||||||
|
default="Now",
|
||||||
|
args=Args["group", str, "Now"],
|
||||||
|
),
|
||||||
|
Option(
|
||||||
|
"-l|--list",
|
||||||
|
default=False,
|
||||||
|
action=store_true,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
# rule=game_is_running,
|
||||||
|
use_cmd_start=True,
|
||||||
|
permission=SUPERUSER,
|
||||||
|
block=True,
|
||||||
|
priority=13,
|
||||||
|
)
|
||||||
|
|
||||||
|
# handle_update = on_alconna(
|
||||||
|
# "更新词库",
|
||||||
|
# aliases=("刷新词库", "猜成语刷新词库"),
|
||||||
|
# rule=to_me(),
|
||||||
|
# use_cmd_start=True,
|
||||||
|
# block=True,
|
||||||
|
# priority=13,
|
||||||
|
# )
|
||||||
|
|
||||||
|
|
||||||
|
handle_idiom = on_regex(
|
||||||
|
r"^(?P<idiom>[\u4e00-\u9fa5]{4})$",
|
||||||
|
rule=game_is_running,
|
||||||
|
block=True,
|
||||||
|
priority=14,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def stop_game(user_id: str):
|
||||||
|
if timer := timers.pop(user_id, None):
|
||||||
|
timer.cancel()
|
||||||
|
games.pop(user_id, None)
|
||||||
|
|
||||||
|
|
||||||
|
async def stop_game_timeout(matcher: Matcher, user_id: str):
|
||||||
|
game = games.get(user_id, None)
|
||||||
|
stop_game(user_id)
|
||||||
|
if game:
|
||||||
|
msg = "猜成语超时,游戏结束。"
|
||||||
|
if len(game.guessed_idiom) >= 1:
|
||||||
|
msg += f"\n{game.result}"
|
||||||
|
await matcher.finish(msg)
|
||||||
|
|
||||||
|
|
||||||
|
def set_timeout(matcher: Matcher, user_id: str, timeout: float = 300):
|
||||||
|
if timer := timers.get(user_id, None):
|
||||||
|
timer.cancel()
|
||||||
|
loop = asyncio.get_running_loop()
|
||||||
|
timer = loop.call_later(
|
||||||
|
timeout, lambda: asyncio.ensure_future(stop_game_timeout(matcher, user_id))
|
||||||
|
)
|
||||||
|
timers[user_id] = timer
|
||||||
|
|
||||||
|
|
||||||
|
@handle.handle()
|
||||||
|
async def _(
|
||||||
|
matcher: Matcher,
|
||||||
|
user_id: UserId,
|
||||||
|
strict: Query[bool] = AlconnaQuery("strict.value", False),
|
||||||
|
):
|
||||||
|
is_strict = handle_config.handle_strict_mode or strict.result
|
||||||
|
idiom, explanation = random_idiom()
|
||||||
|
game = Handle(idiom, explanation, strict=is_strict)
|
||||||
|
|
||||||
|
games[user_id] = game
|
||||||
|
set_timeout(matcher, user_id)
|
||||||
|
|
||||||
|
msg = Text(
|
||||||
|
f"你有{game.times}次机会猜一个四字成语,"
|
||||||
|
+ ("发送有效成语以参与游戏。" if is_strict else "发送任意四字词语以参与游戏。")
|
||||||
|
) + Image(raw=await run_sync(game.draw)())
|
||||||
|
await msg.send()
|
||||||
|
|
||||||
|
|
||||||
|
@handle_hint.handle()
|
||||||
|
async def _(matcher: Matcher, user_id: UserId):
|
||||||
|
game = games[user_id]
|
||||||
|
set_timeout(matcher, user_id)
|
||||||
|
|
||||||
|
await UniMessage.image(raw=await run_sync(game.draw_hint)()).send()
|
||||||
|
|
||||||
|
|
||||||
|
@handle_stop.handle()
|
||||||
|
async def _(matcher: Matcher, user_id: UserId):
|
||||||
|
game = games[user_id]
|
||||||
|
stop_game(user_id)
|
||||||
|
|
||||||
|
msg = "游戏已结束"
|
||||||
|
if len(game.guessed_idiom) >= 1:
|
||||||
|
msg += f"\n{game.result}"
|
||||||
|
await matcher.finish(msg)
|
||||||
|
|
||||||
|
|
||||||
|
# @handle_update.handle()
|
||||||
|
|
||||||
|
|
||||||
|
@handle_idiom.handle()
|
||||||
|
async def _(matcher: Matcher, user_id: UserId, matched: Dict[str, Any] = RegexDict()):
|
||||||
|
game = games[user_id]
|
||||||
|
set_timeout(matcher, user_id)
|
||||||
|
|
||||||
|
idiom = str(matched["idiom"])
|
||||||
|
result = game.guess(idiom)
|
||||||
|
|
||||||
|
if result in [GuessResult.WIN, GuessResult.LOSS]:
|
||||||
|
stop_game(user_id)
|
||||||
|
msg = Text(
|
||||||
|
(
|
||||||
|
"恭喜你猜出了成语!"
|
||||||
|
if result == GuessResult.WIN
|
||||||
|
else "很遗憾,没有人猜出来呢"
|
||||||
|
)
|
||||||
|
+ f"\n{game.result}"
|
||||||
|
) + Image(raw=await run_sync(game.draw)())
|
||||||
|
await msg.send()
|
||||||
|
|
||||||
|
elif result == GuessResult.DUPLICATE:
|
||||||
|
await matcher.finish("你已经猜过这个成语了呢")
|
||||||
|
|
||||||
|
elif result == GuessResult.ILLEGAL:
|
||||||
|
await matcher.finish(f"你确定“{idiom}”是个成语吗?")
|
||||||
|
|
||||||
|
else:
|
||||||
|
await UniMessage.image(raw=await run_sync(game.draw)()).send()
|
||||||
|
|
||||||
|
|
||||||
|
@handle_answer.handle()
|
||||||
|
async def _(
|
||||||
|
result: Arparma,
|
||||||
|
matcher: Matcher,
|
||||||
|
user_id: UserId,
|
||||||
|
):
|
||||||
|
|
||||||
|
if result.options["list"].value:
|
||||||
|
await handle_answer.finish(
|
||||||
|
UniMessage.text(
|
||||||
|
"\n".join("{}-{}".format(i, j.idiom) for i, j in games.items())
|
||||||
|
)
|
||||||
|
)
|
||||||
|
return
|
||||||
|
|
||||||
|
try:
|
||||||
|
if result.options["group"].args["group"] == "Now":
|
||||||
|
session_numstr = user_id
|
||||||
|
else:
|
||||||
|
session_numstr = "qq_OneBot V11_2378756507_{}".format(
|
||||||
|
result.options["group"].args["group"]
|
||||||
|
)
|
||||||
|
except:
|
||||||
|
session_numstr = user_id
|
||||||
|
|
||||||
|
if session_numstr in games.keys():
|
||||||
|
await handle_answer.finish(UniMessage.text(games[session_numstr].idiom))
|
||||||
|
else:
|
||||||
|
await handle_answer.finish(
|
||||||
|
UniMessage.text("{} 不存在开局的游戏".format(session_numstr))
|
||||||
|
)
|
10
src/plugins/trimo_plugin_handle/config.py
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
from nonebot import get_plugin_config
|
||||||
|
from pydantic import BaseModel
|
||||||
|
|
||||||
|
|
||||||
|
class Config(BaseModel):
|
||||||
|
handle_strict_mode: bool = False
|
||||||
|
handle_color_enhance: bool = False
|
||||||
|
|
||||||
|
|
||||||
|
handle_config = get_plugin_config(Config)
|
284
src/plugins/trimo_plugin_handle/data_source.py
Normal file
@ -0,0 +1,284 @@
|
|||||||
|
from dataclasses import dataclass
|
||||||
|
from enum import Enum
|
||||||
|
from io import BytesIO
|
||||||
|
from typing import List, Optional, Tuple
|
||||||
|
|
||||||
|
from PIL import Image, ImageDraw
|
||||||
|
from PIL.Image import Image as IMG
|
||||||
|
|
||||||
|
from .config import handle_config
|
||||||
|
from .utils import get_pinyin, legal_idiom, load_font, save_jpg
|
||||||
|
|
||||||
|
|
||||||
|
class GuessResult(Enum):
|
||||||
|
WIN = 0 # 猜出正确成语
|
||||||
|
LOSS = 1 # 达到最大可猜次数,未猜出正确成语
|
||||||
|
DUPLICATE = 2 # 成语重复
|
||||||
|
ILLEGAL = 3 # 成语不合法
|
||||||
|
|
||||||
|
|
||||||
|
class GuessState(Enum):
|
||||||
|
CORRECT = 0 # 存在且位置正确
|
||||||
|
EXIST = 1 # 存在但位置不正确
|
||||||
|
WRONG = 2 # 不存在
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class ColorGroup:
|
||||||
|
bg_color: str # 背景颜色
|
||||||
|
block_color: str # 方块颜色
|
||||||
|
correct_color: str # 存在且位置正确时的颜色
|
||||||
|
exist_color: str # 存在但位置不正确时的颜色
|
||||||
|
wrong_color_pinyin: str # 不存在时的颜色
|
||||||
|
wrong_color_char: str # 不存在时的颜色
|
||||||
|
|
||||||
|
|
||||||
|
NORMAL_COLOR = ColorGroup(
|
||||||
|
"#ffffff", "#f7f8f9", "#1d9c9c", "#de7525", "#b4b8be", "#5d6673"
|
||||||
|
)
|
||||||
|
|
||||||
|
ENHANCED_COLOR = ColorGroup(
|
||||||
|
"#ffffff", "#f7f8f9", "#5ba554", "#ff46ff", "#b4b8be", "#5d6673"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class Handle:
|
||||||
|
def __init__(self, idiom: str, explanation: str, strict: bool = False):
|
||||||
|
self.idiom: str = idiom # 成语
|
||||||
|
self.explanation: str = explanation # 释义
|
||||||
|
self.strict: bool = strict # 是否判断输入词语为成语
|
||||||
|
self.result = f"【成语】:{idiom}\n【释义】:{explanation}"
|
||||||
|
self.pinyin: List[Tuple[str, str, str]] = get_pinyin(idiom) # 拼音
|
||||||
|
self.length = 4
|
||||||
|
self.times: int = 10 # 可猜次数
|
||||||
|
self.guessed_idiom: List[str] = [] # 记录已猜成语
|
||||||
|
self.guessed_pinyin: List[List[Tuple[str, str, str]]] = [] # 记录已猜成语的拼音
|
||||||
|
|
||||||
|
self.block_size = (160, 160) # 文字块尺寸
|
||||||
|
self.block_padding = (20, 20) # 文字块之间间距
|
||||||
|
self.padding = (40, 40) # 边界间距
|
||||||
|
font_size_char = 60 # 汉字字体大小
|
||||||
|
font_size_pinyin = 30 # 拼音字体大小
|
||||||
|
font_size_tone = 22 # 声调字体大小
|
||||||
|
self.font_char = load_font("NotoSerifSC-Regular.otf", font_size_char)
|
||||||
|
self.font_pinyin = load_font("NotoSansMono-Regular.ttf", font_size_pinyin)
|
||||||
|
self.font_tone = load_font("NotoSansMono-Regular.ttf", font_size_tone)
|
||||||
|
|
||||||
|
self.colors = (
|
||||||
|
ENHANCED_COLOR if handle_config.handle_color_enhance else NORMAL_COLOR
|
||||||
|
)
|
||||||
|
|
||||||
|
def guess(self, idiom: str) -> Optional[GuessResult]:
|
||||||
|
if self.strict and not legal_idiom(idiom):
|
||||||
|
return GuessResult.ILLEGAL
|
||||||
|
if idiom in self.guessed_idiom:
|
||||||
|
return GuessResult.DUPLICATE
|
||||||
|
self.guessed_idiom.append(idiom)
|
||||||
|
self.guessed_pinyin.append(get_pinyin(idiom))
|
||||||
|
if idiom == self.idiom:
|
||||||
|
return GuessResult.WIN
|
||||||
|
if len(self.guessed_idiom) == self.times:
|
||||||
|
return GuessResult.LOSS
|
||||||
|
|
||||||
|
def draw_block(
|
||||||
|
self,
|
||||||
|
block_color: str,
|
||||||
|
char: str = "",
|
||||||
|
char_color: str = "",
|
||||||
|
initial: str = "",
|
||||||
|
initial_color: str = "",
|
||||||
|
final: str = "",
|
||||||
|
final_color: str = "",
|
||||||
|
tone: str = "",
|
||||||
|
tone_color: str = "",
|
||||||
|
underline: bool = False,
|
||||||
|
underline_color: str = "",
|
||||||
|
) -> IMG:
|
||||||
|
block = Image.new("RGB", self.block_size, block_color)
|
||||||
|
if not char:
|
||||||
|
return block
|
||||||
|
draw = ImageDraw.Draw(block)
|
||||||
|
|
||||||
|
char_size = self.font_char.getbbox(char)[2:]
|
||||||
|
x = (self.block_size[0] - char_size[0]) / 2
|
||||||
|
y = (self.block_size[1] - char_size[1]) / 5 * 3
|
||||||
|
draw.text((x, y), char, font=self.font_char, fill=char_color)
|
||||||
|
|
||||||
|
space = 5
|
||||||
|
need_space = bool(initial and final)
|
||||||
|
py_length = self.font_pinyin.getlength(initial + final)
|
||||||
|
if need_space:
|
||||||
|
py_length += space
|
||||||
|
py_start = (self.block_size[0] - py_length) / 2
|
||||||
|
x = py_start
|
||||||
|
y = self.block_size[0] / 8
|
||||||
|
draw.text((x, y), initial, font=self.font_pinyin, fill=initial_color)
|
||||||
|
x += self.font_pinyin.getlength(initial)
|
||||||
|
if need_space:
|
||||||
|
x += space
|
||||||
|
draw.text((x, y), final, font=self.font_pinyin, fill=final_color)
|
||||||
|
|
||||||
|
tone_size = self.font_tone.getbbox(tone)[2:]
|
||||||
|
x = (self.block_size[0] + py_length) / 2 + tone_size[0] / 3
|
||||||
|
y -= tone_size[1] / 3
|
||||||
|
draw.text((x, y), tone, font=self.font_tone, fill=tone_color)
|
||||||
|
|
||||||
|
if underline:
|
||||||
|
x = py_start
|
||||||
|
py_size = self.font_pinyin.getbbox(initial + final)[2:]
|
||||||
|
y = self.block_size[0] / 8 + py_size[1] + 2
|
||||||
|
draw.line((x, y, x + py_length, y), fill=underline_color, width=1)
|
||||||
|
y += 3
|
||||||
|
draw.line((x, y, x + py_length, y), fill=underline_color, width=1)
|
||||||
|
|
||||||
|
return block
|
||||||
|
|
||||||
|
def draw(self) -> BytesIO:
|
||||||
|
rows = min(len(self.guessed_idiom) + 1, self.times)
|
||||||
|
board_w = self.length * self.block_size[0]
|
||||||
|
board_w += (self.length - 1) * self.block_padding[0] + 2 * self.padding[0]
|
||||||
|
board_h = rows * self.block_size[1]
|
||||||
|
board_h += (rows - 1) * self.block_padding[1] + 2 * self.padding[1]
|
||||||
|
board_size = (board_w, board_h)
|
||||||
|
board = Image.new("RGB", board_size, self.colors.bg_color)
|
||||||
|
|
||||||
|
def get_states(guessed: List[str], answer: List[str]) -> List[GuessState]:
|
||||||
|
states = []
|
||||||
|
incorrect = []
|
||||||
|
for i in range(self.length):
|
||||||
|
if guessed[i] != answer[i]:
|
||||||
|
incorrect.append(answer[i])
|
||||||
|
else:
|
||||||
|
incorrect.append("_")
|
||||||
|
for i in range(self.length):
|
||||||
|
if guessed[i] == answer[i]:
|
||||||
|
states.append(GuessState.CORRECT)
|
||||||
|
elif guessed[i] in incorrect:
|
||||||
|
states.append(GuessState.EXIST)
|
||||||
|
incorrect[incorrect.index(guessed[i])] = "_"
|
||||||
|
else:
|
||||||
|
states.append(GuessState.WRONG)
|
||||||
|
return states
|
||||||
|
|
||||||
|
def get_pinyin_color(state: GuessState) -> str:
|
||||||
|
if state == GuessState.CORRECT:
|
||||||
|
return self.colors.correct_color
|
||||||
|
elif state == GuessState.EXIST:
|
||||||
|
return self.colors.exist_color
|
||||||
|
else:
|
||||||
|
return self.colors.wrong_color_pinyin
|
||||||
|
|
||||||
|
def get_char_color(state: GuessState) -> str:
|
||||||
|
if state == GuessState.CORRECT:
|
||||||
|
return self.colors.correct_color
|
||||||
|
elif state == GuessState.EXIST:
|
||||||
|
return self.colors.exist_color
|
||||||
|
else:
|
||||||
|
return self.colors.wrong_color_char
|
||||||
|
|
||||||
|
def block_pos(row: int, col: int) -> Tuple[int, int]:
|
||||||
|
x = self.padding[0] + (self.block_size[0] + self.block_padding[0]) * col
|
||||||
|
y = self.padding[1] + (self.block_size[1] + self.block_padding[1]) * row
|
||||||
|
return x, y
|
||||||
|
|
||||||
|
for i in range(len(self.guessed_idiom)):
|
||||||
|
idiom = self.guessed_idiom[i]
|
||||||
|
pinyin = self.guessed_pinyin[i]
|
||||||
|
char_states = get_states(list(idiom), list(self.idiom))
|
||||||
|
initial_states = get_states(
|
||||||
|
[p[0] for p in pinyin], [p[0] for p in self.pinyin]
|
||||||
|
)
|
||||||
|
final_states = get_states(
|
||||||
|
[p[1] for p in pinyin], [p[1] for p in self.pinyin]
|
||||||
|
)
|
||||||
|
tone_states = get_states(
|
||||||
|
[p[2] for p in pinyin], [p[2] for p in self.pinyin]
|
||||||
|
)
|
||||||
|
underline_states = get_states(
|
||||||
|
[p[0] + p[1] for p in pinyin], [p[0] + p[1] for p in self.pinyin]
|
||||||
|
)
|
||||||
|
for j in range(self.length):
|
||||||
|
char = idiom[j]
|
||||||
|
i2, f2, t2 = pinyin[j]
|
||||||
|
if char == self.idiom[j]:
|
||||||
|
block_color = self.colors.correct_color
|
||||||
|
char_color = initial_color = final_color = tone_color = (
|
||||||
|
self.colors.bg_color
|
||||||
|
)
|
||||||
|
underline = False
|
||||||
|
underline_color = ""
|
||||||
|
else:
|
||||||
|
block_color = self.colors.block_color
|
||||||
|
char_color = get_char_color(char_states[j])
|
||||||
|
initial_color = get_pinyin_color(initial_states[j])
|
||||||
|
final_color = get_pinyin_color(final_states[j])
|
||||||
|
tone_color = get_pinyin_color(tone_states[j])
|
||||||
|
underline_color = get_pinyin_color(underline_states[j])
|
||||||
|
underline = underline_color in (
|
||||||
|
self.colors.correct_color,
|
||||||
|
self.colors.exist_color,
|
||||||
|
)
|
||||||
|
block = self.draw_block(
|
||||||
|
block_color,
|
||||||
|
char,
|
||||||
|
char_color,
|
||||||
|
i2,
|
||||||
|
initial_color,
|
||||||
|
f2,
|
||||||
|
final_color,
|
||||||
|
t2,
|
||||||
|
tone_color,
|
||||||
|
underline,
|
||||||
|
underline_color,
|
||||||
|
)
|
||||||
|
board.paste(block, block_pos(i, j))
|
||||||
|
|
||||||
|
for i in range(len(self.guessed_idiom), rows):
|
||||||
|
for j in range(self.length):
|
||||||
|
block = self.draw_block(self.colors.block_color)
|
||||||
|
board.paste(block, block_pos(i, j))
|
||||||
|
|
||||||
|
return save_jpg(board)
|
||||||
|
|
||||||
|
def draw_hint(self) -> BytesIO:
|
||||||
|
guessed_char = set("".join(self.guessed_idiom))
|
||||||
|
guessed_initial = set()
|
||||||
|
guessed_final = set()
|
||||||
|
guessed_tone = set()
|
||||||
|
for pinyin in self.guessed_pinyin:
|
||||||
|
for p in pinyin:
|
||||||
|
guessed_initial.add(p[0])
|
||||||
|
guessed_final.add(p[1])
|
||||||
|
guessed_tone.add(p[2])
|
||||||
|
|
||||||
|
board_w = self.length * self.block_size[0]
|
||||||
|
board_w += (self.length - 1) * self.block_padding[0] + 2 * self.padding[0]
|
||||||
|
board_h = self.block_size[1] + 2 * self.padding[1]
|
||||||
|
board = Image.new("RGB", (board_w, board_h), self.colors.bg_color)
|
||||||
|
|
||||||
|
for i in range(self.length):
|
||||||
|
char = self.idiom[i]
|
||||||
|
hi, hf, ht = self.pinyin[i]
|
||||||
|
color = char_c = initial_c = final_c = tone_c = self.colors.correct_color
|
||||||
|
if char not in guessed_char:
|
||||||
|
char = "?"
|
||||||
|
color = self.colors.block_color
|
||||||
|
char_c = self.colors.wrong_color_char
|
||||||
|
else:
|
||||||
|
char_c = initial_c = final_c = tone_c = self.colors.bg_color
|
||||||
|
if hi not in guessed_initial:
|
||||||
|
hi = "?"
|
||||||
|
initial_c = self.colors.wrong_color_pinyin
|
||||||
|
if hf not in guessed_final:
|
||||||
|
hf = "?"
|
||||||
|
final_c = self.colors.wrong_color_pinyin
|
||||||
|
if ht not in guessed_tone:
|
||||||
|
ht = "?"
|
||||||
|
tone_c = self.colors.wrong_color_pinyin
|
||||||
|
block = self.draw_block(
|
||||||
|
color, char, char_c, hi, initial_c, hf, final_c, ht, tone_c
|
||||||
|
)
|
||||||
|
x = self.padding[0] + (self.block_size[0] + self.block_padding[0]) * i
|
||||||
|
y = self.padding[1]
|
||||||
|
board.paste(block, (x, y))
|
||||||
|
return save_jpg(board)
|
@ -0,0 +1,21 @@
|
|||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2022 MeetWq
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
113
src/plugins/trimo_plugin_handle/utils.py
Normal file
@ -0,0 +1,113 @@
|
|||||||
|
import json
|
||||||
|
import random
|
||||||
|
from io import BytesIO
|
||||||
|
from pathlib import Path
|
||||||
|
from typing import Dict, List, Tuple
|
||||||
|
|
||||||
|
# from watchdog.observers import Observer
|
||||||
|
# from watchdog.events import FileSystemEventHandler, FileModifiedEvent
|
||||||
|
|
||||||
|
from PIL import ImageFont
|
||||||
|
from PIL.Image import Image as IMG
|
||||||
|
from PIL.ImageFont import FreeTypeFont
|
||||||
|
from pypinyin import Style, pinyin
|
||||||
|
|
||||||
|
resource_dir = Path(__file__).parent / "resources"
|
||||||
|
fonts_dir = resource_dir / "fonts"
|
||||||
|
data_dir = resource_dir / "data"
|
||||||
|
idiom_path = data_dir / "idioms.txt"
|
||||||
|
answer_path = data_dir / "answers.json"
|
||||||
|
|
||||||
|
|
||||||
|
LEGAL_PHRASES = [
|
||||||
|
idiom.strip() for idiom in idiom_path.open("r", encoding="utf-8").readlines()
|
||||||
|
]
|
||||||
|
ANSWER_PHRASES: List[Dict[str, str]] = json.load(answer_path.open("r", encoding="utf-8"))
|
||||||
|
|
||||||
|
|
||||||
|
# class LegalPhrasesModifiedHandler(FileSystemEventHandler):
|
||||||
|
# """
|
||||||
|
# Handler for resource file changes
|
||||||
|
# """
|
||||||
|
|
||||||
|
# def on_modified(self, event):
|
||||||
|
# print(f"{event.src_path} modified, reloading resource...")
|
||||||
|
# if "idioms.txt" in event.src_path:
|
||||||
|
# global LEGAL_PHRASES
|
||||||
|
# LEGAL_PHRASES = [
|
||||||
|
# idiom.strip()
|
||||||
|
# for idiom in idiom_path.open("r", encoding="utf-8").readlines()
|
||||||
|
# ]
|
||||||
|
# elif "answers.json" in event.src_path:
|
||||||
|
# global ANSWER_PHRASES
|
||||||
|
# ANSWER_PHRASES = json.load(
|
||||||
|
# answer_path.open("r", encoding="utf-8")
|
||||||
|
# )
|
||||||
|
|
||||||
|
|
||||||
|
# Observer().schedule(
|
||||||
|
# LegalPhrasesModifiedHandler(),
|
||||||
|
# data_dir,
|
||||||
|
# recursive=False,
|
||||||
|
# event_filter=FileModifiedEvent,
|
||||||
|
# )
|
||||||
|
|
||||||
|
|
||||||
|
def legal_idiom(word: str) -> bool:
|
||||||
|
return word in LEGAL_PHRASES
|
||||||
|
|
||||||
|
|
||||||
|
def random_idiom() -> Tuple[str, str]:
|
||||||
|
answer = random.choice(ANSWER_PHRASES)
|
||||||
|
return answer["word"], answer["explanation"]
|
||||||
|
|
||||||
|
|
||||||
|
# fmt: off
|
||||||
|
# 声母
|
||||||
|
INITIALS = [
|
||||||
|
"zh", "z", "y", "x", "w", "t", "sh", "s", "r", "q", "p",
|
||||||
|
"n", "m", "l", "k", "j", "h", "g", "f", "d", "ch", "c", "b"
|
||||||
|
]
|
||||||
|
# 韵母
|
||||||
|
FINALS = [
|
||||||
|
"ün", "üe", "üan", "ü", "uo", "un", "ui", "ue", "uang",
|
||||||
|
"uan", "uai","ua", "ou", "iu", "iong", "ong", "io", "ing",
|
||||||
|
"in", "ie", "iao", "iang", "ian", "ia", "er", "eng", "en",
|
||||||
|
"ei", "ao", "ang", "an", "ai", "u", "o", "i", "e", "a"
|
||||||
|
]
|
||||||
|
# fmt: on
|
||||||
|
|
||||||
|
|
||||||
|
def get_pinyin(idiom: str) -> List[Tuple[str, str, str]]:
|
||||||
|
pys = pinyin(idiom, style=Style.TONE3, v_to_u=True)
|
||||||
|
results = []
|
||||||
|
for p in pys:
|
||||||
|
py = p[0]
|
||||||
|
if py[-1].isdigit():
|
||||||
|
tone = py[-1]
|
||||||
|
py = py[:-1]
|
||||||
|
else:
|
||||||
|
tone = ""
|
||||||
|
initial = ""
|
||||||
|
for i in INITIALS:
|
||||||
|
if py.startswith(i):
|
||||||
|
initial = i
|
||||||
|
break
|
||||||
|
final = ""
|
||||||
|
for f in FINALS:
|
||||||
|
if py.endswith(f):
|
||||||
|
final = f
|
||||||
|
break
|
||||||
|
results.append((initial, final, tone)) # 声母,韵母,声调
|
||||||
|
return results
|
||||||
|
|
||||||
|
|
||||||
|
def save_jpg(frame: IMG) -> BytesIO:
|
||||||
|
output = BytesIO()
|
||||||
|
frame = frame.convert("RGB")
|
||||||
|
frame.save(output, format="jpeg")
|
||||||
|
return output
|
||||||
|
|
||||||
|
|
||||||
|
def load_font(name: str, fontsize: int) -> FreeTypeFont:
|
||||||
|
return ImageFont.truetype(str(fonts_dir / name), fontsize, encoding="utf-8")
|
13
src/plugins/trimo_plugin_msctconverter/LICENSE.md
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
|
||||||
|
## 版权声明
|
||||||
|
|
||||||
|
本插件由 汉钰律许可协议 授权开源,兼容并继承自 Apache 2.0 许可协议。
|
||||||
|
|
||||||
|
Copyright © 2024 金羿("Eilles Wan") & 诸葛亮与八卦阵("bgArray") with TriM Org.
|
||||||
|
|
||||||
|
版权所有 © 2024 金羿("Eilles Wan") & 诸葛亮与八卦阵("bgArray") with TriM Org.
|
||||||
|
|
||||||
|
伶伦转换器(trimo_plugin_msctconverter)根据 第一版 汉钰律许可协议(“本协议”)授权。\
|
||||||
|
任何人皆可从以下地址获得本协议副本:[汉钰律许可协议 第一版](https://gitee.com/EillesWan/YulvLicenses/raw/master/%E6%B1%89%E9%92%B0%E5%BE%8B%E8%AE%B8%E5%8F%AF%E5%8D%8F%E8%AE%AE/%E6%B1%89%E9%92%B0%E5%BE%8B%E8%AE%B8%E5%8F%AF%E5%8D%8F%E8%AE%AE.MD)。\
|
||||||
|
若非因法律要求或经过了特殊准许,此作品在根据本协议“原样”提供的基础上,不予提供任何形式的担保、任何明示、任何暗示或类似承诺。也就是说,用户将自行承担因此作品的质量或性能问题而产生的全部风险。\
|
||||||
|
详细的准许和限制条款请见原协议文本。
|
@ -0,0 +1,218 @@
|
|||||||
|
**注意,以下条款或版权声明应当且必须是高于此仓库中任何其他声明的**
|
||||||
|
|
||||||
|
1. 伶伦的全部开发者享有其完整版权,其开发者可以在任一时刻终止以后伶伦源代码开放,若经由其开发者授予特殊权利,则授权对象可以将源代码进行特定的被特殊授权的操作
|
||||||
|
2. 伶伦或(及)其代码允许在 Apache2.0 协议的条款与说明下进行非商业使用
|
||||||
|
3. 除部分代码特殊声明外,伶伦允许对其或(及)其代码进行商业化使用,但是需要经过伶伦主要开发者(诸葛亮与八卦阵、金羿)的一致授权,同时,授权对象在商业化授权的使用过程中必须依照 Apache2.0 协议的条款与说明
|
||||||
|
4. 若存在对于伶伦包含的部分代码的特殊开源声明,则此部分代码依照其特定的开源方式授权,但若此部分代码经由此部分代码的主要开发者一致特殊授权后商用,则授权对象在商用时依照此部分的开发者所准许的方式(或条款)进行商用,在经此部分的开发者准许后无其他特殊授权时,默认依照 Apache2.0 协议进行商业化使用
|
||||||
|
5. Apache2.0 协议的英文原文副本可见下文
|
||||||
|
|
||||||
|
> The English Translation of the TERMS AND CONDITIONS above is listed below
|
||||||
|
>
|
||||||
|
> This translated version is for reference only and has no legal effect.
|
||||||
|
>
|
||||||
|
> The version with legal effect is the Chinese version above.
|
||||||
|
|
||||||
|
**Note, The TERMS AND CONDITIONS below should and must be above all others in this repository**
|
||||||
|
|
||||||
|
1. _Linglun Studio_ is fully copyrighted by all its developers, the developers have the right to make _Linglun Studio_ close sourced at any time. Operations are permitted under specific terms instructed by its developer(s).
|
||||||
|
2. Non-commercial use of _Linglun Studio_ and(or) its source code is permitted under Apache License 2.0.
|
||||||
|
3. Commercial use of _Linglun Studio_ is permitted under Apache License 2.0 with the unanimous permission of the steering developers of _Linglun Studio_ (*bgArray*诸葛亮与八卦阵 and *Eilles*金羿).
|
||||||
|
4. _Linglun Studio_ is open sourced under priority given:
|
||||||
|
1. License granted by the core developer(s) of a section after negotiation.
|
||||||
|
2. Explicitly stated license.
|
||||||
|
3. Apache 2.0 License.
|
||||||
|
5. A copy of the original Apache Lisence 2.0 can be found below.
|
||||||
|
|
||||||
|
```text
|
||||||
|
Apache License
|
||||||
|
Version 2.0, January 2004
|
||||||
|
http://www.apache.org/licenses/
|
||||||
|
|
||||||
|
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||||
|
|
||||||
|
1. Definitions.
|
||||||
|
|
||||||
|
"License" shall mean the terms and conditions for use, reproduction,
|
||||||
|
and distribution as defined by Sections 1 through 9 of this document.
|
||||||
|
|
||||||
|
"Licensor" shall mean the copyright owner or entity authorized by
|
||||||
|
the copyright owner that is granting the License.
|
||||||
|
|
||||||
|
"Legal Entity" shall mean the union of the acting entity and all
|
||||||
|
other entities that control, are controlled by, or are under common
|
||||||
|
control with that entity. For the purposes of this definition,
|
||||||
|
"control" means (i) the power, direct or indirect, to cause the
|
||||||
|
direction or management of such entity, whether by contract or
|
||||||
|
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||||
|
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||||
|
|
||||||
|
"You" (or "Your") shall mean an individual or Legal Entity
|
||||||
|
exercising permissions granted by this License.
|
||||||
|
|
||||||
|
"Source" form shall mean the preferred form for making modifications,
|
||||||
|
including but not limited to software source code, documentation
|
||||||
|
source, and configuration files.
|
||||||
|
|
||||||
|
"Object" form shall mean any form resulting from mechanical
|
||||||
|
transformation or translation of a Source form, including but
|
||||||
|
not limited to compiled object code, generated documentation,
|
||||||
|
and conversions to other media types.
|
||||||
|
|
||||||
|
"Work" shall mean the work of authorship, whether in Source or
|
||||||
|
Object form, made available under the License, as indicated by a
|
||||||
|
copyright notice that is included in or attached to the work
|
||||||
|
(an example is provided in the Appendix below).
|
||||||
|
|
||||||
|
"Derivative Works" shall mean any work, whether in Source or Object
|
||||||
|
form, that is based on (or derived from) the Work and for which the
|
||||||
|
editorial revisions, annotations, elaborations, or other modifications
|
||||||
|
represent, as a whole, an original work of authorship. For the purposes
|
||||||
|
of this License, Derivative Works shall not include works that remain
|
||||||
|
separable from, or merely link (or bind by name) to the interfaces of,
|
||||||
|
the Work and Derivative Works thereof.
|
||||||
|
|
||||||
|
"Contribution" shall mean any work of authorship, including
|
||||||
|
the original version of the Work and any modifications or additions
|
||||||
|
to that Work or Derivative Works thereof, that is intentionally
|
||||||
|
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||||
|
or by an individual or Legal Entity authorized to submit on behalf of
|
||||||
|
the copyright owner. For the purposes of this definition, "submitted"
|
||||||
|
means any form of electronic, verbal, or written communication sent
|
||||||
|
to the Licensor or its representatives, including but not limited to
|
||||||
|
communication on electronic mailing lists, source code control systems,
|
||||||
|
and issue tracking systems that are managed by, or on behalf of, the
|
||||||
|
Licensor for the purpose of discussing and improving the Work, but
|
||||||
|
excluding communication that is conspicuously marked or otherwise
|
||||||
|
designated in writing by the copyright owner as "Not a Contribution."
|
||||||
|
|
||||||
|
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||||
|
on behalf of whom a Contribution has been received by Licensor and
|
||||||
|
subsequently incorporated within the Work.
|
||||||
|
|
||||||
|
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||||
|
this License, each Contributor hereby grants to You a perpetual,
|
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||||
|
copyright license to reproduce, prepare Derivative Works of,
|
||||||
|
publicly display, publicly perform, sublicense, and distribute the
|
||||||
|
Work and such Derivative Works in Source or Object form.
|
||||||
|
|
||||||
|
3. Grant of Patent License. Subject to the terms and conditions of
|
||||||
|
this License, each Contributor hereby grants to You a perpetual,
|
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||||
|
(except as stated in this section) patent license to make, have made,
|
||||||
|
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||||
|
where such license applies only to those patent claims licensable
|
||||||
|
by such Contributor that are necessarily infringed by their
|
||||||
|
Contribution(s) alone or by combination of their Contribution(s)
|
||||||
|
with the Work to which such Contribution(s) was submitted. If You
|
||||||
|
institute patent litigation against any entity (including a
|
||||||
|
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||||
|
or a Contribution incorporated within the Work constitutes direct
|
||||||
|
or contributory patent infringement, then any patent licenses
|
||||||
|
granted to You under this License for that Work shall terminate
|
||||||
|
as of the date such litigation is filed.
|
||||||
|
|
||||||
|
4. Redistribution. You may reproduce and distribute copies of the
|
||||||
|
Work or Derivative Works thereof in any medium, with or without
|
||||||
|
modifications, and in Source or Object form, provided that You
|
||||||
|
meet the following conditions:
|
||||||
|
|
||||||
|
(a) You must give any other recipients of the Work or
|
||||||
|
Derivative Works a copy of this License; and
|
||||||
|
|
||||||
|
(b) You must cause any modified files to carry prominent notices
|
||||||
|
stating that You changed the files; and
|
||||||
|
|
||||||
|
(c) You must retain, in the Source form of any Derivative Works
|
||||||
|
that You distribute, all copyright, patent, trademark, and
|
||||||
|
attribution notices from the Source form of the Work,
|
||||||
|
excluding those notices that do not pertain to any part of
|
||||||
|
the Derivative Works; and
|
||||||
|
|
||||||
|
(d) If the Work includes a "NOTICE" text file as part of its
|
||||||
|
distribution, then any Derivative Works that You distribute must
|
||||||
|
include a readable copy of the attribution notices contained
|
||||||
|
within such NOTICE file, excluding those notices that do not
|
||||||
|
pertain to any part of the Derivative Works, in at least one
|
||||||
|
of the following places: within a NOTICE text file distributed
|
||||||
|
as part of the Derivative Works; within the Source form or
|
||||||
|
documentation, if provided along with the Derivative Works; or,
|
||||||
|
within a display generated by the Derivative Works, if and
|
||||||
|
wherever such third-party notices normally appear. The contents
|
||||||
|
of the NOTICE file are for informational purposes only and
|
||||||
|
do not modify the License. You may add Your own attribution
|
||||||
|
notices within Derivative Works that You distribute, alongside
|
||||||
|
or as an addendum to the NOTICE text from the Work, provided
|
||||||
|
that such additional attribution notices cannot be construed
|
||||||
|
as modifying the License.
|
||||||
|
|
||||||
|
You may add Your own copyright statement to Your modifications and
|
||||||
|
may provide additional or different license terms and conditions
|
||||||
|
for use, reproduction, or distribution of Your modifications, or
|
||||||
|
for any such Derivative Works as a whole, provided Your use,
|
||||||
|
reproduction, and distribution of the Work otherwise complies with
|
||||||
|
the conditions stated in this License.
|
||||||
|
|
||||||
|
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||||
|
any Contribution intentionally submitted for inclusion in the Work
|
||||||
|
by You to the Licensor shall be under the terms and conditions of
|
||||||
|
this License, without any additional terms or conditions.
|
||||||
|
Notwithstanding the above, nothing herein shall supersede or modify
|
||||||
|
the terms of any separate license agreement you may have executed
|
||||||
|
with Licensor regarding such Contributions.
|
||||||
|
|
||||||
|
6. Trademarks. This License does not grant permission to use the trade
|
||||||
|
names, trademarks, service marks, or product names of the Licensor,
|
||||||
|
except as required for reasonable and customary use in describing the
|
||||||
|
origin of the Work and reproducing the content of the NOTICE file.
|
||||||
|
|
||||||
|
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||||
|
agreed to in writing, Licensor provides the Work (and each
|
||||||
|
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||||
|
implied, including, without limitation, any warranties or conditions
|
||||||
|
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||||
|
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||||
|
appropriateness of using or redistributing the Work and assume any
|
||||||
|
risks associated with Your exercise of permissions under this License.
|
||||||
|
|
||||||
|
8. Limitation of Liability. In no event and under no legal theory,
|
||||||
|
whether in tort (including negligence), contract, or otherwise,
|
||||||
|
unless required by applicable law (such as deliberate and grossly
|
||||||
|
negligent acts) or agreed to in writing, shall any Contributor be
|
||||||
|
liable to You for damages, including any direct, indirect, special,
|
||||||
|
incidental, or consequential damages of any character arising as a
|
||||||
|
result of this License or out of the use or inability to use the
|
||||||
|
Work (including but not limited to damages for loss of goodwill,
|
||||||
|
work stoppage, computer failure or malfunction, or any and all
|
||||||
|
other commercial damages or losses), even if such Contributor
|
||||||
|
has been advised of the possibility of such damages.
|
||||||
|
|
||||||
|
9. Accepting Warranty or Additional Liability. While redistributing
|
||||||
|
the Work or Derivative Works thereof, You may choose to offer,
|
||||||
|
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||||
|
or other liability obligations and/or rights consistent with this
|
||||||
|
License. However, in accepting such obligations, You may act only
|
||||||
|
on Your own behalf and on Your sole responsibility, not on behalf
|
||||||
|
of any other Contributor, and only if You agree to indemnify,
|
||||||
|
defend, and hold each Contributor harmless for any liability
|
||||||
|
incurred by, or claims asserted against, such Contributor by reason
|
||||||
|
of your accepting any such warranty or additional liability.
|
||||||
|
|
||||||
|
END OF TERMS AND CONDITIONS
|
||||||
|
|
||||||
|
|
||||||
|
Copyright © 2024 金羿("Eilles Wan") & 诸葛亮与八卦阵("bgArray") with TriM Org.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
```
|
62
src/plugins/trimo_plugin_msctconverter/checker.py
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def isin(sth: str, range_list: dict):
|
||||||
|
sth = sth.lower()
|
||||||
|
for bool_value, res_list in range_list.items():
|
||||||
|
if sth in res_list:
|
||||||
|
return bool_value
|
||||||
|
raise ValueError(
|
||||||
|
"不在可选范围内:{}".format([j for i in range_list.values() for j in i])
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
# 真假字符串判断
|
||||||
|
def bool_str(sth: str):
|
||||||
|
try:
|
||||||
|
return bool(float(sth))
|
||||||
|
except ValueError:
|
||||||
|
if str(sth).lower() in ("true", "真", "是", "y", "t"):
|
||||||
|
return True
|
||||||
|
elif str(sth).lower() in ("false", "假", "否", "f", "n"):
|
||||||
|
return False
|
||||||
|
else:
|
||||||
|
raise ValueError("非法逻辑字串")
|
||||||
|
|
||||||
|
|
||||||
|
def float_str(sth: str):
|
||||||
|
try:
|
||||||
|
return float(sth)
|
||||||
|
except ValueError:
|
||||||
|
return float(
|
||||||
|
sth.replace("壹", "1")
|
||||||
|
.replace("贰", "2")
|
||||||
|
.replace("叁", "3")
|
||||||
|
.replace("肆", "4")
|
||||||
|
.replace("伍", "5")
|
||||||
|
.replace("陆", "6")
|
||||||
|
.replace("柒", "7")
|
||||||
|
.replace("捌", "8")
|
||||||
|
.replace("玖", "9")
|
||||||
|
.replace("零", "0")
|
||||||
|
.replace("一", "1")
|
||||||
|
.replace("二", "2")
|
||||||
|
.replace("三", "3")
|
||||||
|
.replace("四", "4")
|
||||||
|
.replace("五", "5")
|
||||||
|
.replace("六", "6")
|
||||||
|
.replace("七", "7")
|
||||||
|
.replace("八", "8")
|
||||||
|
.replace("九", "9")
|
||||||
|
.replace("〇", "0")
|
||||||
|
.replace("洞", "0")
|
||||||
|
.replace("幺", "1")
|
||||||
|
.replace("俩", "2")
|
||||||
|
.replace("两", "2")
|
||||||
|
.replace("拐", "7")
|
||||||
|
.replace("点", ".")
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def int_str(sth: str):
|
||||||
|
return int(float_str(sth))
|
@ -0,0 +1,139 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
|
||||||
|
'''
|
||||||
|
|
||||||
|
Copyright © 2022 Team-Ryoun
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
|
||||||
|
'''
|
||||||
|
|
||||||
|
# 自动转换exe指令格式
|
||||||
|
import uuid
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def isfloatable(sth: str) -> bool:
|
||||||
|
try:float(sth);return True
|
||||||
|
except:return False
|
||||||
|
|
||||||
|
|
||||||
|
# 极限挑战
|
||||||
|
# execute @a[name="abc 123"] ~~ ~ execute @s ~9 346 ~-8 detect ^6 ^7 ^2 concrete 18 execute @p[r=3,scores={a=3}] 324 ~324 5 scoreboard players add @s[tag="999 888aasd asd "] QWE_AS 2
|
||||||
|
|
||||||
|
# /execute @a~~~/w @s aaa
|
||||||
|
|
||||||
|
# execute@s[tag="[]] 666"]~ 1~576detect^6^^66concrete 1 execute @s [scores={n=0}] ~ ~ ~0.09 execute@s~~~detect 0 0 0 bedrock -1 execute@a [name="999\"]]] jjj\""]~~ ~/execute@s[tag="℃♞\""]~ 32 ~5423 execute@s~~~detect ~~-1~ redstone_block 0 give @s [scores={name=12..}] command_block 1 1 {"name_tag":["\"a ":"b你 KKK\""]}
|
||||||
|
|
||||||
|
# 感谢 尘风、籽怼、邯潘阳(Happy2018New) 为本程序的试错提供了非常有效的支持
|
||||||
|
# 也感谢 尘风、Happy2018New、Dislink Sforza 为作者提供相关参考意见
|
||||||
|
|
||||||
|
|
||||||
|
def auto_translate(sentence:str):
|
||||||
|
'''传入一行旧的execute指令,则将其转换为新格式
|
||||||
|
:param sentence: 旧的execute指令
|
||||||
|
:return: 新的execute指令
|
||||||
|
'''
|
||||||
|
|
||||||
|
|
||||||
|
if not 'execute' in sentence:
|
||||||
|
return sentence
|
||||||
|
elif 'run' in sentence:
|
||||||
|
return sentence[:sentence.find('run')+4]+auto_translate(sentence[sentence.find('run')+4:])
|
||||||
|
|
||||||
|
# 避免不规范的语法
|
||||||
|
sentence = ((__ := str(uuid.uuid4()),strings:=[(r"\"",__),], sentence.replace(r"\"",__)) if r"\"" in sentence else (None,strings:=[],sentence))[2]
|
||||||
|
|
||||||
|
|
||||||
|
# 如果有字符串包含其中
|
||||||
|
# 我们可以看作一个神奇的pattern
|
||||||
|
def foreSentence(sent,ptnA,ptnB):
|
||||||
|
startcatch = False
|
||||||
|
tempstring = ""
|
||||||
|
for i in sent:
|
||||||
|
if startcatch:
|
||||||
|
if i == ptnB:
|
||||||
|
startcatch = False
|
||||||
|
tpp = '{}'.format(tempstring)
|
||||||
|
tag = str(uuid.uuid4())
|
||||||
|
sent = sent.replace(tpp,tag)
|
||||||
|
strings.append((tpp,tag))
|
||||||
|
tempstring = ""
|
||||||
|
else:
|
||||||
|
tempstring += i
|
||||||
|
else:
|
||||||
|
if i == ptnA:
|
||||||
|
startcatch = True
|
||||||
|
# tempstring += i
|
||||||
|
# print(ptnA,ptnB,sent)
|
||||||
|
return sent
|
||||||
|
|
||||||
|
# print(sentence,"\n")
|
||||||
|
# 如果选择器的中括号包括空格
|
||||||
|
sentence = ((sentence[:sentence.find("@")+2]+sentence[sentence.find('['):]) if (sum(0 if i == ' ' else 1 for i in sentence[sentence.find('@')+2:sentence.find('[')])==0) else sentence).replace("/"," ").lower()
|
||||||
|
|
||||||
|
|
||||||
|
sentence = foreSentence(foreSentence(foreSentence(sentence,'"','"'),"[","]"),"{","}")
|
||||||
|
list_sentence = list(sentence)
|
||||||
|
# 如果有神奇的东西在坐标后面,那就神奇了
|
||||||
|
for i in range(len(list_sentence)):
|
||||||
|
if list_sentence[i] in ('^','~'):
|
||||||
|
j = i + 1
|
||||||
|
while ((isfloatable("".join(list_sentence[i+1:j+1])))or(list_sentence[i+1] == "-" and isfloatable("".join(list_sentence[i+1:j+2]))))and j <= len(list_sentence):
|
||||||
|
j += 1
|
||||||
|
# print(j,"\t","".join(sentence[j:]))
|
||||||
|
if list_sentence[j] == " " or isfloatable(list_sentence[j]):
|
||||||
|
continue
|
||||||
|
else:
|
||||||
|
list_sentence.insert(j,' ')
|
||||||
|
|
||||||
|
sentence = "".join(list_sentence)
|
||||||
|
|
||||||
|
def backfor_sentence(a):
|
||||||
|
return [(a := a.replace(tag,tpp)) for tpp,tag in strings[::-1]][-1] if strings else a
|
||||||
|
|
||||||
|
# 下面是重点,只有我和老天爷看得懂
|
||||||
|
if 'detect' in sentence[:sentence.find("execute",8) if "execute" in sentence[8:] else -1]:
|
||||||
|
___ = [ j for i in [[i,] if sum([isfloatable(_) for _ in i]) else ((["~"+j for j in i[1:].split("~")] if i.startswith("~") else ["~"+j for j in i.split("~")]) if "~" in i else ([i,] if not "^" in i else (["^"+j for j in i[1:].split("^")] if i.startswith("^") else ["^"+j for j in i.split("^")]))) for i in sentence[sentence.find("detect")+6:].strip().split(" ",4)] for j in i ]
|
||||||
|
|
||||||
|
____ = " ".join(___[3:]).split(" ")
|
||||||
|
|
||||||
|
return backfor_sentence('execute as {0} positioned as @s positioned {1} if block {2} {3} {4} at @s positioned {1} run {5}'.format(sentence[sentence.find("execute")+7:(sentence.find("]") if "[" in sentence[:sentence.find("@")+5] else sentence.find("@")+1)+1].strip(),sentence[(sentence.find("]") if "[" in sentence[:sentence.find("@")+3] else sentence.find("@")+1)+1:sentence.find("detect")-1].strip()," ".join(___[0:3]),____[0],____[1],auto_translate(" ".join(____[2:]))))
|
||||||
|
|
||||||
|
else:
|
||||||
|
|
||||||
|
___ = [ j for i in [[i,] if sum([isfloatable(_) for _ in i]) else ((["~"+j for j in i[1:].split("~")] if i.startswith("~") else ["~"+j for j in i.split("~")]) if "~" in i else ([i,] if not "^" in i else (["^"+j for j in i[1:].split("^")] if i.startswith("^") else ["^"+j for j in i.split("^")]))) for i in sentence[(sentence.find("]") if ("]" in sentence)and(sum(0 if i == ' ' else 1 for i in sentence[sentence.find('@')+2:sentence.find('[')])==0) else sentence.find("@")+1)+1:].strip().split(" ",4)] for j in i ]
|
||||||
|
|
||||||
|
return backfor_sentence('execute as {0} positioned as @s positioned {1} at @s positioned {1} run {2}'.format(sentence[sentence.find("execute")+7:(sentence.find("]") if "[" in sentence[:sentence.find("@")+5] else sentence.find("@")+1)+1].strip(), " ".join(___[0:3]),auto_translate(" ".join(___[3:]))))
|
||||||
|
|
||||||
|
# 我是一个善良的人,没有用下面这个恶心你们
|
||||||
|
# backSentence('execute as {0} positioned as @s positioned {1} if block {2} {3} {4} at @s positioned {1} run {5}'.format(sentence[sentence.find("execute")+7:(sentence.find("]") if "[" in sentence[:sentence.find("@")+5] else sentence.find("@")+1)+1].strip(),sentence[(sentence.find("]") if "[" in sentence[:sentence.find("@")+3] else sentence.find("@")+1)+1:sentence.find("detect")-1].strip()," ".join([ j for i in [[i,] if sum([isfloatable(_) for _ in i]) else ((["~"+j for j in i[1:].split("~")] if i.startswith("~") else ["~"+j for j in i.split("~")]) if "~" in i else ([i,] if not "^" in i else (["^"+j for j in i[1:].split("^")] if i.startswith("^") else ["^"+j for j in i.split("^")]))) for i in sentence[sentence.find("detect")+6:].strip().split(" ",4)] for j in i ][0:3])," ".join([ j for i in [[i,] if sum([isfloatable(_) for _ in i]) else ((["~"+j for j in i[1:].split("~")] if i.startswith("~") else ["~"+j for j in i.split("~")]) if "~" in i else ([i,] if not "^" in i else (["^"+j for j in i[1:].split("^")] if i.startswith("^") else ["^"+j for j in i.split("^")]))) for i in sentence[sentence.find("detect")+6:].strip().split(" ",4)] for j in i ][3:]).split(" ")[0]," ".join([ j for i in [[i,] if sum([isfloatable(_) for _ in i]) else ((["~"+j for j in i[1:].split("~")] if i.startswith("~") else ["~"+j for j in i.split("~")]) if "~" in i else ([i,] if not "^" in i else (["^"+j for j in i[1:].split("^")] if i.startswith("^") else ["^"+j for j in i.split("^")]))) for i in sentence[sentence.find("detect")+6:].strip().split(" ",4)] for j in i ][3:]).split(" ")[1],autoTranslate(" ".join(" ".join([ j for i in [[i,] if sum([isfloatable(_) for _ in i]) else ((["~"+j for j in i[1:].split("~")] if i.startswith("~") else ["~"+j for j in i.split("~")]) if "~" in i else ([i,] if not "^" in i else (["^"+j for j in i[1:].split("^")] if i.startswith("^") else ["^"+j for j in i.split("^")]))) for i in sentence[sentence.find("detect")+6:].strip().split(" ",4)] for j in i ][3:]).split(" ")[2:])))) if 'detect' in sentence[:sentence.find("execute",8) if "execute" in sentence[8:] else -1] else backSentence('execute as {0} positioned as @s positioned {1} at @s positioned {1} run {2}'.format(sentence[sentence.find("execute")+7:(sentence.find("]") if "[" in sentence[:sentence.find("@")+5] else sentence.find("@")+1)+1].strip(), " ".join([ j for i in [[i,] if sum([isfloatable(_) for _ in i]) else ((["~"+j for j in i[1:].split("~")] if i.startswith("~") else ["~"+j for j in i.split("~")]) if "~" in i else ([i,] if not "^" in i else (["^"+j for j in i[1:].split("^")] if i.startswith("^") else ["^"+j for j in i.split("^")]))) for i in sentence[(sentence.find("]") if ("]" in sentence)and(sum(0 if i == ' ' else 1 for i in sentence[sentence.find('@')+2:sentence.find('[')])==0) else sentence.find("@")+1)+1:].strip().split(" ",4)] for j in i ][0:3]),autoTranslate(" ".join([ j for i in [[i,] if sum([isfloatable(_) for _ in i]) else ((["~"+j for j in i[1:].split("~")] if i.startswith("~") else ["~"+j for j in i.split("~")]) if "~" in i else ([i,] if not "^" in i else (["^"+j for j in i[1:].split("^")] if i.startswith("^") else ["^"+j for j in i.split("^")]))) for i in sentence[(sentence.find("]") if ("]" in sentence)and(sum(0 if i == ' ' else 1 for i in sentence[sentence.find('@')+2:sentence.find('[')])==0) else sentence.find("@")+1)+1:].strip().split(" ",4)] for j in i ][3:]))))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def __main__():
|
||||||
|
'''主函数
|
||||||
|
'''
|
||||||
|
while True:
|
||||||
|
try:
|
||||||
|
sentence = input()
|
||||||
|
print()
|
||||||
|
print(auto_translate(sentence))
|
||||||
|
print("="*10)
|
||||||
|
except EOFError:
|
||||||
|
break
|
||||||
|
# except Exception as e:
|
||||||
|
# print(e)
|
||||||
|
# continue
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
__main__()
|
||||||
|
|
||||||
|
# 没写完,我也不知道咋写,但是总得写不是吗
|
685
src/plugins/trimo_plugin_msctconverter/msctexec.py
Normal file
@ -0,0 +1,685 @@
|
|||||||
|
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import time
|
||||||
|
import json
|
||||||
|
import shutil
|
||||||
|
import requests
|
||||||
|
from io import StringIO
|
||||||
|
from pathlib import Path
|
||||||
|
from typing import Annotated, Any
|
||||||
|
|
||||||
|
# from nonebot import require
|
||||||
|
|
||||||
|
import zhDateTime
|
||||||
|
import Musicreater
|
||||||
|
|
||||||
|
from Musicreater.plugin.bdxfile import to_BDX_file_in_delay, to_BDX_file_in_score
|
||||||
|
from Musicreater.plugin.addonpack import (
|
||||||
|
to_addon_pack_in_delay,
|
||||||
|
# to_addon_pack_in_repeater,
|
||||||
|
to_addon_pack_in_score,
|
||||||
|
)
|
||||||
|
from Musicreater.plugin.mcstructfile import (
|
||||||
|
to_mcstructure_file_in_delay,
|
||||||
|
# to_mcstructure_file_in_repeater,
|
||||||
|
to_mcstructure_file_in_score,
|
||||||
|
)
|
||||||
|
|
||||||
|
# import Musicreater.types
|
||||||
|
# import Musicreater.subclass
|
||||||
|
# import Musicreater.constants
|
||||||
|
|
||||||
|
import nonebot
|
||||||
|
import nonebot.adapters
|
||||||
|
import nonebot.drivers
|
||||||
|
import nonebot.rule
|
||||||
|
|
||||||
|
from nonebot.params import CommandArg
|
||||||
|
from nonebot.permission import SUPERUSER
|
||||||
|
from nonebot.adapters.onebot.v11.event import GroupUploadNoticeEvent, GroupMessageEvent
|
||||||
|
|
||||||
|
from src.utils.base.ly_typing import T_Bot, T_MessageEvent
|
||||||
|
|
||||||
|
from src.utils import event as event_utils
|
||||||
|
from src.utils.base.language import get_user_lang
|
||||||
|
from src.utils.base.config import get_config
|
||||||
|
|
||||||
|
from .execute_auto_translator import auto_translate # type: ignore
|
||||||
|
|
||||||
|
|
||||||
|
nonebot.require("nonebot_plugin_alconna")
|
||||||
|
nonebot.require("nonebot_plugin_apscheduler")
|
||||||
|
|
||||||
|
|
||||||
|
from nonebot_plugin_alconna import (
|
||||||
|
on_alconna,
|
||||||
|
Alconna,
|
||||||
|
Subcommand,
|
||||||
|
UniMessage,
|
||||||
|
Option,
|
||||||
|
store_true,
|
||||||
|
store_false,
|
||||||
|
store_value,
|
||||||
|
Arparma,
|
||||||
|
Args,
|
||||||
|
)
|
||||||
|
|
||||||
|
from nonebot_plugin_apscheduler import scheduler
|
||||||
|
|
||||||
|
|
||||||
|
(config_dir := Path(__file__).parent / "config").mkdir(exist_ok=True)
|
||||||
|
(database_dir := Path(__file__).parent / "db").mkdir(exist_ok=True)
|
||||||
|
temporary_dir = Path(__file__).parent / "temp"
|
||||||
|
|
||||||
|
|
||||||
|
if (config_path := config_dir / "config.json").exists():
|
||||||
|
configdict: dict = json.load(config_path.open("r", encoding="utf-8"))
|
||||||
|
else:
|
||||||
|
configdict = {
|
||||||
|
"donateCodePath": "https://foruda.gitee.com/images/1690165328188128032/d7f24fb5_9911226.jpeg",
|
||||||
|
"donateSite": "",
|
||||||
|
"helpPicPath": "https://foruda.gitee.com/images/1685873169584963569/95fe9b0b_9911226.png",
|
||||||
|
"maxCacheSize": 1048576,
|
||||||
|
"maxCacheTime": {".mcfunction": 900, ".mid .midi": 1800, ".json": 1800},
|
||||||
|
"maxSingleFileSize": {
|
||||||
|
".mcfunction": 524288,
|
||||||
|
".mid .midi": 131072,
|
||||||
|
".json": 8192,
|
||||||
|
},
|
||||||
|
"maxPersonConvert": {
|
||||||
|
"music": 20,
|
||||||
|
"structure": 20,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
if (saves_path := database_dir / "file_saver.json").exists():
|
||||||
|
filesaves: dict = json.load(saves_path.open("r", encoding="utf-8"))
|
||||||
|
else:
|
||||||
|
filesaves = {}
|
||||||
|
|
||||||
|
max_cache_size = configdict["maxCacheSize"]
|
||||||
|
cache_limit_data: dict[str, tuple[int, int]] = {}
|
||||||
|
|
||||||
|
for cache_subtype in configdict["maxCacheTime"].keys():
|
||||||
|
for i in cache_subtype.split(" "):
|
||||||
|
cache_limit_data[i] = (
|
||||||
|
configdict["maxSingleFileSize"][cache_subtype],
|
||||||
|
configdict["maxCacheTime"][cache_subtype],
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
# if not os.path.exists("./temp/"):
|
||||||
|
# os.makedirs("./temp/")
|
||||||
|
|
||||||
|
|
||||||
|
def save_configdict():
|
||||||
|
json.dump(
|
||||||
|
configdict,
|
||||||
|
config_path.open("w", encoding="utf-8"),
|
||||||
|
indent=4,
|
||||||
|
ensure_ascii=False,
|
||||||
|
sort_keys=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def save_filesaves():
|
||||||
|
json.dump(
|
||||||
|
filesaves,
|
||||||
|
saves_path.open("w", encoding="utf-8"),
|
||||||
|
indent=4,
|
||||||
|
ensure_ascii=False,
|
||||||
|
sort_keys=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
save_configdict()
|
||||||
|
save_filesaves()
|
||||||
|
|
||||||
|
enable_auto_exe_translate = {}
|
||||||
|
|
||||||
|
people_convert_times = {}
|
||||||
|
|
||||||
|
|
||||||
|
# 每天1点更新
|
||||||
|
@scheduler.scheduled_job("cron", hour=0)
|
||||||
|
async def every_day_update():
|
||||||
|
# ulang = Language(get_default_lang_code(), "zh-WY")
|
||||||
|
global people_convert_times
|
||||||
|
people_convert_times = {}
|
||||||
|
nonebot.logger.success("已重置每日转换次数")
|
||||||
|
|
||||||
|
|
||||||
|
@nonebot.get_driver().on_startup
|
||||||
|
async def _():
|
||||||
|
nonebot.logger.info("正在删除临时文件目录")
|
||||||
|
while temporary_dir.exists():
|
||||||
|
time.sleep(1)
|
||||||
|
try:
|
||||||
|
shutil.rmtree(temporary_dir)
|
||||||
|
except Exception as E:
|
||||||
|
nonebot.logger.warning(f"清空临时目录错误,我真是栓Q:{E}")
|
||||||
|
continue
|
||||||
|
|
||||||
|
os.makedirs(temporary_dir)
|
||||||
|
nonebot.logger.success("删除临时文件目录完成")
|
||||||
|
|
||||||
|
|
||||||
|
@scheduler.scheduled_job("interval", seconds=30)
|
||||||
|
async def _():
|
||||||
|
nonebot.logger.info(
|
||||||
|
"-删除文件检测-",
|
||||||
|
)
|
||||||
|
qqidlist = list(filesaves.keys()).copy()
|
||||||
|
save_file = False
|
||||||
|
for qqid in qqidlist:
|
||||||
|
namelist = list(filesaves[qqid].keys()).copy()
|
||||||
|
for name in namelist:
|
||||||
|
if name == "totalSize":
|
||||||
|
continue
|
||||||
|
elif (
|
||||||
|
zhDateTime.DateTime.now()
|
||||||
|
- zhDateTime.DateTime(*filesaves[qqid][name]["date"])
|
||||||
|
).seconds > cache_limit_data[os.path.splitext(name)[-1]][1]:
|
||||||
|
try:
|
||||||
|
os.remove(database_dir / qqid / name)
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
filesaves[qqid]["totalSize"] -= filesaves[qqid][name]["size"]
|
||||||
|
nonebot.logger.info(
|
||||||
|
"\t删除{}".format(name),
|
||||||
|
)
|
||||||
|
filesaves[qqid].pop(name)
|
||||||
|
save_file = True
|
||||||
|
|
||||||
|
try:
|
||||||
|
is_dir_empty = not os.listdir(
|
||||||
|
database_dir / qqid,
|
||||||
|
)
|
||||||
|
except:
|
||||||
|
is_dir_empty = True
|
||||||
|
|
||||||
|
if (
|
||||||
|
(filesaves[qqid]["totalSize"] <= 0)
|
||||||
|
or len(filesaves[qqid].keys()) == 1
|
||||||
|
or is_dir_empty
|
||||||
|
):
|
||||||
|
try:
|
||||||
|
shutil.rmtree(
|
||||||
|
database_dir / qqid,
|
||||||
|
)
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
filesaves.pop(qqid)
|
||||||
|
save_file = True
|
||||||
|
if save_file:
|
||||||
|
nonebot.logger.success("-已删除过期文件-")
|
||||||
|
save_filesaves()
|
||||||
|
else:
|
||||||
|
nonebot.logger.success("-无过期文件需要删除-")
|
||||||
|
|
||||||
|
|
||||||
|
# @nonebot.rule.Rule
|
||||||
|
# def file_receive_rule(event: GroupMessageEvent):
|
||||||
|
# return event.get_type() == "group_upload"
|
||||||
|
|
||||||
|
notece_ = nonebot.on_notice()
|
||||||
|
|
||||||
|
|
||||||
|
@notece_.handle()
|
||||||
|
async def _(
|
||||||
|
event: GroupUploadNoticeEvent,
|
||||||
|
bot: T_Bot,
|
||||||
|
):
|
||||||
|
# global cache_limit_data
|
||||||
|
file_infomation = event.model_dump()["file"]
|
||||||
|
file_subtype: str = os.path.splitext(file_infomation["name"])[-1].lower()
|
||||||
|
if file_subtype in cache_limit_data.keys():
|
||||||
|
if file_infomation["size"] > cache_limit_data[file_subtype][0]:
|
||||||
|
await notece_.finish(
|
||||||
|
"文件 {} 大小过大,这不是网盘\n单个{}文件不应大于 {} 千字节".format(
|
||||||
|
file_infomation["name"],
|
||||||
|
file_subtype.upper(),
|
||||||
|
cache_limit_data[file_subtype][0] / 1024,
|
||||||
|
),
|
||||||
|
at_sender=True,
|
||||||
|
)
|
||||||
|
return
|
||||||
|
elif (usr_id := str(event.user_id)) in filesaves.keys():
|
||||||
|
if (
|
||||||
|
filesaves[usr_id]["totalSize"] + file_infomation["size"]
|
||||||
|
> max_cache_size
|
||||||
|
):
|
||||||
|
await notece_.send(
|
||||||
|
"缓存容量已经耗尽,当前你在服务器内的占有为 {} 字节,合 {}/{} 千字节\n而服务器最多支持每个人占有 {} 兆字节(即 {} 字节)".format(
|
||||||
|
filesaves[usr_id]["totalSize"],
|
||||||
|
int(filesaves[usr_id]["totalSize"] / 10.24 + 0.5) / 100,
|
||||||
|
max_cache_size / 1024,
|
||||||
|
max_cache_size / 1048576,
|
||||||
|
max_cache_size,
|
||||||
|
),
|
||||||
|
at_sender=True,
|
||||||
|
)
|
||||||
|
await notece_.finish(
|
||||||
|
f"执行指令 清除缓存(clearCache) 以清除在服务器内存储的缓存文件。",
|
||||||
|
)
|
||||||
|
return
|
||||||
|
if file_infomation["name"] in filesaves[usr_id]:
|
||||||
|
await notece_.finish(
|
||||||
|
"你的缓存中已经包含了名称为 {} 的文件,不可重复上传。".format(
|
||||||
|
file_infomation["name"]
|
||||||
|
)
|
||||||
|
)
|
||||||
|
return
|
||||||
|
savepath = database_dir / usr_id
|
||||||
|
|
||||||
|
os.makedirs(savepath, exist_ok=True)
|
||||||
|
|
||||||
|
(savepath / file_infomation["name"]).open("wb").write(
|
||||||
|
requests.get(
|
||||||
|
file_infomation["url"],
|
||||||
|
verify=False,
|
||||||
|
).content
|
||||||
|
)
|
||||||
|
now = zhDateTime.DateTime.now()
|
||||||
|
try:
|
||||||
|
filesaves[usr_id][file_infomation["name"]] = {
|
||||||
|
"date": [
|
||||||
|
now.year,
|
||||||
|
now.month,
|
||||||
|
now.day,
|
||||||
|
now.hour,
|
||||||
|
now.minute,
|
||||||
|
],
|
||||||
|
"size": file_infomation["size"],
|
||||||
|
}
|
||||||
|
filesaves[usr_id]["totalSize"] += file_infomation["size"]
|
||||||
|
except:
|
||||||
|
filesaves[usr_id] = {
|
||||||
|
file_infomation["name"]: {
|
||||||
|
"date": [
|
||||||
|
now.year,
|
||||||
|
now.month,
|
||||||
|
now.day,
|
||||||
|
now.hour,
|
||||||
|
now.minute,
|
||||||
|
],
|
||||||
|
"size": file_infomation["size"],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
filesaves[usr_id]["totalSize"] = file_infomation["size"]
|
||||||
|
save_filesaves()
|
||||||
|
await notece_.finish(
|
||||||
|
"文件 {} 已经保存,此文件在{:.1f}分内有效。".format(
|
||||||
|
file_infomation["name"], cache_limit_data[file_subtype][1] / 60
|
||||||
|
),
|
||||||
|
at_sender=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
on_clear_cache = on_alconna(
|
||||||
|
command=Alconna("清除缓存"),
|
||||||
|
aliases={
|
||||||
|
"clearCache",
|
||||||
|
"clearcache",
|
||||||
|
"ClearCache",
|
||||||
|
"清除文件缓存",
|
||||||
|
"清除缓存文件",
|
||||||
|
"清空缓存",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@on_clear_cache.handle()
|
||||||
|
async def _(
|
||||||
|
event: GroupMessageEvent,
|
||||||
|
bot: T_Bot,
|
||||||
|
):
|
||||||
|
if (usr_id := str(event.user_id)) in filesaves.keys():
|
||||||
|
shutil.rmtree(database_dir / usr_id)
|
||||||
|
genText = (
|
||||||
|
"、".join([i if i != "totalSize" else "" for i in filesaves[usr_id].keys()])
|
||||||
|
.replace("、、", "、")
|
||||||
|
.strip("、")
|
||||||
|
)
|
||||||
|
del filesaves[usr_id]
|
||||||
|
save_filesaves()
|
||||||
|
await on_clear_cache.finish(
|
||||||
|
UniMessage.text("文件 {} 已经清除。".format(genText)),
|
||||||
|
at_sender=True,
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
await on_clear_cache.finish(
|
||||||
|
UniMessage.text("服务器内未存有阁下的缓存文件。"),
|
||||||
|
at_sender=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
on_list_cache = on_alconna(
|
||||||
|
command=Alconna("查看缓存"),
|
||||||
|
aliases={"listCache", "listcache", "ListCache", "查看文件缓存", "查看缓存文件"},
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@on_list_cache.handle()
|
||||||
|
async def _(
|
||||||
|
event: GroupMessageEvent,
|
||||||
|
bot: T_Bot,
|
||||||
|
):
|
||||||
|
if (usr_id := str(event.user_id)) in filesaves.keys():
|
||||||
|
genText = (
|
||||||
|
"\n".join(
|
||||||
|
[
|
||||||
|
(
|
||||||
|
"{}({}千字节): 剩余{}秒".format(
|
||||||
|
i,
|
||||||
|
int(j["size"] / 10.24 + 0.5) / 100,
|
||||||
|
cache_limit_data[os.path.splitext(i)[-1].lower()][1]
|
||||||
|
- (
|
||||||
|
zhDateTime.DateTime.now()
|
||||||
|
- zhDateTime.DateTime(*j["date"])
|
||||||
|
).seconds,
|
||||||
|
)
|
||||||
|
if i != "totalSize"
|
||||||
|
else ""
|
||||||
|
)
|
||||||
|
for i, j in filesaves[usr_id].items()
|
||||||
|
]
|
||||||
|
)
|
||||||
|
.replace("\n\n", "\n")
|
||||||
|
.strip("\n")
|
||||||
|
)
|
||||||
|
await on_list_cache.finish(
|
||||||
|
UniMessage.text(
|
||||||
|
"服务器中保有你的如下文件:\n{}\n共计 {}/{} 字节,合 {}/{} 千字节".format(
|
||||||
|
genText,
|
||||||
|
filesaves[usr_id]["totalSize"],
|
||||||
|
max_cache_size,
|
||||||
|
int(filesaves[usr_id]["totalSize"] / 10.24 + 0.5) / 100,
|
||||||
|
max_cache_size / 1024,
|
||||||
|
)
|
||||||
|
),
|
||||||
|
at_sender=True,
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
await on_clear_cache.finish(
|
||||||
|
UniMessage.text("服务器内未存有阁下的缓存文件。"),
|
||||||
|
at_sender=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
# def convert_midi(
|
||||||
|
# midi_file_path: str,
|
||||||
|
# play_speed: float = 1,
|
||||||
|
# default_tempo: int = Musicreater.mido.midifiles.midifiles.DEFAULT_TEMPO,
|
||||||
|
# pitched_note_table: Musicreater.MidiInstrumentTableType = Musicreater.MM_TOUCH_PITCHED_INSTRUMENT_TABLE,
|
||||||
|
# percussion_note_table: Musicreater.MidiInstrumentTableType = Musicreater.MM_TOUCH_PERCUSSION_INSTRUMENT_TABLE,
|
||||||
|
# old_exe_format: bool = False,
|
||||||
|
# min_volume: float = 0.1,
|
||||||
|
# vol_processing_func: Musicreater.FittingFunctionType = Musicreater.natural_curve,
|
||||||
|
# )
|
||||||
|
|
||||||
|
musicreater_convert = on_alconna(
|
||||||
|
aliases={"musicreater_convert", "音乐转换", "midi转换"},
|
||||||
|
command=Alconna(
|
||||||
|
"msctcvt",
|
||||||
|
Option("-f|--file", default="all", args=Args["file", str, "all"]), # ALL
|
||||||
|
Option("-emr|--enable-mismatch-error", default=False, action=store_true),
|
||||||
|
Option("-ps|--play-speed", default=1.0, args=Args["play-speed", float, 1.0]),
|
||||||
|
Option(
|
||||||
|
"-dftp|--default-tempo",
|
||||||
|
default=Musicreater.mido.midifiles.midifiles.DEFAULT_TEMPO,
|
||||||
|
args=Args[
|
||||||
|
"default-tempo", int, Musicreater.mido.midifiles.midifiles.DEFAULT_TEMPO
|
||||||
|
],
|
||||||
|
),
|
||||||
|
Option(
|
||||||
|
"-ptc|--pitched-note-table",
|
||||||
|
default="touch",
|
||||||
|
args=Args["pitched-note-table", str, "touch"],
|
||||||
|
),
|
||||||
|
Option(
|
||||||
|
"-pcs|--percussion-note-table",
|
||||||
|
default="touch",
|
||||||
|
args=Args["percussion-note-table", str, "touch"],
|
||||||
|
),
|
||||||
|
Option("-e|--old-execute-format", default=False, action=store_true),
|
||||||
|
Option(
|
||||||
|
"-mv|--minimal-volume", default=0.1, args=Args["minimal-volume", float, 0.1]
|
||||||
|
),
|
||||||
|
Option(
|
||||||
|
"-vpf|--volume-processing-function",
|
||||||
|
default="natural",
|
||||||
|
args=Args["volume-processing-function", str, "natural"],
|
||||||
|
),
|
||||||
|
Option("-t|-type", default="all", args=Args["type", str, "all"]),
|
||||||
|
Option(
|
||||||
|
"-pgb|--progress-bar",
|
||||||
|
default={
|
||||||
|
"base_s": r"▶ %%N [ %%s/%^s %%% §e__________§r %%t|%^t ]",
|
||||||
|
"to_play_s": r"§7=",
|
||||||
|
"played_s": r"=",
|
||||||
|
},
|
||||||
|
args=Args["base_s", str, r"▶ %%N [ %%s/%^s %%% §e__________§r %%t|%^t ]"][
|
||||||
|
"to_play_s", str, r"§7="
|
||||||
|
]["played_s", str, r"="],
|
||||||
|
),
|
||||||
|
Option(
|
||||||
|
"-s|--scoreboard-name",
|
||||||
|
default="mscplay",
|
||||||
|
args=Args["scoreboard-name", str, "mscplay"],
|
||||||
|
),
|
||||||
|
Option("-dsa|--disable-scoreboard-autoreset", default=False, action=store_true),
|
||||||
|
Option(
|
||||||
|
"-p|--player-selector",
|
||||||
|
default="@a",
|
||||||
|
args=Args["player-selector", str, "@a"],
|
||||||
|
),
|
||||||
|
Option("-h|--height-limit", default=32, args=Args["height-limit", int, 32]),
|
||||||
|
Option("-a|--author", default="Eilles", args=Args["author", str, "Eilles"]),
|
||||||
|
Option("-fa|--forward-axis", default="x+", args=Args["forward-axis", str, "x+"]),
|
||||||
|
),
|
||||||
|
permission=SUPERUSER,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@musicreater_convert.handle()
|
||||||
|
async def _(
|
||||||
|
result: Arparma,
|
||||||
|
event: GroupMessageEvent,
|
||||||
|
bot: T_Bot,
|
||||||
|
):
|
||||||
|
_args: dict = {
|
||||||
|
"file": "all",
|
||||||
|
"enable-mismatch-error": False,
|
||||||
|
"play-speed": 1.0,
|
||||||
|
"default-tempo": 500000,
|
||||||
|
"pitched-note-table": "touch",
|
||||||
|
"percussion-note-table": "touch",
|
||||||
|
"old-execute-format": False,
|
||||||
|
"minimal-volume": 0.1,
|
||||||
|
"volume-processing-function": "natural",
|
||||||
|
"type": "all",
|
||||||
|
"progress-bar": {
|
||||||
|
"base_s": r"▶ %%N [ %%s/%^s %%% §e__________§r %%t|%^t ]",
|
||||||
|
"to_play_s": r"§7=",
|
||||||
|
"played_s": r"=",
|
||||||
|
},
|
||||||
|
"scoreboard-name": "mscplay",
|
||||||
|
"disable-scoreboard-autoreset": False,
|
||||||
|
"player-selector": "@a",
|
||||||
|
"height-limit": 32,
|
||||||
|
"author": "Eilles",
|
||||||
|
}
|
||||||
|
for arg in _args.keys():
|
||||||
|
_args[arg] = (
|
||||||
|
(
|
||||||
|
result.options[arg].args[arg]
|
||||||
|
if arg in result.options[arg].args.keys()
|
||||||
|
else result.options[arg].args
|
||||||
|
)
|
||||||
|
if (_vlu := result.options[arg].value) is None
|
||||||
|
else _vlu
|
||||||
|
)
|
||||||
|
# await musicreater_convert.finish(
|
||||||
|
# UniMessage.text(json.dumps(_args, indent=4, sort_keys=True, ensure_ascii=False))
|
||||||
|
# )
|
||||||
|
|
||||||
|
usr_id = str(event.user_id)
|
||||||
|
usr_data_path = database_dir / usr_id
|
||||||
|
(usr_temp_path := temporary_dir / usr_id).mkdir(exist_ok=True)
|
||||||
|
|
||||||
|
if (_ppnt:=_args["pitched-note-table"].lower() )in ["touch","classic","dislink"]:
|
||||||
|
pitched_notechart = Musicreater.MM_DISLINK_PITCHED_INSTRUMENT_TABLE if _ppnt == "dislink" else (Musicreater.MM_CLASSIC_PITCHED_INSTRUMENT_TABLE if _ppnt == "classic" else Musicreater.MM_TOUCH_PITCHED_INSTRUMENT_TABLE)
|
||||||
|
elif (_ppnt:=(usr_data_path / _args["pitched-note-table"])).exists():
|
||||||
|
pitched_notechart = Musicreater.MM_TOUCH_PITCHED_INSTRUMENT_TABLE.copy()
|
||||||
|
pitched_notechart.update(json.load(_ppnt.open("r")))
|
||||||
|
else:
|
||||||
|
await musicreater_convert.finish(UniMessage.text("乐器对照表 {} 不存在".format(_args["pitched-note-table"])))
|
||||||
|
return
|
||||||
|
|
||||||
|
|
||||||
|
if (_ppnt:=_args["percussion-note-table"].lower() )in ["touch","classic","dislink"]:
|
||||||
|
percussion_notechart = Musicreater.MM_DISLINK_PERCUSSION_INSTRUMENT_TABLE if _ppnt == "dislink" else (Musicreater.MM_CLASSIC_PERCUSSION_INSTRUMENT_TABLE if _ppnt == "classic" else Musicreater.MM_TOUCH_PERCUSSION_INSTRUMENT_TABLE)
|
||||||
|
elif (_ppnt:=(usr_data_path / _args["percussion-note-table"])).exists():
|
||||||
|
percussion_notechart = Musicreater.MM_TOUCH_PERCUSSION_INSTRUMENT_TABLE.copy()
|
||||||
|
percussion_notechart.update(json.load(_ppnt.open("r")))
|
||||||
|
else:
|
||||||
|
await musicreater_convert.finish(UniMessage.text("乐器对照表 {} 不存在".format(_args["percussion-note-table"])))
|
||||||
|
return
|
||||||
|
|
||||||
|
if (_ppnt:=_args["volume-processing-function"].lower()) in ["natural","straight"]:
|
||||||
|
volume_curve = Musicreater.straight_line if _ppnt == "straight" else Musicreater.natural_curve
|
||||||
|
else:
|
||||||
|
await musicreater_convert.finish(UniMessage.text("音量处理曲线 {} 不存在".format(_args["volume-processing-function"])))
|
||||||
|
return
|
||||||
|
|
||||||
|
if (_ppnt:=_args["type"].lower()) == "all":
|
||||||
|
all_cvt_types = ["addon-delay","addon-score","mcstructure-dalay","mcstructure-score","bdx-delay","bdx-score",]
|
||||||
|
else:
|
||||||
|
all_cvt_types = _ppnt.split("&")
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# 重定向标准输出
|
||||||
|
buffer = StringIO()
|
||||||
|
sys.stdout = buffer
|
||||||
|
sys.stderr = buffer
|
||||||
|
|
||||||
|
try:
|
||||||
|
|
||||||
|
progress_bar_style = Musicreater.ProgressBarStyle(**_args["progress-bar"])
|
||||||
|
|
||||||
|
all_files = []
|
||||||
|
|
||||||
|
for file_to_convert in (
|
||||||
|
filesaves[usr_id].keys()
|
||||||
|
if _args["file"].lower() == "all"
|
||||||
|
else _args["file"].split("&")
|
||||||
|
):
|
||||||
|
if file_to_convert.endswith(".mid") or file_to_convert.endswith(".midi"):
|
||||||
|
nonebot.logger.info("载入转换文件:", file_to_convert)
|
||||||
|
all_files.append(file_to_convert)
|
||||||
|
msct_obj = Musicreater.MidiConvert.from_midi_file(
|
||||||
|
midi_file_path=usr_data_path / file_to_convert,
|
||||||
|
mismatch_error_ignorance=not _args["enable-mismatch-error"],
|
||||||
|
play_speed=_args["play-speed"],
|
||||||
|
default_tempo=_args["default-tempo"],
|
||||||
|
pitched_note_table=pitched_notechart,
|
||||||
|
percussion_note_table=percussion_notechart,
|
||||||
|
old_exe_format=_args["old-execute-format"],
|
||||||
|
min_volume=_args["minimal-volume"],
|
||||||
|
vol_processing_func=volume_curve,
|
||||||
|
)
|
||||||
|
|
||||||
|
a = ["mcstructure-dalay","mcstructure-score","bdx-delay","bdx-score",]
|
||||||
|
if "addon-delay" in all_cvt_types:
|
||||||
|
to_addon_pack_in_delay(
|
||||||
|
midi_cvt=msct_obj,
|
||||||
|
dist_path=str(usr_temp_path),
|
||||||
|
progressbar_style=progress_bar_style,
|
||||||
|
player=_args["player-selector"],
|
||||||
|
max_height=_args["height-limit"],
|
||||||
|
)
|
||||||
|
all_cvt_types.remove("addon-delay")
|
||||||
|
if "addon-score" in all_cvt_types:
|
||||||
|
to_addon_pack_in_score()
|
||||||
|
|
||||||
|
if not all_files:
|
||||||
|
nonebot.logger.warning("无可供转换的文件",)
|
||||||
|
await musicreater_convert.send(UniMessage("不是哥们,空气咱这转不成面包,那是中科院的事。"))
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
nonebot.logger.error("转换存在错误:",e)
|
||||||
|
|
||||||
|
|
||||||
|
sys.stdout = sys.__stdout__
|
||||||
|
sys.stderr = sys.__stderr__
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
execute_cmd_convert_ablity = on_alconna(
|
||||||
|
command=Alconna("指令自动更新"),
|
||||||
|
aliases={
|
||||||
|
"指令更新自动",
|
||||||
|
"自动指令更新",
|
||||||
|
"指令更新",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@execute_cmd_convert_ablity.handle()
|
||||||
|
async def _(
|
||||||
|
event: T_MessageEvent,
|
||||||
|
bot: T_Bot,
|
||||||
|
):
|
||||||
|
ulang = get_user_lang(usrid := str(event_utils.get_user_id(event)))
|
||||||
|
global enable_auto_exe_translate
|
||||||
|
enable_auto_exe_translate[usrid] = not enable_auto_exe_translate.get(usrid, True)
|
||||||
|
await execute_cmd_convert_ablity.finish(
|
||||||
|
UniMessage.text(
|
||||||
|
ulang.get(
|
||||||
|
"upexecute.enable"
|
||||||
|
if enable_auto_exe_translate[usrid]
|
||||||
|
else "upexecute.disable"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
execute_cmd_convert = nonebot.on_startswith(
|
||||||
|
"execute",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@execute_cmd_convert.handle()
|
||||||
|
async def _(
|
||||||
|
event: T_MessageEvent,
|
||||||
|
bot: T_Bot,
|
||||||
|
):
|
||||||
|
global enable_auto_exe_translate
|
||||||
|
if not enable_auto_exe_translate.get(
|
||||||
|
usrid := str(event_utils.get_user_id(event)), True
|
||||||
|
):
|
||||||
|
execute_cmd_convert.destroy()
|
||||||
|
return
|
||||||
|
ulang = get_user_lang(usrid)
|
||||||
|
if (
|
||||||
|
result_execmd := auto_translate(event.get_plaintext())
|
||||||
|
) == event.get_plaintext():
|
||||||
|
await execute_cmd_convert.finish(ulang.get("upexecute.same"))
|
||||||
|
else:
|
||||||
|
await execute_cmd_convert.finish(result_execmd)
|
||||||
|
|
||||||
|
|
||||||
|
# test_exec = nonebot.on_command(
|
||||||
|
# "test-exec",
|
||||||
|
# rule=to_me(),
|
||||||
|
# permission=SUPERUSER,
|
||||||
|
# )
|
||||||
|
|
||||||
|
|
||||||
|
# @test_exec.handle()
|
||||||
|
# async def _(args: Annotated[nonebot.adapters.Message, CommandArg()]):
|
||||||
|
# await test_exec.finish(exec(args.extract_plain_text()))
|
@ -12,7 +12,7 @@
|
|||||||
height: 100px;
|
height: 100px;
|
||||||
display: flex;
|
display: flex;
|
||||||
background-color: rgba(255, 255, 255, 0.9);
|
background-color: rgba(255, 255, 255, 0.9);
|
||||||
border-radius: 100px;
|
/* border-radius: 100px; */
|
||||||
margin-bottom: 10px;
|
margin-bottom: 10px;
|
||||||
padding-right: 10px;
|
padding-right: 10px;
|
||||||
}
|
}
|
||||||
@ -25,7 +25,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.row-icon {
|
.row-icon {
|
||||||
border-radius: 50%;
|
/* border-radius: 50%; */
|
||||||
margin-right: auto;
|
margin-right: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -0,0 +1,4 @@
|
|||||||
|
|
||||||
|
upexecute.same=指令转换前后一致
|
||||||
|
upexecute.enable=execute指令自动更新已启用
|
||||||
|
upexecute.disable=已禁用execute指令自动更新
|
@ -0,0 +1,4 @@
|
|||||||
|
|
||||||
|
upexecute.same=令未变
|
||||||
|
upexecute.enable=更令之法方用
|
||||||
|
upexecute.disable=更令之法方去
|
3
src/resources/trim_plugin_msctconverter/metadata.yml
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
name: 伶伦转换器附件
|
||||||
|
description: 伶伦转换器所用之资源包
|
||||||
|
ersion: 2024.7.19
|
@ -163,4 +163,7 @@ yanlun.refresh.failed=更新 言·论 信息发生 {ERR} 错误:{ERRCODE}
|
|||||||
yanlun.errtype.net=互联网连接
|
yanlun.errtype.net=互联网连接
|
||||||
yanlun.errtype.unknown=未知
|
yanlun.errtype.unknown=未知
|
||||||
yanlun.count.head=出处ttt数量(占比)
|
yanlun.count.head=出处ttt数量(占比)
|
||||||
yanlun.count.tail=...(共 {NUM} 条)
|
yanlun.count.tail=...(共 {NUM} 条)
|
||||||
|
yanlun.length.toolong=言·论过长
|
||||||
|
yanlun.length.tooshort=言·论过短
|
||||||
|
yanlun.length.float=言·论不可是非整数长度
|
@ -163,4 +163,7 @@ yanlun.refresh.failed=言·论 因{ERR}而无以新:{ERRCODE}
|
|||||||
yanlun.errtype.net=遥讯不得
|
yanlun.errtype.net=遥讯不得
|
||||||
yanlun.errtype.unknown=无名之亏
|
yanlun.errtype.unknown=无名之亏
|
||||||
yanlun.count.head=所缘ttt几何(比率)
|
yanlun.count.head=所缘ttt几何(比率)
|
||||||
yanlun.count.tail=...(合{NUM}条)
|
yanlun.count.tail=...(合{NUM}条)
|
||||||
|
yanlun.length.toolong=繁言勿论
|
||||||
|
yanlun.length.tooshort=无言以论
|
||||||
|
yanlun.length.float=此言不获世之滋垢,皭然泥而不滓者也,故不论之
|
||||||
|
@ -18,7 +18,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="bot-detail">
|
<div class="bot-detail">
|
||||||
<div class="bot-name" style="text-align:start">
|
<div class="bot-name" style="text-align:start">
|
||||||
TriM-Liteyuki
|
TriMO-Liteyuki
|
||||||
</div>
|
</div>
|
||||||
<hr>
|
<hr>
|
||||||
<div class="bot-icon-tags">
|
<div class="bot-icon-tags">
|
||||||
|
@ -6,7 +6,7 @@ import time
|
|||||||
|
|
||||||
import nonebot
|
import nonebot
|
||||||
|
|
||||||
__NAME__ = "LiteyukiBot-Trim"
|
__NAME__ = "LiteyukiBot-TriMO"
|
||||||
__VERSION__ = "6.3.3" # 60201
|
__VERSION__ = "6.3.3" # 60201
|
||||||
# __VERSION_I__ = 99060303
|
# __VERSION_I__ = 99060303
|
||||||
|
|
||||||
|
@ -43,9 +43,9 @@ def load_from_lang(file_path: str, lang_code: str = None):
|
|||||||
if lang_code not in _language_data:
|
if lang_code not in _language_data:
|
||||||
_language_data[lang_code] = {}
|
_language_data[lang_code] = {}
|
||||||
_language_data[lang_code].update(data)
|
_language_data[lang_code].update(data)
|
||||||
nonebot.logger.debug(f"Loaded language data from {file_path}")
|
nonebot.logger.debug(f"已从 {file_path} 读取语言文件")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
nonebot.logger.error(f"Failed to load language data from {file_path}: {e}")
|
nonebot.logger.error(f"无法读取以下目录中的语言文件 {file_path},详阅:{e}")
|
||||||
|
|
||||||
|
|
||||||
def load_from_json(file_path: str, lang_code: str = None):
|
def load_from_json(file_path: str, lang_code: str = None):
|
||||||
@ -196,7 +196,7 @@ def get_user_lang(user_id: str) -> Language:
|
|||||||
user_id = str(user_id)
|
user_id = str(user_id)
|
||||||
|
|
||||||
if user_id not in _user_lang:
|
if user_id not in _user_lang:
|
||||||
nonebot.logger.debug(f"Loading user language for {user_id}")
|
nonebot.logger.debug(f"正为 {user_id} 加载语言包…")
|
||||||
user = user_db.where_one(
|
user = user_db.where_one(
|
||||||
User(),
|
User(),
|
||||||
"user_id = ?",
|
"user_id = ?",
|
||||||
|
@ -6,7 +6,7 @@ from os import getcwd
|
|||||||
import aiofiles
|
import aiofiles
|
||||||
import nonebot
|
import nonebot
|
||||||
|
|
||||||
from nonebot_plugin_htmlrender import *
|
from nonebot_plugin_htmlrender import * # type: ignore
|
||||||
from .tools import random_hex_string
|
from .tools import random_hex_string
|
||||||
|
|
||||||
# import imgkit
|
# import imgkit
|
||||||
|
@ -34,7 +34,9 @@ async def broadcast_to_superusers(message: str | T_Message, markdown: bool = Fal
|
|||||||
for bot in nonebot.get_bots().values():
|
for bot in nonebot.get_bots().values():
|
||||||
for user_id in config.get("superusers", []):
|
for user_id in config.get("superusers", []):
|
||||||
if markdown:
|
if markdown:
|
||||||
await MarkdownMessage.send_md(message, bot, message_type="private", session_id=user_id)
|
await MarkdownMessage.send_md(
|
||||||
|
message, bot, message_type="private", session_id=user_id
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
await bot.send_private_msg(user_id=user_id, message=message)
|
await bot.send_private_msg(user_id=user_id, message=message)
|
||||||
|
|
||||||
@ -42,13 +44,14 @@ async def broadcast_to_superusers(message: str | T_Message, markdown: bool = Fal
|
|||||||
class MarkdownMessage:
|
class MarkdownMessage:
|
||||||
@staticmethod
|
@staticmethod
|
||||||
async def send_md(
|
async def send_md(
|
||||||
markdown: str,
|
markdown: str,
|
||||||
bot: T_Bot, *,
|
bot: T_Bot,
|
||||||
message_type: str = None,
|
*,
|
||||||
session_id: str | int = None,
|
message_type: str = None,
|
||||||
event: T_MessageEvent = None,
|
session_id: str | int = None,
|
||||||
retry_as_image: bool = True,
|
event: T_MessageEvent = None,
|
||||||
**kwargs
|
retry_as_image: bool = True,
|
||||||
|
**kwargs,
|
||||||
) -> dict[str, Any] | None:
|
) -> dict[str, Any] | None:
|
||||||
"""
|
"""
|
||||||
发送Markdown消息,支持自动转为图片发送
|
发送Markdown消息,支持自动转为图片发送
|
||||||
@ -64,7 +67,7 @@ class MarkdownMessage:
|
|||||||
Returns:
|
Returns:
|
||||||
|
|
||||||
"""
|
"""
|
||||||
formatted_md = v11.unescape(markdown).replace("\n", r"\n").replace('"', r'\\\"')
|
formatted_md = v11.unescape(markdown).replace("\n", r"\n").replace('"', r"\\\"")
|
||||||
if event is not None and message_type is None:
|
if event is not None and message_type is None:
|
||||||
if isinstance(event, satori.event.Event):
|
if isinstance(event, satori.event.Event):
|
||||||
message_type = "private" if event.guild is None else "group"
|
message_type = "private" if event.guild is None else "group"
|
||||||
@ -73,83 +76,81 @@ class MarkdownMessage:
|
|||||||
assert event is not None
|
assert event is not None
|
||||||
message_type = event.message_type
|
message_type = event.message_type
|
||||||
group_id = event.group_id if message_type == "group" else None
|
group_id = event.group_id if message_type == "group" else None
|
||||||
user_id = event.user.id if isinstance(event, satori.event.Event) else event.user_id
|
user_id = (
|
||||||
|
event.user.id
|
||||||
|
if isinstance(event, satori.event.Event)
|
||||||
|
else event.user_id
|
||||||
|
)
|
||||||
session_id = user_id if message_type == "private" else group_id
|
session_id = user_id if message_type == "private" else group_id
|
||||||
|
|
||||||
|
|
||||||
try:
|
|
||||||
raise TencentBannedMarkdownError("Tencent banned markdown")
|
|
||||||
forward_id = await bot.call_api(
|
|
||||||
"send_private_forward_msg",
|
|
||||||
messages=[
|
|
||||||
{
|
|
||||||
"type": "node",
|
|
||||||
"data": {
|
|
||||||
"content": [
|
|
||||||
{
|
|
||||||
"data": {
|
|
||||||
"content": "{\"content\":\"%s\"}" % formatted_md,
|
|
||||||
},
|
|
||||||
"type": "markdown"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"name": "[]",
|
|
||||||
"uin": bot.self_id
|
|
||||||
}
|
|
||||||
}
|
|
||||||
],
|
|
||||||
user_id=bot.self_id
|
|
||||||
|
|
||||||
)
|
# try:
|
||||||
|
# raise TencentBannedMarkdownError("Tencent banned markdown")
|
||||||
|
# forward_id = await bot.call_api(
|
||||||
|
# "send_private_forward_msg",
|
||||||
|
# messages=[
|
||||||
|
# {
|
||||||
|
# "type": "node",
|
||||||
|
# "data": {
|
||||||
|
# "content": [
|
||||||
|
# {
|
||||||
|
# "data": {
|
||||||
|
# "content": "{\"content\":\"%s\"}" % formatted_md,
|
||||||
|
# },
|
||||||
|
# "type": "markdown"
|
||||||
|
# }
|
||||||
|
# ],
|
||||||
|
# "name": "[]",
|
||||||
|
# "uin": bot.self_id
|
||||||
|
# }
|
||||||
|
# }
|
||||||
|
# ],
|
||||||
|
# user_id=bot.self_id
|
||||||
|
|
||||||
|
# )
|
||||||
|
# data = await bot.send_msg(
|
||||||
|
# user_id=session_id,
|
||||||
|
# group_id=session_id,
|
||||||
|
# message_type=message_type,
|
||||||
|
# message=[
|
||||||
|
# {
|
||||||
|
# "type": "longmsg",
|
||||||
|
# "data": {
|
||||||
|
# "id": forward_id
|
||||||
|
# }
|
||||||
|
# },
|
||||||
|
# ],
|
||||||
|
# **kwargs
|
||||||
|
# )
|
||||||
|
# except BaseException as e:
|
||||||
|
|
||||||
|
nonebot.logger.error(f"因未能发送Markdown消息,已转为图片发送。")
|
||||||
|
# 发送失败,渲染为图片发送
|
||||||
|
# if not retry_as_image:
|
||||||
|
# return None
|
||||||
|
|
||||||
|
# plain_markdown = markdown.replace("[🔗", "[")
|
||||||
|
md_image_bytes = await md_to_pic(md=markdown, width=540, device_scale_factor=4)
|
||||||
|
if isinstance(bot, satori.Bot):
|
||||||
|
msg_seg = satori.MessageSegment.image(raw=md_image_bytes, mime="image/png")
|
||||||
|
data = await bot.send(event=event, message=msg_seg)
|
||||||
|
else:
|
||||||
data = await bot.send_msg(
|
data = await bot.send_msg(
|
||||||
user_id=session_id,
|
|
||||||
group_id=session_id,
|
|
||||||
message_type=message_type,
|
message_type=message_type,
|
||||||
message=[
|
group_id=session_id,
|
||||||
{
|
user_id=session_id,
|
||||||
"type": "longmsg",
|
message=v11.MessageSegment.image(md_image_bytes),
|
||||||
"data": {
|
|
||||||
"id": forward_id
|
|
||||||
}
|
|
||||||
},
|
|
||||||
],
|
|
||||||
**kwargs
|
|
||||||
)
|
)
|
||||||
except BaseException as e:
|
|
||||||
nonebot.logger.error(f"send markdown error, retry as image: {e}")
|
|
||||||
# 发送失败,渲染为图片发送
|
|
||||||
# if not retry_as_image:
|
|
||||||
# return None
|
|
||||||
|
|
||||||
plain_markdown = markdown.replace("[🔗", "[")
|
|
||||||
md_image_bytes = await md_to_pic(
|
|
||||||
md=plain_markdown,
|
|
||||||
width=540,
|
|
||||||
device_scale_factor=4
|
|
||||||
)
|
|
||||||
if isinstance(bot, satori.Bot):
|
|
||||||
msg_seg = satori.MessageSegment.image(raw=md_image_bytes,mime="image/png")
|
|
||||||
data = await bot.send(
|
|
||||||
event=event,
|
|
||||||
message=msg_seg
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
data = await bot.send_msg(
|
|
||||||
message_type=message_type,
|
|
||||||
group_id=session_id,
|
|
||||||
user_id=session_id,
|
|
||||||
message=v11.MessageSegment.image(md_image_bytes),
|
|
||||||
)
|
|
||||||
return data
|
return data
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
async def send_image(
|
async def send_image(
|
||||||
image: bytes | str,
|
image: bytes | str,
|
||||||
bot: T_Bot, *,
|
bot: T_Bot,
|
||||||
message_type: str = None,
|
*,
|
||||||
session_id: str | int = None,
|
message_type: str = None,
|
||||||
event: T_MessageEvent = None,
|
session_id: str | int = None,
|
||||||
**kwargs
|
event: T_MessageEvent = None,
|
||||||
|
**kwargs,
|
||||||
) -> dict:
|
) -> dict:
|
||||||
"""
|
"""
|
||||||
发送单张装逼大图
|
发送单张装逼大图
|
||||||
@ -181,23 +182,35 @@ class MarkdownMessage:
|
|||||||
if method == 2:
|
if method == 2:
|
||||||
base64_string = base64.b64encode(image).decode("utf-8")
|
base64_string = base64.b64encode(image).decode("utf-8")
|
||||||
data = await bot.call_api("upload_image", file=f"base64://{base64_string}")
|
data = await bot.call_api("upload_image", file=f"base64://{base64_string}")
|
||||||
await MarkdownMessage.send_md(MarkdownMessage.image(data, Image.open(io.BytesIO(image)).size), bot,
|
await MarkdownMessage.send_md(
|
||||||
event=event, message_type=message_type,
|
MarkdownMessage.image(data, Image.open(io.BytesIO(image)).size),
|
||||||
session_id=session_id, **kwargs)
|
bot,
|
||||||
|
event=event,
|
||||||
|
message_type=message_type,
|
||||||
|
session_id=session_id,
|
||||||
|
**kwargs,
|
||||||
|
)
|
||||||
|
|
||||||
# 其他实现端方案
|
# 其他实现端方案
|
||||||
else:
|
else:
|
||||||
image_message_id = (await bot.send_private_msg(
|
image_message_id = (
|
||||||
user_id=bot.self_id,
|
await bot.send_private_msg(
|
||||||
message=[
|
user_id=bot.self_id, message=[v11.MessageSegment.image(file=image)]
|
||||||
v11.MessageSegment.image(file=image)
|
)
|
||||||
]
|
)["message_id"]
|
||||||
))["message_id"]
|
image_url = (await bot.get_msg(message_id=image_message_id))["message"][0][
|
||||||
image_url = (await bot.get_msg(message_id=image_message_id))["message"][0]["data"]["url"]
|
"data"
|
||||||
|
]["url"]
|
||||||
image_size = Image.open(io.BytesIO(image)).size
|
image_size = Image.open(io.BytesIO(image)).size
|
||||||
image_md = MarkdownMessage.image(image_url, image_size)
|
image_md = MarkdownMessage.image(image_url, image_size)
|
||||||
return await MarkdownMessage.send_md(image_md, bot, message_type=message_type, session_id=session_id,
|
return await MarkdownMessage.send_md(
|
||||||
event=event, **kwargs)
|
image_md,
|
||||||
|
bot,
|
||||||
|
message_type=message_type,
|
||||||
|
session_id=session_id,
|
||||||
|
event=event,
|
||||||
|
**kwargs,
|
||||||
|
)
|
||||||
|
|
||||||
if data is None:
|
if data is None:
|
||||||
data = await bot.send_msg(
|
data = await bot.send_msg(
|
||||||
@ -205,7 +218,7 @@ class MarkdownMessage:
|
|||||||
group_id=session_id,
|
group_id=session_id,
|
||||||
user_id=session_id,
|
user_id=session_id,
|
||||||
message=v11.MessageSegment.image(image),
|
message=v11.MessageSegment.image(image),
|
||||||
**kwargs
|
**kwargs,
|
||||||
)
|
)
|
||||||
return data
|
return data
|
||||||
|
|
||||||
@ -232,7 +245,9 @@ class MarkdownMessage:
|
|||||||
markdown格式的可点击回调按钮
|
markdown格式的可点击回调按钮
|
||||||
|
|
||||||
"""
|
"""
|
||||||
if "" not in config.get("command_start", ["/"]) and config.get("alconna_use_command_start", False):
|
if "" not in config.get("command_start", ["/"]) and config.get(
|
||||||
|
"alconna_use_command_start", False
|
||||||
|
):
|
||||||
cmd = f"{config['command_start'][0]}{cmd}"
|
cmd = f"{config['command_start'][0]}{cmd}"
|
||||||
return f"[{name}](mqqapi://aio/inlinecmd?command={quote(cmd)}&reply={str(reply).lower()}&enter={str(enter).lower()})"
|
return f"[{name}](mqqapi://aio/inlinecmd?command={quote(cmd)}&reply={str(reply).lower()}&enter={str(enter).lower()})"
|
||||||
|
|
||||||
|
@ -1,15 +1,15 @@
|
|||||||
from nonebot.plugin import PluginMetadata
|
from nonebot.plugin import PluginMetadata
|
||||||
from nonebot import get_driver
|
from nonebot import get_driver
|
||||||
|
|
||||||
__plugin_meta__ = PluginMetadata(
|
__plugin_meta__ = PluginMetadata(
|
||||||
name="Minecraft工具箱",
|
name="Minecraft工具箱",
|
||||||
description="一些Minecraft相关工具箱",
|
description="一些Minecraft相关工具箱",
|
||||||
usage="我觉得你应该会用",
|
usage="我觉得你应该会用",
|
||||||
type="application",
|
type="application",
|
||||||
homepage="https://github.com/snowykami/LiteyukiBot",
|
homepage="https://github.com/snowykami/LiteyukiBot",
|
||||||
extra={
|
extra={
|
||||||
"liteyuki" : True,
|
"liteyuki" : True,
|
||||||
"toggleable" : True,
|
"toggleable" : True,
|
||||||
"default_enable": True,
|
"default_enable": True,
|
||||||
}
|
}
|
||||||
)
|
)
|
@ -1,20 +1,20 @@
|
|||||||
from nonebot.plugin import PluginMetadata
|
from nonebot.plugin import PluginMetadata
|
||||||
|
|
||||||
__author__ = "snowykami"
|
__author__ = "snowykami"
|
||||||
__plugin_meta__ = PluginMetadata(
|
__plugin_meta__ = PluginMetadata(
|
||||||
name="轻雪包管理器v2",
|
name="轻雪包管理器v2",
|
||||||
description="详细看文档",
|
description="详细看文档",
|
||||||
usage=(
|
usage=(
|
||||||
"npm list\n"
|
"npm list\n"
|
||||||
"npm enable/disable <plugin_name>\n"
|
"npm enable/disable <plugin_name>\n"
|
||||||
"npm search <keywords...>\n"
|
"npm search <keywords...>\n"
|
||||||
"npm install/uninstall <plugin_name>\n"
|
"npm install/uninstall <plugin_name>\n"
|
||||||
),
|
),
|
||||||
type="application",
|
type="application",
|
||||||
homepage="https://github.com/snowykami/LiteyukiBot",
|
homepage="https://github.com/snowykami/LiteyukiBot",
|
||||||
extra={
|
extra={
|
||||||
"liteyuki": True,
|
"liteyuki": True,
|
||||||
"toggleable" : False,
|
"toggleable" : False,
|
||||||
"default_enable" : False,
|
"default_enable" : False,
|
||||||
}
|
}
|
||||||
)
|
)
|
@ -1,27 +1,27 @@
|
|||||||
from nonebot.plugin import PluginMetadata
|
from nonebot.plugin import PluginMetadata
|
||||||
from nonebot import get_driver
|
from nonebot import get_driver
|
||||||
from .qweather import *
|
from .qweather import *
|
||||||
|
|
||||||
__plugin_meta__ = PluginMetadata(
|
__plugin_meta__ = PluginMetadata(
|
||||||
name="轻雪天气",
|
name="轻雪天气",
|
||||||
description="基于和风天气api的天气插件",
|
description="基于和风天气api的天气插件",
|
||||||
usage="",
|
usage="",
|
||||||
type="application",
|
type="application",
|
||||||
homepage="https://github.com/snowykami/LiteyukiBot",
|
homepage="https://github.com/snowykami/LiteyukiBot",
|
||||||
extra={
|
extra={
|
||||||
"liteyuki" : True,
|
"liteyuki" : True,
|
||||||
"toggleable" : True,
|
"toggleable" : True,
|
||||||
"default_enable": True,
|
"default_enable": True,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
from ...utils.base.data_manager import set_memory_data
|
from ...utils.base.data_manager import set_memory_data
|
||||||
|
|
||||||
driver = get_driver()
|
driver = get_driver()
|
||||||
|
|
||||||
|
|
||||||
@driver.on_startup
|
@driver.on_startup
|
||||||
async def _():
|
async def _():
|
||||||
# 检查是否为开发者模式
|
# 检查是否为开发者模式
|
||||||
is_dev = await check_key_dev(get_config("weather_key", ""))
|
is_dev = await check_key_dev(get_config("weather_key", ""))
|
||||||
set_memory_data("weather.is_dev", is_dev)
|
set_memory_data("weather.is_dev", is_dev)
|
@ -1,171 +1,171 @@
|
|||||||
import aiohttp
|
import aiohttp
|
||||||
|
|
||||||
from .qw_models import *
|
from .qw_models import *
|
||||||
import httpx
|
import httpx
|
||||||
|
|
||||||
from ...utils.base.data_manager import get_memory_data
|
from ...utils.base.data_manager import get_memory_data
|
||||||
from ...utils.base.language import Language
|
from ...utils.base.language import Language
|
||||||
|
|
||||||
dev_url = "https://devapi.qweather.com/" # 开发HBa
|
dev_url = "https://devapi.qweather.com/" # 开发HBa
|
||||||
com_url = "https://api.qweather.com/" # 正式环境
|
com_url = "https://api.qweather.com/" # 正式环境
|
||||||
|
|
||||||
|
|
||||||
def get_qw_lang(lang: str) -> str:
|
def get_qw_lang(lang: str) -> str:
|
||||||
if lang in ["zh-HK", "zh-TW"]:
|
if lang in ["zh-HK", "zh-TW"]:
|
||||||
return "zh-hant"
|
return "zh-hant"
|
||||||
elif lang.startswith("zh"):
|
elif lang.startswith("zh"):
|
||||||
return "zh"
|
return "zh"
|
||||||
elif lang.startswith("en"):
|
elif lang.startswith("en"):
|
||||||
return "en"
|
return "en"
|
||||||
else:
|
else:
|
||||||
return lang
|
return lang
|
||||||
|
|
||||||
|
|
||||||
async def check_key_dev(key: str) -> bool:
|
async def check_key_dev(key: str) -> bool:
|
||||||
url = "https://api.qweather.com/v7/weather/now?"
|
url = "https://api.qweather.com/v7/weather/now?"
|
||||||
params = {
|
params = {
|
||||||
"location": "101010100",
|
"location": "101010100",
|
||||||
"key" : key,
|
"key" : key,
|
||||||
}
|
}
|
||||||
async with aiohttp.ClientSession() as client:
|
async with aiohttp.ClientSession() as client:
|
||||||
resp = await client.get(url, params=params)
|
resp = await client.get(url, params=params)
|
||||||
return (await resp.json()).get("code") != "200" # 查询不到付费数据为开发版
|
return (await resp.json()).get("code") != "200" # 查询不到付费数据为开发版
|
||||||
|
|
||||||
|
|
||||||
def get_local_data(ulang_code: str) -> dict:
|
def get_local_data(ulang_code: str) -> dict:
|
||||||
"""
|
"""
|
||||||
获取本地化数据
|
获取本地化数据
|
||||||
Args:
|
Args:
|
||||||
ulang_code:
|
ulang_code:
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
|
|
||||||
"""
|
"""
|
||||||
ulang = Language(ulang_code)
|
ulang = Language(ulang_code)
|
||||||
return {
|
return {
|
||||||
"monday" : ulang.get("weather.monday"),
|
"monday" : ulang.get("weather.monday"),
|
||||||
"tuesday" : ulang.get("weather.tuesday"),
|
"tuesday" : ulang.get("weather.tuesday"),
|
||||||
"wednesday": ulang.get("weather.wednesday"),
|
"wednesday": ulang.get("weather.wednesday"),
|
||||||
"thursday" : ulang.get("weather.thursday"),
|
"thursday" : ulang.get("weather.thursday"),
|
||||||
"friday" : ulang.get("weather.friday"),
|
"friday" : ulang.get("weather.friday"),
|
||||||
"saturday" : ulang.get("weather.saturday"),
|
"saturday" : ulang.get("weather.saturday"),
|
||||||
"sunday" : ulang.get("weather.sunday"),
|
"sunday" : ulang.get("weather.sunday"),
|
||||||
"today" : ulang.get("weather.today"),
|
"today" : ulang.get("weather.today"),
|
||||||
"tomorrow" : ulang.get("weather.tomorrow"),
|
"tomorrow" : ulang.get("weather.tomorrow"),
|
||||||
"day" : ulang.get("weather.day"),
|
"day" : ulang.get("weather.day"),
|
||||||
"night" : ulang.get("weather.night"),
|
"night" : ulang.get("weather.night"),
|
||||||
"no_aqi" : ulang.get("weather.no_aqi"),
|
"no_aqi" : ulang.get("weather.no_aqi"),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
async def city_lookup(
|
async def city_lookup(
|
||||||
location: str,
|
location: str,
|
||||||
key: str,
|
key: str,
|
||||||
adm: str = "",
|
adm: str = "",
|
||||||
number: int = 20,
|
number: int = 20,
|
||||||
lang: str = "zh",
|
lang: str = "zh",
|
||||||
) -> CityLookup:
|
) -> CityLookup:
|
||||||
"""
|
"""
|
||||||
通过关键字搜索城市信息
|
通过关键字搜索城市信息
|
||||||
Args:
|
Args:
|
||||||
location:
|
location:
|
||||||
key:
|
key:
|
||||||
adm:
|
adm:
|
||||||
number:
|
number:
|
||||||
lang: 可传入标准i18n语言代码,如zh-CN、en-US等
|
lang: 可传入标准i18n语言代码,如zh-CN、en-US等
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
|
|
||||||
"""
|
"""
|
||||||
url = "https://geoapi.qweather.com/v2/city/lookup?"
|
url = "https://geoapi.qweather.com/v2/city/lookup?"
|
||||||
params = {
|
params = {
|
||||||
"location": location,
|
"location": location,
|
||||||
"adm" : adm,
|
"adm" : adm,
|
||||||
"number" : number,
|
"number" : number,
|
||||||
"key" : key,
|
"key" : key,
|
||||||
"lang" : lang,
|
"lang" : lang,
|
||||||
}
|
}
|
||||||
async with httpx.AsyncClient() as client:
|
async with httpx.AsyncClient() as client:
|
||||||
resp = await client.get(url, params=params)
|
resp = await client.get(url, params=params)
|
||||||
return CityLookup.parse_obj(resp.json())
|
return CityLookup.parse_obj(resp.json())
|
||||||
|
|
||||||
|
|
||||||
async def get_weather_now(
|
async def get_weather_now(
|
||||||
key: str,
|
key: str,
|
||||||
location: str,
|
location: str,
|
||||||
lang: str = "zh",
|
lang: str = "zh",
|
||||||
unit: str = "m",
|
unit: str = "m",
|
||||||
dev: bool = get_memory_data("is_dev", True),
|
dev: bool = get_memory_data("is_dev", True),
|
||||||
) -> dict:
|
) -> dict:
|
||||||
url_path = "v7/weather/now?"
|
url_path = "v7/weather/now?"
|
||||||
url = dev_url + url_path if dev else com_url + url_path
|
url = dev_url + url_path if dev else com_url + url_path
|
||||||
params = {
|
params = {
|
||||||
"location": location,
|
"location": location,
|
||||||
"key" : key,
|
"key" : key,
|
||||||
"lang" : lang,
|
"lang" : lang,
|
||||||
"unit" : unit,
|
"unit" : unit,
|
||||||
}
|
}
|
||||||
async with httpx.AsyncClient() as client:
|
async with httpx.AsyncClient() as client:
|
||||||
resp = await client.get(url, params=params)
|
resp = await client.get(url, params=params)
|
||||||
return resp.json()
|
return resp.json()
|
||||||
|
|
||||||
|
|
||||||
async def get_weather_daily(
|
async def get_weather_daily(
|
||||||
key: str,
|
key: str,
|
||||||
location: str,
|
location: str,
|
||||||
lang: str = "zh",
|
lang: str = "zh",
|
||||||
unit: str = "m",
|
unit: str = "m",
|
||||||
dev: bool = get_memory_data("is_dev", True),
|
dev: bool = get_memory_data("is_dev", True),
|
||||||
) -> dict:
|
) -> dict:
|
||||||
url_path = "v7/weather/%dd?" % (7 if dev else 30)
|
url_path = "v7/weather/%dd?" % (7 if dev else 30)
|
||||||
url = dev_url + url_path if dev else com_url + url_path
|
url = dev_url + url_path if dev else com_url + url_path
|
||||||
params = {
|
params = {
|
||||||
"location": location,
|
"location": location,
|
||||||
"key" : key,
|
"key" : key,
|
||||||
"lang" : lang,
|
"lang" : lang,
|
||||||
"unit" : unit,
|
"unit" : unit,
|
||||||
}
|
}
|
||||||
async with httpx.AsyncClient() as client:
|
async with httpx.AsyncClient() as client:
|
||||||
resp = await client.get(url, params=params)
|
resp = await client.get(url, params=params)
|
||||||
return resp.json()
|
return resp.json()
|
||||||
|
|
||||||
|
|
||||||
async def get_weather_hourly(
|
async def get_weather_hourly(
|
||||||
key: str,
|
key: str,
|
||||||
location: str,
|
location: str,
|
||||||
lang: str = "zh",
|
lang: str = "zh",
|
||||||
unit: str = "m",
|
unit: str = "m",
|
||||||
dev: bool = get_memory_data("is_dev", True),
|
dev: bool = get_memory_data("is_dev", True),
|
||||||
) -> dict:
|
) -> dict:
|
||||||
url_path = "v7/weather/%dh?" % (24 if dev else 168)
|
url_path = "v7/weather/%dh?" % (24 if dev else 168)
|
||||||
url = dev_url + url_path if dev else com_url + url_path
|
url = dev_url + url_path if dev else com_url + url_path
|
||||||
params = {
|
params = {
|
||||||
"location": location,
|
"location": location,
|
||||||
"key" : key,
|
"key" : key,
|
||||||
"lang" : lang,
|
"lang" : lang,
|
||||||
"unit" : unit,
|
"unit" : unit,
|
||||||
}
|
}
|
||||||
async with httpx.AsyncClient() as client:
|
async with httpx.AsyncClient() as client:
|
||||||
resp = await client.get(url, params=params)
|
resp = await client.get(url, params=params)
|
||||||
return resp.json()
|
return resp.json()
|
||||||
|
|
||||||
|
|
||||||
async def get_airquality(
|
async def get_airquality(
|
||||||
key: str,
|
key: str,
|
||||||
location: str,
|
location: str,
|
||||||
lang: str,
|
lang: str,
|
||||||
pollutant: bool = False,
|
pollutant: bool = False,
|
||||||
station: bool = False,
|
station: bool = False,
|
||||||
dev: bool = get_memory_data("is_dev", True),
|
dev: bool = get_memory_data("is_dev", True),
|
||||||
) -> dict:
|
) -> dict:
|
||||||
url_path = f"airquality/v1/now/{location}?"
|
url_path = f"airquality/v1/now/{location}?"
|
||||||
url = dev_url + url_path if dev else com_url + url_path
|
url = dev_url + url_path if dev else com_url + url_path
|
||||||
params = {
|
params = {
|
||||||
"key" : key,
|
"key" : key,
|
||||||
"lang" : lang,
|
"lang" : lang,
|
||||||
"pollutant": pollutant,
|
"pollutant": pollutant,
|
||||||
"station" : station,
|
"station" : station,
|
||||||
}
|
}
|
||||||
async with httpx.AsyncClient() as client:
|
async with httpx.AsyncClient() as client:
|
||||||
resp = await client.get(url, params=params)
|
resp = await client.get(url, params=params)
|
||||||
return resp.json()
|
return resp.json()
|
@ -1,62 +1,62 @@
|
|||||||
from src.utils.base.data import LiteModel
|
from src.utils.base.data import LiteModel
|
||||||
|
|
||||||
|
|
||||||
class Location(LiteModel):
|
class Location(LiteModel):
|
||||||
name: str = ""
|
name: str = ""
|
||||||
id: str = ""
|
id: str = ""
|
||||||
lat: str = ""
|
lat: str = ""
|
||||||
lon: str = ""
|
lon: str = ""
|
||||||
adm2: str = ""
|
adm2: str = ""
|
||||||
adm1: str = ""
|
adm1: str = ""
|
||||||
country: str = ""
|
country: str = ""
|
||||||
tz: str = ""
|
tz: str = ""
|
||||||
utcOffset: str = ""
|
utcOffset: str = ""
|
||||||
isDst: str = ""
|
isDst: str = ""
|
||||||
type: str = ""
|
type: str = ""
|
||||||
rank: str = ""
|
rank: str = ""
|
||||||
fxLink: str = ""
|
fxLink: str = ""
|
||||||
sources: str = ""
|
sources: str = ""
|
||||||
license: str = ""
|
license: str = ""
|
||||||
|
|
||||||
|
|
||||||
class CityLookup(LiteModel):
|
class CityLookup(LiteModel):
|
||||||
code: str = ""
|
code: str = ""
|
||||||
location: list[Location] = [Location()]
|
location: list[Location] = [Location()]
|
||||||
|
|
||||||
|
|
||||||
class Now(LiteModel):
|
class Now(LiteModel):
|
||||||
obsTime: str = ""
|
obsTime: str = ""
|
||||||
temp: str = ""
|
temp: str = ""
|
||||||
feelsLike: str = ""
|
feelsLike: str = ""
|
||||||
icon: str = ""
|
icon: str = ""
|
||||||
text: str = ""
|
text: str = ""
|
||||||
wind360: str = ""
|
wind360: str = ""
|
||||||
windDir: str = ""
|
windDir: str = ""
|
||||||
windScale: str = ""
|
windScale: str = ""
|
||||||
windSpeed: str = ""
|
windSpeed: str = ""
|
||||||
humidity: str = ""
|
humidity: str = ""
|
||||||
precip: str = ""
|
precip: str = ""
|
||||||
pressure: str = ""
|
pressure: str = ""
|
||||||
vis: str = ""
|
vis: str = ""
|
||||||
cloud: str = ""
|
cloud: str = ""
|
||||||
dew: str = ""
|
dew: str = ""
|
||||||
sources: str = ""
|
sources: str = ""
|
||||||
license: str = ""
|
license: str = ""
|
||||||
|
|
||||||
|
|
||||||
class WeatherNow(LiteModel):
|
class WeatherNow(LiteModel):
|
||||||
code: str = ""
|
code: str = ""
|
||||||
updateTime: str = ""
|
updateTime: str = ""
|
||||||
fxLink: str = ""
|
fxLink: str = ""
|
||||||
now: Now = Now()
|
now: Now = Now()
|
||||||
|
|
||||||
|
|
||||||
class Daily(LiteModel):
|
class Daily(LiteModel):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class WeatherDaily(LiteModel):
|
class WeatherDaily(LiteModel):
|
||||||
code: str = ""
|
code: str = ""
|
||||||
updateTime: str = ""
|
updateTime: str = ""
|
||||||
fxLink: str = ""
|
fxLink: str = ""
|
||||||
daily: list[str] = []
|
daily: list[str] = []
|
@ -1,101 +1,101 @@
|
|||||||
from nonebot import require, on_endswith
|
from nonebot import require, on_endswith
|
||||||
from nonebot.adapters import satori
|
from nonebot.adapters import satori
|
||||||
from nonebot.adapters.onebot.v11 import MessageSegment
|
from nonebot.adapters.onebot.v11 import MessageSegment
|
||||||
from nonebot.internal.matcher import Matcher
|
from nonebot.internal.matcher import Matcher
|
||||||
|
|
||||||
from src.utils.base.config import get_config
|
from src.utils.base.config import get_config
|
||||||
from src.utils.base.ly_typing import T_MessageEvent
|
from src.utils.base.ly_typing import T_MessageEvent
|
||||||
|
|
||||||
from .qw_api import *
|
from .qw_api import *
|
||||||
from src.utils.base.data_manager import User, user_db
|
from src.utils.base.data_manager import User, user_db
|
||||||
from src.utils.base.language import Language, get_user_lang
|
from src.utils.base.language import Language, get_user_lang
|
||||||
from src.utils.base.resource import get_path
|
from src.utils.base.resource import get_path
|
||||||
from src.utils.message.html_tool import template2image
|
from src.utils.message.html_tool import template2image
|
||||||
from src.utils import event as event_utils
|
from src.utils import event as event_utils
|
||||||
|
|
||||||
require("nonebot_plugin_alconna")
|
require("nonebot_plugin_alconna")
|
||||||
from nonebot_plugin_alconna import on_alconna, Alconna, Args, MultiVar, Arparma, UniMessage
|
from nonebot_plugin_alconna import on_alconna, Alconna, Args, MultiVar, Arparma, UniMessage
|
||||||
|
|
||||||
wx_alc = on_alconna(
|
wx_alc = on_alconna(
|
||||||
aliases={"天气"},
|
aliases={"天气"},
|
||||||
command=Alconna(
|
command=Alconna(
|
||||||
"weather",
|
"weather",
|
||||||
Args["keywords", MultiVar(str), []],
|
Args["keywords", MultiVar(str), []],
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@wx_alc.handle()
|
@wx_alc.handle()
|
||||||
async def _(result: Arparma, event: T_MessageEvent, matcher: Matcher):
|
async def _(result: Arparma, event: T_MessageEvent, matcher: Matcher):
|
||||||
"""await alconna.send("weather", city)"""
|
"""await alconna.send("weather", city)"""
|
||||||
kws = result.main_args.get("keywords")
|
kws = result.main_args.get("keywords")
|
||||||
image = await get_weather_now_card(matcher, event, kws)
|
image = await get_weather_now_card(matcher, event, kws)
|
||||||
await wx_alc.finish(UniMessage.image(raw=image))
|
await wx_alc.finish(UniMessage.image(raw=image))
|
||||||
|
|
||||||
|
|
||||||
@on_endswith(("天气", "weather")).handle()
|
@on_endswith(("天气", "weather")).handle()
|
||||||
async def _(event: T_MessageEvent, matcher: Matcher):
|
async def _(event: T_MessageEvent, matcher: Matcher):
|
||||||
"""await alconna.send("weather", city)"""
|
"""await alconna.send("weather", city)"""
|
||||||
# kws = event.message.extract_plain_text()
|
# kws = event.message.extract_plain_text()
|
||||||
kws = event.get_plaintext()
|
kws = event.get_plaintext()
|
||||||
image = await get_weather_now_card(matcher, event, [kws.replace("天气", "").replace("weather", "")], False)
|
image = await get_weather_now_card(matcher, event, [kws.replace("天气", "").replace("weather", "")], False)
|
||||||
if isinstance(event, satori.event.Event):
|
if isinstance(event, satori.event.Event):
|
||||||
await matcher.finish(satori.MessageSegment.image(raw=image, mime="image/png"))
|
await matcher.finish(satori.MessageSegment.image(raw=image, mime="image/png"))
|
||||||
else:
|
else:
|
||||||
await matcher.finish(MessageSegment.image(image))
|
await matcher.finish(MessageSegment.image(image))
|
||||||
|
|
||||||
|
|
||||||
async def get_weather_now_card(matcher: Matcher, event: T_MessageEvent, keyword: list[str], tip: bool = True):
|
async def get_weather_now_card(matcher: Matcher, event: T_MessageEvent, keyword: list[str], tip: bool = True):
|
||||||
ulang = get_user_lang(event_utils.get_user_id(event))
|
ulang = get_user_lang(event_utils.get_user_id(event))
|
||||||
qw_lang = get_qw_lang(ulang.lang_code)
|
qw_lang = get_qw_lang(ulang.lang_code)
|
||||||
key = get_config("weather_key")
|
key = get_config("weather_key")
|
||||||
is_dev = get_memory_data("weather.is_dev", True)
|
is_dev = get_memory_data("weather.is_dev", True)
|
||||||
user: User = user_db.where_one(User(), "user_id = ?", event_utils.get_user_id(event), default=User())
|
user: User = user_db.where_one(User(), "user_id = ?", event_utils.get_user_id(event), default=User())
|
||||||
# params
|
# params
|
||||||
unit = user.profile.get("unit", "m")
|
unit = user.profile.get("unit", "m")
|
||||||
stored_location = user.profile.get("location", None)
|
stored_location = user.profile.get("location", None)
|
||||||
|
|
||||||
if not key:
|
if not key:
|
||||||
await matcher.finish(ulang.get("weather.no_key") if tip else None)
|
await matcher.finish(ulang.get("weather.no_key") if tip else None)
|
||||||
|
|
||||||
if keyword:
|
if keyword:
|
||||||
if len(keyword) >= 2:
|
if len(keyword) >= 2:
|
||||||
adm = keyword[0]
|
adm = keyword[0]
|
||||||
city = keyword[-1]
|
city = keyword[-1]
|
||||||
else:
|
else:
|
||||||
adm = ""
|
adm = ""
|
||||||
city = keyword[0]
|
city = keyword[0]
|
||||||
city_info = await city_lookup(city, key, adm=adm, lang=qw_lang)
|
city_info = await city_lookup(city, key, adm=adm, lang=qw_lang)
|
||||||
city_name = " ".join(keyword)
|
city_name = " ".join(keyword)
|
||||||
else:
|
else:
|
||||||
if not stored_location:
|
if not stored_location:
|
||||||
await matcher.finish(ulang.get("liteyuki.invalid_command", TEXT="location") if tip else None)
|
await matcher.finish(ulang.get("liteyuki.invalid_command", TEXT="location") if tip else None)
|
||||||
city_info = await city_lookup(stored_location, key, lang=qw_lang)
|
city_info = await city_lookup(stored_location, key, lang=qw_lang)
|
||||||
city_name = stored_location
|
city_name = stored_location
|
||||||
if city_info.code == "200":
|
if city_info.code == "200":
|
||||||
location_data = city_info.location[0]
|
location_data = city_info.location[0]
|
||||||
else:
|
else:
|
||||||
await matcher.finish(ulang.get("weather.city_not_found", CITY=city_name) if tip else None)
|
await matcher.finish(ulang.get("weather.city_not_found", CITY=city_name) if tip else None)
|
||||||
weather_now = await get_weather_now(key, location_data.id, lang=qw_lang, unit=unit, dev=is_dev)
|
weather_now = await get_weather_now(key, location_data.id, lang=qw_lang, unit=unit, dev=is_dev)
|
||||||
weather_daily = await get_weather_daily(key, location_data.id, lang=qw_lang, unit=unit, dev=is_dev)
|
weather_daily = await get_weather_daily(key, location_data.id, lang=qw_lang, unit=unit, dev=is_dev)
|
||||||
weather_hourly = await get_weather_hourly(key, location_data.id, lang=qw_lang, unit=unit, dev=is_dev)
|
weather_hourly = await get_weather_hourly(key, location_data.id, lang=qw_lang, unit=unit, dev=is_dev)
|
||||||
aqi = await get_airquality(key, location_data.id, lang=qw_lang, dev=is_dev)
|
aqi = await get_airquality(key, location_data.id, lang=qw_lang, dev=is_dev)
|
||||||
|
|
||||||
image = await template2image(
|
image = await template2image(
|
||||||
template=get_path("templates/weather_now.html", abs_path=True),
|
template=get_path("templates/weather_now.html", abs_path=True),
|
||||||
templates={
|
templates={
|
||||||
"data": {
|
"data": {
|
||||||
"params" : {
|
"params" : {
|
||||||
"unit": unit,
|
"unit": unit,
|
||||||
"lang": ulang.lang_code,
|
"lang": ulang.lang_code,
|
||||||
},
|
},
|
||||||
"weatherNow" : weather_now,
|
"weatherNow" : weather_now,
|
||||||
"weatherDaily" : weather_daily,
|
"weatherDaily" : weather_daily,
|
||||||
"weatherHourly": weather_hourly,
|
"weatherHourly": weather_hourly,
|
||||||
"aqi" : aqi,
|
"aqi" : aqi,
|
||||||
"location" : location_data.dump(),
|
"location" : location_data.dump(),
|
||||||
"localization" : get_local_data(ulang.lang_code)
|
"localization" : get_local_data(ulang.lang_code)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
return image
|
return image
|
@ -1,12 +1,12 @@
|
|||||||
weather.monday=Mon
|
weather.monday=Mon
|
||||||
weather.tuesday=Tue
|
weather.tuesday=Tue
|
||||||
weather.wednesday=Wed
|
weather.wednesday=Wed
|
||||||
weather.thursday=Thu
|
weather.thursday=Thu
|
||||||
weather.friday=Fri
|
weather.friday=Fri
|
||||||
weather.saturday=Sat
|
weather.saturday=Sat
|
||||||
weather.sunday=Sun
|
weather.sunday=Sun
|
||||||
weather.day=Day
|
weather.day=Day
|
||||||
weather.night=Night
|
weather.night=Night
|
||||||
weather.today=Today
|
weather.today=Today
|
||||||
weather.tomorrow=Tomorrow
|
weather.tomorrow=Tomorrow
|
||||||
weather.no_aqi=No AQI data
|
weather.no_aqi=No AQI data
|
@ -1,12 +1,12 @@
|
|||||||
weather.monday=月
|
weather.monday=月
|
||||||
weather.tuesday=火
|
weather.tuesday=火
|
||||||
weather.wednesday=水
|
weather.wednesday=水
|
||||||
weather.thursday=木
|
weather.thursday=木
|
||||||
weather.friday=金
|
weather.friday=金
|
||||||
weather.saturday=土
|
weather.saturday=土
|
||||||
weather.sunday=日
|
weather.sunday=日
|
||||||
weather.day=昼
|
weather.day=昼
|
||||||
weather.night=夜
|
weather.night=夜
|
||||||
weather.today=今日
|
weather.today=今日
|
||||||
weather.tomorrow=明日
|
weather.tomorrow=明日
|
||||||
weather.no_aqi=空気質データなし
|
weather.no_aqi=空気質データなし
|
@ -1,12 +1,12 @@
|
|||||||
weather.monday=周一
|
weather.monday=周一
|
||||||
weather.tuesday=周二
|
weather.tuesday=周二
|
||||||
weather.wednesday=周三
|
weather.wednesday=周三
|
||||||
weather.thursday=周四
|
weather.thursday=周四
|
||||||
weather.friday=周五
|
weather.friday=周五
|
||||||
weather.saturday=周六
|
weather.saturday=周六
|
||||||
weather.sunday=周日
|
weather.sunday=周日
|
||||||
weather.day=白天
|
weather.day=白天
|
||||||
weather.night=夜晚
|
weather.night=夜晚
|
||||||
weather.today=今天
|
weather.today=今天
|
||||||
weather.tomorrow=明天
|
weather.tomorrow=明天
|
||||||
weather.no_aqi=暂无AQI数据
|
weather.no_aqi=暂无AQI数据
|
@ -1,3 +1,3 @@
|
|||||||
name: 轻雪天气资源包
|
name: 轻雪天气资源包
|
||||||
description: For Liteyuki Weather
|
description: For Liteyuki Weather
|
||||||
version: 2024.4.26
|
version: 2024.4.26
|
@ -1,184 +1,184 @@
|
|||||||
:root {
|
:root {
|
||||||
--main-text-color: #fff;
|
--main-text-color: #fff;
|
||||||
--sub-text-color: #ccc;
|
--sub-text-color: #ccc;
|
||||||
--tip-text-color: #999;
|
--tip-text-color: #999;
|
||||||
--device-info-width: 240px;
|
--device-info-width: 240px;
|
||||||
--sub-border-radius: 60px;
|
--sub-border-radius: 60px;
|
||||||
}
|
}
|
||||||
|
|
||||||
#weather-info {
|
#weather-info {
|
||||||
color: white;
|
color: white;
|
||||||
/*justify-content: center;*/
|
/*justify-content: center;*/
|
||||||
/*align-items: center;*/
|
/*align-items: center;*/
|
||||||
/*align-content: center;*/
|
/*align-content: center;*/
|
||||||
}
|
}
|
||||||
|
|
||||||
.icon {
|
.icon {
|
||||||
/* icon 类img阴影*/
|
/* icon 类img阴影*/
|
||||||
filter: drop-shadow(1px 1px 10px #00000044);
|
filter: drop-shadow(1px 1px 10px #00000044);
|
||||||
}
|
}
|
||||||
|
|
||||||
#main-info {
|
#main-info {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
#main-left {
|
#main-left {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: flex-end;
|
justify-content: flex-end;
|
||||||
width: 50%;
|
width: 50%;
|
||||||
}
|
}
|
||||||
|
|
||||||
#main-right {
|
#main-right {
|
||||||
width: 50%;
|
width: 50%;
|
||||||
}
|
}
|
||||||
|
|
||||||
#time {
|
#time {
|
||||||
font-size: 25px;
|
font-size: 25px;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
font-style: italic;
|
font-style: italic;
|
||||||
text-align: right;
|
text-align: right;
|
||||||
color: var(--sub-text-color);
|
color: var(--sub-text-color);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#adm {
|
#adm {
|
||||||
font-size: 32px;
|
font-size: 32px;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
color: var(--sub-text-color);
|
color: var(--sub-text-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
#city {
|
#city {
|
||||||
margin-top: 20px;
|
margin-top: 20px;
|
||||||
font-size: 70px;
|
font-size: 70px;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
#temperature {
|
#temperature {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: baseline;
|
align-items: baseline;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#temperature-now {
|
#temperature-now {
|
||||||
font-size: 70px;
|
font-size: 70px;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
}
|
}
|
||||||
|
|
||||||
#temperature-range {
|
#temperature-range {
|
||||||
font-size: 40px;
|
font-size: 40px;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
color: var(--sub-text-color);
|
color: var(--sub-text-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
#description {
|
#description {
|
||||||
font-size: 50px;
|
font-size: 50px;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
#aqi {
|
#aqi {
|
||||||
height: 50px;
|
height: 50px;
|
||||||
display: flex;
|
display: flex;
|
||||||
border-radius: 60px;
|
border-radius: 60px;
|
||||||
padding: 5px;
|
padding: 5px;
|
||||||
font-size: 40px;
|
font-size: 40px;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
align-content: center;
|
align-content: center;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
#aqi-dot {
|
#aqi-dot {
|
||||||
height: 80%;
|
height: 80%;
|
||||||
aspect-ratio: 1 / 1;
|
aspect-ratio: 1 / 1;
|
||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
background-color: var(--sub-text-color);
|
background-color: var(--sub-text-color);
|
||||||
margin-right: 20px;
|
margin-right: 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.main-icon {
|
.main-icon {
|
||||||
width: 240px;
|
width: 240px;
|
||||||
height: 240px;
|
height: 240px;
|
||||||
}
|
}
|
||||||
|
|
||||||
#hours-info {
|
#hours-info {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
}
|
}
|
||||||
|
|
||||||
.hourly-item {
|
.hourly-item {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
background-color: #ffffff44;
|
background-color: #ffffff44;
|
||||||
border-radius: var(--sub-border-radius);
|
border-radius: var(--sub-border-radius);
|
||||||
align-items: center;
|
align-items: center;
|
||||||
padding: 20px 10px;
|
padding: 20px 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.hourly-icon{
|
.hourly-icon{
|
||||||
width: 80%;
|
width: 80%;
|
||||||
margin-bottom: 20px;
|
margin-bottom: 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.hourly-temperature {
|
.hourly-temperature {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
color: var(--main-text-color);
|
color: var(--main-text-color);
|
||||||
font-size: 30px;
|
font-size: 30px;
|
||||||
margin-bottom: 20px;
|
margin-bottom: 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.hourly-time {
|
.hourly-time {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
color: var(--main-text-color);
|
color: var(--main-text-color);
|
||||||
font-size: 25px;
|
font-size: 25px;
|
||||||
margin-bottom: 10px;
|
margin-bottom: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**/
|
/**/
|
||||||
.daily-item {
|
.daily-item {
|
||||||
display: flex;
|
display: flex;
|
||||||
position: relative;
|
position: relative;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
background-color: #ffffff44;
|
background-color: #ffffff44;
|
||||||
height: 90px;
|
height: 90px;
|
||||||
border-radius: var(--sub-border-radius);
|
border-radius: var(--sub-border-radius);
|
||||||
margin-bottom: 20px;
|
margin-bottom: 20px;
|
||||||
padding: 0 30px;
|
padding: 0 30px;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*最后一个没有margin_button*/
|
/*最后一个没有margin_button*/
|
||||||
.daily-item:last-child {
|
.daily-item:last-child {
|
||||||
margin-bottom: 0;
|
margin-bottom: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.icon-day {
|
.icon-day {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
left: 60%;
|
left: 60%;
|
||||||
height: 80px;
|
height: 80px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.icon-night {
|
.icon-night {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
left: 70%;
|
left: 70%;
|
||||||
height: 80px;
|
height: 80px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.daily-weather{
|
.daily-weather{
|
||||||
position: absolute;
|
position: absolute;
|
||||||
left: 30%;
|
left: 30%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.daily-temperature{
|
.daily-temperature{
|
||||||
position: absolute;
|
position: absolute;
|
||||||
left: 83%;
|
left: 83%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.daily-day, .daily-weather, .daily-temperature {
|
.daily-day, .daily-weather, .daily-temperature {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
color: var(--main-text-color);
|
color: var(--main-text-color);
|
||||||
font-size: 30px;
|
font-size: 30px;
|
||||||
}
|
}
|
Before Width: | Height: | Size: 7.2 KiB After Width: | Height: | Size: 7.2 KiB |
Before Width: | Height: | Size: 6.4 KiB After Width: | Height: | Size: 6.4 KiB |
Before Width: | Height: | Size: 7.0 KiB After Width: | Height: | Size: 7.0 KiB |
Before Width: | Height: | Size: 6.7 KiB After Width: | Height: | Size: 6.7 KiB |
Before Width: | Height: | Size: 5.2 KiB After Width: | Height: | Size: 5.2 KiB |
Before Width: | Height: | Size: 5.1 KiB After Width: | Height: | Size: 5.1 KiB |
Before Width: | Height: | Size: 5.9 KiB After Width: | Height: | Size: 5.9 KiB |
Before Width: | Height: | Size: 5.7 KiB After Width: | Height: | Size: 5.7 KiB |
Before Width: | Height: | Size: 5.7 KiB After Width: | Height: | Size: 5.7 KiB |
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 16 KiB |
Before Width: | Height: | Size: 9.6 KiB After Width: | Height: | Size: 9.6 KiB |
Before Width: | Height: | Size: 10 KiB After Width: | Height: | Size: 10 KiB |
Before Width: | Height: | Size: 8.8 KiB After Width: | Height: | Size: 8.8 KiB |
Before Width: | Height: | Size: 9.0 KiB After Width: | Height: | Size: 9.0 KiB |
Before Width: | Height: | Size: 9.5 KiB After Width: | Height: | Size: 9.5 KiB |
Before Width: | Height: | Size: 8.5 KiB After Width: | Height: | Size: 8.5 KiB |
Before Width: | Height: | Size: 9.4 KiB After Width: | Height: | Size: 9.4 KiB |
Before Width: | Height: | Size: 9.1 KiB After Width: | Height: | Size: 9.1 KiB |
Before Width: | Height: | Size: 9.5 KiB After Width: | Height: | Size: 9.5 KiB |
Before Width: | Height: | Size: 8.6 KiB After Width: | Height: | Size: 8.6 KiB |
Before Width: | Height: | Size: 9.9 KiB After Width: | Height: | Size: 9.9 KiB |
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 12 KiB |
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 12 KiB |
Before Width: | Height: | Size: 6.8 KiB After Width: | Height: | Size: 6.8 KiB |
Before Width: | Height: | Size: 9.3 KiB After Width: | Height: | Size: 9.3 KiB |
Before Width: | Height: | Size: 9.1 KiB After Width: | Height: | Size: 9.1 KiB |
Before Width: | Height: | Size: 9.8 KiB After Width: | Height: | Size: 9.8 KiB |
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 11 KiB |
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 12 KiB |
Before Width: | Height: | Size: 9.1 KiB After Width: | Height: | Size: 9.1 KiB |
Before Width: | Height: | Size: 9.5 KiB After Width: | Height: | Size: 9.5 KiB |
Before Width: | Height: | Size: 8.6 KiB After Width: | Height: | Size: 8.6 KiB |
Before Width: | Height: | Size: 6.5 KiB After Width: | Height: | Size: 6.5 KiB |
Before Width: | Height: | Size: 7.5 KiB After Width: | Height: | Size: 7.5 KiB |
Before Width: | Height: | Size: 8.9 KiB After Width: | Height: | Size: 8.9 KiB |
Before Width: | Height: | Size: 10 KiB After Width: | Height: | Size: 10 KiB |
Before Width: | Height: | Size: 9.0 KiB After Width: | Height: | Size: 9.0 KiB |
Before Width: | Height: | Size: 9.0 KiB After Width: | Height: | Size: 9.0 KiB |
Before Width: | Height: | Size: 10 KiB After Width: | Height: | Size: 10 KiB |
Before Width: | Height: | Size: 10 KiB After Width: | Height: | Size: 10 KiB |
Before Width: | Height: | Size: 7.5 KiB After Width: | Height: | Size: 7.5 KiB |
Before Width: | Height: | Size: 8.9 KiB After Width: | Height: | Size: 8.9 KiB |
Before Width: | Height: | Size: 10 KiB After Width: | Height: | Size: 10 KiB |
Before Width: | Height: | Size: 9.5 KiB After Width: | Height: | Size: 9.5 KiB |
Before Width: | Height: | Size: 9.5 KiB After Width: | Height: | Size: 9.5 KiB |
Before Width: | Height: | Size: 5.8 KiB After Width: | Height: | Size: 5.8 KiB |
Before Width: | Height: | Size: 4.6 KiB After Width: | Height: | Size: 4.6 KiB |
Before Width: | Height: | Size: 4.6 KiB After Width: | Height: | Size: 4.6 KiB |
Before Width: | Height: | Size: 4.7 KiB After Width: | Height: | Size: 4.7 KiB |
Before Width: | Height: | Size: 6.8 KiB After Width: | Height: | Size: 6.8 KiB |
Before Width: | Height: | Size: 6.8 KiB After Width: | Height: | Size: 6.8 KiB |
Before Width: | Height: | Size: 6.7 KiB After Width: | Height: | Size: 6.7 KiB |
Before Width: | Height: | Size: 6.9 KiB After Width: | Height: | Size: 6.9 KiB |
Before Width: | Height: | Size: 5.4 KiB After Width: | Height: | Size: 5.4 KiB |
Before Width: | Height: | Size: 4.9 KiB After Width: | Height: | Size: 4.9 KiB |
Before Width: | Height: | Size: 6.1 KiB After Width: | Height: | Size: 6.1 KiB |
Before Width: | Height: | Size: 6.9 KiB After Width: | Height: | Size: 6.9 KiB |