fix: 插件列表显示错误问题

This commit is contained in:
snowy 2024-03-24 09:43:34 +08:00
parent de0c073c26
commit fab5be70b3
45 changed files with 501 additions and 303 deletions

View File

@ -1,10 +1,10 @@
import nonebot import nonebot
from nonebot.plugin import PluginMetadata from nonebot.plugin import PluginMetadata
from src.utils.language import get_system_lang from liteyuki.utils.language import get_default_lang
from src.utils.data_manager import * from liteyuki.utils.data_manager import *
from .loader import * from .loader import *
from .webdash import * from .webdash import *
from src.utils.config import config from liteyuki.utils.config import config
__author__ = "snowykami" __author__ = "snowykami"
__plugin_meta__ = PluginMetadata( __plugin_meta__ = PluginMetadata(
@ -20,6 +20,6 @@ __plugin_meta__ = PluginMetadata(
auto_migrate() # 自动迁移数据库 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.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)}"))

View 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)

View File

@ -3,8 +3,8 @@ import psutil
from dash import Dash, Input, Output, dcc, html from dash import Dash, Input, Output, dcc, html
from starlette.middleware.wsgi import WSGIMiddleware from starlette.middleware.wsgi import WSGIMiddleware
from src.utils.language import Language from liteyuki.utils.language import Language
from src.utils.tools import convert_size from liteyuki.utils.tools import convert_size
app = nonebot.get_app() app = nonebot.get_app()

View File

@ -6,8 +6,8 @@ from nonebot.params import CommandArg
from nonebot.permission import SUPERUSER from nonebot.permission import SUPERUSER
from nonebot.plugin import PluginMetadata from nonebot.plugin import PluginMetadata
from src.utils.message import send_markdown from liteyuki.utils.message import send_markdown
from src.utils.ly_typing import T_Message, T_Bot, v11, T_MessageEvent from liteyuki.utils.ly_typing import T_Message, T_Bot, v11, T_MessageEvent
md_test = on_command("mdts", aliases={"会话md"}, permission=SUPERUSER) md_test = on_command("mdts", aliases={"会话md"}, permission=SUPERUSER)
md_group = on_command("mdg", aliases={"群md"}, permission=SUPERUSER) md_group = on_command("mdg", aliases={"群md"}, permission=SUPERUSER)

View File

@ -4,9 +4,9 @@ from typing import Optional
import aiofiles import aiofiles
import nonebot.plugin import nonebot.plugin
from src.utils.data import Database, LiteModel from liteyuki.utils.data import Database, LiteModel
from src.utils.data_manager import GroupChat, InstalledPlugin, User, group_db, plugin_db, user_db from liteyuki.utils.data_manager import GroupChat, InstalledPlugin, User, group_db, plugin_db, user_db
from src.utils.ly_typing import T_MessageEvent from liteyuki.utils.ly_typing import T_MessageEvent
LNPM_COMMAND_START = "lnpm" LNPM_COMMAND_START = "lnpm"
@ -58,16 +58,17 @@ def get_plugin_default_enable(plugin_module_name: str) -> bool:
Returns: Returns:
bool: 插件默认状态 bool: 插件默认状态
""" """
return (nonebot.plugin.get_plugin(plugin_module_name).metadata.extra.get('default_enable', True) plug = nonebot.plugin.get_plugin_by_module_name(plugin_module_name)
if nonebot.plugin.get_plugin(plugin_module_name) and nonebot.plugin.get_plugin(plugin_module_name).metadata else True) \ return (plug.metadata.extra.get('default_enable', True)
if nonebot.plugin.get_plugin(plugin_module_name) else False 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: Args:
event: 会话事件
plugin_module_name (str): 插件模块名 plugin_module_name (str): 插件模块名
Returns: 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 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: 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: Returns:
bool: 插件是否可以被启用/停用 bool: 插件是否可以被启用/停用
""" """
return nonebot.plugin.get_plugin(plugin_module_name).metadata.extra.get('toggleable', True) \ plug = nonebot.plugin.get_plugin_by_module_name(plugin_module_name)
if nonebot.plugin.get_plugin(plugin_module_name) and nonebot.plugin.get_plugin(plugin_module_name).metadata else True return plug.metadata.extra.get('toggleable', True) if plug and plug.metadata else True

View File

@ -9,9 +9,9 @@ from arclet.alconna import Arparma, MultiVar
from nonebot.permission import SUPERUSER from nonebot.permission import SUPERUSER
from nonebot_plugin_alconna import Alconna, Args, Subcommand, on_alconna from nonebot_plugin_alconna import Alconna, Args, Subcommand, on_alconna
from src.utils.language import get_user_lang from liteyuki.utils.language import get_user_lang
from src.utils.ly_typing import T_Bot from liteyuki.utils.ly_typing import T_Bot
from src.utils.message import Markdown as md, send_markdown from liteyuki.utils.message import Markdown as md, send_markdown
from .common import * from .common import *
npm_alc = on_alconna( npm_alc = on_alconna(
@ -45,9 +45,6 @@ npm_alc = on_alconna(
) )
@npm_alc.handle() @npm_alc.handle()
async def _(result: Arparma, event: T_MessageEvent, bot: T_Bot): async def _(result: Arparma, event: T_MessageEvent, bot: T_Bot):
ulang = get_user_lang(str(event.user_id)) 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 r_load:
if found_in_db_plugin is None: 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转义 info = ulang.get('npm.install_success', NAME=store_plugin.name).replace('_', r'\\_') # markdown转义
await send_markdown( await send_markdown(
f"{info}\n\n" f"{info}\n\n"
@ -192,9 +189,6 @@ async def npm_search(keywords: list[str]) -> list[StorePlugin]:
return results return results
def npm_install(plugin_module_name) -> tuple[bool, str]: def npm_install(plugin_module_name) -> tuple[bool, str]:
""" """
Args: Args:
@ -209,8 +203,8 @@ def npm_install(plugin_module_name) -> tuple[bool, str]:
sys.stderr = buffer sys.stderr = buffer
mirrors = [ mirrors = [
"https://pypi.mirrors.cqupt.edu.cn/simple", # 重庆邮电大学
"https://pypi.tuna.tsinghua.edu.cn/simple", # 清华大学 "https://pypi.tuna.tsinghua.edu.cn/simple", # 清华大学
"https://pypi.mirrors.cqupt.edu.cn/simple", # 重庆邮电大学
"https://pypi.liteyuki.icu/simple", # 轻雪镜像 "https://pypi.liteyuki.icu/simple", # 轻雪镜像
"https://pypi.org/simple", # 官方源 "https://pypi.org/simple", # 官方源
] ]

View File

@ -8,23 +8,35 @@ from nonebot.message import run_preprocessor
from nonebot.permission import SUPERUSER from nonebot.permission import SUPERUSER
from nonebot_plugin_alconna import on_alconna, Alconna, Args, Arparma 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 liteyuki.utils.data_manager import GroupChat, InstalledPlugin, User, group_db, plugin_db, user_db
from src.utils.message import Markdown as md, send_markdown from liteyuki.utils.message import Markdown as md, send_markdown
from src.utils.permission import GROUP_ADMIN, GROUP_OWNER from liteyuki.utils.permission import GROUP_ADMIN, GROUP_OWNER
from src.utils.ly_typing import T_Bot, T_MessageEvent from liteyuki.utils.ly_typing import T_Bot, T_MessageEvent
from src.utils.language import get_user_lang from liteyuki.utils.language import get_user_lang
from .common import get_plugin_can_be_toggle, get_plugin_current_enable, get_plugin_default_enable 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 from .installer import get_store_plugin, npm_update
list_plugins = on_command("list-plugin", aliases={"列出插件", "插件列表"}, priority=0) list_plugins = on_alconna(
# toggle_plugin = on_command("enable-plugin", aliases={"启用插件", "停用插件", "disable-plugin"}, priority=0) Alconna(
['list-plugins', "插件列表", "列出插件"],
)
)
toggle_plugin = on_alconna( toggle_plugin = on_alconna(
Alconna( Alconna(
['enable-plugin', 'disable-plugin'], ['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() @list_plugins.handle()
async def _(event: T_MessageEvent, bot: T_Bot): 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(): for plugin in nonebot.get_loaded_plugins():
# 检查是否有 metadata 属性 # 检查是否有 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) 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: if store_plugin:
btn_homepage = md.link(lang.get('npm.homepage'), store_plugin.homepage) 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
btn_homepage = md.link(lang.get('npm.homepage'), "https://github.com/snowykami/LiteyukiBot") 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: else:
btn_homepage = lang.get('npm.homepage') btn_homepage = lang.get('npm.homepage')
show_name = plugin.name
show_desc = lang.get('npm.no_description')
if plugin.metadata: if plugin.metadata:
reply += (f"\n**{md.escape(plugin.metadata.name)}**\n" reply += (f"\n**{md.escape(show_name)}**\n"
f"\n > {plugin.metadata.description}") f"\n > {md.escape(show_desc)}")
else: else:
reply += (f"**{md.escape(plugin.name)}**\n" reply += (f"**{md.escape(show_name)}**\n"
f"\n > {lang.get('npm.no_description')}") f"\n > {md.escape(show_desc)}")
reply += f"\n > {btn_usage} {btn_homepage}" reply += f"\n > {btn_usage} {btn_homepage}"
if await GROUP_ADMIN(bot, event) or await GROUP_OWNER(bot, event) or await SUPERUSER(bot, event): 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') \ cmd_toggle = f"{'disable' if session_enable else 'enable'}-plugin {plugin.module_name}"
else md.button(lang.get('npm.disable'), f'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}" reply += f" {btn_toggle}"
if await SUPERUSER(bot, event): if await SUPERUSER(bot, event):
plugin_in_database = plugin_db.first(InstalledPlugin, 'module_name = ?', plugin.module_name) plugin_in_database = plugin_db.first(InstalledPlugin, 'module_name = ?', plugin.module_name)
# 添加移除插件 # 添加移除插件和全局切换按钮
global_enable = get_plugin_global_enable(plugin.module_name)
btn_uninstall = ( btn_uninstall = (
md.button(lang.get('npm.uninstall'), f'npm uninstall {plugin.module_name}')) if plugin_in_database else lang.get( md.button(lang.get('npm.uninstall'), f'npm uninstall {plugin.module_name}')) if plugin_in_database else lang.get(
'npm.uninstall') '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 += f" {btn_uninstall} {btn_toggle_global}"
reply += "\n\n***\n" 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") plugin_module_name = result.args.get("plugin_name")
toggle = result.header_result == 'enable-plugin' # 判断是启用还是停用 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) # 获取插件默认状态 default_enable = get_plugin_default_enable(plugin_module_name) # 获取插件默认状态
can_be_toggled = get_plugin_can_be_toggle(plugin_module_name) # 获取插件是否可以被启用/停用 can_be_toggled = get_plugin_can_be_toggle(plugin_module_name) # 获取插件是否可以被启用/停用
if not can_be_toggled: 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)) session = group_db.first(GroupChat, "group_id = ?", event.group_id, default=GroupChat(group_id=event.group_id))
else: else:
raise FinishedException(ulang.get("Permission Denied")) raise FinishedException(ulang.get("Permission Denied"))
# 启用 已停用的默认启用插件 将其从停用列表移除
# 启用 已停用的默认停用插件 将其放到启用列表
# 停用 已启用的默认启用插件 将其放到停用列表
# 停用 已启用的默认停用插件 将其从启用列表移除
try: try:
if toggle: if toggle:
if default_enable: if default_enable:
@ -126,14 +155,21 @@ async def _(result: Arparma, event: T_MessageEvent, bot: T_Bot):
ERROR=str(e)) 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": if event.message_type == "private":
user_db.save(session) user_db.upsert(session)
else: else:
group_db.save(session) group_db.upsert(session)
@run_preprocessor @run_preprocessor
async def _(event: T_MessageEvent, matcher: Matcher): async def _(event: T_MessageEvent, matcher: Matcher):
plugin = matcher.plugin plugin = matcher.plugin
# TODO 插件启用/停用检查hook # TODO 插件启用/停用检查hook
nonebot.logger.info(f"Plugin: {plugin.module_name}") nonebot.logger.info(f"Plugin Callapi: {plugin.module_name}")

View File

@ -1,5 +1,5 @@
# 插件权限管理器对api调用进行hook限制防止插件滥用api # 插件权限管理器对api调用进行hook限制防止插件滥用api
from src.utils.data import LiteModel from liteyuki.utils.data import LiteModel
class PermissionAllow(LiteModel): class PermissionAllow(LiteModel):

View File

@ -2,11 +2,11 @@ from typing import Optional
from nonebot_plugin_alconna import Alconna, Args, Arparma, Subcommand, on_alconna from nonebot_plugin_alconna import Alconna, Args, Arparma, Subcommand, on_alconna
from src.utils.data import LiteModel from liteyuki.utils.data import LiteModel
from src.utils.data_manager import User, user_db from liteyuki.utils.data_manager import User, user_db
from src.utils.language import Language, get_all_lang, get_user_lang from liteyuki.utils.language import Language, get_all_lang, get_user_lang
from src.utils.ly_typing import T_Bot, T_MessageEvent from liteyuki.utils.ly_typing import T_Bot, T_MessageEvent
from src.utils.message import Markdown as md, send_markdown from liteyuki.utils.message import Markdown as md, send_markdown
profile_alc = on_alconna( profile_alc = on_alconna(
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"]) r = set_profile(result.args["key"], result.args["value"])
if r: if r:
user.profile[result.args["key"]] = result.args["value"] user.profile[result.args["key"]] = result.args["value"]
user_db.save(user) # 数据库保存 user_db.upsert(user) # 数据库保存
await profile_alc.finish( await profile_alc.finish(
ulang.get( ulang.get(
"user.profile.set_success", "user.profile.set_success",

View File

@ -1,6 +1,12 @@
language.name=English 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.enable_webdash=Web dashboard is enabled: {URL}
main.monitor.title=Liteyuki Monitor main.monitor.title=Liteyuki Monitor
main.monitor.description=Monitor your server with 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_cannot_be_toggled=Plugin {NAME} cannot be toggled
npm.plugin_already=Plugin {NAME} is already in {STATUS} state, no need for repeated operation 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_failed=Failed to {STATUS} plugin {NAME}: {ERROR}
npm.toggle_success=Succeeded in {STATUS} plugin {NAME}
user.profile.edit=Edit user.profile.edit=Edit
user.profile.set=Set user.profile.set=Set

View File

@ -1,5 +1,11 @@
language.name = 日本語 language.name = 日本語
log.debug=デバッグ
log.info=情報
log.warning=警告
log.error=エラー
log.success=成功
main.current_language = 現在のシステム言語: {LANG} main.current_language = 現在のシステム言語: {LANG}
main.enable_webdash = ウェブダッシュボードが有効になりました: {URL} main.enable_webdash = ウェブダッシュボードが有効になりました: {URL}
main.monitor.title = Liteyukiモニタリングパネル main.monitor.title = Liteyukiモニタリングパネル
@ -46,6 +52,7 @@ npm.prev_page = 前のページ
npm.plugin_cannot_be_toggled=プラグイン {NAME} は有効または無効にできません npm.plugin_cannot_be_toggled=プラグイン {NAME} は有効または無効にできません
npm.plugin_already=プラグイン {NAME} はすでに {STATUS} 状態です。繰り返し操作する必要はありません npm.plugin_already=プラグイン {NAME} はすでに {STATUS} 状態です。繰り返し操作する必要はありません
npm.toggle_failed=プラグイン {NAME} を {STATUS} にするのに失敗しました: {ERROR} npm.toggle_failed=プラグイン {NAME} を {STATUS} にするのに失敗しました: {ERROR}
npm.toggle_success=プラグイン {NAME} が {STATUS} になりました
user.profile.edit=編集 user.profile.edit=編集
user.profile.set=設定 user.profile.set=設定

View File

@ -1,6 +1,12 @@
language.name=简体中文 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.enable_webdash=已启用网页监控面板: {URL}
main.monitor.title=轻雪监控面板 main.monitor.title=轻雪监控面板
main.monitor.description=轻雪机器人监控面板 main.monitor.description=轻雪机器人监控面板
@ -32,7 +38,7 @@ npm.search_no_result=无搜索结果
npm.too_many_results=内容过多,{HIDE_NUM}项已隐藏,请限制关键字搜索 npm.too_many_results=内容过多,{HIDE_NUM}项已隐藏,请限制关键字搜索
npm.install_success={NAME} 安装成功 npm.install_success={NAME} 安装成功
npm.install_failed={NAME} 安装失败,请查看日志获取详细信息,如不能解决,请访问{HOMEPAGE}寻求帮助 npm.install_failed={NAME} 安装失败,请查看日志获取详细信息,如不能解决,请访问{HOMEPAGE}寻求帮助
npm.uninstall_success={NAME} 卸载成功 npm.uninstall_success={NAME} 卸载成功,下次重启生效
npm.uninstall_failed={NAME} 卸载失败 npm.uninstall_failed={NAME} 卸载失败
npm.load_failed={NAME} 加载失败,请在控制台查看详细信息,检查依赖或配置是否正确,如不能解决,请访问{HOMEPAGE}寻求帮助 npm.load_failed={NAME} 加载失败,请在控制台查看详细信息,检查依赖或配置是否正确,如不能解决,请访问{HOMEPAGE}寻求帮助
npm.plugin_not_found=未在商店中找到 {NAME},请尝试更新商店信息或检查拼写 npm.plugin_not_found=未在商店中找到 {NAME},请尝试更新商店信息或检查拼写
@ -46,6 +52,7 @@ npm.prev_page=上一页
npm.plugin_cannot_be_toggled=插件 {NAME} 无法被启用或停用 npm.plugin_cannot_be_toggled=插件 {NAME} 无法被启用或停用
npm.plugin_already=插件 {NAME} 已经是 {STATUS} 状态,无需重复操作 npm.plugin_already=插件 {NAME} 已经是 {STATUS} 状态,无需重复操作
npm.toggle_failed=插件 {NAME} {STATUS} 失败: {ERROR} npm.toggle_failed=插件 {NAME} {STATUS} 失败: {ERROR}
npm.toggle_success=插件 {NAME} {STATUS} 成功
user.profile.edit=修改 user.profile.edit=修改
user.profile.set=设置 user.profile.set=设置

View File

@ -1,4 +1,10 @@
language.name=繁體中文 language.name=繁體中文(香港)
log.debug=調試
log.info=信息
log.warning=警告
log.error=錯誤
log.success=成功
main.current_language=當前系統語言為:{LANG} main.current_language=當前系統語言為:{LANG}
main.enable_webdash=已啟用網頁監控面板:{URL} main.enable_webdash=已啟用網頁監控面板:{URL}
@ -46,6 +52,8 @@ npm.prev_page=上一頁
npm.plugin_cannot_be_toggled=無法啟用或停用插件 {NAME} npm.plugin_cannot_be_toggled=無法啟用或停用插件 {NAME}
npm.plugin_already=插件 {NAME} 已處於 {STATUS} 狀態,無需重複操作 npm.plugin_already=插件 {NAME} 已處於 {STATUS} 狀態,無需重複操作
npm.toggle_failed=插件 {NAME} {STATUS} 失敗: {ERROR} npm.toggle_failed=插件 {NAME} {STATUS} 失敗: {ERROR}
npm.toggle_success=插件 {NAME} {STATUS} 成功
user.profile.edit=編輯 user.profile.edit=編輯
user.profile.set=設定 user.profile.set=設定

View File

@ -1,5 +1,11 @@
language.name=简体中文(轻雪版) language.name=简体中文(轻雪版)
log.debug=调试
log.info=信息
log.warning=有问题哦
log.error=出错啦
log.success=成功啦
main.current_language=现在系统用的语言是:{LANG} 喔! main.current_language=现在系统用的语言是:{LANG} 喔!
main.enable_webdash=已经打开了网页监控板:{URL} 啦! main.enable_webdash=已经打开了网页监控板:{URL} 啦!
main.monitor.title=监控板 main.monitor.title=监控板

View File

@ -1,4 +1,10 @@
language.name=漢字 language.name=中文(華夏)
log.debug=調試
log.info=信息
log.warning=警告
log.error=錯誤
log.success=成功
main.current_language=當前之系統語言為:{LANG} main.current_language=當前之系統語言為:{LANG}
main.enable_webdash=已啟用網頁監控板:{URL} main.enable_webdash=已啟用網頁監控板:{URL}

View 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")

View File

@ -4,7 +4,7 @@ import nonebot
import yaml import yaml
from pydantic import BaseModel from pydantic import BaseModel
config = None config = {}
class BasicConfig(BaseModel): class BasicConfig(BaseModel):

View File

@ -31,7 +31,7 @@ class BaseORMAdapter(ABC):
""" """
raise NotImplementedError raise NotImplementedError
def save(self, *args, **kwargs): def upsert(self, *args, **kwargs):
"""存储数据 """存储数据
Returns: Returns:
@ -171,7 +171,7 @@ class Database(BaseORMAdapter):
self.conn.commit() self.conn.commit()
nonebot.logger.debug(f'Table {table_name} migrated successfully') 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字段则更新没有则插入 """存储数据检查id字段如果有id字段则更新没有则插入
Args: Args:
@ -192,7 +192,7 @@ class Database(BaseORMAdapter):
for field, value in model.__dict__.items(): for field, value in model.__dict__.items():
if isinstance(value, LiteModel): if isinstance(value, LiteModel):
key_list.append(f'{self.FOREIGNID}{field}') 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): elif isinstance(value, list):
key_list.append(f'{self.LIST}{field}') key_list.append(f'{self.LIST}{field}')
value_list.append(self._flat(value)) value_list.append(self._flat(value))
@ -225,7 +225,7 @@ class Database(BaseORMAdapter):
return_data = {} return_data = {}
for k, v in data.items(): for k, v in data.items():
if isinstance(v, LiteModel): 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): elif isinstance(v, list):
return_data[f'{self.LIST}{k}'] = self._flat(v) return_data[f'{self.LIST}{k}'] = self._flat(v)
elif isinstance(v, dict): elif isinstance(v, dict):
@ -239,7 +239,7 @@ class Database(BaseORMAdapter):
return_data = [] return_data = []
for v in data: for v in data:
if isinstance(v, LiteModel): 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): elif isinstance(v, list):
return_data.append(self._flat(v)) return_data.append(self._flat(v))
elif isinstance(v, dict): elif isinstance(v, dict):

View 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())

View File

@ -9,8 +9,8 @@ from typing import Any
import nonebot import nonebot
from src.utils.config import config from liteyuki.utils.config import config
from src.utils.data_manager import User, user_db from liteyuki.utils.data_manager import User, user_db
_default_lang_code = "en" _default_lang_code = "en"
_language_data = { _language_data = {
@ -38,7 +38,6 @@ def load_from_lang(file_path: str, lang_code: str = None):
if not line or line.startswith('#'): # 空行或注释 if not line or line.startswith('#'): # 空行或注释
continue continue
key, value = line.split('=', 1) key, value = line.split('=', 1)
nonebot.logger.debug(f"Loaded language text: {key.strip()} -> {value.strip()}")
data[key.strip()] = value.strip() data[key.strip()] = value.strip()
if lang_code not in _language_data: if lang_code not in _language_data:
_language_data[lang_code] = {} _language_data[lang_code] = {}
@ -139,7 +138,7 @@ def get_user_lang(user_id: str) -> Language:
username="Unknown" 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: def get_system_lang_code() -> str:
@ -149,11 +148,11 @@ def get_system_lang_code() -> str:
return locale.getdefaultlocale()[0].replace('_', '-') 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]: def get_all_lang() -> dict[str, str]:

View File

@ -1,35 +1,14 @@
import sys import sys
import logging import logging
from typing import TYPE_CHECKING from typing import TYPE_CHECKING
from colored import fg
from .language import get_default_lang
import loguru import loguru
if TYPE_CHECKING: if TYPE_CHECKING:
# avoid sphinx autodoc resolve annotation failed
# because loguru module do not have `Logger` class actually
from loguru import Logger, Record from loguru import Logger, Record
# logger = logging.getLogger("nonebot")
logger: "Logger" = loguru.logger 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 class LoguruHandler(logging.Handler): # pragma: no cover
@ -59,7 +38,7 @@ def default_filter(record: "Record"):
default_format: str = ( 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> " "<lvl>[{level.icon}]</lvl> "
"<c><{name}></c> " "<c><{name}></c> "
"{message}" "{message}"
@ -74,12 +53,12 @@ logger_id = logger.add(
filter=default_filter, filter=default_filter,
format=default_format, format=default_format,
) )
slang = get_default_lang()
logger.level("DEBUG", color="<cyan>", icon="DEBU") logger.level("DEBUG", color="<blue>", icon=f"*️⃣ DDDEBUG")
logger.level("INFO", color="<white>", icon="INFO") logger.level("INFO", color="<white>", icon=f" IIIINFO")
logger.level("SUCCESS", color="<green>", icon="SUCC") logger.level("SUCCESS", color="<green>", icon=f" SUCCESS")
logger.level("WARNING", color="<yellow>", icon="⚠️WARN") logger.level("WARNING", color="<yellow>", icon=f"⚠️ WARNING")
logger.level("ERROR", color="<red>", icon="ERRO") logger.level("ERROR", color="<red>", icon=f"⭕ EEERROR")
"""默认日志处理器 id""" """默认日志处理器 id"""

207
liteyuki/utils/orm.py Normal file
View 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")

View File

@ -1,6 +1,6 @@
from nonebot.adapters.onebot import v11 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_ADMIN = v11.GROUP_ADMIN
GROUP_OWNER = v11.GROUP_OWNER GROUP_OWNER = v11.GROUP_OWNER

View File

@ -4,7 +4,7 @@ import nonebot
import yaml import yaml
from typing import Any from typing import Any
from src.utils.data import LiteModel from liteyuki.utils.data import LiteModel
_resource_data = {} _resource_data = {}
_loaded_resource_packs = [] # 按照加载顺序排序 _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("\\", "/") relative_path = os.path.relpath(os.path.join(root, file), path).replace("\\", "/")
abs_path = os.path.join(root, file).replace("\\", "/") abs_path = os.path.join(root, file).replace("\\", "/")
_resource_data[relative_path] = abs_path _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")): if os.path.exists(os.path.join(path, "metadata.yml")):
with open(os.path.join(path, "metadata.yml"), "r", encoding="utf-8") as f: with open(os.path.join(path, "metadata.yml"), "r", encoding="utf-8") as f:
metadata = yaml.safe_load(f) metadata = yaml.safe_load(f)
@ -39,12 +38,12 @@ def load_resource_from_dir(path: str):
metadata = ResourceMetadata() metadata = ResourceMetadata()
metadata["path"] = path metadata["path"] = path
if os.path.exists(os.path.join(path, "lang")): 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")) load_from_dir(os.path.join(path, "lang"))
_loaded_resource_packs.append(ResourceMetadata(**metadata)) _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: Args:

View File

@ -1,3 +1,4 @@
from importlib.metadata import PackageNotFoundError, version
from urllib.parse import quote 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: if keyword in text:
return True return True
return False return False
def check_for_package(package_name: str) -> bool:
try:
version(package_name)
return True
except PackageNotFoundError:
return False

10
main.py
View File

@ -1,11 +1,9 @@
import nonebot import nonebot
from nonebot.adapters.onebot import v11, v12 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 init()
from src.utils.config import load_from_yaml
nonebot.logger = logger
nonebot.init(**load_from_yaml("config.yml")) nonebot.init(**load_from_yaml("config.yml"))
adapters = [v11.Adapter, v12.Adapter] adapters = [v11.Adapter, v12.Adapter]
@ -14,7 +12,7 @@ driver = nonebot.get_driver()
for adapter in adapters: for adapter in adapters:
driver.register_adapter(adapter) driver.register_adapter(adapter)
nonebot.load_plugin("src.liteyuki_main") nonebot.load_plugin("liteyuki.liteyuki_main")
if __name__ == "__main__": if __name__ == "__main__":
nonebot.run() nonebot.run()

View File

@ -2,13 +2,16 @@ aiohttp==3.9.3
aiofiles==23.2.1 aiofiles==23.2.1
arclet-alconna==1.8.5 arclet-alconna==1.8.5
arclet-alconna-tools==0.7.0 arclet-alconna-tools==0.7.0
colored==2.2.4
dash==2.16.1 dash==2.16.1
nonebot2[fastapi]==2.2.1 nonebot2[fastapi]==2.2.1
nonebot-adapter-onebot==2.4.3 nonebot-adapter-onebot==2.4.3
nonebot-plugin-alconna==0.41.0 nonebot-plugin-alconna==0.41.0
pip==24.0 pip==24.0
psutil==5.9.8 psutil==5.9.8
pydantic==2.6.4 pydantic==1.10.14
pytz==2024.1 pytz==2024.1
PyYAML~=6.0.1 PyYAML~=6.0.1
starlette~=0.36.3 starlette~=0.36.3
loguru==0.7.2
importlib_metadata==7.0.2

View File

@ -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)

View File

@ -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,
}
)

View File

@ -1 +0,0 @@
from .log import logger

View File

@ -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)