🤡我知道您急,您先别急,这是有bug的,我还没改完,我就想换台设备来写
4
.gitignore
vendored
@ -18,6 +18,10 @@ config.yml
|
||||
config.example.yml
|
||||
compile.bat
|
||||
src/resources/templates/latest-debug.html
|
||||
|
||||
src/plugins/trimo_plugin_msctconverter/config
|
||||
src/plugins/trimo_plugin_msctconverter/temp
|
||||
|
||||
# vuepress
|
||||
.github
|
||||
pyproject.toml
|
||||
|
@ -42,7 +42,7 @@
|
||||
版权所有 © 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)。\
|
||||
若非因法律要求或经过了特殊准许,此作品在根据本协议“原样”提供的基础上,不予提供任何形式的担保、任何明示、任何暗示或类似承诺。也就是说,用户将自行承担因此作品的质量或性能问题而产生的全部风险。\
|
||||
详细的准许和限制条款请见原协议文本。
|
||||
@ -57,4 +57,4 @@
|
||||
[lightyuki-link]: /
|
||||
[python-link]: https://www.python.org/
|
||||
[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
|
||||
|
||||
remote_urls = [
|
||||
"https://gitee.com/TriM-Organization/LiteyukiBot-TriM.git",
|
||||
"https://github.com/TriM-Organization/LiteyukiBot-TriM.git"
|
||||
"https://gitee.com/TriM-Organization/LiteyukiBot-TriMO.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_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
|
||||
|
||||
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(
|
||||
@ -36,42 +46,38 @@ markdown_image = common_db.where_one(StoredConfig(), default=StoredConfig()).con
|
||||
"ryounecho",
|
||||
Args["text", str, ""],
|
||||
),
|
||||
permission=SUPERUSER
|
||||
permission=SUPERUSER,
|
||||
).handle()
|
||||
# Satori OK
|
||||
async def _(bot: T_Bot, matcher: Matcher, result: Arparma):
|
||||
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:
|
||||
await matcher.finish(f"君安!灵温向你问好~\n此机 {bot.self_id}")
|
||||
|
||||
|
||||
@on_alconna(
|
||||
command=Alconna(
|
||||
"liteecho",
|
||||
Args["text", str, ""],
|
||||
# Args["text", str, ""],
|
||||
),
|
||||
permission=SUPERUSER
|
||||
# permission=SUPERUSER
|
||||
).handle()
|
||||
# Satori OK
|
||||
async def _(bot: T_Bot, matcher: Matcher, result: Arparma):
|
||||
if result.main_args.get("text"):
|
||||
await matcher.finish(Message(unescape(result.main_args.get("text"))))
|
||||
else:
|
||||
await matcher.finish(f"Hello! TriM-Liteyuki!\nRyBot {bot.self_id}")
|
||||
await matcher.finish(f"Hello! TriMO-Liteyuki!\nRyBot {bot.self_id}")
|
||||
|
||||
|
||||
@on_alconna(
|
||||
aliases={"更新灵温"},
|
||||
command=Alconna(
|
||||
"update-ryoun"
|
||||
),
|
||||
permission=SUPERUSER
|
||||
aliases={"更新灵温"}, command=Alconna("update-ryoun"), permission=SUPERUSER
|
||||
).handle()
|
||||
# Satori OK
|
||||
async def _(bot: T_Bot, event: T_MessageEvent):
|
||||
# 使用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()
|
||||
reply = "尹灵温 更新完成!\n"
|
||||
reply += f"```\n{logs}\n```\n"
|
||||
@ -82,11 +88,11 @@ async def _(bot: T_Bot, event: T_MessageEvent):
|
||||
|
||||
|
||||
@on_alconna(
|
||||
aliases={"重启灵温","重启尹灵温", "重载灵温"},
|
||||
aliases={"重启灵温", "重启尹灵温", "重载灵温"},
|
||||
command=Alconna(
|
||||
"reload-ryoun",
|
||||
),
|
||||
permission=SUPERUSER
|
||||
permission=SUPERUSER,
|
||||
).handle()
|
||||
# Satori OK
|
||||
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(
|
||||
{
|
||||
"reload" : True,
|
||||
"reload_time" : time.time(),
|
||||
"reload_bot_id" : bot.self_id,
|
||||
"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,
|
||||
satori.event.Event) else event.channel.id,
|
||||
"delta_time" : 0
|
||||
"reload": True,
|
||||
"reload_time": time.time(),
|
||||
"reload_bot_id": bot.self_id,
|
||||
"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, 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",
|
||||
Args["key", str]["value", Any],
|
||||
alias=["设置"],
|
||||
|
||||
),
|
||||
Subcommand(
|
||||
"get",
|
||||
Args["key", str, None],
|
||||
alias=["查询", "获取"]
|
||||
),
|
||||
Subcommand(
|
||||
"remove",
|
||||
Args["key", str],
|
||||
alias=["删除"]
|
||||
)
|
||||
Subcommand("get", Args["key", str, None], alias=["查询", "获取"]),
|
||||
Subcommand("remove", Args["key", str], alias=["删除"]),
|
||||
),
|
||||
permission=SUPERUSER
|
||||
permission=SUPERUSER,
|
||||
).handle()
|
||||
# Satori OK
|
||||
async def _(result: Arparma, event: T_MessageEvent, bot: T_Bot, matcher: Matcher):
|
||||
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"):
|
||||
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:
|
||||
value = eval(value)
|
||||
except:
|
||||
pass
|
||||
stored_config.config[key] = value
|
||||
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"):
|
||||
key = result.subcommands.get("get").args.get("key")
|
||||
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:
|
||||
stored_config.config.pop(key)
|
||||
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:
|
||||
await matcher.finish(f"{ulang.get('liteyuki.invalid_command', TEXT=key)}")
|
||||
|
||||
|
||||
@on_alconna(
|
||||
aliases={"切换图片模式"},
|
||||
command=Alconna(
|
||||
"switch-image-mode"
|
||||
),
|
||||
permission=SUPERUSER
|
||||
aliases={"切换图片模式"}, command=Alconna("switch-image-mode"), permission=SUPERUSER
|
||||
).handle()
|
||||
# Satori OK
|
||||
async def _(event: T_MessageEvent, matcher: Matcher):
|
||||
global markdown_image
|
||||
# 切换图片模式,False以图片形式发送,True以markdown形式发送
|
||||
ulang = get_user_lang(str(event_utils.get_user_id(event)))
|
||||
stored_config: StoredConfig = common_db.where_one(StoredConfig(), default=StoredConfig())
|
||||
stored_config.config["markdown_image"] = not stored_config.config.get("markdown_image", False)
|
||||
stored_config: StoredConfig = common_db.where_one(
|
||||
StoredConfig(), default=StoredConfig()
|
||||
)
|
||||
stored_config.config["markdown_image"] = not stored_config.config.get(
|
||||
"markdown_image", False
|
||||
)
|
||||
markdown_image = stored_config.config["markdown_image"]
|
||||
common_db.save(stored_config)
|
||||
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(
|
||||
@ -209,7 +222,7 @@ async def _(event: T_MessageEvent, matcher: Matcher):
|
||||
"/function",
|
||||
Args["function", str]["args", MultiVar(str), ()],
|
||||
),
|
||||
permission=SUPERUSER
|
||||
permission=SUPERUSER,
|
||||
).handle()
|
||||
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 = []
|
||||
_kwargs = {
|
||||
"USER_ID" : str(event.user_id),
|
||||
"GROUP_ID": str(event.group_id) if event.message_type == "group" else "0",
|
||||
"BOT_ID" : str(bot.self_id)
|
||||
"USER_ID": str(event.user_id),
|
||||
"GROUP_ID": str(event.group_id) if event.message_type == "group" else "0",
|
||||
"BOT_ID": str(bot.self_id),
|
||||
}
|
||||
|
||||
for arg in args:
|
||||
@ -256,7 +269,7 @@ async def _(result: Arparma, bot: T_Bot, event: T_MessageEvent, matcher: Matcher
|
||||
"/api",
|
||||
Args["api", str]["args", MultiVar(AnyStr), ()],
|
||||
),
|
||||
permission=SUPERUSER
|
||||
permission=SUPERUSER,
|
||||
).handle()
|
||||
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")
|
||||
args: tuple[str] = result.main_args.get("args", ()) # 类似于url参数,但每个参数间用空格分隔,空格是%20
|
||||
args: tuple[str] = result.main_args.get(
|
||||
"args", ()
|
||||
) # 类似于url参数,但每个参数间用空格分隔,空格是%20
|
||||
args_dict = {}
|
||||
|
||||
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:
|
||||
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)
|
||||
|
||||
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 # 图片模式检测
|
||||
async def test_for_md_image(bot: T_Bot, api: str, data: dict):
|
||||
# 截获大图发送,转换为markdown发送
|
||||
if api in ["send_msg", "send_private_msg", "send_group_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":
|
||||
if (
|
||||
api in ["send_msg", "send_private_msg", "send_group_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_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_id = data.get("group_id")
|
||||
else:
|
||||
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:// http:// base64://
|
||||
if file.startswith("http"):
|
||||
result = await md.send_md(await md.image_async(file), bot, message_type=session_type,
|
||||
session_id=session_id)
|
||||
result = await md.send_md(
|
||||
await md.image_async(file),
|
||||
bot,
|
||||
message_type=session_type,
|
||||
session_id=session_id,
|
||||
)
|
||||
elif file.startswith("file"):
|
||||
file = file.replace("file://", "")
|
||||
result = await md.send_image(open(file, "rb").read(), bot, message_type=session_type,
|
||||
session_id=session_id)
|
||||
result = await md.send_image(
|
||||
open(file, "rb").read(),
|
||||
bot,
|
||||
message_type=session_type,
|
||||
session_id=session_id,
|
||||
)
|
||||
elif file.startswith("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:
|
||||
return
|
||||
raise MockApiException(result=result)
|
||||
@ -364,7 +407,7 @@ async def _(bot: T_Bot):
|
||||
if isinstance(bot, satori.Bot):
|
||||
await bot.send_message(
|
||||
channel_id=reload_session_id,
|
||||
message="灵温 重载耗时 %.2f 秒" % delta_time
|
||||
message="灵温 重载耗时 %.2f 秒" % delta_time,
|
||||
)
|
||||
else:
|
||||
await bot.call_api(
|
||||
@ -372,7 +415,7 @@ async def _(bot: T_Bot):
|
||||
message_type=reload_session_type,
|
||||
user_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
|
||||
need_user_id = (
|
||||
"send_private_msg",
|
||||
"send_msg",
|
||||
"set_group_card",
|
||||
"set_group_special_title",
|
||||
"get_stranger_info",
|
||||
"get_group_member_info"
|
||||
"send_private_msg",
|
||||
"send_msg",
|
||||
"set_group_card",
|
||||
"set_group_special_title",
|
||||
"get_stranger_info",
|
||||
"get_group_member_info",
|
||||
)
|
||||
|
||||
need_group_id = (
|
||||
"send_group_msg",
|
||||
"send_msg",
|
||||
"set_group_card",
|
||||
"set_group_name",
|
||||
|
||||
"set_group_special_title",
|
||||
"get_group_member_info",
|
||||
"get_group_member_list",
|
||||
"get_group_honor_info"
|
||||
)
|
||||
"send_group_msg",
|
||||
"send_msg",
|
||||
"set_group_card",
|
||||
"set_group_name",
|
||||
"set_group_special_title",
|
||||
"get_group_member_info",
|
||||
"get_group_member_list",
|
||||
"get_group_honor_info",
|
||||
)
|
||||
|
@ -183,4 +183,7 @@ async def _(bot: T_Bot, event: T_MessageEvent, result: Arparma, matcher: Matcher
|
||||
if send_as_md:
|
||||
await md.send_md(reply, bot, event=event)
|
||||
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,
|
||||
# Query,
|
||||
Arparma,
|
||||
Args,
|
||||
)
|
||||
|
||||
require("nonebot_plugin_apscheduler")
|
||||
@ -59,12 +60,18 @@ yanlun = on_alconna(
|
||||
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点更新
|
||||
@scheduler.scheduled_job("cron", hour=4)
|
||||
yanlun_path = (
|
||||
"https://gitee.com/TriM-Organization/LinglunStudio/raw/master/resources/myWords.txt"
|
||||
)
|
||||
|
||||
|
||||
# 每天1点更新
|
||||
@scheduler.scheduled_job("cron", hour=1)
|
||||
async def every_day_update():
|
||||
ulang = Language(get_default_lang_code(), "zh-WY")
|
||||
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)
|
||||
|
||||
if solar_date == (4, 3):
|
||||
yanlun_texts = ["金羿ELS 生日快乐~!"]
|
||||
yanlun_texts = ["金羿ELS 生日快乐~!", "Happy Birthday, Eilles!"]
|
||||
elif solar_date == (8, 6):
|
||||
yanlun_texts = ["诸葛八卦 生日快乐~!"]
|
||||
yanlun_texts = ["诸葛亮与八卦阵 生日快乐~!", "Happy Birthday, bgArray~!"]
|
||||
elif solar_date == (8, 16):
|
||||
yanlun_texts = ["鱼旧梦 生日快乐~!", "Happy Birthday, ElapsingDreams~!"]
|
||||
|
||||
else:
|
||||
try:
|
||||
yanlun_texts = (
|
||||
requests.get(
|
||||
"https://gitee.com/TriM-Organization/LinglunStudio/raw/master/resources/myWords.txt",
|
||||
yanlun_path,
|
||||
)
|
||||
.text.strip("\n")
|
||||
.split("\n")
|
||||
@ -165,13 +174,13 @@ async def _(
|
||||
# count: Query[bool] = AlconnaQuery("count.value", False),
|
||||
):
|
||||
# print(result.options)
|
||||
ulang = get_user_lang(event_utils.get_user_id(event)) # type: ignore
|
||||
if result.options["refresh"].value:
|
||||
ulang = get_user_lang(event_utils.get_user_id(event)) # type: ignore
|
||||
global yanlun_texts
|
||||
try:
|
||||
yanlun_texts = (
|
||||
requests.get(
|
||||
"https://gitee.com/TriM-Organization/LinglunStudio/raw/master/resources/myWords.txt",
|
||||
yanlun_path,
|
||||
)
|
||||
.text.strip("\n")
|
||||
.split("\n")
|
||||
@ -205,7 +214,6 @@ async def _(
|
||||
)
|
||||
yanlun_texts = ["灵光焕发 深艺献心"]
|
||||
if result.options["count"].value:
|
||||
ulang = get_user_lang(event_utils.get_user_id(event)) # type: ignore
|
||||
authors = [
|
||||
(
|
||||
("B站")
|
||||
@ -250,4 +258,27 @@ async def _(
|
||||
+ 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;
|
||||
display: flex;
|
||||
background-color: rgba(255, 255, 255, 0.9);
|
||||
border-radius: 100px;
|
||||
/* border-radius: 100px; */
|
||||
margin-bottom: 10px;
|
||||
padding-right: 10px;
|
||||
}
|
||||
@ -25,7 +25,7 @@
|
||||
}
|
||||
|
||||
.row-icon {
|
||||
border-radius: 50%;
|
||||
/* border-radius: 50%; */
|
||||
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.unknown=未知
|
||||
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.unknown=无名之亏
|
||||
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 class="bot-detail">
|
||||
<div class="bot-name" style="text-align:start">
|
||||
TriM-Liteyuki
|
||||
TriMO-Liteyuki
|
||||
</div>
|
||||
<hr>
|
||||
<div class="bot-icon-tags">
|
||||
|
@ -6,7 +6,7 @@ import time
|
||||
|
||||
import nonebot
|
||||
|
||||
__NAME__ = "LiteyukiBot-Trim"
|
||||
__NAME__ = "LiteyukiBot-TriMO"
|
||||
__VERSION__ = "6.3.3" # 60201
|
||||
# __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:
|
||||
_language_data[lang_code] = {}
|
||||
_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:
|
||||
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):
|
||||
@ -196,7 +196,7 @@ def get_user_lang(user_id: str) -> Language:
|
||||
user_id = str(user_id)
|
||||
|
||||
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_id = ?",
|
||||
|
@ -6,7 +6,7 @@ from os import getcwd
|
||||
import aiofiles
|
||||
import nonebot
|
||||
|
||||
from nonebot_plugin_htmlrender import *
|
||||
from nonebot_plugin_htmlrender import * # type: ignore
|
||||
from .tools import random_hex_string
|
||||
|
||||
# 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 user_id in config.get("superusers", []):
|
||||
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:
|
||||
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:
|
||||
@staticmethod
|
||||
async def send_md(
|
||||
markdown: str,
|
||||
bot: T_Bot, *,
|
||||
message_type: str = None,
|
||||
session_id: str | int = None,
|
||||
event: T_MessageEvent = None,
|
||||
retry_as_image: bool = True,
|
||||
**kwargs
|
||||
markdown: str,
|
||||
bot: T_Bot,
|
||||
*,
|
||||
message_type: str = None,
|
||||
session_id: str | int = None,
|
||||
event: T_MessageEvent = None,
|
||||
retry_as_image: bool = True,
|
||||
**kwargs,
|
||||
) -> dict[str, Any] | None:
|
||||
"""
|
||||
发送Markdown消息,支持自动转为图片发送
|
||||
@ -64,7 +67,7 @@ class MarkdownMessage:
|
||||
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 isinstance(event, satori.event.Event):
|
||||
message_type = "private" if event.guild is None else "group"
|
||||
@ -73,83 +76,81 @@ class MarkdownMessage:
|
||||
assert event is not None
|
||||
message_type = event.message_type
|
||||
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
|
||||
|
||||
|
||||
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(
|
||||
user_id=session_id,
|
||||
group_id=session_id,
|
||||
message_type=message_type,
|
||||
message=[
|
||||
{
|
||||
"type": "longmsg",
|
||||
"data": {
|
||||
"id": forward_id
|
||||
}
|
||||
},
|
||||
],
|
||||
**kwargs
|
||||
group_id=session_id,
|
||||
user_id=session_id,
|
||||
message=v11.MessageSegment.image(md_image_bytes),
|
||||
)
|
||||
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
|
||||
|
||||
@staticmethod
|
||||
async def send_image(
|
||||
image: bytes | str,
|
||||
bot: T_Bot, *,
|
||||
message_type: str = None,
|
||||
session_id: str | int = None,
|
||||
event: T_MessageEvent = None,
|
||||
**kwargs
|
||||
image: bytes | str,
|
||||
bot: T_Bot,
|
||||
*,
|
||||
message_type: str = None,
|
||||
session_id: str | int = None,
|
||||
event: T_MessageEvent = None,
|
||||
**kwargs,
|
||||
) -> dict:
|
||||
"""
|
||||
发送单张装逼大图
|
||||
@ -181,23 +182,35 @@ class MarkdownMessage:
|
||||
if method == 2:
|
||||
base64_string = base64.b64encode(image).decode("utf-8")
|
||||
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,
|
||||
event=event, message_type=message_type,
|
||||
session_id=session_id, **kwargs)
|
||||
await MarkdownMessage.send_md(
|
||||
MarkdownMessage.image(data, Image.open(io.BytesIO(image)).size),
|
||||
bot,
|
||||
event=event,
|
||||
message_type=message_type,
|
||||
session_id=session_id,
|
||||
**kwargs,
|
||||
)
|
||||
|
||||
# 其他实现端方案
|
||||
else:
|
||||
image_message_id = (await bot.send_private_msg(
|
||||
user_id=bot.self_id,
|
||||
message=[
|
||||
v11.MessageSegment.image(file=image)
|
||||
]
|
||||
))["message_id"]
|
||||
image_url = (await bot.get_msg(message_id=image_message_id))["message"][0]["data"]["url"]
|
||||
image_message_id = (
|
||||
await bot.send_private_msg(
|
||||
user_id=bot.self_id, message=[v11.MessageSegment.image(file=image)]
|
||||
)
|
||||
)["message_id"]
|
||||
image_url = (await bot.get_msg(message_id=image_message_id))["message"][0][
|
||||
"data"
|
||||
]["url"]
|
||||
image_size = Image.open(io.BytesIO(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,
|
||||
event=event, **kwargs)
|
||||
return await MarkdownMessage.send_md(
|
||||
image_md,
|
||||
bot,
|
||||
message_type=message_type,
|
||||
session_id=session_id,
|
||||
event=event,
|
||||
**kwargs,
|
||||
)
|
||||
|
||||
if data is None:
|
||||
data = await bot.send_msg(
|
||||
@ -205,7 +218,7 @@ class MarkdownMessage:
|
||||
group_id=session_id,
|
||||
user_id=session_id,
|
||||
message=v11.MessageSegment.image(image),
|
||||
**kwargs
|
||||
**kwargs,
|
||||
)
|
||||
return data
|
||||
|
||||
@ -232,7 +245,9 @@ class MarkdownMessage:
|
||||
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}"
|
||||
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 import get_driver
|
||||
|
||||
__plugin_meta__ = PluginMetadata(
|
||||
name="Minecraft工具箱",
|
||||
description="一些Minecraft相关工具箱",
|
||||
usage="我觉得你应该会用",
|
||||
type="application",
|
||||
homepage="https://github.com/snowykami/LiteyukiBot",
|
||||
extra={
|
||||
"liteyuki" : True,
|
||||
"toggleable" : True,
|
||||
"default_enable": True,
|
||||
}
|
||||
from nonebot.plugin import PluginMetadata
|
||||
from nonebot import get_driver
|
||||
|
||||
__plugin_meta__ = PluginMetadata(
|
||||
name="Minecraft工具箱",
|
||||
description="一些Minecraft相关工具箱",
|
||||
usage="我觉得你应该会用",
|
||||
type="application",
|
||||
homepage="https://github.com/snowykami/LiteyukiBot",
|
||||
extra={
|
||||
"liteyuki" : True,
|
||||
"toggleable" : True,
|
||||
"default_enable": True,
|
||||
}
|
||||
)
|
@ -1,20 +1,20 @@
|
||||
from nonebot.plugin import PluginMetadata
|
||||
|
||||
__author__ = "snowykami"
|
||||
__plugin_meta__ = PluginMetadata(
|
||||
name="轻雪包管理器v2",
|
||||
description="详细看文档",
|
||||
usage=(
|
||||
"npm list\n"
|
||||
"npm enable/disable <plugin_name>\n"
|
||||
"npm search <keywords...>\n"
|
||||
"npm install/uninstall <plugin_name>\n"
|
||||
),
|
||||
type="application",
|
||||
homepage="https://github.com/snowykami/LiteyukiBot",
|
||||
extra={
|
||||
"liteyuki": True,
|
||||
"toggleable" : False,
|
||||
"default_enable" : False,
|
||||
}
|
||||
)
|
||||
from nonebot.plugin import PluginMetadata
|
||||
|
||||
__author__ = "snowykami"
|
||||
__plugin_meta__ = PluginMetadata(
|
||||
name="轻雪包管理器v2",
|
||||
description="详细看文档",
|
||||
usage=(
|
||||
"npm list\n"
|
||||
"npm enable/disable <plugin_name>\n"
|
||||
"npm search <keywords...>\n"
|
||||
"npm install/uninstall <plugin_name>\n"
|
||||
),
|
||||
type="application",
|
||||
homepage="https://github.com/snowykami/LiteyukiBot",
|
||||
extra={
|
||||
"liteyuki": True,
|
||||
"toggleable" : False,
|
||||
"default_enable" : False,
|
||||
}
|
||||
)
|
@ -1,27 +1,27 @@
|
||||
from nonebot.plugin import PluginMetadata
|
||||
from nonebot import get_driver
|
||||
from .qweather import *
|
||||
|
||||
__plugin_meta__ = PluginMetadata(
|
||||
name="轻雪天气",
|
||||
description="基于和风天气api的天气插件",
|
||||
usage="",
|
||||
type="application",
|
||||
homepage="https://github.com/snowykami/LiteyukiBot",
|
||||
extra={
|
||||
"liteyuki" : True,
|
||||
"toggleable" : True,
|
||||
"default_enable": True,
|
||||
}
|
||||
)
|
||||
|
||||
from ...utils.base.data_manager import set_memory_data
|
||||
|
||||
driver = get_driver()
|
||||
|
||||
|
||||
@driver.on_startup
|
||||
async def _():
|
||||
# 检查是否为开发者模式
|
||||
is_dev = await check_key_dev(get_config("weather_key", ""))
|
||||
set_memory_data("weather.is_dev", is_dev)
|
||||
from nonebot.plugin import PluginMetadata
|
||||
from nonebot import get_driver
|
||||
from .qweather import *
|
||||
|
||||
__plugin_meta__ = PluginMetadata(
|
||||
name="轻雪天气",
|
||||
description="基于和风天气api的天气插件",
|
||||
usage="",
|
||||
type="application",
|
||||
homepage="https://github.com/snowykami/LiteyukiBot",
|
||||
extra={
|
||||
"liteyuki" : True,
|
||||
"toggleable" : True,
|
||||
"default_enable": True,
|
||||
}
|
||||
)
|
||||
|
||||
from ...utils.base.data_manager import set_memory_data
|
||||
|
||||
driver = get_driver()
|
||||
|
||||
|
||||
@driver.on_startup
|
||||
async def _():
|
||||
# 检查是否为开发者模式
|
||||
is_dev = await check_key_dev(get_config("weather_key", ""))
|
||||
set_memory_data("weather.is_dev", is_dev)
|
@ -1,171 +1,171 @@
|
||||
import aiohttp
|
||||
|
||||
from .qw_models import *
|
||||
import httpx
|
||||
|
||||
from ...utils.base.data_manager import get_memory_data
|
||||
from ...utils.base.language import Language
|
||||
|
||||
dev_url = "https://devapi.qweather.com/" # 开发HBa
|
||||
com_url = "https://api.qweather.com/" # 正式环境
|
||||
|
||||
|
||||
def get_qw_lang(lang: str) -> str:
|
||||
if lang in ["zh-HK", "zh-TW"]:
|
||||
return "zh-hant"
|
||||
elif lang.startswith("zh"):
|
||||
return "zh"
|
||||
elif lang.startswith("en"):
|
||||
return "en"
|
||||
else:
|
||||
return lang
|
||||
|
||||
|
||||
async def check_key_dev(key: str) -> bool:
|
||||
url = "https://api.qweather.com/v7/weather/now?"
|
||||
params = {
|
||||
"location": "101010100",
|
||||
"key" : key,
|
||||
}
|
||||
async with aiohttp.ClientSession() as client:
|
||||
resp = await client.get(url, params=params)
|
||||
return (await resp.json()).get("code") != "200" # 查询不到付费数据为开发版
|
||||
|
||||
|
||||
def get_local_data(ulang_code: str) -> dict:
|
||||
"""
|
||||
获取本地化数据
|
||||
Args:
|
||||
ulang_code:
|
||||
|
||||
Returns:
|
||||
|
||||
"""
|
||||
ulang = Language(ulang_code)
|
||||
return {
|
||||
"monday" : ulang.get("weather.monday"),
|
||||
"tuesday" : ulang.get("weather.tuesday"),
|
||||
"wednesday": ulang.get("weather.wednesday"),
|
||||
"thursday" : ulang.get("weather.thursday"),
|
||||
"friday" : ulang.get("weather.friday"),
|
||||
"saturday" : ulang.get("weather.saturday"),
|
||||
"sunday" : ulang.get("weather.sunday"),
|
||||
"today" : ulang.get("weather.today"),
|
||||
"tomorrow" : ulang.get("weather.tomorrow"),
|
||||
"day" : ulang.get("weather.day"),
|
||||
"night" : ulang.get("weather.night"),
|
||||
"no_aqi" : ulang.get("weather.no_aqi"),
|
||||
}
|
||||
|
||||
|
||||
async def city_lookup(
|
||||
location: str,
|
||||
key: str,
|
||||
adm: str = "",
|
||||
number: int = 20,
|
||||
lang: str = "zh",
|
||||
) -> CityLookup:
|
||||
"""
|
||||
通过关键字搜索城市信息
|
||||
Args:
|
||||
location:
|
||||
key:
|
||||
adm:
|
||||
number:
|
||||
lang: 可传入标准i18n语言代码,如zh-CN、en-US等
|
||||
|
||||
Returns:
|
||||
|
||||
"""
|
||||
url = "https://geoapi.qweather.com/v2/city/lookup?"
|
||||
params = {
|
||||
"location": location,
|
||||
"adm" : adm,
|
||||
"number" : number,
|
||||
"key" : key,
|
||||
"lang" : lang,
|
||||
}
|
||||
async with httpx.AsyncClient() as client:
|
||||
resp = await client.get(url, params=params)
|
||||
return CityLookup.parse_obj(resp.json())
|
||||
|
||||
|
||||
async def get_weather_now(
|
||||
key: str,
|
||||
location: str,
|
||||
lang: str = "zh",
|
||||
unit: str = "m",
|
||||
dev: bool = get_memory_data("is_dev", True),
|
||||
) -> dict:
|
||||
url_path = "v7/weather/now?"
|
||||
url = dev_url + url_path if dev else com_url + url_path
|
||||
params = {
|
||||
"location": location,
|
||||
"key" : key,
|
||||
"lang" : lang,
|
||||
"unit" : unit,
|
||||
}
|
||||
async with httpx.AsyncClient() as client:
|
||||
resp = await client.get(url, params=params)
|
||||
return resp.json()
|
||||
|
||||
|
||||
async def get_weather_daily(
|
||||
key: str,
|
||||
location: str,
|
||||
lang: str = "zh",
|
||||
unit: str = "m",
|
||||
dev: bool = get_memory_data("is_dev", True),
|
||||
) -> dict:
|
||||
url_path = "v7/weather/%dd?" % (7 if dev else 30)
|
||||
url = dev_url + url_path if dev else com_url + url_path
|
||||
params = {
|
||||
"location": location,
|
||||
"key" : key,
|
||||
"lang" : lang,
|
||||
"unit" : unit,
|
||||
}
|
||||
async with httpx.AsyncClient() as client:
|
||||
resp = await client.get(url, params=params)
|
||||
return resp.json()
|
||||
|
||||
|
||||
async def get_weather_hourly(
|
||||
key: str,
|
||||
location: str,
|
||||
lang: str = "zh",
|
||||
unit: str = "m",
|
||||
dev: bool = get_memory_data("is_dev", True),
|
||||
) -> dict:
|
||||
url_path = "v7/weather/%dh?" % (24 if dev else 168)
|
||||
url = dev_url + url_path if dev else com_url + url_path
|
||||
params = {
|
||||
"location": location,
|
||||
"key" : key,
|
||||
"lang" : lang,
|
||||
"unit" : unit,
|
||||
}
|
||||
async with httpx.AsyncClient() as client:
|
||||
resp = await client.get(url, params=params)
|
||||
return resp.json()
|
||||
|
||||
|
||||
async def get_airquality(
|
||||
key: str,
|
||||
location: str,
|
||||
lang: str,
|
||||
pollutant: bool = False,
|
||||
station: bool = False,
|
||||
dev: bool = get_memory_data("is_dev", True),
|
||||
) -> dict:
|
||||
url_path = f"airquality/v1/now/{location}?"
|
||||
url = dev_url + url_path if dev else com_url + url_path
|
||||
params = {
|
||||
"key" : key,
|
||||
"lang" : lang,
|
||||
"pollutant": pollutant,
|
||||
"station" : station,
|
||||
}
|
||||
async with httpx.AsyncClient() as client:
|
||||
resp = await client.get(url, params=params)
|
||||
return resp.json()
|
||||
import aiohttp
|
||||
|
||||
from .qw_models import *
|
||||
import httpx
|
||||
|
||||
from ...utils.base.data_manager import get_memory_data
|
||||
from ...utils.base.language import Language
|
||||
|
||||
dev_url = "https://devapi.qweather.com/" # 开发HBa
|
||||
com_url = "https://api.qweather.com/" # 正式环境
|
||||
|
||||
|
||||
def get_qw_lang(lang: str) -> str:
|
||||
if lang in ["zh-HK", "zh-TW"]:
|
||||
return "zh-hant"
|
||||
elif lang.startswith("zh"):
|
||||
return "zh"
|
||||
elif lang.startswith("en"):
|
||||
return "en"
|
||||
else:
|
||||
return lang
|
||||
|
||||
|
||||
async def check_key_dev(key: str) -> bool:
|
||||
url = "https://api.qweather.com/v7/weather/now?"
|
||||
params = {
|
||||
"location": "101010100",
|
||||
"key" : key,
|
||||
}
|
||||
async with aiohttp.ClientSession() as client:
|
||||
resp = await client.get(url, params=params)
|
||||
return (await resp.json()).get("code") != "200" # 查询不到付费数据为开发版
|
||||
|
||||
|
||||
def get_local_data(ulang_code: str) -> dict:
|
||||
"""
|
||||
获取本地化数据
|
||||
Args:
|
||||
ulang_code:
|
||||
|
||||
Returns:
|
||||
|
||||
"""
|
||||
ulang = Language(ulang_code)
|
||||
return {
|
||||
"monday" : ulang.get("weather.monday"),
|
||||
"tuesday" : ulang.get("weather.tuesday"),
|
||||
"wednesday": ulang.get("weather.wednesday"),
|
||||
"thursday" : ulang.get("weather.thursday"),
|
||||
"friday" : ulang.get("weather.friday"),
|
||||
"saturday" : ulang.get("weather.saturday"),
|
||||
"sunday" : ulang.get("weather.sunday"),
|
||||
"today" : ulang.get("weather.today"),
|
||||
"tomorrow" : ulang.get("weather.tomorrow"),
|
||||
"day" : ulang.get("weather.day"),
|
||||
"night" : ulang.get("weather.night"),
|
||||
"no_aqi" : ulang.get("weather.no_aqi"),
|
||||
}
|
||||
|
||||
|
||||
async def city_lookup(
|
||||
location: str,
|
||||
key: str,
|
||||
adm: str = "",
|
||||
number: int = 20,
|
||||
lang: str = "zh",
|
||||
) -> CityLookup:
|
||||
"""
|
||||
通过关键字搜索城市信息
|
||||
Args:
|
||||
location:
|
||||
key:
|
||||
adm:
|
||||
number:
|
||||
lang: 可传入标准i18n语言代码,如zh-CN、en-US等
|
||||
|
||||
Returns:
|
||||
|
||||
"""
|
||||
url = "https://geoapi.qweather.com/v2/city/lookup?"
|
||||
params = {
|
||||
"location": location,
|
||||
"adm" : adm,
|
||||
"number" : number,
|
||||
"key" : key,
|
||||
"lang" : lang,
|
||||
}
|
||||
async with httpx.AsyncClient() as client:
|
||||
resp = await client.get(url, params=params)
|
||||
return CityLookup.parse_obj(resp.json())
|
||||
|
||||
|
||||
async def get_weather_now(
|
||||
key: str,
|
||||
location: str,
|
||||
lang: str = "zh",
|
||||
unit: str = "m",
|
||||
dev: bool = get_memory_data("is_dev", True),
|
||||
) -> dict:
|
||||
url_path = "v7/weather/now?"
|
||||
url = dev_url + url_path if dev else com_url + url_path
|
||||
params = {
|
||||
"location": location,
|
||||
"key" : key,
|
||||
"lang" : lang,
|
||||
"unit" : unit,
|
||||
}
|
||||
async with httpx.AsyncClient() as client:
|
||||
resp = await client.get(url, params=params)
|
||||
return resp.json()
|
||||
|
||||
|
||||
async def get_weather_daily(
|
||||
key: str,
|
||||
location: str,
|
||||
lang: str = "zh",
|
||||
unit: str = "m",
|
||||
dev: bool = get_memory_data("is_dev", True),
|
||||
) -> dict:
|
||||
url_path = "v7/weather/%dd?" % (7 if dev else 30)
|
||||
url = dev_url + url_path if dev else com_url + url_path
|
||||
params = {
|
||||
"location": location,
|
||||
"key" : key,
|
||||
"lang" : lang,
|
||||
"unit" : unit,
|
||||
}
|
||||
async with httpx.AsyncClient() as client:
|
||||
resp = await client.get(url, params=params)
|
||||
return resp.json()
|
||||
|
||||
|
||||
async def get_weather_hourly(
|
||||
key: str,
|
||||
location: str,
|
||||
lang: str = "zh",
|
||||
unit: str = "m",
|
||||
dev: bool = get_memory_data("is_dev", True),
|
||||
) -> dict:
|
||||
url_path = "v7/weather/%dh?" % (24 if dev else 168)
|
||||
url = dev_url + url_path if dev else com_url + url_path
|
||||
params = {
|
||||
"location": location,
|
||||
"key" : key,
|
||||
"lang" : lang,
|
||||
"unit" : unit,
|
||||
}
|
||||
async with httpx.AsyncClient() as client:
|
||||
resp = await client.get(url, params=params)
|
||||
return resp.json()
|
||||
|
||||
|
||||
async def get_airquality(
|
||||
key: str,
|
||||
location: str,
|
||||
lang: str,
|
||||
pollutant: bool = False,
|
||||
station: bool = False,
|
||||
dev: bool = get_memory_data("is_dev", True),
|
||||
) -> dict:
|
||||
url_path = f"airquality/v1/now/{location}?"
|
||||
url = dev_url + url_path if dev else com_url + url_path
|
||||
params = {
|
||||
"key" : key,
|
||||
"lang" : lang,
|
||||
"pollutant": pollutant,
|
||||
"station" : station,
|
||||
}
|
||||
async with httpx.AsyncClient() as client:
|
||||
resp = await client.get(url, params=params)
|
||||
return resp.json()
|
@ -1,62 +1,62 @@
|
||||
from src.utils.base.data import LiteModel
|
||||
|
||||
|
||||
class Location(LiteModel):
|
||||
name: str = ""
|
||||
id: str = ""
|
||||
lat: str = ""
|
||||
lon: str = ""
|
||||
adm2: str = ""
|
||||
adm1: str = ""
|
||||
country: str = ""
|
||||
tz: str = ""
|
||||
utcOffset: str = ""
|
||||
isDst: str = ""
|
||||
type: str = ""
|
||||
rank: str = ""
|
||||
fxLink: str = ""
|
||||
sources: str = ""
|
||||
license: str = ""
|
||||
|
||||
|
||||
class CityLookup(LiteModel):
|
||||
code: str = ""
|
||||
location: list[Location] = [Location()]
|
||||
|
||||
|
||||
class Now(LiteModel):
|
||||
obsTime: str = ""
|
||||
temp: str = ""
|
||||
feelsLike: str = ""
|
||||
icon: str = ""
|
||||
text: str = ""
|
||||
wind360: str = ""
|
||||
windDir: str = ""
|
||||
windScale: str = ""
|
||||
windSpeed: str = ""
|
||||
humidity: str = ""
|
||||
precip: str = ""
|
||||
pressure: str = ""
|
||||
vis: str = ""
|
||||
cloud: str = ""
|
||||
dew: str = ""
|
||||
sources: str = ""
|
||||
license: str = ""
|
||||
|
||||
|
||||
class WeatherNow(LiteModel):
|
||||
code: str = ""
|
||||
updateTime: str = ""
|
||||
fxLink: str = ""
|
||||
now: Now = Now()
|
||||
|
||||
|
||||
class Daily(LiteModel):
|
||||
pass
|
||||
|
||||
|
||||
class WeatherDaily(LiteModel):
|
||||
code: str = ""
|
||||
updateTime: str = ""
|
||||
fxLink: str = ""
|
||||
daily: list[str] = []
|
||||
from src.utils.base.data import LiteModel
|
||||
|
||||
|
||||
class Location(LiteModel):
|
||||
name: str = ""
|
||||
id: str = ""
|
||||
lat: str = ""
|
||||
lon: str = ""
|
||||
adm2: str = ""
|
||||
adm1: str = ""
|
||||
country: str = ""
|
||||
tz: str = ""
|
||||
utcOffset: str = ""
|
||||
isDst: str = ""
|
||||
type: str = ""
|
||||
rank: str = ""
|
||||
fxLink: str = ""
|
||||
sources: str = ""
|
||||
license: str = ""
|
||||
|
||||
|
||||
class CityLookup(LiteModel):
|
||||
code: str = ""
|
||||
location: list[Location] = [Location()]
|
||||
|
||||
|
||||
class Now(LiteModel):
|
||||
obsTime: str = ""
|
||||
temp: str = ""
|
||||
feelsLike: str = ""
|
||||
icon: str = ""
|
||||
text: str = ""
|
||||
wind360: str = ""
|
||||
windDir: str = ""
|
||||
windScale: str = ""
|
||||
windSpeed: str = ""
|
||||
humidity: str = ""
|
||||
precip: str = ""
|
||||
pressure: str = ""
|
||||
vis: str = ""
|
||||
cloud: str = ""
|
||||
dew: str = ""
|
||||
sources: str = ""
|
||||
license: str = ""
|
||||
|
||||
|
||||
class WeatherNow(LiteModel):
|
||||
code: str = ""
|
||||
updateTime: str = ""
|
||||
fxLink: str = ""
|
||||
now: Now = Now()
|
||||
|
||||
|
||||
class Daily(LiteModel):
|
||||
pass
|
||||
|
||||
|
||||
class WeatherDaily(LiteModel):
|
||||
code: str = ""
|
||||
updateTime: str = ""
|
||||
fxLink: str = ""
|
||||
daily: list[str] = []
|
@ -1,101 +1,101 @@
|
||||
from nonebot import require, on_endswith
|
||||
from nonebot.adapters import satori
|
||||
from nonebot.adapters.onebot.v11 import MessageSegment
|
||||
from nonebot.internal.matcher import Matcher
|
||||
|
||||
from src.utils.base.config import get_config
|
||||
from src.utils.base.ly_typing import T_MessageEvent
|
||||
|
||||
from .qw_api import *
|
||||
from src.utils.base.data_manager import User, user_db
|
||||
from src.utils.base.language import Language, get_user_lang
|
||||
from src.utils.base.resource import get_path
|
||||
from src.utils.message.html_tool import template2image
|
||||
from src.utils import event as event_utils
|
||||
|
||||
require("nonebot_plugin_alconna")
|
||||
from nonebot_plugin_alconna import on_alconna, Alconna, Args, MultiVar, Arparma, UniMessage
|
||||
|
||||
wx_alc = on_alconna(
|
||||
aliases={"天气"},
|
||||
command=Alconna(
|
||||
"weather",
|
||||
Args["keywords", MultiVar(str), []],
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
@wx_alc.handle()
|
||||
async def _(result: Arparma, event: T_MessageEvent, matcher: Matcher):
|
||||
"""await alconna.send("weather", city)"""
|
||||
kws = result.main_args.get("keywords")
|
||||
image = await get_weather_now_card(matcher, event, kws)
|
||||
await wx_alc.finish(UniMessage.image(raw=image))
|
||||
|
||||
|
||||
@on_endswith(("天气", "weather")).handle()
|
||||
async def _(event: T_MessageEvent, matcher: Matcher):
|
||||
"""await alconna.send("weather", city)"""
|
||||
# kws = event.message.extract_plain_text()
|
||||
kws = event.get_plaintext()
|
||||
image = await get_weather_now_card(matcher, event, [kws.replace("天气", "").replace("weather", "")], False)
|
||||
if isinstance(event, satori.event.Event):
|
||||
await matcher.finish(satori.MessageSegment.image(raw=image, mime="image/png"))
|
||||
else:
|
||||
await matcher.finish(MessageSegment.image(image))
|
||||
|
||||
|
||||
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))
|
||||
qw_lang = get_qw_lang(ulang.lang_code)
|
||||
key = get_config("weather_key")
|
||||
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())
|
||||
# params
|
||||
unit = user.profile.get("unit", "m")
|
||||
stored_location = user.profile.get("location", None)
|
||||
|
||||
if not key:
|
||||
await matcher.finish(ulang.get("weather.no_key") if tip else None)
|
||||
|
||||
if keyword:
|
||||
if len(keyword) >= 2:
|
||||
adm = keyword[0]
|
||||
city = keyword[-1]
|
||||
else:
|
||||
adm = ""
|
||||
city = keyword[0]
|
||||
city_info = await city_lookup(city, key, adm=adm, lang=qw_lang)
|
||||
city_name = " ".join(keyword)
|
||||
else:
|
||||
if not stored_location:
|
||||
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_name = stored_location
|
||||
if city_info.code == "200":
|
||||
location_data = city_info.location[0]
|
||||
else:
|
||||
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_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)
|
||||
aqi = await get_airquality(key, location_data.id, lang=qw_lang, dev=is_dev)
|
||||
|
||||
image = await template2image(
|
||||
template=get_path("templates/weather_now.html", abs_path=True),
|
||||
templates={
|
||||
"data": {
|
||||
"params" : {
|
||||
"unit": unit,
|
||||
"lang": ulang.lang_code,
|
||||
},
|
||||
"weatherNow" : weather_now,
|
||||
"weatherDaily" : weather_daily,
|
||||
"weatherHourly": weather_hourly,
|
||||
"aqi" : aqi,
|
||||
"location" : location_data.dump(),
|
||||
"localization" : get_local_data(ulang.lang_code)
|
||||
}
|
||||
},
|
||||
)
|
||||
return image
|
||||
from nonebot import require, on_endswith
|
||||
from nonebot.adapters import satori
|
||||
from nonebot.adapters.onebot.v11 import MessageSegment
|
||||
from nonebot.internal.matcher import Matcher
|
||||
|
||||
from src.utils.base.config import get_config
|
||||
from src.utils.base.ly_typing import T_MessageEvent
|
||||
|
||||
from .qw_api import *
|
||||
from src.utils.base.data_manager import User, user_db
|
||||
from src.utils.base.language import Language, get_user_lang
|
||||
from src.utils.base.resource import get_path
|
||||
from src.utils.message.html_tool import template2image
|
||||
from src.utils import event as event_utils
|
||||
|
||||
require("nonebot_plugin_alconna")
|
||||
from nonebot_plugin_alconna import on_alconna, Alconna, Args, MultiVar, Arparma, UniMessage
|
||||
|
||||
wx_alc = on_alconna(
|
||||
aliases={"天气"},
|
||||
command=Alconna(
|
||||
"weather",
|
||||
Args["keywords", MultiVar(str), []],
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
@wx_alc.handle()
|
||||
async def _(result: Arparma, event: T_MessageEvent, matcher: Matcher):
|
||||
"""await alconna.send("weather", city)"""
|
||||
kws = result.main_args.get("keywords")
|
||||
image = await get_weather_now_card(matcher, event, kws)
|
||||
await wx_alc.finish(UniMessage.image(raw=image))
|
||||
|
||||
|
||||
@on_endswith(("天气", "weather")).handle()
|
||||
async def _(event: T_MessageEvent, matcher: Matcher):
|
||||
"""await alconna.send("weather", city)"""
|
||||
# kws = event.message.extract_plain_text()
|
||||
kws = event.get_plaintext()
|
||||
image = await get_weather_now_card(matcher, event, [kws.replace("天气", "").replace("weather", "")], False)
|
||||
if isinstance(event, satori.event.Event):
|
||||
await matcher.finish(satori.MessageSegment.image(raw=image, mime="image/png"))
|
||||
else:
|
||||
await matcher.finish(MessageSegment.image(image))
|
||||
|
||||
|
||||
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))
|
||||
qw_lang = get_qw_lang(ulang.lang_code)
|
||||
key = get_config("weather_key")
|
||||
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())
|
||||
# params
|
||||
unit = user.profile.get("unit", "m")
|
||||
stored_location = user.profile.get("location", None)
|
||||
|
||||
if not key:
|
||||
await matcher.finish(ulang.get("weather.no_key") if tip else None)
|
||||
|
||||
if keyword:
|
||||
if len(keyword) >= 2:
|
||||
adm = keyword[0]
|
||||
city = keyword[-1]
|
||||
else:
|
||||
adm = ""
|
||||
city = keyword[0]
|
||||
city_info = await city_lookup(city, key, adm=adm, lang=qw_lang)
|
||||
city_name = " ".join(keyword)
|
||||
else:
|
||||
if not stored_location:
|
||||
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_name = stored_location
|
||||
if city_info.code == "200":
|
||||
location_data = city_info.location[0]
|
||||
else:
|
||||
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_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)
|
||||
aqi = await get_airquality(key, location_data.id, lang=qw_lang, dev=is_dev)
|
||||
|
||||
image = await template2image(
|
||||
template=get_path("templates/weather_now.html", abs_path=True),
|
||||
templates={
|
||||
"data": {
|
||||
"params" : {
|
||||
"unit": unit,
|
||||
"lang": ulang.lang_code,
|
||||
},
|
||||
"weatherNow" : weather_now,
|
||||
"weatherDaily" : weather_daily,
|
||||
"weatherHourly": weather_hourly,
|
||||
"aqi" : aqi,
|
||||
"location" : location_data.dump(),
|
||||
"localization" : get_local_data(ulang.lang_code)
|
||||
}
|
||||
},
|
||||
)
|
||||
return image
|
@ -1,12 +1,12 @@
|
||||
weather.monday=Mon
|
||||
weather.tuesday=Tue
|
||||
weather.wednesday=Wed
|
||||
weather.thursday=Thu
|
||||
weather.friday=Fri
|
||||
weather.saturday=Sat
|
||||
weather.sunday=Sun
|
||||
weather.day=Day
|
||||
weather.night=Night
|
||||
weather.today=Today
|
||||
weather.tomorrow=Tomorrow
|
||||
weather.monday=Mon
|
||||
weather.tuesday=Tue
|
||||
weather.wednesday=Wed
|
||||
weather.thursday=Thu
|
||||
weather.friday=Fri
|
||||
weather.saturday=Sat
|
||||
weather.sunday=Sun
|
||||
weather.day=Day
|
||||
weather.night=Night
|
||||
weather.today=Today
|
||||
weather.tomorrow=Tomorrow
|
||||
weather.no_aqi=No AQI data
|
@ -1,12 +1,12 @@
|
||||
weather.monday=月
|
||||
weather.tuesday=火
|
||||
weather.wednesday=水
|
||||
weather.thursday=木
|
||||
weather.friday=金
|
||||
weather.saturday=土
|
||||
weather.sunday=日
|
||||
weather.day=昼
|
||||
weather.night=夜
|
||||
weather.today=今日
|
||||
weather.tomorrow=明日
|
||||
weather.monday=月
|
||||
weather.tuesday=火
|
||||
weather.wednesday=水
|
||||
weather.thursday=木
|
||||
weather.friday=金
|
||||
weather.saturday=土
|
||||
weather.sunday=日
|
||||
weather.day=昼
|
||||
weather.night=夜
|
||||
weather.today=今日
|
||||
weather.tomorrow=明日
|
||||
weather.no_aqi=空気質データなし
|
@ -1,12 +1,12 @@
|
||||
weather.monday=周一
|
||||
weather.tuesday=周二
|
||||
weather.wednesday=周三
|
||||
weather.thursday=周四
|
||||
weather.friday=周五
|
||||
weather.saturday=周六
|
||||
weather.sunday=周日
|
||||
weather.day=白天
|
||||
weather.night=夜晚
|
||||
weather.today=今天
|
||||
weather.tomorrow=明天
|
||||
weather.monday=周一
|
||||
weather.tuesday=周二
|
||||
weather.wednesday=周三
|
||||
weather.thursday=周四
|
||||
weather.friday=周五
|
||||
weather.saturday=周六
|
||||
weather.sunday=周日
|
||||
weather.day=白天
|
||||
weather.night=夜晚
|
||||
weather.today=今天
|
||||
weather.tomorrow=明天
|
||||
weather.no_aqi=暂无AQI数据
|
@ -1,3 +1,3 @@
|
||||
name: 轻雪天气资源包
|
||||
description: For Liteyuki Weather
|
||||
name: 轻雪天气资源包
|
||||
description: For Liteyuki Weather
|
||||
version: 2024.4.26
|
@ -1,184 +1,184 @@
|
||||
:root {
|
||||
--main-text-color: #fff;
|
||||
--sub-text-color: #ccc;
|
||||
--tip-text-color: #999;
|
||||
--device-info-width: 240px;
|
||||
--sub-border-radius: 60px;
|
||||
}
|
||||
|
||||
#weather-info {
|
||||
color: white;
|
||||
/*justify-content: center;*/
|
||||
/*align-items: center;*/
|
||||
/*align-content: center;*/
|
||||
}
|
||||
|
||||
.icon {
|
||||
/* icon 类img阴影*/
|
||||
filter: drop-shadow(1px 1px 10px #00000044);
|
||||
}
|
||||
|
||||
#main-info {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
#main-left {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
width: 50%;
|
||||
}
|
||||
|
||||
#main-right {
|
||||
width: 50%;
|
||||
}
|
||||
|
||||
#time {
|
||||
font-size: 25px;
|
||||
font-weight: bold;
|
||||
font-style: italic;
|
||||
text-align: right;
|
||||
color: var(--sub-text-color);
|
||||
|
||||
}
|
||||
|
||||
#adm {
|
||||
font-size: 32px;
|
||||
font-weight: bold;
|
||||
text-align: center;
|
||||
color: var(--sub-text-color);
|
||||
}
|
||||
|
||||
#city {
|
||||
margin-top: 20px;
|
||||
font-size: 70px;
|
||||
font-weight: bold;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
#temperature {
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
|
||||
}
|
||||
|
||||
#temperature-now {
|
||||
font-size: 70px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
#temperature-range {
|
||||
font-size: 40px;
|
||||
font-weight: bold;
|
||||
color: var(--sub-text-color);
|
||||
}
|
||||
|
||||
#description {
|
||||
font-size: 50px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
|
||||
#aqi {
|
||||
height: 50px;
|
||||
display: flex;
|
||||
border-radius: 60px;
|
||||
padding: 5px;
|
||||
font-size: 40px;
|
||||
text-align: center;
|
||||
align-content: center;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
#aqi-dot {
|
||||
height: 80%;
|
||||
aspect-ratio: 1 / 1;
|
||||
border-radius: 50%;
|
||||
background-color: var(--sub-text-color);
|
||||
margin-right: 20px;
|
||||
}
|
||||
|
||||
.main-icon {
|
||||
width: 240px;
|
||||
height: 240px;
|
||||
}
|
||||
|
||||
#hours-info {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.hourly-item {
|
||||
text-align: center;
|
||||
background-color: #ffffff44;
|
||||
border-radius: var(--sub-border-radius);
|
||||
align-items: center;
|
||||
padding: 20px 10px;
|
||||
}
|
||||
|
||||
.hourly-icon{
|
||||
width: 80%;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.hourly-temperature {
|
||||
text-align: center;
|
||||
color: var(--main-text-color);
|
||||
font-size: 30px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.hourly-time {
|
||||
text-align: center;
|
||||
color: var(--main-text-color);
|
||||
font-size: 25px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
/**/
|
||||
.daily-item {
|
||||
display: flex;
|
||||
position: relative;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
background-color: #ffffff44;
|
||||
height: 90px;
|
||||
border-radius: var(--sub-border-radius);
|
||||
margin-bottom: 20px;
|
||||
padding: 0 30px;
|
||||
}
|
||||
|
||||
/*最后一个没有margin_button*/
|
||||
.daily-item:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.icon-day {
|
||||
position: absolute;
|
||||
left: 60%;
|
||||
height: 80px;
|
||||
}
|
||||
|
||||
.icon-night {
|
||||
position: absolute;
|
||||
left: 70%;
|
||||
height: 80px;
|
||||
}
|
||||
|
||||
.daily-weather{
|
||||
position: absolute;
|
||||
left: 30%;
|
||||
}
|
||||
|
||||
.daily-temperature{
|
||||
position: absolute;
|
||||
left: 83%;
|
||||
}
|
||||
|
||||
.daily-day, .daily-weather, .daily-temperature {
|
||||
text-align: center;
|
||||
color: var(--main-text-color);
|
||||
font-size: 30px;
|
||||
:root {
|
||||
--main-text-color: #fff;
|
||||
--sub-text-color: #ccc;
|
||||
--tip-text-color: #999;
|
||||
--device-info-width: 240px;
|
||||
--sub-border-radius: 60px;
|
||||
}
|
||||
|
||||
#weather-info {
|
||||
color: white;
|
||||
/*justify-content: center;*/
|
||||
/*align-items: center;*/
|
||||
/*align-content: center;*/
|
||||
}
|
||||
|
||||
.icon {
|
||||
/* icon 类img阴影*/
|
||||
filter: drop-shadow(1px 1px 10px #00000044);
|
||||
}
|
||||
|
||||
#main-info {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
#main-left {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
width: 50%;
|
||||
}
|
||||
|
||||
#main-right {
|
||||
width: 50%;
|
||||
}
|
||||
|
||||
#time {
|
||||
font-size: 25px;
|
||||
font-weight: bold;
|
||||
font-style: italic;
|
||||
text-align: right;
|
||||
color: var(--sub-text-color);
|
||||
|
||||
}
|
||||
|
||||
#adm {
|
||||
font-size: 32px;
|
||||
font-weight: bold;
|
||||
text-align: center;
|
||||
color: var(--sub-text-color);
|
||||
}
|
||||
|
||||
#city {
|
||||
margin-top: 20px;
|
||||
font-size: 70px;
|
||||
font-weight: bold;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
#temperature {
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
|
||||
}
|
||||
|
||||
#temperature-now {
|
||||
font-size: 70px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
#temperature-range {
|
||||
font-size: 40px;
|
||||
font-weight: bold;
|
||||
color: var(--sub-text-color);
|
||||
}
|
||||
|
||||
#description {
|
||||
font-size: 50px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
|
||||
#aqi {
|
||||
height: 50px;
|
||||
display: flex;
|
||||
border-radius: 60px;
|
||||
padding: 5px;
|
||||
font-size: 40px;
|
||||
text-align: center;
|
||||
align-content: center;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
#aqi-dot {
|
||||
height: 80%;
|
||||
aspect-ratio: 1 / 1;
|
||||
border-radius: 50%;
|
||||
background-color: var(--sub-text-color);
|
||||
margin-right: 20px;
|
||||
}
|
||||
|
||||
.main-icon {
|
||||
width: 240px;
|
||||
height: 240px;
|
||||
}
|
||||
|
||||
#hours-info {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.hourly-item {
|
||||
text-align: center;
|
||||
background-color: #ffffff44;
|
||||
border-radius: var(--sub-border-radius);
|
||||
align-items: center;
|
||||
padding: 20px 10px;
|
||||
}
|
||||
|
||||
.hourly-icon{
|
||||
width: 80%;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.hourly-temperature {
|
||||
text-align: center;
|
||||
color: var(--main-text-color);
|
||||
font-size: 30px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.hourly-time {
|
||||
text-align: center;
|
||||
color: var(--main-text-color);
|
||||
font-size: 25px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
/**/
|
||||
.daily-item {
|
||||
display: flex;
|
||||
position: relative;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
background-color: #ffffff44;
|
||||
height: 90px;
|
||||
border-radius: var(--sub-border-radius);
|
||||
margin-bottom: 20px;
|
||||
padding: 0 30px;
|
||||
}
|
||||
|
||||
/*最后一个没有margin_button*/
|
||||
.daily-item:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.icon-day {
|
||||
position: absolute;
|
||||
left: 60%;
|
||||
height: 80px;
|
||||
}
|
||||
|
||||
.icon-night {
|
||||
position: absolute;
|
||||
left: 70%;
|
||||
height: 80px;
|
||||
}
|
||||
|
||||
.daily-weather{
|
||||
position: absolute;
|
||||
left: 30%;
|
||||
}
|
||||
|
||||
.daily-temperature{
|
||||
position: absolute;
|
||||
left: 83%;
|
||||
}
|
||||
|
||||
.daily-day, .daily-weather, .daily-temperature {
|
||||
text-align: center;
|
||||
color: var(--main-text-color);
|
||||
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 |