forked from bot/app
fix: 插件列表显示错误问题
This commit is contained in:
parent
de0c073c26
commit
fab5be70b3
@ -1,10 +1,10 @@
|
||||
import nonebot
|
||||
from nonebot.plugin import PluginMetadata
|
||||
from src.utils.language import get_system_lang
|
||||
from src.utils.data_manager import *
|
||||
from liteyuki.utils.language import get_default_lang
|
||||
from liteyuki.utils.data_manager import *
|
||||
from .loader import *
|
||||
from .webdash import *
|
||||
from src.utils.config import config
|
||||
from liteyuki.utils.config import config
|
||||
|
||||
__author__ = "snowykami"
|
||||
__plugin_meta__ = PluginMetadata(
|
||||
@ -20,6 +20,6 @@ __plugin_meta__ = PluginMetadata(
|
||||
|
||||
auto_migrate() # 自动迁移数据库
|
||||
|
||||
sys_lang = get_system_lang()
|
||||
sys_lang = get_default_lang()
|
||||
nonebot.logger.info(sys_lang.get("main.current_language", LANG=sys_lang.get("language.name")))
|
||||
nonebot.logger.info(sys_lang.get("main.enable_webdash", URL=f"http://127.0.0.1:{config.get('port', 8080)}"))
|
||||
nonebot.logger.info(sys_lang.get("main.enable_webdash", URL=f"http://127.0.0.1:{config.get('port', 20216)}"))
|
22
liteyuki/liteyuki_main/loader.py
Normal file
22
liteyuki/liteyuki_main/loader.py
Normal file
@ -0,0 +1,22 @@
|
||||
import os
|
||||
|
||||
import nonebot.plugin
|
||||
|
||||
from liteyuki.utils.data_manager import InstalledPlugin, plugin_db
|
||||
from liteyuki.utils.resource import load_resource_from_dir
|
||||
from liteyuki.utils.tools import check_for_package
|
||||
|
||||
THIS_PLUGIN_NAME = os.path.basename(os.path.dirname(__file__))
|
||||
RESOURCE_PATH = "liteyuki/resources"
|
||||
load_resource_from_dir(RESOURCE_PATH)
|
||||
|
||||
nonebot.plugin.load_plugins("liteyuki/plugins")
|
||||
nonebot.plugin.load_plugins("plugins")
|
||||
|
||||
installed_plugins = plugin_db.all(InstalledPlugin)
|
||||
if installed_plugins:
|
||||
for installed_plugin in plugin_db.all(InstalledPlugin):
|
||||
if not check_for_package(installed_plugin.module_name):
|
||||
nonebot.logger.error(f"{installed_plugin.module_name} not installed, but in loading database. please run `npm fixup` in chat to reinstall it.")
|
||||
else:
|
||||
nonebot.load_plugin(installed_plugin.module_name)
|
@ -3,8 +3,8 @@ import psutil
|
||||
from dash import Dash, Input, Output, dcc, html
|
||||
from starlette.middleware.wsgi import WSGIMiddleware
|
||||
|
||||
from src.utils.language import Language
|
||||
from src.utils.tools import convert_size
|
||||
from liteyuki.utils.language import Language
|
||||
from liteyuki.utils.tools import convert_size
|
||||
|
||||
app = nonebot.get_app()
|
||||
|
@ -6,8 +6,8 @@ from nonebot.params import CommandArg
|
||||
from nonebot.permission import SUPERUSER
|
||||
from nonebot.plugin import PluginMetadata
|
||||
|
||||
from src.utils.message import send_markdown
|
||||
from src.utils.ly_typing import T_Message, T_Bot, v11, T_MessageEvent
|
||||
from liteyuki.utils.message import send_markdown
|
||||
from liteyuki.utils.ly_typing import T_Message, T_Bot, v11, T_MessageEvent
|
||||
|
||||
md_test = on_command("mdts", aliases={"会话md"}, permission=SUPERUSER)
|
||||
md_group = on_command("mdg", aliases={"群md"}, permission=SUPERUSER)
|
@ -4,9 +4,9 @@ from typing import Optional
|
||||
import aiofiles
|
||||
import nonebot.plugin
|
||||
|
||||
from src.utils.data import Database, LiteModel
|
||||
from src.utils.data_manager import GroupChat, InstalledPlugin, User, group_db, plugin_db, user_db
|
||||
from src.utils.ly_typing import T_MessageEvent
|
||||
from liteyuki.utils.data import Database, LiteModel
|
||||
from liteyuki.utils.data_manager import GroupChat, InstalledPlugin, User, group_db, plugin_db, user_db
|
||||
from liteyuki.utils.ly_typing import T_MessageEvent
|
||||
|
||||
LNPM_COMMAND_START = "lnpm"
|
||||
|
||||
@ -58,16 +58,17 @@ def get_plugin_default_enable(plugin_module_name: str) -> bool:
|
||||
Returns:
|
||||
bool: 插件默认状态
|
||||
"""
|
||||
return (nonebot.plugin.get_plugin(plugin_module_name).metadata.extra.get('default_enable', True)
|
||||
if nonebot.plugin.get_plugin(plugin_module_name) and nonebot.plugin.get_plugin(plugin_module_name).metadata else True) \
|
||||
if nonebot.plugin.get_plugin(plugin_module_name) else False
|
||||
plug = nonebot.plugin.get_plugin_by_module_name(plugin_module_name)
|
||||
return (plug.metadata.extra.get('default_enable', True)
|
||||
if plug.metadata else True) if plug else True
|
||||
|
||||
|
||||
def get_plugin_current_enable(event: T_MessageEvent, plugin_module_name: str) -> bool:
|
||||
def get_plugin_session_enable(event: T_MessageEvent, plugin_module_name: str) -> bool:
|
||||
"""
|
||||
获取插件当前启用状态
|
||||
获取插件当前会话启用状态
|
||||
|
||||
Args:
|
||||
event: 会话事件
|
||||
plugin_module_name (str): 插件模块名
|
||||
|
||||
Returns:
|
||||
@ -88,6 +89,10 @@ def get_plugin_current_enable(event: T_MessageEvent, plugin_module_name: str) ->
|
||||
return plugin_module_name in session.enabled_plugins
|
||||
|
||||
|
||||
def get_plugin_global_enable(plugin_module_name: str) -> bool:
|
||||
return True
|
||||
|
||||
|
||||
def get_plugin_can_be_toggle(plugin_module_name: str) -> bool:
|
||||
"""
|
||||
获取插件是否可以被启用/停用
|
||||
@ -98,5 +103,5 @@ def get_plugin_can_be_toggle(plugin_module_name: str) -> bool:
|
||||
Returns:
|
||||
bool: 插件是否可以被启用/停用
|
||||
"""
|
||||
return nonebot.plugin.get_plugin(plugin_module_name).metadata.extra.get('toggleable', True) \
|
||||
if nonebot.plugin.get_plugin(plugin_module_name) and nonebot.plugin.get_plugin(plugin_module_name).metadata else True
|
||||
plug = nonebot.plugin.get_plugin_by_module_name(plugin_module_name)
|
||||
return plug.metadata.extra.get('toggleable', True) if plug and plug.metadata else True
|
@ -9,9 +9,9 @@ from arclet.alconna import Arparma, MultiVar
|
||||
from nonebot.permission import SUPERUSER
|
||||
from nonebot_plugin_alconna import Alconna, Args, Subcommand, on_alconna
|
||||
|
||||
from src.utils.language import get_user_lang
|
||||
from src.utils.ly_typing import T_Bot
|
||||
from src.utils.message import Markdown as md, send_markdown
|
||||
from liteyuki.utils.language import get_user_lang
|
||||
from liteyuki.utils.ly_typing import T_Bot
|
||||
from liteyuki.utils.message import Markdown as md, send_markdown
|
||||
from .common import *
|
||||
|
||||
npm_alc = on_alconna(
|
||||
@ -45,9 +45,6 @@ npm_alc = on_alconna(
|
||||
)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@npm_alc.handle()
|
||||
async def _(result: Arparma, event: T_MessageEvent, bot: T_Bot):
|
||||
ulang = get_user_lang(str(event.user_id))
|
||||
@ -103,7 +100,7 @@ async def _(result: Arparma, event: T_MessageEvent, bot: T_Bot):
|
||||
|
||||
if r_load:
|
||||
if found_in_db_plugin is None:
|
||||
plugin_db.save(installed_plugin)
|
||||
plugin_db.upsert(installed_plugin)
|
||||
info = ulang.get('npm.install_success', NAME=store_plugin.name).replace('_', r'\\_') # markdown转义
|
||||
await send_markdown(
|
||||
f"{info}\n\n"
|
||||
@ -192,9 +189,6 @@ async def npm_search(keywords: list[str]) -> list[StorePlugin]:
|
||||
return results
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
def npm_install(plugin_module_name) -> tuple[bool, str]:
|
||||
"""
|
||||
Args:
|
||||
@ -209,8 +203,8 @@ def npm_install(plugin_module_name) -> tuple[bool, str]:
|
||||
sys.stderr = buffer
|
||||
|
||||
mirrors = [
|
||||
"https://pypi.mirrors.cqupt.edu.cn/simple", # 重庆邮电大学
|
||||
"https://pypi.tuna.tsinghua.edu.cn/simple", # 清华大学
|
||||
"https://pypi.mirrors.cqupt.edu.cn/simple", # 重庆邮电大学
|
||||
"https://pypi.liteyuki.icu/simple", # 轻雪镜像
|
||||
"https://pypi.org/simple", # 官方源
|
||||
]
|
@ -8,23 +8,35 @@ from nonebot.message import run_preprocessor
|
||||
from nonebot.permission import SUPERUSER
|
||||
from nonebot_plugin_alconna import on_alconna, Alconna, Args, Arparma
|
||||
|
||||
from src.utils.data_manager import GroupChat, InstalledPlugin, User, group_db, plugin_db, user_db
|
||||
from src.utils.message import Markdown as md, send_markdown
|
||||
from src.utils.permission import GROUP_ADMIN, GROUP_OWNER
|
||||
from src.utils.ly_typing import T_Bot, T_MessageEvent
|
||||
from src.utils.language import get_user_lang
|
||||
from .common import get_plugin_can_be_toggle, get_plugin_current_enable, get_plugin_default_enable
|
||||
from liteyuki.utils.data_manager import GroupChat, InstalledPlugin, User, group_db, plugin_db, user_db
|
||||
from liteyuki.utils.message import Markdown as md, send_markdown
|
||||
from liteyuki.utils.permission import GROUP_ADMIN, GROUP_OWNER
|
||||
from liteyuki.utils.ly_typing import T_Bot, T_MessageEvent
|
||||
from liteyuki.utils.language import get_user_lang
|
||||
from .common import get_plugin_can_be_toggle, get_plugin_global_enable, get_plugin_session_enable, get_plugin_default_enable
|
||||
from .installer import get_store_plugin, npm_update
|
||||
|
||||
list_plugins = on_command("list-plugin", aliases={"列出插件", "插件列表"}, priority=0)
|
||||
# toggle_plugin = on_command("enable-plugin", aliases={"启用插件", "停用插件", "disable-plugin"}, priority=0)
|
||||
list_plugins = on_alconna(
|
||||
Alconna(
|
||||
['list-plugins', "插件列表", "列出插件"],
|
||||
)
|
||||
)
|
||||
|
||||
toggle_plugin = on_alconna(
|
||||
Alconna(
|
||||
['enable-plugin', 'disable-plugin'],
|
||||
Args['plugin_name', str]['global', bool, False],
|
||||
Args['plugin_name', str],
|
||||
)
|
||||
)
|
||||
|
||||
global_toggle = on_alconna(
|
||||
Alconna(
|
||||
['toggle-global'],
|
||||
Args['plugin_name', str],
|
||||
),
|
||||
permission=SUPERUSER
|
||||
)
|
||||
|
||||
|
||||
@list_plugins.handle()
|
||||
async def _(event: T_MessageEvent, bot: T_Bot):
|
||||
@ -35,39 +47,59 @@ async def _(event: T_MessageEvent, bot: T_Bot):
|
||||
for plugin in nonebot.get_loaded_plugins():
|
||||
# 检查是否有 metadata 属性
|
||||
# 添加帮助按钮
|
||||
btn_usage = md.button(lang.get('npm.usage'), f'help {plugin.name}', False)
|
||||
btn_usage = md.button(lang.get('npm.usage'), f'help {plugin.module_name}', False)
|
||||
store_plugin = await get_store_plugin(plugin.module_name)
|
||||
|
||||
session_enable = get_plugin_session_enable(event, plugin.module_name)
|
||||
default_enable = get_plugin_default_enable(plugin.module_name)
|
||||
print(session_enable, default_enable, plugin.module_name)
|
||||
|
||||
if store_plugin:
|
||||
btn_homepage = md.link(lang.get('npm.homepage'), store_plugin.homepage)
|
||||
elif plugin.metadata and plugin.metadata.extra.get('liteyuki'):
|
||||
show_name = store_plugin.name
|
||||
show_desc = store_plugin.desc
|
||||
elif plugin.metadata:
|
||||
if plugin.metadata.extra.get('liteyuki'):
|
||||
btn_homepage = md.link(lang.get('npm.homepage'), "https://github.com/snowykami/LiteyukiBot")
|
||||
else:
|
||||
btn_homepage = lang.get('npm.homepage')
|
||||
show_name = plugin.metadata.name
|
||||
show_desc = plugin.metadata.description
|
||||
else:
|
||||
btn_homepage = lang.get('npm.homepage')
|
||||
show_name = plugin.name
|
||||
show_desc = lang.get('npm.no_description')
|
||||
|
||||
if plugin.metadata:
|
||||
reply += (f"\n**{md.escape(plugin.metadata.name)}**\n"
|
||||
f"\n > {plugin.metadata.description}")
|
||||
reply += (f"\n**{md.escape(show_name)}**\n"
|
||||
f"\n > {md.escape(show_desc)}")
|
||||
else:
|
||||
reply += (f"**{md.escape(plugin.name)}**\n"
|
||||
f"\n > {lang.get('npm.no_description')}")
|
||||
reply += (f"**{md.escape(show_name)}**\n"
|
||||
f"\n > {md.escape(show_desc)}")
|
||||
|
||||
reply += f"\n > {btn_usage} {btn_homepage}"
|
||||
|
||||
if await GROUP_ADMIN(bot, event) or await GROUP_OWNER(bot, event) or await SUPERUSER(bot, event):
|
||||
# 添加启用/停用插件按钮
|
||||
btn_toggle = lang.get('npm.disable') if plugin.metadata and not plugin.metadata.extra.get('toggleable') \
|
||||
else md.button(lang.get('npm.disable'), f'enable-plugin {plugin.module_name}')
|
||||
cmd_toggle = f"{'disable' if session_enable else 'enable'}-plugin {plugin.module_name}"
|
||||
text_toggle = lang.get('npm.disable' if session_enable else 'npm.enable')
|
||||
can_be_toggle = get_plugin_can_be_toggle(plugin.module_name)
|
||||
btn_toggle = text_toggle if not can_be_toggle else md.button(text_toggle, cmd_toggle)
|
||||
|
||||
reply += f" {btn_toggle}"
|
||||
|
||||
if await SUPERUSER(bot, event):
|
||||
plugin_in_database = plugin_db.first(InstalledPlugin, 'module_name = ?', plugin.module_name)
|
||||
# 添加移除插件
|
||||
# 添加移除插件和全局切换按钮
|
||||
global_enable = get_plugin_global_enable(plugin.module_name)
|
||||
btn_uninstall = (
|
||||
md.button(lang.get('npm.uninstall'), f'npm uninstall {plugin.module_name}')) if plugin_in_database else lang.get(
|
||||
'npm.uninstall')
|
||||
btn_toggle_global = lang.get('npm.disable') if plugin.metadata and not plugin.metadata.extra.get('toggleable') \
|
||||
else md.button(lang.get('npm.disable_global'), f'disable-plugin {plugin.module_name} true')
|
||||
|
||||
btn_toggle_global_text = lang.get('npm.disable_global' if global_enable else 'npm.enable_global')
|
||||
cmd_toggle_global = f'npm toggle-global {plugin.module_name}'
|
||||
btn_toggle_global = btn_toggle_global_text if not can_be_toggle else md.button(btn_toggle_global_text, cmd_toggle_global)
|
||||
|
||||
reply += f" {btn_uninstall} {btn_toggle_global}"
|
||||
|
||||
reply += "\n\n***\n"
|
||||
@ -83,9 +115,10 @@ async def _(result: Arparma, event: T_MessageEvent, bot: T_Bot):
|
||||
plugin_module_name = result.args.get("plugin_name")
|
||||
|
||||
toggle = result.header_result == 'enable-plugin' # 判断是启用还是停用
|
||||
current_enable = get_plugin_current_enable(event, plugin_module_name) # 获取插件当前状态
|
||||
current_enable = get_plugin_session_enable(event, plugin_module_name) # 获取插件当前状态
|
||||
|
||||
default_enable = get_plugin_default_enable(plugin_module_name) # 获取插件默认状态
|
||||
|
||||
can_be_toggled = get_plugin_can_be_toggle(plugin_module_name) # 获取插件是否可以被启用/停用
|
||||
|
||||
if not can_be_toggled:
|
||||
@ -102,10 +135,6 @@ async def _(result: Arparma, event: T_MessageEvent, bot: T_Bot):
|
||||
session = group_db.first(GroupChat, "group_id = ?", event.group_id, default=GroupChat(group_id=event.group_id))
|
||||
else:
|
||||
raise FinishedException(ulang.get("Permission Denied"))
|
||||
# 启用 已停用的默认启用插件 将其从停用列表移除
|
||||
# 启用 已停用的默认停用插件 将其放到启用列表
|
||||
# 停用 已启用的默认启用插件 将其放到停用列表
|
||||
# 停用 已启用的默认停用插件 将其从启用列表移除
|
||||
try:
|
||||
if toggle:
|
||||
if default_enable:
|
||||
@ -126,14 +155,21 @@ async def _(result: Arparma, event: T_MessageEvent, bot: T_Bot):
|
||||
ERROR=str(e))
|
||||
)
|
||||
|
||||
await toggle_plugin.finish(
|
||||
ulang.get(
|
||||
"npm.toggle_success",
|
||||
NAME=plugin_module_name,
|
||||
STATUS=ulang.get("npm.enable") if toggle else ulang.get("npm.disable"))
|
||||
)
|
||||
|
||||
if event.message_type == "private":
|
||||
user_db.save(session)
|
||||
user_db.upsert(session)
|
||||
else:
|
||||
group_db.save(session)
|
||||
group_db.upsert(session)
|
||||
|
||||
|
||||
@run_preprocessor
|
||||
async def _(event: T_MessageEvent, matcher: Matcher):
|
||||
plugin = matcher.plugin
|
||||
# TODO 插件启用/停用检查hook
|
||||
nonebot.logger.info(f"Plugin: {plugin.module_name}")
|
||||
nonebot.logger.info(f"Plugin Callapi: {plugin.module_name}")
|
@ -1,5 +1,5 @@
|
||||
# 插件权限管理器,对api调用进行hook限制,防止插件滥用api
|
||||
from src.utils.data import LiteModel
|
||||
from liteyuki.utils.data import LiteModel
|
||||
|
||||
|
||||
class PermissionAllow(LiteModel):
|
@ -2,11 +2,11 @@ from typing import Optional
|
||||
|
||||
from nonebot_plugin_alconna import Alconna, Args, Arparma, Subcommand, on_alconna
|
||||
|
||||
from src.utils.data import LiteModel
|
||||
from src.utils.data_manager import User, user_db
|
||||
from src.utils.language import Language, get_all_lang, get_user_lang
|
||||
from src.utils.ly_typing import T_Bot, T_MessageEvent
|
||||
from src.utils.message import Markdown as md, send_markdown
|
||||
from liteyuki.utils.data import LiteModel
|
||||
from liteyuki.utils.data_manager import User, user_db
|
||||
from liteyuki.utils.language import Language, get_all_lang, get_user_lang
|
||||
from liteyuki.utils.ly_typing import T_Bot, T_MessageEvent
|
||||
from liteyuki.utils.message import Markdown as md, send_markdown
|
||||
|
||||
profile_alc = on_alconna(
|
||||
Alconna(
|
||||
@ -43,7 +43,7 @@ async def _(result: Arparma, event: T_MessageEvent, bot: T_Bot):
|
||||
r = set_profile(result.args["key"], result.args["value"])
|
||||
if r:
|
||||
user.profile[result.args["key"]] = result.args["value"]
|
||||
user_db.save(user) # 数据库保存
|
||||
user_db.upsert(user) # 数据库保存
|
||||
await profile_alc.finish(
|
||||
ulang.get(
|
||||
"user.profile.set_success",
|
@ -1,6 +1,12 @@
|
||||
language.name=English
|
||||
|
||||
main.current_language=Current system language: {LANG}
|
||||
log.debug=Debug
|
||||
log.info=Info
|
||||
log.warning=WARN
|
||||
log.error=Error
|
||||
log.success=Success
|
||||
|
||||
main.current_language=Current config language: {LANG}
|
||||
main.enable_webdash=Web dashboard is enabled: {URL}
|
||||
main.monitor.title=Liteyuki Monitor
|
||||
main.monitor.description=Monitor your server with Liteyuki Monitor
|
||||
@ -46,6 +52,7 @@ npm.prev_page=Prev
|
||||
npm.plugin_cannot_be_toggled=Plugin {NAME} cannot be toggled
|
||||
npm.plugin_already=Plugin {NAME} is already in {STATUS} state, no need for repeated operation
|
||||
npm.toggle_failed=Failed to {STATUS} plugin {NAME}: {ERROR}
|
||||
npm.toggle_success=Succeeded in {STATUS} plugin {NAME}
|
||||
|
||||
user.profile.edit=Edit
|
||||
user.profile.set=Set
|
@ -1,5 +1,11 @@
|
||||
language.name = 日本語
|
||||
|
||||
log.debug=デバッグ
|
||||
log.info=情報
|
||||
log.warning=警告
|
||||
log.error=エラー
|
||||
log.success=成功
|
||||
|
||||
main.current_language = 現在のシステム言語: {LANG}
|
||||
main.enable_webdash = ウェブダッシュボードが有効になりました: {URL}
|
||||
main.monitor.title = Liteyukiモニタリングパネル
|
||||
@ -46,6 +52,7 @@ npm.prev_page = 前のページ
|
||||
npm.plugin_cannot_be_toggled=プラグイン {NAME} は有効または無効にできません
|
||||
npm.plugin_already=プラグイン {NAME} はすでに {STATUS} 状態です。繰り返し操作する必要はありません
|
||||
npm.toggle_failed=プラグイン {NAME} を {STATUS} にするのに失敗しました: {ERROR}
|
||||
npm.toggle_success=プラグイン {NAME} が {STATUS} になりました
|
||||
|
||||
user.profile.edit=編集
|
||||
user.profile.set=設定
|
@ -1,6 +1,12 @@
|
||||
language.name=简体中文
|
||||
|
||||
main.current_language=当前系统语言为: {LANG}
|
||||
log.debug=调试
|
||||
log.info=信息
|
||||
log.warning=警告
|
||||
log.error=错误
|
||||
log.success=成功
|
||||
|
||||
main.current_language=当前配置语言为: {LANG}
|
||||
main.enable_webdash=已启用网页监控面板: {URL}
|
||||
main.monitor.title=轻雪监控面板
|
||||
main.monitor.description=轻雪机器人监控面板
|
||||
@ -32,7 +38,7 @@ npm.search_no_result=无搜索结果
|
||||
npm.too_many_results=内容过多,{HIDE_NUM}项已隐藏,请限制关键字搜索
|
||||
npm.install_success={NAME} 安装成功
|
||||
npm.install_failed={NAME} 安装失败,请查看日志获取详细信息,如不能解决,请访问{HOMEPAGE}寻求帮助
|
||||
npm.uninstall_success={NAME} 卸载成功
|
||||
npm.uninstall_success={NAME} 卸载成功,下次重启生效
|
||||
npm.uninstall_failed={NAME} 卸载失败
|
||||
npm.load_failed={NAME} 加载失败,请在控制台查看详细信息,检查依赖或配置是否正确,如不能解决,请访问{HOMEPAGE}寻求帮助
|
||||
npm.plugin_not_found=未在商店中找到 {NAME},请尝试更新商店信息或检查拼写
|
||||
@ -46,6 +52,7 @@ npm.prev_page=上一页
|
||||
npm.plugin_cannot_be_toggled=插件 {NAME} 无法被启用或停用
|
||||
npm.plugin_already=插件 {NAME} 已经是 {STATUS} 状态,无需重复操作
|
||||
npm.toggle_failed=插件 {NAME} {STATUS} 失败: {ERROR}
|
||||
npm.toggle_success=插件 {NAME} {STATUS} 成功
|
||||
|
||||
user.profile.edit=修改
|
||||
user.profile.set=设置
|
@ -1,4 +1,10 @@
|
||||
language.name=繁體中文
|
||||
language.name=繁體中文(香港)
|
||||
|
||||
log.debug=調試
|
||||
log.info=信息
|
||||
log.warning=警告
|
||||
log.error=錯誤
|
||||
log.success=成功
|
||||
|
||||
main.current_language=當前系統語言為:{LANG}
|
||||
main.enable_webdash=已啟用網頁監控面板:{URL}
|
||||
@ -46,6 +52,8 @@ npm.prev_page=上一頁
|
||||
npm.plugin_cannot_be_toggled=無法啟用或停用插件 {NAME}
|
||||
npm.plugin_already=插件 {NAME} 已處於 {STATUS} 狀態,無需重複操作
|
||||
npm.toggle_failed=插件 {NAME} {STATUS} 失敗: {ERROR}
|
||||
npm.toggle_success=插件 {NAME} {STATUS} 成功
|
||||
|
||||
|
||||
user.profile.edit=編輯
|
||||
user.profile.set=設定
|
@ -1,5 +1,11 @@
|
||||
language.name=简体中文(轻雪版)
|
||||
|
||||
log.debug=调试
|
||||
log.info=信息
|
||||
log.warning=有问题哦
|
||||
log.error=出错啦
|
||||
log.success=成功啦
|
||||
|
||||
main.current_language=现在系统用的语言是:{LANG} 喔!
|
||||
main.enable_webdash=已经打开了网页监控板:{URL} 啦!
|
||||
main.monitor.title=监控板
|
@ -1,4 +1,10 @@
|
||||
language.name=漢字
|
||||
language.name=中文(華夏)
|
||||
|
||||
log.debug=調試
|
||||
log.info=信息
|
||||
log.warning=警告
|
||||
log.error=錯誤
|
||||
log.success=成功
|
||||
|
||||
main.current_language=當前之系統語言為:{LANG}
|
||||
main.enable_webdash=已啟用網頁監控板:{URL}
|
37
liteyuki/utils/__init__.py
Normal file
37
liteyuki/utils/__init__.py
Normal file
@ -0,0 +1,37 @@
|
||||
import nonebot
|
||||
|
||||
from .log import logger
|
||||
import sys
|
||||
|
||||
__NAME__ = "LiteyukiBot"
|
||||
__VERSION__ = "6.2.1" # 60201
|
||||
major, minor, patch = map(int, __VERSION__.split("."))
|
||||
__VERSION_I__ = major * 10000 + minor * 100 + patch
|
||||
|
||||
|
||||
def init():
|
||||
"""
|
||||
初始化
|
||||
Returns:
|
||||
|
||||
"""
|
||||
# 检测python版本是否高于3.10
|
||||
if sys.version_info < (3, 10):
|
||||
nonebot.logger.error("This project requires Python3.10+ to run, please upgrade your Python Environment.")
|
||||
exit(1)
|
||||
|
||||
print("\033[34m" + r""" __ ______ ________ ________ __ __ __ __ __ __ ______
|
||||
/ | / |/ |/ |/ \ / |/ | / |/ | / |/ |
|
||||
$$ | $$$$$$/ $$$$$$$$/ $$$$$$$$/ $$ \ /$$/ $$ | $$ |$$ | /$$/ $$$$$$/
|
||||
$$ | $$ | $$ | $$ |__ $$ \/$$/ $$ | $$ |$$ |/$$/ $$ |
|
||||
$$ | $$ | $$ | $$ | $$ $$/ $$ | $$ |$$ $$< $$ |
|
||||
$$ | $$ | $$ | $$$$$/ $$$$/ $$ | $$ |$$$$$ \ $$ |
|
||||
$$ |_____ _$$ |_ $$ | $$ |_____ $$ | $$ \__$$ |$$ |$$ \ _$$ |_
|
||||
$$ |/ $$ | $$ | $$ | $$ | $$ $$/ $$ | $$ |/ $$ |
|
||||
$$$$$$$$/ $$$$$$/ $$/ $$$$$$$$/ $$/ $$$$$$/ $$/ $$/ $$$$$$/ """ + "\033[0m")
|
||||
nonebot.logger.info(
|
||||
f"Run Liteyuki with Python{sys.version_info.major}.{sys.version_info.minor}.{sys.version_info.micro} "
|
||||
f"at {sys.executable}"
|
||||
)
|
||||
|
||||
nonebot.logger.info(f"{__NAME__} {__VERSION__}({__VERSION_I__}) is running")
|
@ -4,7 +4,7 @@ import nonebot
|
||||
import yaml
|
||||
from pydantic import BaseModel
|
||||
|
||||
config = None
|
||||
config = {}
|
||||
|
||||
|
||||
class BasicConfig(BaseModel):
|
@ -31,7 +31,7 @@ class BaseORMAdapter(ABC):
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
def upsert(self, *args, **kwargs):
|
||||
"""存储数据
|
||||
|
||||
Returns:
|
||||
@ -171,7 +171,7 @@ class Database(BaseORMAdapter):
|
||||
self.conn.commit()
|
||||
nonebot.logger.debug(f'Table {table_name} migrated successfully')
|
||||
|
||||
def save(self, *models: LiteModel) -> int | tuple:
|
||||
def upsert(self, *models: LiteModel) -> int | tuple:
|
||||
"""存储数据,检查id字段,如果有id字段则更新,没有则插入
|
||||
|
||||
Args:
|
||||
@ -192,7 +192,7 @@ class Database(BaseORMAdapter):
|
||||
for field, value in model.__dict__.items():
|
||||
if isinstance(value, LiteModel):
|
||||
key_list.append(f'{self.FOREIGNID}{field}')
|
||||
value_list.append(f'{self.ID}:{value.__class__.__name__}:{self.save(value)}')
|
||||
value_list.append(f'{self.ID}:{value.__class__.__name__}:{self.upsert(value)}')
|
||||
elif isinstance(value, list):
|
||||
key_list.append(f'{self.LIST}{field}')
|
||||
value_list.append(self._flat(value))
|
||||
@ -225,7 +225,7 @@ class Database(BaseORMAdapter):
|
||||
return_data = {}
|
||||
for k, v in data.items():
|
||||
if isinstance(v, LiteModel):
|
||||
return_data[f'{self.FOREIGNID}{k}'] = f'{self.ID}:{v.__class__.__name__}:{self.save(v)}'
|
||||
return_data[f'{self.FOREIGNID}{k}'] = f'{self.ID}:{v.__class__.__name__}:{self.upsert(v)}'
|
||||
elif isinstance(v, list):
|
||||
return_data[f'{self.LIST}{k}'] = self._flat(v)
|
||||
elif isinstance(v, dict):
|
||||
@ -239,7 +239,7 @@ class Database(BaseORMAdapter):
|
||||
return_data = []
|
||||
for v in data:
|
||||
if isinstance(v, LiteModel):
|
||||
return_data.append(f'{self.ID}:{v.__class__.__name__}:{self.save(v)}')
|
||||
return_data.append(f'{self.ID}:{v.__class__.__name__}:{self.upsert(v)}')
|
||||
elif isinstance(v, list):
|
||||
return_data.append(self._flat(v))
|
||||
elif isinstance(v, dict):
|
45
liteyuki/utils/data_manager.py
Normal file
45
liteyuki/utils/data_manager.py
Normal file
@ -0,0 +1,45 @@
|
||||
import os
|
||||
|
||||
from pydantic import Field
|
||||
|
||||
from liteyuki.utils.data import LiteModel, Database as DB
|
||||
|
||||
DATA_PATH = "data/liteyuki"
|
||||
|
||||
user_db = DB(os.path.join(DATA_PATH, 'users.ldb'))
|
||||
group_db = DB(os.path.join(DATA_PATH, 'groups.ldb'))
|
||||
plugin_db = DB(os.path.join(DATA_PATH, 'plugins.ldb'))
|
||||
common_db = DB(os.path.join(DATA_PATH, 'common.ldb'))
|
||||
|
||||
|
||||
class User(LiteModel):
|
||||
user_id: str = Field(str(), alias='user_id')
|
||||
username: str = Field(str(), alias='username')
|
||||
profile: dict[str, str] = Field(dict(), alias='profile')
|
||||
enabled_plugins: list[str] = Field(list(), alias='enabled_plugins')
|
||||
disabled_plugins: list[str] = Field(list(), alias='disabled_plugins')
|
||||
|
||||
|
||||
class GroupChat(LiteModel):
|
||||
# Group是一个关键字,所以这里用GroupChat
|
||||
group_id: str = Field(str(), alias='group_id')
|
||||
group_name: str = Field(str(), alias='group_name')
|
||||
enabled_plugins: list[str] = Field([], alias='enabled_plugins')
|
||||
disabled_plugins: list[str] = Field([], alias='disabled_plugins')
|
||||
|
||||
|
||||
class InstalledPlugin(LiteModel):
|
||||
module_name: str = Field(str(), alias='module_name')
|
||||
version: str = Field(str(), alias='version')
|
||||
|
||||
|
||||
class GlobalPlugin(LiteModel):
|
||||
module_name: str = Field(str(), alias='module_name')
|
||||
enabled: bool = Field(True, alias='enabled')
|
||||
|
||||
|
||||
def auto_migrate():
|
||||
user_db.auto_migrate(User())
|
||||
group_db.auto_migrate(GroupChat())
|
||||
plugin_db.auto_migrate(InstalledPlugin())
|
||||
common_db.auto_migrate(GlobalPlugin())
|
@ -9,8 +9,8 @@ from typing import Any
|
||||
|
||||
import nonebot
|
||||
|
||||
from src.utils.config import config
|
||||
from src.utils.data_manager import User, user_db
|
||||
from liteyuki.utils.config import config
|
||||
from liteyuki.utils.data_manager import User, user_db
|
||||
|
||||
_default_lang_code = "en"
|
||||
_language_data = {
|
||||
@ -38,7 +38,6 @@ def load_from_lang(file_path: str, lang_code: str = None):
|
||||
if not line or line.startswith('#'): # 空行或注释
|
||||
continue
|
||||
key, value = line.split('=', 1)
|
||||
nonebot.logger.debug(f"Loaded language text: {key.strip()} -> {value.strip()}")
|
||||
data[key.strip()] = value.strip()
|
||||
if lang_code not in _language_data:
|
||||
_language_data[lang_code] = {}
|
||||
@ -139,7 +138,7 @@ def get_user_lang(user_id: str) -> Language:
|
||||
username="Unknown"
|
||||
))
|
||||
|
||||
return Language(user.profile.get('lang',config.get("default_language", get_system_lang_code()) ))
|
||||
return Language(user.profile.get('lang', config.get("default_language", get_system_lang_code())))
|
||||
|
||||
|
||||
def get_system_lang_code() -> str:
|
||||
@ -149,11 +148,11 @@ def get_system_lang_code() -> str:
|
||||
return locale.getdefaultlocale()[0].replace('_', '-')
|
||||
|
||||
|
||||
def get_system_lang() -> Language:
|
||||
def get_default_lang() -> Language:
|
||||
"""
|
||||
获取系统语言
|
||||
获取默认/系统语言
|
||||
"""
|
||||
return Language(get_system_lang_code())
|
||||
return Language(config.get("default_language", get_system_lang_code()))
|
||||
|
||||
|
||||
def get_all_lang() -> dict[str, str]:
|
@ -1,35 +1,14 @@
|
||||
import sys
|
||||
import logging
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from colored import fg
|
||||
from .language import get_default_lang
|
||||
import loguru
|
||||
|
||||
if TYPE_CHECKING:
|
||||
# avoid sphinx autodoc resolve annotation failed
|
||||
# because loguru module do not have `Logger` class actually
|
||||
from loguru import Logger, Record
|
||||
|
||||
# logger = logging.getLogger("nonebot")
|
||||
logger: "Logger" = loguru.logger
|
||||
"""NoneBot 日志记录器对象。
|
||||
|
||||
默认信息:
|
||||
|
||||
- 格式: `[%(asctime)s %(name)s] %(levelname)s: %(message)s`
|
||||
- 等级: `INFO` ,根据 `config.log_level` 配置改变
|
||||
- 输出: 输出至 stdout
|
||||
|
||||
用法:
|
||||
```python
|
||||
from nonebot.log import logger
|
||||
```
|
||||
"""
|
||||
|
||||
|
||||
# default_handler = logging.StreamHandler(sys.stdout)
|
||||
# default_handler.setFormatter(
|
||||
# logging.Formatter("[%(asctime)s %(name)s] %(levelname)s: %(message)s"))
|
||||
# logger.addHandler(default_handler)
|
||||
|
||||
|
||||
class LoguruHandler(logging.Handler): # pragma: no cover
|
||||
@ -59,7 +38,7 @@ def default_filter(record: "Record"):
|
||||
|
||||
|
||||
default_format: str = (
|
||||
"<g>{time:MM-DD HH:mm:ss}</g> "
|
||||
"<c>{time:YYYY-MM-DD}</c> <blue>{time:HH:mm:ss}</blue> "
|
||||
"<lvl>[{level.icon}]</lvl> "
|
||||
"<c><{name}></c> "
|
||||
"{message}"
|
||||
@ -74,12 +53,12 @@ logger_id = logger.add(
|
||||
filter=default_filter,
|
||||
format=default_format,
|
||||
)
|
||||
|
||||
logger.level("DEBUG", color="<cyan>", icon="㊙️DEBU")
|
||||
logger.level("INFO", color="<white>", icon="ℹ️INFO")
|
||||
logger.level("SUCCESS", color="<green>", icon="✅SUCC")
|
||||
logger.level("WARNING", color="<yellow>", icon="⚠️WARN")
|
||||
logger.level("ERROR", color="<red>", icon="☢️ERRO")
|
||||
slang = get_default_lang()
|
||||
logger.level("DEBUG", color="<blue>", icon=f"*️⃣ DDDEBUG")
|
||||
logger.level("INFO", color="<white>", icon=f"ℹ️ IIIINFO")
|
||||
logger.level("SUCCESS", color="<green>", icon=f"✅ SUCCESS")
|
||||
logger.level("WARNING", color="<yellow>", icon=f"⚠️ WARNING")
|
||||
logger.level("ERROR", color="<red>", icon=f"⭕ EEERROR")
|
||||
|
||||
"""默认日志处理器 id"""
|
||||
|
207
liteyuki/utils/orm.py
Normal file
207
liteyuki/utils/orm.py
Normal file
@ -0,0 +1,207 @@
|
||||
import os
|
||||
import pickle
|
||||
import sqlite3
|
||||
from types import NoneType
|
||||
from typing import Any
|
||||
|
||||
import nonebot
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
|
||||
class LiteModel(BaseModel):
|
||||
"""轻量级模型基类
|
||||
类型注解统一使用Python3.9的PEP585标准,如需使用泛型请使用typing模块的泛型类型
|
||||
不允许使用id, table_name以及其他SQLite关键字作为字段名,不允许使用JSON和ID,必须指定默认值,且默认值类型必须与字段类型一致
|
||||
"""
|
||||
__ID__: int = Field(None, alias='id')
|
||||
__TABLE_NAME__: str = Field(None, alias='table_name')
|
||||
|
||||
|
||||
class Database:
|
||||
TYPE_MAPPING = {
|
||||
int : "INTEGER",
|
||||
float : "REAL",
|
||||
str : "TEXT",
|
||||
bool : "INTEGER",
|
||||
bytes : "BLOB",
|
||||
NoneType: "NULL",
|
||||
|
||||
dict : "BLOB", # LITEYUKIDICT{key_name}
|
||||
list : "BLOB", # LITEYUKILIST{key_name}
|
||||
tuple : "BLOB", # LITEYUKITUPLE{key_name}
|
||||
set : "BLOB", # LITEYUKISET{key_name}
|
||||
}
|
||||
|
||||
# 基础类型
|
||||
BASIC_TYPE = [int, float, str, bool, bytes, NoneType]
|
||||
# 可序列化类型
|
||||
ITERABLE_TYPE = [dict, list, tuple, set]
|
||||
|
||||
LITEYUKI = "LITEYUKI"
|
||||
|
||||
# 字段前缀映射,默认基础类型为""
|
||||
FIELD_PREFIX_MAPPING = {
|
||||
dict : f"{LITEYUKI}DICT",
|
||||
list : f"{LITEYUKI}LIST",
|
||||
tuple : f"{LITEYUKI}TUPLE",
|
||||
set : f"{LITEYUKI}SET",
|
||||
type(LiteModel): f"{LITEYUKI}MODEL"
|
||||
}
|
||||
|
||||
def __init__(self, db_name: str):
|
||||
if not os.path.exists(os.path.dirname(db_name)):
|
||||
os.makedirs(os.path.dirname(db_name))
|
||||
self.conn = sqlite3.connect(db_name) # 连接对象
|
||||
self.conn.row_factory = sqlite3.Row # 以字典形式返回查询结果
|
||||
self.cursor = self.conn.cursor() # 游标对象
|
||||
|
||||
def auto_migrate(self, *args: LiteModel):
|
||||
"""
|
||||
自动迁移模型
|
||||
Args:
|
||||
*args: 模型类实例化对象,支持空默认值,不支持嵌套迁移
|
||||
|
||||
Returns:
|
||||
|
||||
"""
|
||||
for model in args:
|
||||
if not model.__TABLE_NAME__:
|
||||
raise ValueError(f"数据模型{model.__class__.__name__}未提供表名")
|
||||
|
||||
# 若无则创建表
|
||||
self.cursor.execute(
|
||||
f'CREATE TABLE IF NOT EXISTS {model.__TABLE_NAME__} (id INTEGER PRIMARY KEY AUTOINCREMENT)'
|
||||
)
|
||||
|
||||
# 获取表结构
|
||||
new_fields, new_stored_types = (
|
||||
zip(
|
||||
*[(self._get_stored_field_prefix(model.__getattribute__(field)) + field, self._get_stored_type(model.__getattribute__(field)))
|
||||
for field in model.__annotations__]
|
||||
)
|
||||
)
|
||||
|
||||
# 原有的字段列表
|
||||
existing_fields = self.cursor.execute(f'PRAGMA table_info({model.__TABLE_NAME__})').fetchall()
|
||||
existing_types = [field['name'] for field in existing_fields]
|
||||
|
||||
# 检测缺失字段,由于SQLite是动态类型,所以不需要检测类型
|
||||
for n_field, n_type in zip(new_fields, new_stored_types):
|
||||
if n_field not in existing_types:
|
||||
nonebot.logger.debug(f'ALTER TABLE {model.__TABLE_NAME__} ADD COLUMN {n_field} {n_type}')
|
||||
self.cursor.execute(
|
||||
f'ALTER TABLE {model.__TABLE_NAME__} ADD COLUMN {n_field} {n_type}'
|
||||
)
|
||||
|
||||
# 检测多余字段进行删除
|
||||
for e_field in existing_types:
|
||||
if e_field not in new_fields and e_field not in ['id']:
|
||||
nonebot.logger.debug(f'ALTER TABLE {model.__TABLE_NAME__} DROP COLUMN {e_field}')
|
||||
self.cursor.execute(
|
||||
f'ALTER TABLE {model.__TABLE_NAME__} DROP COLUMN {e_field}'
|
||||
)
|
||||
|
||||
self.conn.commit()
|
||||
|
||||
def save(self, *args: LiteModel) -> [int | tuple[int, ...]]:
|
||||
"""
|
||||
保存或更新模型
|
||||
Args:
|
||||
*args: 模型类实例化对象,支持空默认值,不支持嵌套迁移
|
||||
Returns:
|
||||
|
||||
"""
|
||||
ids = []
|
||||
for model in args:
|
||||
if not model.__TABLE_NAME__:
|
||||
raise ValueError(f"数据模型{model.__class__.__name__}未提供表名")
|
||||
if not self.cursor.execute(f'PRAGMA table_info({model.__TABLE_NAME__})').fetchall():
|
||||
raise ValueError(f"数据表{model.__TABLE_NAME__}不存在,请先迁移{model.__class__.__name__}模型")
|
||||
|
||||
stored_fields, stored_values = [], []
|
||||
for r_field in model.__annotations__:
|
||||
r_value = model.__getattribute__(r_field)
|
||||
stored_fields.append(self._get_stored_field_prefix(r_value) + r_field)
|
||||
|
||||
if type(r_value) in Database.BASIC_TYPE:
|
||||
# int str float bool bytes NoneType
|
||||
stored_values.append(r_value)
|
||||
|
||||
elif type(r_value) in Database.ITERABLE_TYPE:
|
||||
# dict list tuple set
|
||||
stored_values.append(pickle.dumps(self._flat_save(r_value)))
|
||||
|
||||
elif isinstance(r_value, LiteModel):
|
||||
# LiteModel TABLE_NAME:ID
|
||||
stored_values.append(f"{r_value.__TABLE_NAME__}:{self.save(r_value)}")
|
||||
|
||||
else:
|
||||
raise ValueError(f"不支持的数据类型{type(r_value)}")
|
||||
nonebot.logger.debug(f"INSERT OR REPLACE INTO {model.__TABLE_NAME__} ({','.join(stored_fields)}) VALUES ({','.join([_ for _ in stored_values])})")
|
||||
self.cursor.execute(
|
||||
f"INSERT OR REPLACE INTO {model.__TABLE_NAME__} ({','.join(stored_fields)}) VALUES ({','.join(['?' for _ in stored_values])})",
|
||||
stored_values
|
||||
)
|
||||
ids.append(self.cursor.lastrowid)
|
||||
self.conn.commit()
|
||||
return tuple(ids) if len(ids) > 1 else ids[0]
|
||||
|
||||
# 检测id字段是否有1,有则更新,无则插入
|
||||
|
||||
def _flat_save(self, obj) -> Any:
|
||||
"""扁平化存储
|
||||
|
||||
Args:
|
||||
obj: 需要存储的对象
|
||||
|
||||
Returns:
|
||||
存储的字节流
|
||||
"""
|
||||
# TODO 递归扁平化存储
|
||||
if type(obj) in Database.ITERABLE_TYPE:
|
||||
for i, item in enumerate(obj) if type(obj) in [list, tuple, set] else obj.items():
|
||||
if type(item) in Database.BASIC_TYPE:
|
||||
continue
|
||||
elif type(item) in Database.ITERABLE_TYPE:
|
||||
obj[i] = pickle.dumps(self._flat_save(item))
|
||||
elif isinstance(item, LiteModel):
|
||||
obj[i] = f"{item.__TABLE_NAME__}:{self.save(item)}"
|
||||
else:
|
||||
raise ValueError(f"不支持的数据类型{type(item)}")
|
||||
else:
|
||||
raise ValueError(f"不支持的数据类型{type(obj)}")
|
||||
|
||||
@staticmethod
|
||||
def _get_stored_field_prefix(value) -> str:
|
||||
"""获取存储字段前缀,一定在后加上字段名
|
||||
|
||||
LiteModel -> LITEYUKIID
|
||||
|
||||
dict -> LITEYUKIDICT
|
||||
|
||||
list -> LITEYUKILIST
|
||||
|
||||
tuple -> LITEYUKITUPLE
|
||||
|
||||
set -> LITEYUKISET
|
||||
|
||||
* -> ""
|
||||
Args:
|
||||
value: 储存的值
|
||||
|
||||
Returns:
|
||||
Sqlite3存储字段
|
||||
"""
|
||||
return Database.FIELD_PREFIX_MAPPING.get(type(value), "")
|
||||
|
||||
@staticmethod
|
||||
def _get_stored_type(value) -> str:
|
||||
"""获取存储类型
|
||||
|
||||
Args:
|
||||
value: 储存的值
|
||||
|
||||
Returns:
|
||||
Sqlite3存储类型
|
||||
"""
|
||||
return Database.TYPE_MAPPING.get(type(value), "TEXT")
|
@ -1,6 +1,6 @@
|
||||
from nonebot.adapters.onebot import v11
|
||||
|
||||
from src.utils.ly_typing import T_GroupMessageEvent, T_MessageEvent
|
||||
from liteyuki.utils.ly_typing import T_GroupMessageEvent, T_MessageEvent
|
||||
|
||||
GROUP_ADMIN = v11.GROUP_ADMIN
|
||||
GROUP_OWNER = v11.GROUP_OWNER
|
@ -4,7 +4,7 @@ import nonebot
|
||||
import yaml
|
||||
from typing import Any
|
||||
|
||||
from src.utils.data import LiteModel
|
||||
from liteyuki.utils.data import LiteModel
|
||||
|
||||
_resource_data = {}
|
||||
_loaded_resource_packs = [] # 按照加载顺序排序
|
||||
@ -31,7 +31,6 @@ def load_resource_from_dir(path: str):
|
||||
relative_path = os.path.relpath(os.path.join(root, file), path).replace("\\", "/")
|
||||
abs_path = os.path.join(root, file).replace("\\", "/")
|
||||
_resource_data[relative_path] = abs_path
|
||||
nonebot.logger.debug(f"Loaded {relative_path} -> {abs_path}")
|
||||
if os.path.exists(os.path.join(path, "metadata.yml")):
|
||||
with open(os.path.join(path, "metadata.yml"), "r", encoding="utf-8") as f:
|
||||
metadata = yaml.safe_load(f)
|
||||
@ -39,12 +38,12 @@ def load_resource_from_dir(path: str):
|
||||
metadata = ResourceMetadata()
|
||||
metadata["path"] = path
|
||||
if os.path.exists(os.path.join(path, "lang")):
|
||||
from src.utils.language import load_from_dir
|
||||
from liteyuki.utils.language import load_from_dir
|
||||
load_from_dir(os.path.join(path, "lang"))
|
||||
_loaded_resource_packs.append(ResourceMetadata(**metadata))
|
||||
|
||||
|
||||
def get_res(path: str, default: Any = None) -> str | Any:
|
||||
def get(path: str, default: Any = None) -> str | Any:
|
||||
"""
|
||||
获取资源包中的文件
|
||||
Args:
|
@ -1,3 +1,4 @@
|
||||
from importlib.metadata import PackageNotFoundError, version
|
||||
from urllib.parse import quote
|
||||
|
||||
|
||||
@ -72,3 +73,11 @@ def keywords_in_text(keywords: list[str], text: str, all_matched: bool) -> bool:
|
||||
if keyword in text:
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def check_for_package(package_name: str) -> bool:
|
||||
try:
|
||||
version(package_name)
|
||||
return True
|
||||
except PackageNotFoundError:
|
||||
return False
|
10
main.py
10
main.py
@ -1,11 +1,9 @@
|
||||
import nonebot
|
||||
|
||||
from nonebot.adapters.onebot import v11, v12
|
||||
from liteyuki.utils.config import load_from_yaml
|
||||
from liteyuki.utils import init
|
||||
|
||||
from src.utils import logger
|
||||
from src.utils.config import load_from_yaml
|
||||
|
||||
nonebot.logger = logger
|
||||
init()
|
||||
nonebot.init(**load_from_yaml("config.yml"))
|
||||
|
||||
adapters = [v11.Adapter, v12.Adapter]
|
||||
@ -14,7 +12,7 @@ driver = nonebot.get_driver()
|
||||
for adapter in adapters:
|
||||
driver.register_adapter(adapter)
|
||||
|
||||
nonebot.load_plugin("src.liteyuki_main")
|
||||
nonebot.load_plugin("liteyuki.liteyuki_main")
|
||||
|
||||
if __name__ == "__main__":
|
||||
nonebot.run()
|
||||
|
@ -2,13 +2,16 @@ aiohttp==3.9.3
|
||||
aiofiles==23.2.1
|
||||
arclet-alconna==1.8.5
|
||||
arclet-alconna-tools==0.7.0
|
||||
colored==2.2.4
|
||||
dash==2.16.1
|
||||
nonebot2[fastapi]==2.2.1
|
||||
nonebot-adapter-onebot==2.4.3
|
||||
nonebot-plugin-alconna==0.41.0
|
||||
pip==24.0
|
||||
psutil==5.9.8
|
||||
pydantic==2.6.4
|
||||
pydantic==1.10.14
|
||||
pytz==2024.1
|
||||
PyYAML~=6.0.1
|
||||
starlette~=0.36.3
|
||||
loguru==0.7.2
|
||||
importlib_metadata==7.0.2
|
@ -1,18 +0,0 @@
|
||||
import os
|
||||
|
||||
import nonebot.plugin
|
||||
|
||||
from src.utils.data_manager import InstalledPlugin, plugin_db
|
||||
from src.utils.resource import load_resource_from_dir
|
||||
|
||||
THIS_PLUGIN_NAME = os.path.basename(os.path.dirname(__file__))
|
||||
RESOURCE_PATH = "src/resources"
|
||||
load_resource_from_dir(RESOURCE_PATH)
|
||||
|
||||
nonebot.plugin.load_plugins("src/plugins")
|
||||
nonebot.plugin.load_plugins("plugins")
|
||||
|
||||
installed_plugins = plugin_db.all(InstalledPlugin)
|
||||
if installed_plugins:
|
||||
for install_plugin in plugin_db.all(InstalledPlugin):
|
||||
nonebot.load_plugin(install_plugin.module_name)
|
@ -1,122 +0,0 @@
|
||||
from typing import Optional
|
||||
|
||||
import nonebot
|
||||
from nonebot import on_message
|
||||
from arclet.alconna import Arparma, Alconna, Args, Option, Subcommand, Arg
|
||||
from nonebot.plugin import PluginMetadata
|
||||
from nonebot_plugin_alconna import on_alconna
|
||||
from src.utils.data import LiteModel
|
||||
from src.utils.message import send_markdown
|
||||
from src.utils.ly_typing import T_Bot, T_MessageEvent
|
||||
from src.utils.data import Database
|
||||
|
||||
|
||||
class Node(LiteModel):
|
||||
bot_id: str
|
||||
session_type: str
|
||||
session_id: str
|
||||
|
||||
def __str__(self):
|
||||
return f"{self.bot_id}.{self.session_type}.{self.session_id}"
|
||||
|
||||
|
||||
class Push(LiteModel):
|
||||
source: Node
|
||||
target: Node
|
||||
inde: int
|
||||
|
||||
|
||||
pushes_db = Database("data/pushes.ldb")
|
||||
pushes_db.auto_migrate(Push, Node)
|
||||
|
||||
alc = Alconna(
|
||||
"lep",
|
||||
Subcommand(
|
||||
"add",
|
||||
Args["source", str],
|
||||
Args["target", str],
|
||||
Option("bidirectional", Args["bidirectional", bool])
|
||||
),
|
||||
Subcommand(
|
||||
"rm",
|
||||
Args["index", int],
|
||||
|
||||
),
|
||||
Subcommand(
|
||||
"list",
|
||||
)
|
||||
)
|
||||
|
||||
add_push = on_alconna(alc)
|
||||
|
||||
|
||||
@add_push.handle()
|
||||
async def _(result: Arparma):
|
||||
"""bot_id.session_type.session_id"""
|
||||
if result.subcommands.get("add"):
|
||||
source = result.subcommands["add"].args.get("source")
|
||||
target = result.subcommands["add"].args.get("target")
|
||||
if source and target:
|
||||
source = source.split(".")
|
||||
target = target.split(".")
|
||||
push1 = Push(
|
||||
source=Node(bot_id=source[0], session_type=source[1], session_id=source[2]),
|
||||
target=Node(bot_id=target[0], session_type=target[1], session_id=target[2]),
|
||||
inde=len(pushes_db.all(Push, default=[]))
|
||||
)
|
||||
pushes_db.save(push1)
|
||||
|
||||
if result.subcommands["add"].args.get("bidirectional"):
|
||||
push2 = Push(
|
||||
source=Node(bot_id=target[0], session_type=target[1], session_id=target[2]),
|
||||
target=Node(bot_id=source[0], session_type=source[1], session_id=source[2]),
|
||||
inde=len(pushes_db.all(Push, default=[]))
|
||||
)
|
||||
pushes_db.save(push2)
|
||||
await add_push.finish("添加成功")
|
||||
else:
|
||||
await add_push.finish("参数缺失")
|
||||
elif result.subcommands.get("rm"):
|
||||
index = result.subcommands["rm"].args.get("index")
|
||||
if index is not None:
|
||||
try:
|
||||
pushes_db.delete(Push, "inde = ?", index)
|
||||
await add_push.finish("删除成功")
|
||||
except IndexError:
|
||||
await add_push.finish("索引错误")
|
||||
else:
|
||||
await add_push.finish("参数缺失")
|
||||
elif result.subcommands.get("list"):
|
||||
await add_push.finish(
|
||||
"\n".join([f"{push.inde} {push.source.bot_id}.{push.source.session_type}.{push.source.session_id} -> "
|
||||
f"{push.target.bot_id}.{push.target.session_type}.{push.target.session_id}" for i, push in
|
||||
enumerate(pushes_db.all(Push, default=[]))]))
|
||||
else:
|
||||
await add_push.finish("参数错误")
|
||||
|
||||
|
||||
@on_message(block=False).handle()
|
||||
async def _(event: T_MessageEvent, bot: T_Bot):
|
||||
for push in pushes_db.all(Push, default=[]):
|
||||
if str(push.source) == f"{bot.self_id}.{event.message_type}.{event.user_id if event.message_type == 'private' else event.group_id}":
|
||||
bot2 = nonebot.get_bot(push.target.bot_id)
|
||||
msg_formatted = ""
|
||||
for l in str(event.message).split("\n"):
|
||||
msg_formatted += f"**{l.strip()}**\n"
|
||||
push_message = (
|
||||
f"> From {event.sender.nickname}@{push.source.session_type}.{push.source.session_id}\n> Bot {bot.self_id}\n\n"
|
||||
f"{msg_formatted}")
|
||||
await send_markdown(push_message, bot2, message_type=push.target.session_type, session_id=push.target.session_id)
|
||||
return
|
||||
|
||||
|
||||
__author__ = "snowykami"
|
||||
__plugin_meta__ = PluginMetadata(
|
||||
name="轻雪事件推送",
|
||||
description="事件推送插件,支持单向和双向推送,支持跨Bot推送",
|
||||
usage="",
|
||||
homepage="https://github.com/snowykami/LiteyukiBot",
|
||||
extra={
|
||||
"liteyuki": True,
|
||||
}
|
||||
)
|
@ -1 +0,0 @@
|
||||
from .log import logger
|
@ -1,35 +0,0 @@
|
||||
import os
|
||||
|
||||
from src.utils.data import LiteModel, Database as DB
|
||||
|
||||
DATA_PATH = "data/liteyuki"
|
||||
|
||||
user_db = DB(os.path.join(DATA_PATH, 'users.ldb'))
|
||||
group_db = DB(os.path.join(DATA_PATH, 'groups.ldb'))
|
||||
plugin_db = DB(os.path.join(DATA_PATH, 'plugins.ldb'))
|
||||
|
||||
|
||||
class User(LiteModel):
|
||||
user_id: str
|
||||
username: str = ""
|
||||
profile: dict[str, str] = {}
|
||||
enabled_plugins: list[str] = []
|
||||
disabled_plugins: list[str] = []
|
||||
|
||||
|
||||
class GroupChat(LiteModel):
|
||||
# Group是一个关键字,所以这里用GroupChat
|
||||
group_id: str
|
||||
group_name: str = ""
|
||||
enabled_plugins: list[str] = []
|
||||
disabled_plugins: list[str] = []
|
||||
|
||||
|
||||
class InstalledPlugin(LiteModel):
|
||||
module_name: str
|
||||
|
||||
|
||||
def auto_migrate():
|
||||
user_db.auto_migrate(User)
|
||||
group_db.auto_migrate(GroupChat)
|
||||
plugin_db.auto_migrate(InstalledPlugin)
|
Loading…
Reference in New Issue
Block a user