mirror of
https://github.com/LiteyukiStudio/LiteyukiBot.git
synced 2024-11-22 21:27:43 +08:00
🔥 小型重构
This commit is contained in:
parent
35823be13e
commit
8b01943d14
2
.gitignore
vendored
2
.gitignore
vendored
@ -15,7 +15,7 @@ _config.yml
|
||||
config.yml
|
||||
config.example.yml
|
||||
compile.bat
|
||||
liteyuki/resources/templates/latest-debug.html
|
||||
src/resources/templates/latest-debug.html
|
||||
# vuepress
|
||||
.github
|
||||
pyproject.toml
|
||||
|
@ -1,6 +1,6 @@
|
||||
import {defineClientConfig} from "vuepress/client";
|
||||
import resourceStoreComp from "./components/res_store.vue";
|
||||
import pluginStoreComp from "./components/plugin_store.vue";
|
||||
import resourceStoreComp from "./components/ResStore.vue";
|
||||
import pluginStoreComp from "./components/PluginStore.vue";
|
||||
//导入element-plus
|
||||
import ElementPlus from 'element-plus';
|
||||
|
||||
@ -9,6 +9,5 @@ export default defineClientConfig({
|
||||
app.component("resourceStoreComp", resourceStoreComp);
|
||||
app.component("pluginStoreComp", pluginStoreComp);
|
||||
app.use(ElementPlus);
|
||||
|
||||
},
|
||||
});
|
@ -1,6 +1,6 @@
|
||||
<script setup lang="ts">
|
||||
import {ref} from 'vue'
|
||||
import ItemCard from './plugin_item_card.vue'
|
||||
import ItemCard from './PluginItemCard.vue'
|
||||
|
||||
// 插件商店Nonebot
|
||||
let items = ref([])
|
@ -1,10 +1,10 @@
|
||||
<script setup lang="ts">
|
||||
import {ref} from 'vue'
|
||||
import ItemCard from './res_item_card.vue'
|
||||
import ItemCard from './ResItemCard.vue'
|
||||
|
||||
// 从public/assets/resources.json加载插件
|
||||
let items = ref([])
|
||||
fetch('https://bot.liteyuki.icu/assets/resources.json')
|
||||
fetch('/assets/resources.json')
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
items.value = data
|
@ -10,6 +10,7 @@ export default defineUserConfig({
|
||||
description: "LiteyukiBot | 轻雪机器人 | An OneBot Standard ChatBot | 一个OneBot标准的聊天机器人",
|
||||
head: [
|
||||
// 设置 favor.ico,.vuepress/public 下
|
||||
["script", {src: "/js/style.js", "type": "module"}],
|
||||
['link', {rel: 'icon', href: 'https://cdn.liteyuki.icu/favicon.ico'},],
|
||||
|
||||
['link', {rel: 'stylesheet', href: 'https://cdn.bootcdn.net/ajax/libs/firacode/6.2.0/fira_code.min.css'}],
|
||||
|
22
docs/.vuepress/public/js/style.js
Normal file
22
docs/.vuepress/public/js/style.js
Normal file
@ -0,0 +1,22 @@
|
||||
function applyStyle() {
|
||||
let lineNumbers = document.body.querySelectorAll('[class^="language-"].line-numbers-mode')
|
||||
lineNumbers.forEach((item) => {
|
||||
// 插入现成的html文本
|
||||
let title = item.getAttribute('data-title')
|
||||
let tabStr =
|
||||
"<div class='tab' style='display: flex; background-color: #d0e9ff'>" +
|
||||
" <div class='tab-buttons'>" +
|
||||
" <div class='tab-button' style='background-color: #FF5F57'></div>" +
|
||||
" <div class='tab-button' style='background-color: #FFBD2E'></div>" +
|
||||
" <div class='tab-button' style='background-color: #27C93F'></div>" +
|
||||
" </div>" +
|
||||
` <div class='tab-title'>${title}</div>` +
|
||||
" <div style='flex: 1'></div>" +
|
||||
"</div>"
|
||||
// 在代码块前插入选项卡
|
||||
item.insertAdjacentHTML('beforebegin', tabStr);
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
applyStyle()
|
@ -1,5 +1,10 @@
|
||||
// place your custom styles here
|
||||
|
||||
:root {
|
||||
--code-window-border-radius: 10px;
|
||||
--button-distance: 8px;
|
||||
}
|
||||
|
||||
#main-title {
|
||||
font-family: ColorTube, "Fira Code", serif;
|
||||
color: #ff0000 !important; /* 你想要的颜色 */
|
||||
@ -15,11 +20,62 @@ code {
|
||||
font-family: "Fira Code", monospace !important;
|
||||
}
|
||||
|
||||
.vp-hero-image{
|
||||
.vp-hero-image {
|
||||
overflow: hidden;
|
||||
padding: -50px;
|
||||
}
|
||||
|
||||
#main-title{
|
||||
#main-title {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.theme-hope-content pre {
|
||||
overflow: auto;
|
||||
margin: 0 0;
|
||||
padding: 1rem;
|
||||
border-radius: 6px;
|
||||
line-height: 1.375;
|
||||
}
|
||||
// 移除该before
|
||||
.theme-hope-content pre::before {
|
||||
content: none;
|
||||
}
|
||||
|
||||
.theme-hope-content > div[class*=language-] {
|
||||
margin: 0 0 0 0;
|
||||
// 仅下半部分有圆弧
|
||||
border-radius: 0 0 var(--code-window-border-radius) var(--code-window-border-radius);
|
||||
}
|
||||
|
||||
.tab {
|
||||
display: flex;
|
||||
height: 25px;
|
||||
margin-bottom: 0;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
border-top-left-radius: var(--code-window-border-radius);
|
||||
border-top-right-radius: var(--code-window-border-radius);
|
||||
}
|
||||
|
||||
.tab-buttons {
|
||||
padding: 7px;
|
||||
flex: 1;
|
||||
display: flex;
|
||||
justify-content: flex-start;
|
||||
height: 60%;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.tab-button {
|
||||
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
|
||||
margin-right: var(--button-distance);
|
||||
border-radius: 50%;
|
||||
height: 100%;
|
||||
aspect-ratio: 1/1;
|
||||
}
|
||||
|
||||
.tab-title{
|
||||
text-align: center;
|
||||
justify-content: center;
|
||||
flex: 1;
|
||||
}
|
@ -21,6 +21,7 @@
|
||||
"dependencies": {
|
||||
"clipboard": "^2.0.11",
|
||||
"element-plus": "^2.7.0",
|
||||
"element-ui": "^2.15.14"
|
||||
"element-ui": "^2.15.14",
|
||||
"vue-router": "^4.4.0"
|
||||
}
|
||||
}
|
||||
|
@ -14,6 +14,9 @@ dependencies:
|
||||
element-ui:
|
||||
specifier: ^2.15.14
|
||||
version: 2.15.14(vue@3.4.21)
|
||||
vue-router:
|
||||
specifier: ^4.4.0
|
||||
version: 4.4.0(vue@3.4.21)
|
||||
|
||||
devDependencies:
|
||||
'@vuepress/bundler-vite':
|
||||
@ -938,7 +941,6 @@ packages:
|
||||
|
||||
/@vue/devtools-api@6.6.1:
|
||||
resolution: {integrity: sha512-LgPscpE3Vs0x96PzSSB4IGVSZXZBZHpfxs+ZA1d+VEPwHdOXowy/Y2CsvCAIFrf+ssVU1pD1jidj505EpUnfbA==}
|
||||
dev: true
|
||||
|
||||
/@vue/reactivity@3.4.21:
|
||||
resolution: {integrity: sha512-UhenImdc0L0/4ahGCyEzc/pZNwVgcglGy9HVzJ1Bq2Mm9qXOpP8RyNTjookw/gOCUlXSEtuZ2fUg5nrHcoqJcw==}
|
||||
@ -985,7 +987,7 @@ packages:
|
||||
rollup: 4.13.1
|
||||
vite: 5.2.6
|
||||
vue: 3.4.21
|
||||
vue-router: 4.3.0(vue@3.4.21)
|
||||
vue-router: 4.4.0(vue@3.4.21)
|
||||
transitivePeerDependencies:
|
||||
- '@types/node'
|
||||
- jiti
|
||||
@ -1021,7 +1023,7 @@ packages:
|
||||
'@vue/devtools-api': 6.6.1
|
||||
'@vuepress/shared': 2.0.0-rc.9
|
||||
vue: 3.4.21
|
||||
vue-router: 4.3.0(vue@3.4.21)
|
||||
vue-router: 4.4.0(vue@3.4.21)
|
||||
transitivePeerDependencies:
|
||||
- typescript
|
||||
dev: true
|
||||
@ -2739,14 +2741,13 @@ packages:
|
||||
dependencies:
|
||||
vue: 3.4.21
|
||||
|
||||
/vue-router@4.3.0(vue@3.4.21):
|
||||
resolution: {integrity: sha512-dqUcs8tUeG+ssgWhcPbjHvazML16Oga5w34uCUmsk7i0BcnskoLGwjpa15fqMr2Fa5JgVBrdL2MEgqz6XZ/6IQ==}
|
||||
/vue-router@4.4.0(vue@3.4.21):
|
||||
resolution: {integrity: sha512-HB+t2p611aIZraV2aPSRNXf0Z/oLZFrlygJm+sZbdJaW6lcFqEDQwnzUBXn+DApw+/QzDU/I9TeWx9izEjTmsA==}
|
||||
peerDependencies:
|
||||
vue: ^3.2.0
|
||||
dependencies:
|
||||
'@vue/devtools-api': 6.6.1
|
||||
vue: 3.4.21
|
||||
dev: true
|
||||
|
||||
/vue@3.4.21:
|
||||
resolution: {integrity: sha512-5hjyV/jLEIKD/jYl4cavMcnzKwjMKohureP8ejn3hhEjwhWIhWeuzL2kJAjzl/WyVsgPY56Sy4Z40C3lVshxXA==}
|
||||
|
12
main.py
12
main.py
@ -1,8 +1,8 @@
|
||||
import nonebot
|
||||
from liteyuki.utils import adapter_manager, driver_manager, init
|
||||
from liteyuki.utils.base.config import load_from_yaml
|
||||
from liteyuki.utils.base.data_manager import StoredConfig, common_db
|
||||
from liteyuki.utils.base.ly_api import liteyuki_api
|
||||
from src.utils import adapter_manager, driver_manager, init
|
||||
from src.utils.base.config import load_from_yaml
|
||||
from src.utils.base.data_manager import StoredConfig, common_db
|
||||
from src.utils.base.ly_api import liteyuki_api
|
||||
|
||||
if __name__ == "__mp_main__":
|
||||
# Start as multiprocessing
|
||||
@ -15,7 +15,7 @@ if __name__ == "__mp_main__":
|
||||
nonebot.init(**store_config)
|
||||
adapter_manager.register()
|
||||
try:
|
||||
nonebot.load_plugin("liteyuki.liteyuki_main")
|
||||
nonebot.load_plugin("src.liteyuki_main")
|
||||
nonebot.load_from_toml("pyproject.toml")
|
||||
except BaseException as e:
|
||||
if not isinstance(e, KeyboardInterrupt):
|
||||
@ -24,6 +24,6 @@ if __name__ == "__mp_main__":
|
||||
|
||||
if __name__ == "__main__":
|
||||
# Start as __main__
|
||||
from liteyuki.utils.base.reloader import Reloader
|
||||
from src.utils.base.reloader import Reloader
|
||||
|
||||
nonebot.run()
|
||||
|
@ -10,6 +10,7 @@ nonebot-adapter-onebot~=2.4.3
|
||||
nonebot-plugin-alconna~=0.46.3
|
||||
nonebot_plugin_apscheduler~=0.4.0
|
||||
nonebot-adapter-satori~=0.11.5
|
||||
numpy~=2.0.0
|
||||
packaging~=23.1
|
||||
psutil~=5.9.8
|
||||
py-cpuinfo~=9.0.0
|
||||
|
6
src/liteyuki/bot/__init__.py
Normal file
6
src/liteyuki/bot/__init__.py
Normal file
@ -0,0 +1,6 @@
|
||||
import abc
|
||||
|
||||
|
||||
class Bot(abc.ABC):
|
||||
def __init__(self):
|
||||
pass
|
10
src/liteyuki/exception.py
Normal file
10
src/liteyuki/exception.py
Normal file
@ -0,0 +1,10 @@
|
||||
"""exception模块包含了liteyuki运行中的所有错误
|
||||
"""
|
||||
|
||||
from typing import Any, Optional
|
||||
|
||||
|
||||
class LiteyukiException(BaseException):
|
||||
"""Liteyuki的异常基类。"""
|
||||
def __str__(self) -> str:
|
||||
return self.__repr__()
|
@ -1,34 +1,34 @@
|
||||
from nonebot.plugin import PluginMetadata
|
||||
|
||||
from .core import *
|
||||
from .loader import *
|
||||
from .dev import *
|
||||
|
||||
__author__ = "snowykami"
|
||||
__plugin_meta__ = PluginMetadata(
|
||||
name="轻雪核心插件",
|
||||
description="轻雪主程序插件,包含了许多初始化的功能",
|
||||
usage="",
|
||||
homepage="https://github.com/snowykami/LiteyukiBot",
|
||||
extra={
|
||||
"liteyuki" : True,
|
||||
"toggleable": False,
|
||||
}
|
||||
)
|
||||
|
||||
from ..utils.base.language import Language, get_default_lang_code
|
||||
|
||||
print("\033[34m" + r"""
|
||||
__ ______ ________ ________ __ __ __ __ __ __ ______
|
||||
/ | / |/ |/ |/ \ / |/ | / |/ | / |/ |
|
||||
$$ | $$$$$$/ $$$$$$$$/ $$$$$$$$/ $$ \ /$$/ $$ | $$ |$$ | /$$/ $$$$$$/
|
||||
$$ | $$ | $$ | $$ |__ $$ \/$$/ $$ | $$ |$$ |/$$/ $$ |
|
||||
$$ | $$ | $$ | $$ | $$ $$/ $$ | $$ |$$ $$< $$ |
|
||||
$$ | $$ | $$ | $$$$$/ $$$$/ $$ | $$ |$$$$$ \ $$ |
|
||||
$$ |_____ _$$ |_ $$ | $$ |_____ $$ | $$ \__$$ |$$ |$$ \ _$$ |_
|
||||
$$ |/ $$ | $$ | $$ | $$ | $$ $$/ $$ | $$ |/ $$ |
|
||||
$$$$$$$$/ $$$$$$/ $$/ $$$$$$$$/ $$/ $$$$$$/ $$/ $$/ $$$$$$/
|
||||
""" + "\033[0m")
|
||||
|
||||
sys_lang = Language(get_default_lang_code())
|
||||
nonebot.logger.info(sys_lang.get("main.current_language", LANG=sys_lang.get("language.name")))
|
||||
from nonebot.plugin import PluginMetadata
|
||||
|
||||
from .core import *
|
||||
from .loader import *
|
||||
from .dev import *
|
||||
|
||||
__author__ = "snowykami"
|
||||
__plugin_meta__ = PluginMetadata(
|
||||
name="轻雪核心插件",
|
||||
description="轻雪主程序插件,包含了许多初始化的功能",
|
||||
usage="",
|
||||
homepage="https://github.com/snowykami/LiteyukiBot",
|
||||
extra={
|
||||
"liteyuki" : True,
|
||||
"toggleable": False,
|
||||
}
|
||||
)
|
||||
|
||||
from ..utils.base.language import Language, get_default_lang_code
|
||||
|
||||
print("\033[34m" + r"""
|
||||
__ ______ ________ ________ __ __ __ __ __ __ ______
|
||||
/ | / |/ |/ |/ \ / |/ | / |/ | / |/ |
|
||||
$$ | $$$$$$/ $$$$$$$$/ $$$$$$$$/ $$ \ /$$/ $$ | $$ |$$ | /$$/ $$$$$$/
|
||||
$$ | $$ | $$ | $$ |__ $$ \/$$/ $$ | $$ |$$ |/$$/ $$ |
|
||||
$$ | $$ | $$ | $$ | $$ $$/ $$ | $$ |$$ $$< $$ |
|
||||
$$ | $$ | $$ | $$$$$/ $$$$/ $$ | $$ |$$$$$ \ $$ |
|
||||
$$ |_____ _$$ |_ $$ | $$ |_____ $$ | $$ \__$$ |$$ |$$ \ _$$ |_
|
||||
$$ |/ $$ | $$ | $$ | $$ | $$ $$/ $$ | $$ |/ $$ |
|
||||
$$$$$$$$/ $$$$$$/ $$/ $$$$$$$$/ $$/ $$$$$$/ $$/ $$/ $$$$$$/
|
||||
""" + "\033[0m")
|
||||
|
||||
sys_lang = Language(get_default_lang_code())
|
||||
nonebot.logger.info(sys_lang.get("main.current_language", LANG=sys_lang.get("language.name")))
|
@ -1,47 +1,47 @@
|
||||
import nonebot
|
||||
from git import Repo
|
||||
|
||||
from liteyuki.utils.base.config import get_config
|
||||
|
||||
remote_urls = [
|
||||
"https://github.com/snowykami/LiteyukiBot.git",
|
||||
"https://gitee.com/snowykami/LiteyukiBot.git"
|
||||
]
|
||||
|
||||
|
||||
def detect_update() -> bool:
|
||||
# 对每个远程仓库进行检查,只要有一个仓库有更新,就返回True
|
||||
for remote_url in remote_urls:
|
||||
repo = Repo(".")
|
||||
repo.remotes.origin.set_url(remote_url)
|
||||
repo.remotes.origin.fetch()
|
||||
if repo.head.commit != repo.commit('origin/main'):
|
||||
return True
|
||||
|
||||
|
||||
def update_liteyuki() -> tuple[bool, str]:
|
||||
"""更新轻雪
|
||||
:return: 是否更新成功,更新变动"""
|
||||
|
||||
if get_config("allow_update", True):
|
||||
new_commit_detected = detect_update()
|
||||
if new_commit_detected:
|
||||
repo = Repo(".")
|
||||
logs = ""
|
||||
# 对每个远程仓库进行更新
|
||||
for remote_url in remote_urls:
|
||||
try:
|
||||
logs += f"\nremote: {remote_url}"
|
||||
repo.remotes.origin.set_url(remote_url)
|
||||
repo.remotes.origin.pull()
|
||||
diffs = repo.head.commit.diff("origin/main")
|
||||
for diff in diffs.iter_change_type('M'):
|
||||
logs += f"\n{diff.a_path}"
|
||||
return True, logs
|
||||
except:
|
||||
continue
|
||||
else:
|
||||
return False, "Nothing Changed"
|
||||
|
||||
else:
|
||||
raise PermissionError("Update is not allowed.")
|
||||
import nonebot
|
||||
from git import Repo
|
||||
|
||||
from src.utils.base.config import get_config
|
||||
|
||||
remote_urls = [
|
||||
"https://github.com/snowykami/LiteyukiBot.git",
|
||||
"https://gitee.com/snowykami/LiteyukiBot.git"
|
||||
]
|
||||
|
||||
|
||||
def detect_update() -> bool:
|
||||
# 对每个远程仓库进行检查,只要有一个仓库有更新,就返回True
|
||||
for remote_url in remote_urls:
|
||||
repo = Repo(".")
|
||||
repo.remotes.origin.set_url(remote_url)
|
||||
repo.remotes.origin.fetch()
|
||||
if repo.head.commit != repo.commit('origin/main'):
|
||||
return True
|
||||
|
||||
|
||||
def update_liteyuki() -> tuple[bool, str]:
|
||||
"""更新轻雪
|
||||
:return: 是否更新成功,更新变动"""
|
||||
|
||||
if get_config("allow_update", True):
|
||||
new_commit_detected = detect_update()
|
||||
if new_commit_detected:
|
||||
repo = Repo(".")
|
||||
logs = ""
|
||||
# 对每个远程仓库进行更新
|
||||
for remote_url in remote_urls:
|
||||
try:
|
||||
logs += f"\nremote: {remote_url}"
|
||||
repo.remotes.origin.set_url(remote_url)
|
||||
repo.remotes.origin.pull()
|
||||
diffs = repo.head.commit.diff("origin/main")
|
||||
for diff in diffs.iter_change_type('M'):
|
||||
logs += f"\n{diff.a_path}"
|
||||
return True, logs
|
||||
except:
|
||||
continue
|
||||
else:
|
||||
return False, "Nothing Changed"
|
||||
|
||||
else:
|
||||
raise PermissionError("Update is not allowed.")
|
@ -1,399 +1,399 @@
|
||||
import base64
|
||||
import time
|
||||
from typing import Any, AnyStr
|
||||
|
||||
import nonebot
|
||||
import pip
|
||||
from nonebot import Bot, get_driver, require
|
||||
from nonebot.adapters import satori
|
||||
from nonebot.adapters.onebot.v11 import Message, escape, unescape
|
||||
from nonebot.exception import MockApiException
|
||||
from nonebot.internal.matcher import Matcher
|
||||
from nonebot.permission import SUPERUSER
|
||||
|
||||
from liteyuki.utils.base.config import get_config, load_from_yaml
|
||||
from liteyuki.utils.base.data_manager import StoredConfig, TempConfig, common_db
|
||||
from liteyuki.utils.base.language import get_user_lang
|
||||
from liteyuki.utils.base.ly_typing import T_Bot, T_MessageEvent
|
||||
from liteyuki.utils.message.message import MarkdownMessage as md, broadcast_to_superusers
|
||||
from liteyuki.utils.base.reloader import Reloader
|
||||
from liteyuki.utils import event as event_utils, satori_utils
|
||||
from .api import update_liteyuki
|
||||
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_apscheduler import scheduler
|
||||
|
||||
driver = get_driver()
|
||||
|
||||
markdown_image = common_db.where_one(StoredConfig(), default=StoredConfig()).config.get("markdown_image", False)
|
||||
|
||||
|
||||
@on_alconna(
|
||||
command=Alconna(
|
||||
"liteecho",
|
||||
Args["text", str, ""],
|
||||
),
|
||||
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, Liteyuki!\nBot {bot.self_id}")
|
||||
|
||||
|
||||
@on_alconna(
|
||||
aliases={"更新轻雪"},
|
||||
command=Alconna(
|
||||
"update-liteyuki"
|
||||
),
|
||||
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))
|
||||
success, logs = update_liteyuki()
|
||||
reply = "Liteyuki updated!\n"
|
||||
reply += f"```\n{logs}\n```\n"
|
||||
btn_restart = md.btn_cmd(ulang.get("liteyuki.restart_now"), "reload-liteyuki")
|
||||
pip.main(["install", "-r", "requirements.txt"])
|
||||
reply += f"{ulang.get('liteyuki.update_restart', RESTART=btn_restart)}"
|
||||
await md.send_md(reply, bot, event=event, at_sender=False)
|
||||
|
||||
|
||||
@on_alconna(
|
||||
aliases={"重启轻雪"},
|
||||
command=Alconna(
|
||||
"reload-liteyuki"
|
||||
),
|
||||
permission=SUPERUSER
|
||||
).handle()
|
||||
# Satori OK
|
||||
async def _(matcher: Matcher, bot: T_Bot, event: T_MessageEvent):
|
||||
await matcher.send("Liteyuki reloading")
|
||||
temp_data = common_db.where_one(TempConfig(), default=TempConfig())
|
||||
|
||||
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
|
||||
}
|
||||
)
|
||||
|
||||
common_db.save(temp_data)
|
||||
Reloader.reload(0)
|
||||
|
||||
|
||||
@on_alconna(
|
||||
aliases={"配置"},
|
||||
command=Alconna(
|
||||
"config",
|
||||
Subcommand(
|
||||
"set",
|
||||
Args["key", str]["value", Any],
|
||||
alias=["设置"],
|
||||
|
||||
),
|
||||
Subcommand(
|
||||
"get",
|
||||
Args["key", str, None],
|
||||
alias=["查询", "获取"]
|
||||
),
|
||||
Subcommand(
|
||||
"remove",
|
||||
Args["key", str],
|
||||
alias=["删除"]
|
||||
)
|
||||
),
|
||||
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())
|
||||
if result.subcommands.get("set"):
|
||||
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)}")
|
||||
elif result.subcommands.get("get"):
|
||||
key = result.subcommands.get("get").args.get("key")
|
||||
file_config = load_from_yaml("config.yml")
|
||||
reply = f"{ulang.get('liteyuki.current_config')}"
|
||||
if key:
|
||||
reply += f"```dotenv\n{key}={file_config.get(key, stored_config.config.get(key))}\n```"
|
||||
else:
|
||||
reply = f"{ulang.get('liteyuki.current_config')}"
|
||||
reply += f"\n{ulang.get('liteyuki.static_config')}\n```dotenv"
|
||||
for k, v in file_config.items():
|
||||
reply += f"\n{k}={v}"
|
||||
reply += "\n```"
|
||||
if len(stored_config.config) > 0:
|
||||
reply += f"\n{ulang.get('liteyuki.stored_config')}\n```dotenv"
|
||||
for k, v in stored_config.config.items():
|
||||
reply += f"\n{k}={v} {type(v)}"
|
||||
reply += "\n```"
|
||||
await md.send_md(reply, bot, event=event)
|
||||
elif result.subcommands.get("remove"):
|
||||
key = result.subcommands.get("remove").args.get("key")
|
||||
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)}")
|
||||
else:
|
||||
await matcher.finish(f"{ulang.get('liteyuki.invalid_command', TEXT=key)}")
|
||||
|
||||
|
||||
@on_alconna(
|
||||
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)
|
||||
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"))
|
||||
|
||||
|
||||
@on_alconna(
|
||||
command=Alconna(
|
||||
"liteyuki-docs",
|
||||
),
|
||||
aliases={"轻雪文档"},
|
||||
).handle()
|
||||
# Satori OK
|
||||
async def _(matcher: Matcher):
|
||||
await matcher.finish("https://bot.liteyuki.icu/usage")
|
||||
|
||||
|
||||
@on_alconna(
|
||||
command=Alconna(
|
||||
"/function",
|
||||
Args["function", str]["args", MultiVar(str), ()],
|
||||
),
|
||||
permission=SUPERUSER
|
||||
).handle()
|
||||
async def _(result: Arparma, bot: T_Bot, event: T_MessageEvent, matcher: Matcher):
|
||||
"""
|
||||
调用轻雪函数
|
||||
Args:
|
||||
result:
|
||||
bot:
|
||||
event:
|
||||
|
||||
Returns:
|
||||
|
||||
"""
|
||||
function_name = result.main_args.get("function")
|
||||
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)
|
||||
}
|
||||
|
||||
for arg in args:
|
||||
arg = arg.replace("\\=", "EQUAL_SIGN")
|
||||
if "=" in arg:
|
||||
key, value = arg.split("=", 1)
|
||||
value = unescape(value.replace("EQUAL_SIGN", "="))
|
||||
try:
|
||||
value = eval(value)
|
||||
except:
|
||||
value = value
|
||||
_kwargs[key] = value
|
||||
else:
|
||||
_args.append(arg.replace("EQUAL_SIGN", "="))
|
||||
|
||||
ly_func = get_function(function_name)
|
||||
ly_func.bot = bot if "BOT_ID" not in _kwargs else nonebot.get_bot(_kwargs["BOT_ID"])
|
||||
ly_func.matcher = matcher
|
||||
|
||||
await ly_func(*tuple(_args), **_kwargs)
|
||||
|
||||
|
||||
@on_alconna(
|
||||
command=Alconna(
|
||||
"/api",
|
||||
Args["api", str]["args", MultiVar(AnyStr), ()],
|
||||
),
|
||||
permission=SUPERUSER
|
||||
).handle()
|
||||
async def _(result: Arparma, bot: T_Bot, event: T_MessageEvent, matcher: Matcher):
|
||||
"""
|
||||
调用API
|
||||
Args:
|
||||
result:
|
||||
bot:
|
||||
event:
|
||||
|
||||
Returns:
|
||||
|
||||
"""
|
||||
api_name = result.main_args.get("api")
|
||||
args: tuple[str] = result.main_args.get("args", ()) # 类似于url参数,但每个参数间用空格分隔,空格是%20
|
||||
args_dict = {}
|
||||
|
||||
for arg in args:
|
||||
key, value = arg.split("=", 1)
|
||||
|
||||
args_dict[key] = unescape(value.replace("%20", " "))
|
||||
|
||||
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":
|
||||
args_dict["group_id"] = str(event.group_id)
|
||||
|
||||
if "message" in args_dict:
|
||||
args_dict["message"] = Message(eval(args_dict["message"]))
|
||||
|
||||
if "messages" in args_dict:
|
||||
args_dict["messages"] = Message(eval(args_dict["messages"]))
|
||||
|
||||
try:
|
||||
result = await bot.call_api(api_name, **args_dict)
|
||||
except Exception as e:
|
||||
result = str(e)
|
||||
|
||||
args_show = "\n".join("- %s: %s" % (k, v) for k, v in args_dict.items())
|
||||
print(f"API: {api_name}\n\nArgs: \n{args_show}\n\nResult: {result}")
|
||||
await matcher.finish(f"API: {api_name}\n\nArgs: \n{args_show}\n\nResult: {result}")
|
||||
|
||||
|
||||
# system hook
|
||||
@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":
|
||||
session_type = "private"
|
||||
session_id = data.get("user_id")
|
||||
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":
|
||||
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)
|
||||
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)
|
||||
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)
|
||||
else:
|
||||
return
|
||||
raise MockApiException(result=result)
|
||||
|
||||
|
||||
@driver.on_startup
|
||||
async def on_startup():
|
||||
temp_data = common_db.where_one(TempConfig(), default=TempConfig())
|
||||
# 储存重启信息
|
||||
if temp_data.data.get("reload", False):
|
||||
delta_time = time.time() - temp_data.data.get("reload_time", 0)
|
||||
temp_data.data["delta_time"] = delta_time
|
||||
common_db.save(temp_data) # 更新数据
|
||||
|
||||
|
||||
@driver.on_shutdown
|
||||
async def on_shutdown():
|
||||
pass
|
||||
|
||||
|
||||
@driver.on_bot_connect
|
||||
async def _(bot: T_Bot):
|
||||
temp_data = common_db.where_one(TempConfig(), default=TempConfig())
|
||||
if isinstance(bot, satori.Bot):
|
||||
await satori_utils.user_infos.load_friends(bot)
|
||||
# 用于重启计时
|
||||
if temp_data.data.get("reload", False):
|
||||
temp_data.data["reload"] = False
|
||||
reload_bot_id = temp_data.data.get("reload_bot_id", 0)
|
||||
if reload_bot_id != bot.self_id:
|
||||
return
|
||||
reload_session_type = temp_data.data.get("reload_session_type", "private")
|
||||
reload_session_id = temp_data.data.get("reload_session_id", 0)
|
||||
delta_time = temp_data.data.get("delta_time", 0)
|
||||
common_db.save(temp_data) # 更新数据
|
||||
if isinstance(bot, satori.Bot):
|
||||
await bot.send_message(
|
||||
channel_id=reload_session_id,
|
||||
message="Liteyuki reloaded in %.2f s" % delta_time
|
||||
)
|
||||
else:
|
||||
await bot.call_api(
|
||||
"send_msg",
|
||||
message_type=reload_session_type,
|
||||
user_id=reload_session_id,
|
||||
group_id=reload_session_id,
|
||||
message="Liteyuki reloaded in %.2f s" % delta_time
|
||||
)
|
||||
|
||||
|
||||
# 每天4点更新
|
||||
@scheduler.scheduled_job("cron", hour=4)
|
||||
async def every_day_update():
|
||||
if get_config("auto_update", default=True):
|
||||
result, logs = update_liteyuki()
|
||||
pip.main(["install", "-r", "requirements.txt"])
|
||||
if result:
|
||||
await broadcast_to_superusers(f"Liteyuki updated: ```\n{logs}\n```")
|
||||
nonebot.logger.info(f"Liteyuki updated: {logs}")
|
||||
Reloader.reload(5)
|
||||
else:
|
||||
nonebot.logger.info(logs)
|
||||
|
||||
|
||||
# 需要用户id的api
|
||||
need_user_id = (
|
||||
"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"
|
||||
import base64
|
||||
import time
|
||||
from typing import Any, AnyStr
|
||||
|
||||
import nonebot
|
||||
import pip
|
||||
from nonebot import Bot, get_driver, require
|
||||
from nonebot.adapters import satori
|
||||
from nonebot.adapters.onebot.v11 import Message, escape, unescape
|
||||
from nonebot.exception import MockApiException
|
||||
from nonebot.internal.matcher import Matcher
|
||||
from nonebot.permission import SUPERUSER
|
||||
|
||||
from src.utils.base.config import get_config, load_from_yaml
|
||||
from src.utils.base.data_manager import StoredConfig, TempConfig, common_db
|
||||
from src.utils.base.language import get_user_lang
|
||||
from src.utils.base.ly_typing import T_Bot, T_MessageEvent
|
||||
from src.utils.message.message import MarkdownMessage as md, broadcast_to_superusers
|
||||
from src.utils.base.reloader import Reloader
|
||||
from src.utils import event as event_utils, satori_utils
|
||||
from .api import update_liteyuki
|
||||
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_apscheduler import scheduler
|
||||
|
||||
driver = get_driver()
|
||||
|
||||
markdown_image = common_db.where_one(StoredConfig(), default=StoredConfig()).config.get("markdown_image", False)
|
||||
|
||||
|
||||
@on_alconna(
|
||||
command=Alconna(
|
||||
"liteecho",
|
||||
Args["text", str, ""],
|
||||
),
|
||||
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, Liteyuki!\nBot {bot.self_id}")
|
||||
|
||||
|
||||
@on_alconna(
|
||||
aliases={"更新轻雪"},
|
||||
command=Alconna(
|
||||
"update-liteyuki"
|
||||
),
|
||||
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))
|
||||
success, logs = update_liteyuki()
|
||||
reply = "Liteyuki updated!\n"
|
||||
reply += f"```\n{logs}\n```\n"
|
||||
btn_restart = md.btn_cmd(ulang.get("liteyuki.restart_now"), "reload-liteyuki")
|
||||
pip.main(["install", "-r", "requirements.txt"])
|
||||
reply += f"{ulang.get('liteyuki.update_restart', RESTART=btn_restart)}"
|
||||
await md.send_md(reply, bot, event=event, at_sender=False)
|
||||
|
||||
|
||||
@on_alconna(
|
||||
aliases={"重启轻雪"},
|
||||
command=Alconna(
|
||||
"reload-liteyuki"
|
||||
),
|
||||
permission=SUPERUSER
|
||||
).handle()
|
||||
# Satori OK
|
||||
async def _(matcher: Matcher, bot: T_Bot, event: T_MessageEvent):
|
||||
await matcher.send("Liteyuki reloading")
|
||||
temp_data = common_db.where_one(TempConfig(), default=TempConfig())
|
||||
|
||||
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
|
||||
}
|
||||
)
|
||||
|
||||
common_db.save(temp_data)
|
||||
Reloader.reload(0)
|
||||
|
||||
|
||||
@on_alconna(
|
||||
aliases={"配置"},
|
||||
command=Alconna(
|
||||
"config",
|
||||
Subcommand(
|
||||
"set",
|
||||
Args["key", str]["value", Any],
|
||||
alias=["设置"],
|
||||
|
||||
),
|
||||
Subcommand(
|
||||
"get",
|
||||
Args["key", str, None],
|
||||
alias=["查询", "获取"]
|
||||
),
|
||||
Subcommand(
|
||||
"remove",
|
||||
Args["key", str],
|
||||
alias=["删除"]
|
||||
)
|
||||
),
|
||||
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())
|
||||
if result.subcommands.get("set"):
|
||||
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)}")
|
||||
elif result.subcommands.get("get"):
|
||||
key = result.subcommands.get("get").args.get("key")
|
||||
file_config = load_from_yaml("config.yml")
|
||||
reply = f"{ulang.get('liteyuki.current_config')}"
|
||||
if key:
|
||||
reply += f"```dotenv\n{key}={file_config.get(key, stored_config.config.get(key))}\n```"
|
||||
else:
|
||||
reply = f"{ulang.get('liteyuki.current_config')}"
|
||||
reply += f"\n{ulang.get('liteyuki.static_config')}\n```dotenv"
|
||||
for k, v in file_config.items():
|
||||
reply += f"\n{k}={v}"
|
||||
reply += "\n```"
|
||||
if len(stored_config.config) > 0:
|
||||
reply += f"\n{ulang.get('liteyuki.stored_config')}\n```dotenv"
|
||||
for k, v in stored_config.config.items():
|
||||
reply += f"\n{k}={v} {type(v)}"
|
||||
reply += "\n```"
|
||||
await md.send_md(reply, bot, event=event)
|
||||
elif result.subcommands.get("remove"):
|
||||
key = result.subcommands.get("remove").args.get("key")
|
||||
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)}")
|
||||
else:
|
||||
await matcher.finish(f"{ulang.get('liteyuki.invalid_command', TEXT=key)}")
|
||||
|
||||
|
||||
@on_alconna(
|
||||
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)
|
||||
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"))
|
||||
|
||||
|
||||
@on_alconna(
|
||||
command=Alconna(
|
||||
"liteyuki-docs",
|
||||
),
|
||||
aliases={"轻雪文档"},
|
||||
).handle()
|
||||
# Satori OK
|
||||
async def _(matcher: Matcher):
|
||||
await matcher.finish("https://bot.liteyuki.icu/usage")
|
||||
|
||||
|
||||
@on_alconna(
|
||||
command=Alconna(
|
||||
"/function",
|
||||
Args["function", str]["args", MultiVar(str), ()],
|
||||
),
|
||||
permission=SUPERUSER
|
||||
).handle()
|
||||
async def _(result: Arparma, bot: T_Bot, event: T_MessageEvent, matcher: Matcher):
|
||||
"""
|
||||
调用轻雪函数
|
||||
Args:
|
||||
result:
|
||||
bot:
|
||||
event:
|
||||
|
||||
Returns:
|
||||
|
||||
"""
|
||||
function_name = result.main_args.get("function")
|
||||
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)
|
||||
}
|
||||
|
||||
for arg in args:
|
||||
arg = arg.replace("\\=", "EQUAL_SIGN")
|
||||
if "=" in arg:
|
||||
key, value = arg.split("=", 1)
|
||||
value = unescape(value.replace("EQUAL_SIGN", "="))
|
||||
try:
|
||||
value = eval(value)
|
||||
except:
|
||||
value = value
|
||||
_kwargs[key] = value
|
||||
else:
|
||||
_args.append(arg.replace("EQUAL_SIGN", "="))
|
||||
|
||||
ly_func = get_function(function_name)
|
||||
ly_func.bot = bot if "BOT_ID" not in _kwargs else nonebot.get_bot(_kwargs["BOT_ID"])
|
||||
ly_func.matcher = matcher
|
||||
|
||||
await ly_func(*tuple(_args), **_kwargs)
|
||||
|
||||
|
||||
@on_alconna(
|
||||
command=Alconna(
|
||||
"/api",
|
||||
Args["api", str]["args", MultiVar(AnyStr), ()],
|
||||
),
|
||||
permission=SUPERUSER
|
||||
).handle()
|
||||
async def _(result: Arparma, bot: T_Bot, event: T_MessageEvent, matcher: Matcher):
|
||||
"""
|
||||
调用API
|
||||
Args:
|
||||
result:
|
||||
bot:
|
||||
event:
|
||||
|
||||
Returns:
|
||||
|
||||
"""
|
||||
api_name = result.main_args.get("api")
|
||||
args: tuple[str] = result.main_args.get("args", ()) # 类似于url参数,但每个参数间用空格分隔,空格是%20
|
||||
args_dict = {}
|
||||
|
||||
for arg in args:
|
||||
key, value = arg.split("=", 1)
|
||||
|
||||
args_dict[key] = unescape(value.replace("%20", " "))
|
||||
|
||||
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":
|
||||
args_dict["group_id"] = str(event.group_id)
|
||||
|
||||
if "message" in args_dict:
|
||||
args_dict["message"] = Message(eval(args_dict["message"]))
|
||||
|
||||
if "messages" in args_dict:
|
||||
args_dict["messages"] = Message(eval(args_dict["messages"]))
|
||||
|
||||
try:
|
||||
result = await bot.call_api(api_name, **args_dict)
|
||||
except Exception as e:
|
||||
result = str(e)
|
||||
|
||||
args_show = "\n".join("- %s: %s" % (k, v) for k, v in args_dict.items())
|
||||
print(f"API: {api_name}\n\nArgs: \n{args_show}\n\nResult: {result}")
|
||||
await matcher.finish(f"API: {api_name}\n\nArgs: \n{args_show}\n\nResult: {result}")
|
||||
|
||||
|
||||
# system hook
|
||||
@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":
|
||||
session_type = "private"
|
||||
session_id = data.get("user_id")
|
||||
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":
|
||||
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)
|
||||
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)
|
||||
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)
|
||||
else:
|
||||
return
|
||||
raise MockApiException(result=result)
|
||||
|
||||
|
||||
@driver.on_startup
|
||||
async def on_startup():
|
||||
temp_data = common_db.where_one(TempConfig(), default=TempConfig())
|
||||
# 储存重启信息
|
||||
if temp_data.data.get("reload", False):
|
||||
delta_time = time.time() - temp_data.data.get("reload_time", 0)
|
||||
temp_data.data["delta_time"] = delta_time
|
||||
common_db.save(temp_data) # 更新数据
|
||||
|
||||
|
||||
@driver.on_shutdown
|
||||
async def on_shutdown():
|
||||
pass
|
||||
|
||||
|
||||
@driver.on_bot_connect
|
||||
async def _(bot: T_Bot):
|
||||
temp_data = common_db.where_one(TempConfig(), default=TempConfig())
|
||||
if isinstance(bot, satori.Bot):
|
||||
await satori_utils.user_infos.load_friends(bot)
|
||||
# 用于重启计时
|
||||
if temp_data.data.get("reload", False):
|
||||
temp_data.data["reload"] = False
|
||||
reload_bot_id = temp_data.data.get("reload_bot_id", 0)
|
||||
if reload_bot_id != bot.self_id:
|
||||
return
|
||||
reload_session_type = temp_data.data.get("reload_session_type", "private")
|
||||
reload_session_id = temp_data.data.get("reload_session_id", 0)
|
||||
delta_time = temp_data.data.get("delta_time", 0)
|
||||
common_db.save(temp_data) # 更新数据
|
||||
if isinstance(bot, satori.Bot):
|
||||
await bot.send_message(
|
||||
channel_id=reload_session_id,
|
||||
message="Liteyuki reloaded in %.2f s" % delta_time
|
||||
)
|
||||
else:
|
||||
await bot.call_api(
|
||||
"send_msg",
|
||||
message_type=reload_session_type,
|
||||
user_id=reload_session_id,
|
||||
group_id=reload_session_id,
|
||||
message="Liteyuki reloaded in %.2f s" % delta_time
|
||||
)
|
||||
|
||||
|
||||
# 每天4点更新
|
||||
@scheduler.scheduled_job("cron", hour=4)
|
||||
async def every_day_update():
|
||||
if get_config("auto_update", default=True):
|
||||
result, logs = update_liteyuki()
|
||||
pip.main(["install", "-r", "requirements.txt"])
|
||||
if result:
|
||||
await broadcast_to_superusers(f"Liteyuki updated: ```\n{logs}\n```")
|
||||
nonebot.logger.info(f"Liteyuki updated: {logs}")
|
||||
Reloader.reload(5)
|
||||
else:
|
||||
nonebot.logger.info(logs)
|
||||
|
||||
|
||||
# 需要用户id的api
|
||||
need_user_id = (
|
||||
"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"
|
||||
)
|
@ -1,55 +1,59 @@
|
||||
import nonebot
|
||||
from watchdog.observers import Observer
|
||||
from watchdog.events import FileSystemEventHandler
|
||||
|
||||
from liteyuki.utils.base.config import get_config
|
||||
from liteyuki.utils.base.reloader import Reloader
|
||||
from liteyuki.utils.base.resource import load_resources
|
||||
|
||||
if get_config("debug", False):
|
||||
|
||||
src_directories = (
|
||||
"liteyuki/liteyuki_main",
|
||||
"liteyuki/plugins",
|
||||
"liteyuki/utils",
|
||||
)
|
||||
src_excludes_extensions = (
|
||||
"pyc",
|
||||
)
|
||||
|
||||
res_directories = (
|
||||
"liteyuki/resources",
|
||||
"resources",
|
||||
)
|
||||
|
||||
nonebot.logger.info("Liteyuki Reload is enable, watching for file changes...")
|
||||
|
||||
class CodeModifiedHandler(FileSystemEventHandler):
|
||||
"""
|
||||
Handler for code file changes
|
||||
"""
|
||||
def on_modified(self, event):
|
||||
if event.src_path.endswith(src_excludes_extensions) or event.is_directory or "__pycache__" in event.src_path:
|
||||
return
|
||||
nonebot.logger.info(f"{event.src_path} modified, reloading bot...")
|
||||
Reloader.reload()
|
||||
|
||||
|
||||
class ResourceModifiedHandler(FileSystemEventHandler):
|
||||
"""
|
||||
Handler for resource file changes
|
||||
"""
|
||||
def on_modified(self, event):
|
||||
nonebot.logger.info(f"{event.src_path} modified, reloading resource...")
|
||||
load_resources()
|
||||
|
||||
|
||||
code_modified_handler = CodeModifiedHandler()
|
||||
resource_modified_handle = ResourceModifiedHandler()
|
||||
|
||||
observer = Observer()
|
||||
for directory in src_directories:
|
||||
observer.schedule(code_modified_handler, directory, recursive=True)
|
||||
for directory in res_directories:
|
||||
observer.schedule(resource_modified_handle, directory, recursive=True)
|
||||
observer.start()
|
||||
import nonebot
|
||||
from watchdog.observers import Observer
|
||||
from watchdog.events import FileSystemEventHandler
|
||||
|
||||
from src.utils.base.config import get_config
|
||||
from src.utils.base.reloader import Reloader
|
||||
from src.utils.base.resource import load_resources
|
||||
|
||||
if get_config("debug", False):
|
||||
|
||||
src_directories = (
|
||||
"src/liteyuki_main",
|
||||
"src/plugins",
|
||||
"src/utils",
|
||||
)
|
||||
src_excludes_extensions = (
|
||||
"pyc",
|
||||
)
|
||||
|
||||
res_directories = (
|
||||
"src/resources",
|
||||
"resources",
|
||||
)
|
||||
|
||||
nonebot.logger.info("Liteyuki Reload is enable, watching for file changes...")
|
||||
|
||||
|
||||
class CodeModifiedHandler(FileSystemEventHandler):
|
||||
"""
|
||||
Handler for code file changes
|
||||
"""
|
||||
|
||||
def on_modified(self, event):
|
||||
if event.src_path.endswith(
|
||||
src_excludes_extensions) or event.is_directory or "__pycache__" in event.src_path:
|
||||
return
|
||||
nonebot.logger.info(f"{event.src_path} modified, reloading bot...")
|
||||
Reloader.reload()
|
||||
|
||||
|
||||
class ResourceModifiedHandler(FileSystemEventHandler):
|
||||
"""
|
||||
Handler for resource file changes
|
||||
"""
|
||||
|
||||
def on_modified(self, event):
|
||||
nonebot.logger.info(f"{event.src_path} modified, reloading resource...")
|
||||
load_resources()
|
||||
|
||||
|
||||
code_modified_handler = CodeModifiedHandler()
|
||||
resource_modified_handle = ResourceModifiedHandler()
|
||||
|
||||
observer = Observer()
|
||||
for directory in src_directories:
|
||||
observer.schedule(code_modified_handler, directory, recursive=True)
|
||||
for directory in res_directories:
|
||||
observer.schedule(resource_modified_handle, directory, recursive=True)
|
||||
observer.start()
|
@ -1,31 +1,31 @@
|
||||
import nonebot.plugin
|
||||
from nonebot import get_driver
|
||||
from liteyuki.utils import init_log
|
||||
from liteyuki.utils.base.config import get_config
|
||||
from liteyuki.utils.base.data_manager import InstalledPlugin, plugin_db
|
||||
from liteyuki.utils.base.resource import load_resources
|
||||
from liteyuki.utils.message.tools import check_for_package
|
||||
|
||||
load_resources()
|
||||
init_log()
|
||||
|
||||
driver = get_driver()
|
||||
|
||||
|
||||
@driver.on_startup
|
||||
async def load_plugins():
|
||||
nonebot.plugin.load_plugins("liteyuki/plugins")
|
||||
# 从数据库读取已安装的插件
|
||||
if not get_config("safe_mode", False):
|
||||
# 安全模式下,不加载插件
|
||||
installed_plugins: list[InstalledPlugin] = plugin_db.where_all(InstalledPlugin())
|
||||
if installed_plugins:
|
||||
for installed_plugin in installed_plugins:
|
||||
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)
|
||||
nonebot.plugin.load_plugins("plugins")
|
||||
else:
|
||||
nonebot.logger.info("Safe mode is on, no plugin loaded.")
|
||||
import nonebot.plugin
|
||||
from nonebot import get_driver
|
||||
from src.utils import init_log
|
||||
from src.utils.base.config import get_config
|
||||
from src.utils.base.data_manager import InstalledPlugin, plugin_db
|
||||
from src.utils.base.resource import load_resources
|
||||
from src.utils.message.tools import check_for_package
|
||||
|
||||
load_resources()
|
||||
init_log()
|
||||
|
||||
driver = get_driver()
|
||||
|
||||
|
||||
@driver.on_startup
|
||||
async def load_plugins():
|
||||
nonebot.plugin.load_plugins("liteyuki/plugins")
|
||||
# 从数据库读取已安装的插件
|
||||
if not get_config("safe_mode", False):
|
||||
# 安全模式下,不加载插件
|
||||
installed_plugins: list[InstalledPlugin] = plugin_db.where_all(InstalledPlugin())
|
||||
if installed_plugins:
|
||||
for installed_plugin in installed_plugins:
|
||||
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)
|
||||
nonebot.plugin.load_plugins("plugins")
|
||||
else:
|
||||
nonebot.logger.info("Safe mode is on, no plugin loaded.")
|
@ -1,16 +1,16 @@
|
||||
from nonebot.plugin import PluginMetadata
|
||||
from .rt_guide import *
|
||||
from .crt_matchers import *
|
||||
|
||||
__plugin_meta__ = PluginMetadata(
|
||||
name="CRT生成工具",
|
||||
description="一些CRT牌子生成器",
|
||||
usage="我觉得你应该会用",
|
||||
type="application",
|
||||
homepage="https://github.com/snowykami/LiteyukiBot",
|
||||
extra={
|
||||
"liteyuki" : True,
|
||||
"toggleable" : True,
|
||||
"default_enable": True,
|
||||
}
|
||||
)
|
||||
from nonebot.plugin import PluginMetadata
|
||||
from .rt_guide import *
|
||||
from .crt_matchers import *
|
||||
|
||||
__plugin_meta__ = PluginMetadata(
|
||||
name="CRT生成工具",
|
||||
description="一些CRT牌子生成器",
|
||||
usage="我觉得你应该会用",
|
||||
type="application",
|
||||
homepage="https://github.com/snowykami/LiteyukiBot",
|
||||
extra={
|
||||
"liteyuki" : True,
|
||||
"toggleable" : True,
|
||||
"default_enable": True,
|
||||
}
|
||||
)
|
File diff suppressed because it is too large
Load Diff
@ -1,78 +1,78 @@
|
||||
from urllib.parse import quote
|
||||
|
||||
import aiohttp
|
||||
from nonebot import require
|
||||
|
||||
from liteyuki.utils.event import get_user_id
|
||||
from liteyuki.utils.base.language import Language
|
||||
from liteyuki.utils.base.ly_typing import T_MessageEvent
|
||||
from liteyuki.utils.base.resource import get_path
|
||||
from liteyuki.utils.message.html_tool import template2image
|
||||
|
||||
require("nonebot_plugin_alconna")
|
||||
|
||||
from nonebot_plugin_alconna import UniMessage, on_alconna, Alconna, Args, Subcommand, Arparma, Option
|
||||
|
||||
crt_cmd = on_alconna(
|
||||
Alconna(
|
||||
"crt",
|
||||
Subcommand(
|
||||
"route",
|
||||
Args["start", str, "沙坪坝"]["end", str, "上新街"],
|
||||
alias=("r",),
|
||||
help_text="查询两地之间的地铁路线"
|
||||
),
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
@crt_cmd.assign("route")
|
||||
async def _(result: Arparma, event: T_MessageEvent):
|
||||
# 获取语言
|
||||
ulang = Language(get_user_id(event))
|
||||
|
||||
# 获取参数
|
||||
# 你也别问我为什么要quote两次,问就是CRT官网的锅,只有这样才可以运行
|
||||
start = quote(quote(result.other_args.get("start")))
|
||||
end = quote(quote(result.other_args.get("end")))
|
||||
|
||||
# 判断参数语言
|
||||
query_lang_code = ""
|
||||
if start.isalpha() and end.isalpha():
|
||||
query_lang_code = "Eng"
|
||||
|
||||
# 构造请求 URL
|
||||
url = f"https://www.cqmetro.cn/Front/html/TakeLine!queryYs{query_lang_code}TakeLine.action?entity.startStaName={start}&entity.endStaName={end}"
|
||||
|
||||
# 请求数据
|
||||
async with aiohttp.ClientSession() as session:
|
||||
async with session.get(url) as resp:
|
||||
result = await resp.json()
|
||||
|
||||
# 检查结果/无则终止
|
||||
if not result.get("result"):
|
||||
await crt_cmd.send(ulang.get("crt.no_result"))
|
||||
return
|
||||
|
||||
# 模板传参定义
|
||||
templates = {
|
||||
"data" : {
|
||||
"result": result["result"],
|
||||
},
|
||||
"localization": ulang.get_many(
|
||||
"crt.station",
|
||||
"crt.hour",
|
||||
"crt.minute",
|
||||
)
|
||||
|
||||
}
|
||||
|
||||
# 生成图片
|
||||
image = await template2image(
|
||||
template=get_path("templates/crt_route.html"),
|
||||
templates=templates,
|
||||
debug=True
|
||||
)
|
||||
|
||||
# 发送图片
|
||||
await crt_cmd.send(UniMessage.image(raw=image))
|
||||
from urllib.parse import quote
|
||||
|
||||
import aiohttp
|
||||
from nonebot import require
|
||||
|
||||
from src.utils.event import get_user_id
|
||||
from src.utils.base.language import Language
|
||||
from src.utils.base.ly_typing import T_MessageEvent
|
||||
from src.utils.base.resource import get_path
|
||||
from src.utils.message.html_tool import template2image
|
||||
|
||||
require("nonebot_plugin_alconna")
|
||||
|
||||
from nonebot_plugin_alconna import UniMessage, on_alconna, Alconna, Args, Subcommand, Arparma, Option
|
||||
|
||||
crt_cmd = on_alconna(
|
||||
Alconna(
|
||||
"crt",
|
||||
Subcommand(
|
||||
"route",
|
||||
Args["start", str, "沙坪坝"]["end", str, "上新街"],
|
||||
alias=("r",),
|
||||
help_text="查询两地之间的地铁路线"
|
||||
),
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
@crt_cmd.assign("route")
|
||||
async def _(result: Arparma, event: T_MessageEvent):
|
||||
# 获取语言
|
||||
ulang = Language(get_user_id(event))
|
||||
|
||||
# 获取参数
|
||||
# 你也别问我为什么要quote两次,问就是CRT官网的锅,只有这样才可以运行
|
||||
start = quote(quote(result.other_args.get("start")))
|
||||
end = quote(quote(result.other_args.get("end")))
|
||||
|
||||
# 判断参数语言
|
||||
query_lang_code = ""
|
||||
if start.isalpha() and end.isalpha():
|
||||
query_lang_code = "Eng"
|
||||
|
||||
# 构造请求 URL
|
||||
url = f"https://www.cqmetro.cn/Front/html/TakeLine!queryYs{query_lang_code}TakeLine.action?entity.startStaName={start}&entity.endStaName={end}"
|
||||
|
||||
# 请求数据
|
||||
async with aiohttp.ClientSession() as session:
|
||||
async with session.get(url) as resp:
|
||||
result = await resp.json()
|
||||
|
||||
# 检查结果/无则终止
|
||||
if not result.get("result"):
|
||||
await crt_cmd.send(ulang.get("crt.no_result"))
|
||||
return
|
||||
|
||||
# 模板传参定义
|
||||
templates = {
|
||||
"data" : {
|
||||
"result": result["result"],
|
||||
},
|
||||
"localization": ulang.get_many(
|
||||
"crt.station",
|
||||
"crt.hour",
|
||||
"crt.minute",
|
||||
)
|
||||
|
||||
}
|
||||
|
||||
# 生成图片
|
||||
image = await template2image(
|
||||
template=get_path("templates/crt_route.html"),
|
||||
templates=templates,
|
||||
debug=True
|
||||
)
|
||||
|
||||
# 发送图片
|
||||
await crt_cmd.send(UniMessage.image(raw=image))
|
@ -1,419 +1,419 @@
|
||||
import json
|
||||
from typing import List, Any
|
||||
|
||||
from PIL import Image
|
||||
from arclet.alconna import Alconna
|
||||
from nb_cli import run_sync
|
||||
from nonebot import on_command
|
||||
from nonebot_plugin_alconna import on_alconna, Alconna, Subcommand, Args, MultiVar, Arparma, UniMessage
|
||||
from pydantic import BaseModel
|
||||
|
||||
from .canvas import *
|
||||
from ...utils.base.resource import get_path
|
||||
|
||||
resolution = 256
|
||||
|
||||
|
||||
class Entrance(BaseModel):
|
||||
identifier: str
|
||||
size: tuple[int, int]
|
||||
dest: List[str]
|
||||
|
||||
|
||||
class Station(BaseModel):
|
||||
identifier: str
|
||||
chineseName: str
|
||||
englishName: str
|
||||
position: tuple[int, int]
|
||||
|
||||
|
||||
class Line(BaseModel):
|
||||
identifier: str
|
||||
chineseName: str
|
||||
englishName: str
|
||||
color: Any
|
||||
stations: List["Station"]
|
||||
|
||||
|
||||
font_light = get_path("templates/fonts/MiSans/MiSans-Light.woff2")
|
||||
font_bold = get_path("templates/fonts/MiSans/MiSans-Bold.woff2")
|
||||
|
||||
@run_sync
|
||||
def generate_entrance_sign(name: str, aliases: List[str], lineInfo: List[Line], entranceIdentifier: str, ratio: tuple[int | float, int | float],
|
||||
reso: int = resolution):
|
||||
"""
|
||||
Generates an entrance sign for the ride.
|
||||
"""
|
||||
width, height = ratio[0] * reso, ratio[1] * reso
|
||||
baseCanvas = Canvas(Image.new("RGBA", (width, height), Color.WHITE))
|
||||
# 加黑色图框
|
||||
baseCanvas.outline = Img(
|
||||
uv_size=(1, 1),
|
||||
box_size=(1, 1),
|
||||
parent_point=(0, 0),
|
||||
point=(0, 0),
|
||||
img=Shape.rectangle(
|
||||
size=(width, height),
|
||||
fillet=0,
|
||||
fill=(0, 0, 0, 0),
|
||||
width=15,
|
||||
outline=Color.BLACK
|
||||
)
|
||||
)
|
||||
|
||||
baseCanvas.contentPanel = Panel(
|
||||
uv_size=(width, height),
|
||||
box_size=(width - 28, height - 28),
|
||||
parent_point=(0.5, 0.5),
|
||||
point=(0.5, 0.5),
|
||||
)
|
||||
|
||||
linePanelHeight = 0.7 * ratio[1]
|
||||
linePanelWidth = linePanelHeight * 1.3
|
||||
|
||||
# 画线路面板部分
|
||||
|
||||
for i, line in enumerate(lineInfo):
|
||||
linePanel = baseCanvas.contentPanel.__dict__[f"Line_{i}_Panel"] = Panel(
|
||||
uv_size=ratio,
|
||||
box_size=(linePanelWidth, linePanelHeight),
|
||||
parent_point=(i * linePanelWidth / ratio[0], 1),
|
||||
point=(0, 1),
|
||||
)
|
||||
|
||||
linePanel.colorCube = Img(
|
||||
uv_size=(1, 1),
|
||||
box_size=(0.15, 1),
|
||||
parent_point=(0.125, 1),
|
||||
point=(0, 1),
|
||||
img=Shape.rectangle(
|
||||
size=(100, 100),
|
||||
fillet=0,
|
||||
fill=line.color,
|
||||
),
|
||||
keep_ratio=False
|
||||
)
|
||||
|
||||
textPanel = linePanel.TextPanel = Panel(
|
||||
uv_size=(1, 1),
|
||||
box_size=(0.625, 1),
|
||||
parent_point=(1, 1),
|
||||
point=(1, 1)
|
||||
)
|
||||
|
||||
# 中文线路名
|
||||
textPanel.namePanel = Panel(
|
||||
uv_size=(1, 1),
|
||||
box_size=(1, 2 / 3),
|
||||
parent_point=(0, 0),
|
||||
point=(0, 0),
|
||||
)
|
||||
nameSize = baseCanvas.get_actual_pixel_size("contentPanel.Line_{}_Panel.TextPanel.namePanel".format(i))
|
||||
textPanel.namePanel.text = Text(
|
||||
uv_size=(1, 1),
|
||||
box_size=(1, 1),
|
||||
parent_point=(0.5, 0.5),
|
||||
point=(0.5, 0.5),
|
||||
text=line.chineseName,
|
||||
color=Color.BLACK,
|
||||
font_size=int(nameSize[1] * 0.5),
|
||||
force_size=True,
|
||||
font=font_bold
|
||||
|
||||
)
|
||||
|
||||
# 英文线路名
|
||||
textPanel.englishNamePanel = Panel(
|
||||
uv_size=(1, 1),
|
||||
box_size=(1, 1 / 3),
|
||||
parent_point=(0, 1),
|
||||
point=(0, 1),
|
||||
)
|
||||
englishNameSize = baseCanvas.get_actual_pixel_size("contentPanel.Line_{}_Panel.TextPanel.englishNamePanel".format(i))
|
||||
textPanel.englishNamePanel.text = Text(
|
||||
uv_size=(1, 1),
|
||||
box_size=(1, 1),
|
||||
parent_point=(0.5, 0.5),
|
||||
point=(0.5, 0.5),
|
||||
text=line.englishName,
|
||||
color=Color.BLACK,
|
||||
font_size=int(englishNameSize[1] * 0.6),
|
||||
force_size=True,
|
||||
font=font_light
|
||||
)
|
||||
|
||||
# 画名称部分
|
||||
namePanel = baseCanvas.contentPanel.namePanel = Panel(
|
||||
uv_size=(1, 1),
|
||||
box_size=(1, 0.4),
|
||||
parent_point=(0.5, 0),
|
||||
point=(0.5, 0),
|
||||
)
|
||||
|
||||
namePanel.text = Text(
|
||||
uv_size=(1, 1),
|
||||
box_size=(1, 1),
|
||||
parent_point=(0.5, 0.5),
|
||||
point=(0.5, 0.5),
|
||||
text=name,
|
||||
color=Color.BLACK,
|
||||
font_size=int(height * 0.3),
|
||||
force_size=True,
|
||||
font=font_bold
|
||||
)
|
||||
|
||||
aliasesPanel = baseCanvas.contentPanel.aliasesPanel = Panel(
|
||||
uv_size=(1, 1),
|
||||
box_size=(1, 0.5),
|
||||
parent_point=(0.5, 1),
|
||||
point=(0.5, 1),
|
||||
|
||||
)
|
||||
for j, alias in enumerate(aliases):
|
||||
aliasesPanel.__dict__[alias] = Text(
|
||||
uv_size=(1, 1),
|
||||
box_size=(0.35, 0.5),
|
||||
parent_point=(0.5, 0.5 * j),
|
||||
point=(0.5, 0),
|
||||
text=alias,
|
||||
color=Color.BLACK,
|
||||
font_size=int(height * 0.15),
|
||||
font=font_light
|
||||
)
|
||||
|
||||
# 画入口标识
|
||||
entrancePanel = baseCanvas.contentPanel.entrancePanel = Panel(
|
||||
uv_size=(1, 1),
|
||||
box_size=(0.2, 1),
|
||||
parent_point=(1, 0.5),
|
||||
point=(1, 0.5),
|
||||
)
|
||||
# 中文文本
|
||||
entrancePanel.namePanel = Panel(
|
||||
uv_size=(1, 1),
|
||||
box_size=(1, 0.5),
|
||||
parent_point=(1, 0),
|
||||
point=(1, 0),
|
||||
)
|
||||
entrancePanel.namePanel.text = Text(
|
||||
uv_size=(1, 1),
|
||||
box_size=(1, 1),
|
||||
parent_point=(0, 0.5),
|
||||
point=(0, 0.5),
|
||||
text=f"{entranceIdentifier}出入口",
|
||||
color=Color.BLACK,
|
||||
font_size=int(height * 0.2),
|
||||
force_size=True,
|
||||
font=font_bold
|
||||
)
|
||||
# 英文文本
|
||||
entrancePanel.englishNamePanel = Panel(
|
||||
uv_size=(1, 1),
|
||||
box_size=(1, 0.5),
|
||||
parent_point=(1, 1),
|
||||
point=(1, 1),
|
||||
)
|
||||
entrancePanel.englishNamePanel.text = Text(
|
||||
uv_size=(1, 1),
|
||||
box_size=(1, 1),
|
||||
parent_point=(0, 0.5),
|
||||
point=(0, 0.5),
|
||||
text=f"Entrance {entranceIdentifier}",
|
||||
color=Color.BLACK,
|
||||
font_size=int(height * 0.15),
|
||||
force_size=True,
|
||||
font=font_light
|
||||
)
|
||||
|
||||
return baseCanvas.base_img.tobytes()
|
||||
|
||||
|
||||
crt_alc = on_alconna(
|
||||
Alconna(
|
||||
"crt",
|
||||
Subcommand(
|
||||
"entrance",
|
||||
Args["name", str]["lines", str, ""]["entrance", int, 1], # /crt entrance 璧山&Bishan 1号线&Line1&#ff0000,27号线&Line1&#ff0000 1A
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
@crt_alc.assign("entrance")
|
||||
async def _(result: Arparma):
|
||||
args = result.subcommands.get("entrance").args
|
||||
name = args["name"]
|
||||
lines = args["lines"]
|
||||
entrance = args["entrance"]
|
||||
line_info = []
|
||||
for line in lines.split(","):
|
||||
line_args = line.split("&")
|
||||
line_info.append(Line(
|
||||
identifier=1,
|
||||
chineseName=line_args[0],
|
||||
englishName=line_args[1],
|
||||
color=line_args[2],
|
||||
stations=[]
|
||||
))
|
||||
img_bytes = await generate_entrance_sign(
|
||||
name=name,
|
||||
aliases=name.split("&"),
|
||||
lineInfo=line_info,
|
||||
entranceIdentifier=entrance,
|
||||
ratio=(8, 1),
|
||||
reso=256,
|
||||
)
|
||||
await crt_alc.finish(
|
||||
UniMessage.image(raw=img_bytes)
|
||||
)
|
||||
|
||||
|
||||
def generate_platform_line_pic(line: Line, station: Station, ratio=None, reso: int = resolution):
|
||||
"""
|
||||
生成站台线路图
|
||||
:param line: 线路对象
|
||||
:param station: 本站点对象
|
||||
:param ratio: 比例
|
||||
:param reso: 分辨率,1:reso
|
||||
:return: 两个方向的站牌
|
||||
"""
|
||||
if ratio is None:
|
||||
ratio = [4, 1]
|
||||
width, height = ratio[0] * reso, ratio[1] * reso
|
||||
baseCanvas = Canvas(Image.new("RGBA", (width, height), Color.YELLOW))
|
||||
# 加黑色图框
|
||||
baseCanvas.linePanel = Panel(
|
||||
uv_size=(1, 1),
|
||||
box_size=(0.8, 0.15),
|
||||
parent_point=(0.5, 0.5),
|
||||
point=(0.5, 0.5),
|
||||
)
|
||||
|
||||
# 直线块
|
||||
baseCanvas.linePanel.recLine = Img(
|
||||
uv_size=(1, 1),
|
||||
box_size=(1, 1),
|
||||
parent_point=(0.5, 0.5),
|
||||
point=(0.5, 0.5),
|
||||
img=Shape.rectangle(
|
||||
size=(10, 10),
|
||||
fill=line.color,
|
||||
),
|
||||
keep_ratio=False
|
||||
)
|
||||
# 灰色直线块
|
||||
baseCanvas.linePanel.recLineGrey = Img(
|
||||
uv_size=(1, 1),
|
||||
box_size=(1, 1),
|
||||
parent_point=(0.5, 0.5),
|
||||
point=(0.5, 0.5),
|
||||
img=Shape.rectangle(
|
||||
size=(10, 10),
|
||||
fill=Color.GREY,
|
||||
),
|
||||
keep_ratio=False
|
||||
)
|
||||
# 生成各站圆点
|
||||
outline_width = 40
|
||||
circleForward = Shape.circular(
|
||||
radius=200,
|
||||
fill=Color.WHITE,
|
||||
width=outline_width,
|
||||
outline=line.color,
|
||||
)
|
||||
|
||||
circleThisPanel = Canvas(Image.new("RGBA", (200, 200), (0, 0, 0, 0)))
|
||||
circleThisPanel.circleOuter = Img(
|
||||
uv_size=(1, 1),
|
||||
box_size=(1, 1),
|
||||
parent_point=(0.5, 0.5),
|
||||
point=(0.5, 0.5),
|
||||
img=Shape.circular(
|
||||
radius=200,
|
||||
fill=Color.WHITE,
|
||||
width=outline_width,
|
||||
outline=line.color,
|
||||
),
|
||||
)
|
||||
circleThisPanel.circleOuter.circleInner = Img(
|
||||
uv_size=(1, 1),
|
||||
box_size=(0.7, 0.7),
|
||||
parent_point=(0.5, 0.5),
|
||||
point=(0.5, 0.5),
|
||||
img=Shape.circular(
|
||||
radius=200,
|
||||
fill=line.color,
|
||||
width=0,
|
||||
outline=line.color,
|
||||
),
|
||||
)
|
||||
|
||||
circleThisPanel.export("a.png", alpha=True)
|
||||
circleThis = circleThisPanel.base_img
|
||||
|
||||
circlePassed = Shape.circular(
|
||||
radius=200,
|
||||
fill=Color.WHITE,
|
||||
width=outline_width,
|
||||
outline=Color.GREY,
|
||||
)
|
||||
|
||||
arrival = False
|
||||
distance = 1 / (len(line.stations) - 1)
|
||||
for i, sta in enumerate(line.stations):
|
||||
box_size = (1.618, 1.618)
|
||||
if sta.identifier == station.identifier:
|
||||
arrival = True
|
||||
baseCanvas.linePanel.recLine.__dict__["station_{}".format(sta.identifier)] = Img(
|
||||
uv_size=(1, 1),
|
||||
box_size=(1.8, 1.8),
|
||||
parent_point=(distance * i, 0.5),
|
||||
point=(0.5, 0.5),
|
||||
img=circleThis,
|
||||
keep_ratio=True
|
||||
)
|
||||
continue
|
||||
if arrival:
|
||||
# 后方站绘制
|
||||
baseCanvas.linePanel.recLine.__dict__["station_{}".format(sta.identifier)] = Img(
|
||||
uv_size=(1, 1),
|
||||
box_size=box_size,
|
||||
parent_point=(distance * i, 0.5),
|
||||
point=(0.5, 0.5),
|
||||
img=circleForward,
|
||||
keep_ratio=True
|
||||
)
|
||||
else:
|
||||
# 前方站绘制
|
||||
baseCanvas.linePanel.recLine.__dict__["station_{}".format(sta.identifier)] = Img(
|
||||
uv_size=(1, 1),
|
||||
box_size=box_size,
|
||||
parent_point=(distance * i, 0.5),
|
||||
point=(0.5, 0.5),
|
||||
img=circlePassed,
|
||||
keep_ratio=True
|
||||
)
|
||||
return baseCanvas
|
||||
|
||||
|
||||
def generate_platform_sign(name: str, aliases: List[str], lineInfo: List[Line], entranceIdentifier: str, ratio: tuple[int | float, int | float],
|
||||
reso: int = resolution
|
||||
):
|
||||
pass
|
||||
|
||||
# def main():
|
||||
# generate_entrance_sign(
|
||||
# "璧山",
|
||||
# aliases=["Bishan"],
|
||||
# lineInfo=[
|
||||
#
|
||||
# Line(identifier="2", chineseName="1号线", englishName="Line 1", color=Color.RED, stations=[]),
|
||||
# Line(identifier="3", chineseName="27号线", englishName="Line 27", color="#685bc7", stations=[]),
|
||||
# Line(identifier="1", chineseName="璧铜线", englishName="BT Line", color="#685BC7", stations=[]),
|
||||
# ],
|
||||
# entranceIdentifier="1",
|
||||
# ratio=(8, 1)
|
||||
# )
|
||||
#
|
||||
#
|
||||
# main()
|
||||
import json
|
||||
from typing import List, Any
|
||||
|
||||
from PIL import Image
|
||||
from arclet.alconna import Alconna
|
||||
from nb_cli import run_sync
|
||||
from nonebot import on_command
|
||||
from nonebot_plugin_alconna import on_alconna, Alconna, Subcommand, Args, MultiVar, Arparma, UniMessage
|
||||
from pydantic import BaseModel
|
||||
|
||||
from .canvas import *
|
||||
from ...utils.base.resource import get_path
|
||||
|
||||
resolution = 256
|
||||
|
||||
|
||||
class Entrance(BaseModel):
|
||||
identifier: str
|
||||
size: tuple[int, int]
|
||||
dest: List[str]
|
||||
|
||||
|
||||
class Station(BaseModel):
|
||||
identifier: str
|
||||
chineseName: str
|
||||
englishName: str
|
||||
position: tuple[int, int]
|
||||
|
||||
|
||||
class Line(BaseModel):
|
||||
identifier: str
|
||||
chineseName: str
|
||||
englishName: str
|
||||
color: Any
|
||||
stations: List["Station"]
|
||||
|
||||
|
||||
font_light = get_path("templates/fonts/MiSans/MiSans-Light.woff2")
|
||||
font_bold = get_path("templates/fonts/MiSans/MiSans-Bold.woff2")
|
||||
|
||||
@run_sync
|
||||
def generate_entrance_sign(name: str, aliases: List[str], lineInfo: List[Line], entranceIdentifier: str, ratio: tuple[int | float, int | float],
|
||||
reso: int = resolution):
|
||||
"""
|
||||
Generates an entrance sign for the ride.
|
||||
"""
|
||||
width, height = ratio[0] * reso, ratio[1] * reso
|
||||
baseCanvas = Canvas(Image.new("RGBA", (width, height), Color.WHITE))
|
||||
# 加黑色图框
|
||||
baseCanvas.outline = Img(
|
||||
uv_size=(1, 1),
|
||||
box_size=(1, 1),
|
||||
parent_point=(0, 0),
|
||||
point=(0, 0),
|
||||
img=Shape.rectangle(
|
||||
size=(width, height),
|
||||
fillet=0,
|
||||
fill=(0, 0, 0, 0),
|
||||
width=15,
|
||||
outline=Color.BLACK
|
||||
)
|
||||
)
|
||||
|
||||
baseCanvas.contentPanel = Panel(
|
||||
uv_size=(width, height),
|
||||
box_size=(width - 28, height - 28),
|
||||
parent_point=(0.5, 0.5),
|
||||
point=(0.5, 0.5),
|
||||
)
|
||||
|
||||
linePanelHeight = 0.7 * ratio[1]
|
||||
linePanelWidth = linePanelHeight * 1.3
|
||||
|
||||
# 画线路面板部分
|
||||
|
||||
for i, line in enumerate(lineInfo):
|
||||
linePanel = baseCanvas.contentPanel.__dict__[f"Line_{i}_Panel"] = Panel(
|
||||
uv_size=ratio,
|
||||
box_size=(linePanelWidth, linePanelHeight),
|
||||
parent_point=(i * linePanelWidth / ratio[0], 1),
|
||||
point=(0, 1),
|
||||
)
|
||||
|
||||
linePanel.colorCube = Img(
|
||||
uv_size=(1, 1),
|
||||
box_size=(0.15, 1),
|
||||
parent_point=(0.125, 1),
|
||||
point=(0, 1),
|
||||
img=Shape.rectangle(
|
||||
size=(100, 100),
|
||||
fillet=0,
|
||||
fill=line.color,
|
||||
),
|
||||
keep_ratio=False
|
||||
)
|
||||
|
||||
textPanel = linePanel.TextPanel = Panel(
|
||||
uv_size=(1, 1),
|
||||
box_size=(0.625, 1),
|
||||
parent_point=(1, 1),
|
||||
point=(1, 1)
|
||||
)
|
||||
|
||||
# 中文线路名
|
||||
textPanel.namePanel = Panel(
|
||||
uv_size=(1, 1),
|
||||
box_size=(1, 2 / 3),
|
||||
parent_point=(0, 0),
|
||||
point=(0, 0),
|
||||
)
|
||||
nameSize = baseCanvas.get_actual_pixel_size("contentPanel.Line_{}_Panel.TextPanel.namePanel".format(i))
|
||||
textPanel.namePanel.text = Text(
|
||||
uv_size=(1, 1),
|
||||
box_size=(1, 1),
|
||||
parent_point=(0.5, 0.5),
|
||||
point=(0.5, 0.5),
|
||||
text=line.chineseName,
|
||||
color=Color.BLACK,
|
||||
font_size=int(nameSize[1] * 0.5),
|
||||
force_size=True,
|
||||
font=font_bold
|
||||
|
||||
)
|
||||
|
||||
# 英文线路名
|
||||
textPanel.englishNamePanel = Panel(
|
||||
uv_size=(1, 1),
|
||||
box_size=(1, 1 / 3),
|
||||
parent_point=(0, 1),
|
||||
point=(0, 1),
|
||||
)
|
||||
englishNameSize = baseCanvas.get_actual_pixel_size("contentPanel.Line_{}_Panel.TextPanel.englishNamePanel".format(i))
|
||||
textPanel.englishNamePanel.text = Text(
|
||||
uv_size=(1, 1),
|
||||
box_size=(1, 1),
|
||||
parent_point=(0.5, 0.5),
|
||||
point=(0.5, 0.5),
|
||||
text=line.englishName,
|
||||
color=Color.BLACK,
|
||||
font_size=int(englishNameSize[1] * 0.6),
|
||||
force_size=True,
|
||||
font=font_light
|
||||
)
|
||||
|
||||
# 画名称部分
|
||||
namePanel = baseCanvas.contentPanel.namePanel = Panel(
|
||||
uv_size=(1, 1),
|
||||
box_size=(1, 0.4),
|
||||
parent_point=(0.5, 0),
|
||||
point=(0.5, 0),
|
||||
)
|
||||
|
||||
namePanel.text = Text(
|
||||
uv_size=(1, 1),
|
||||
box_size=(1, 1),
|
||||
parent_point=(0.5, 0.5),
|
||||
point=(0.5, 0.5),
|
||||
text=name,
|
||||
color=Color.BLACK,
|
||||
font_size=int(height * 0.3),
|
||||
force_size=True,
|
||||
font=font_bold
|
||||
)
|
||||
|
||||
aliasesPanel = baseCanvas.contentPanel.aliasesPanel = Panel(
|
||||
uv_size=(1, 1),
|
||||
box_size=(1, 0.5),
|
||||
parent_point=(0.5, 1),
|
||||
point=(0.5, 1),
|
||||
|
||||
)
|
||||
for j, alias in enumerate(aliases):
|
||||
aliasesPanel.__dict__[alias] = Text(
|
||||
uv_size=(1, 1),
|
||||
box_size=(0.35, 0.5),
|
||||
parent_point=(0.5, 0.5 * j),
|
||||
point=(0.5, 0),
|
||||
text=alias,
|
||||
color=Color.BLACK,
|
||||
font_size=int(height * 0.15),
|
||||
font=font_light
|
||||
)
|
||||
|
||||
# 画入口标识
|
||||
entrancePanel = baseCanvas.contentPanel.entrancePanel = Panel(
|
||||
uv_size=(1, 1),
|
||||
box_size=(0.2, 1),
|
||||
parent_point=(1, 0.5),
|
||||
point=(1, 0.5),
|
||||
)
|
||||
# 中文文本
|
||||
entrancePanel.namePanel = Panel(
|
||||
uv_size=(1, 1),
|
||||
box_size=(1, 0.5),
|
||||
parent_point=(1, 0),
|
||||
point=(1, 0),
|
||||
)
|
||||
entrancePanel.namePanel.text = Text(
|
||||
uv_size=(1, 1),
|
||||
box_size=(1, 1),
|
||||
parent_point=(0, 0.5),
|
||||
point=(0, 0.5),
|
||||
text=f"{entranceIdentifier}出入口",
|
||||
color=Color.BLACK,
|
||||
font_size=int(height * 0.2),
|
||||
force_size=True,
|
||||
font=font_bold
|
||||
)
|
||||
# 英文文本
|
||||
entrancePanel.englishNamePanel = Panel(
|
||||
uv_size=(1, 1),
|
||||
box_size=(1, 0.5),
|
||||
parent_point=(1, 1),
|
||||
point=(1, 1),
|
||||
)
|
||||
entrancePanel.englishNamePanel.text = Text(
|
||||
uv_size=(1, 1),
|
||||
box_size=(1, 1),
|
||||
parent_point=(0, 0.5),
|
||||
point=(0, 0.5),
|
||||
text=f"Entrance {entranceIdentifier}",
|
||||
color=Color.BLACK,
|
||||
font_size=int(height * 0.15),
|
||||
force_size=True,
|
||||
font=font_light
|
||||
)
|
||||
|
||||
return baseCanvas.base_img.tobytes()
|
||||
|
||||
|
||||
crt_alc = on_alconna(
|
||||
Alconna(
|
||||
"crt",
|
||||
Subcommand(
|
||||
"entrance",
|
||||
Args["name", str]["lines", str, ""]["entrance", int, 1], # /crt entrance 璧山&Bishan 1号线&Line1&#ff0000,27号线&Line1&#ff0000 1A
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
@crt_alc.assign("entrance")
|
||||
async def _(result: Arparma):
|
||||
args = result.subcommands.get("entrance").args
|
||||
name = args["name"]
|
||||
lines = args["lines"]
|
||||
entrance = args["entrance"]
|
||||
line_info = []
|
||||
for line in lines.split(","):
|
||||
line_args = line.split("&")
|
||||
line_info.append(Line(
|
||||
identifier=1,
|
||||
chineseName=line_args[0],
|
||||
englishName=line_args[1],
|
||||
color=line_args[2],
|
||||
stations=[]
|
||||
))
|
||||
img_bytes = await generate_entrance_sign(
|
||||
name=name,
|
||||
aliases=name.split("&"),
|
||||
lineInfo=line_info,
|
||||
entranceIdentifier=entrance,
|
||||
ratio=(8, 1),
|
||||
reso=256,
|
||||
)
|
||||
await crt_alc.finish(
|
||||
UniMessage.image(raw=img_bytes)
|
||||
)
|
||||
|
||||
|
||||
def generate_platform_line_pic(line: Line, station: Station, ratio=None, reso: int = resolution):
|
||||
"""
|
||||
生成站台线路图
|
||||
:param line: 线路对象
|
||||
:param station: 本站点对象
|
||||
:param ratio: 比例
|
||||
:param reso: 分辨率,1:reso
|
||||
:return: 两个方向的站牌
|
||||
"""
|
||||
if ratio is None:
|
||||
ratio = [4, 1]
|
||||
width, height = ratio[0] * reso, ratio[1] * reso
|
||||
baseCanvas = Canvas(Image.new("RGBA", (width, height), Color.YELLOW))
|
||||
# 加黑色图框
|
||||
baseCanvas.linePanel = Panel(
|
||||
uv_size=(1, 1),
|
||||
box_size=(0.8, 0.15),
|
||||
parent_point=(0.5, 0.5),
|
||||
point=(0.5, 0.5),
|
||||
)
|
||||
|
||||
# 直线块
|
||||
baseCanvas.linePanel.recLine = Img(
|
||||
uv_size=(1, 1),
|
||||
box_size=(1, 1),
|
||||
parent_point=(0.5, 0.5),
|
||||
point=(0.5, 0.5),
|
||||
img=Shape.rectangle(
|
||||
size=(10, 10),
|
||||
fill=line.color,
|
||||
),
|
||||
keep_ratio=False
|
||||
)
|
||||
# 灰色直线块
|
||||
baseCanvas.linePanel.recLineGrey = Img(
|
||||
uv_size=(1, 1),
|
||||
box_size=(1, 1),
|
||||
parent_point=(0.5, 0.5),
|
||||
point=(0.5, 0.5),
|
||||
img=Shape.rectangle(
|
||||
size=(10, 10),
|
||||
fill=Color.GREY,
|
||||
),
|
||||
keep_ratio=False
|
||||
)
|
||||
# 生成各站圆点
|
||||
outline_width = 40
|
||||
circleForward = Shape.circular(
|
||||
radius=200,
|
||||
fill=Color.WHITE,
|
||||
width=outline_width,
|
||||
outline=line.color,
|
||||
)
|
||||
|
||||
circleThisPanel = Canvas(Image.new("RGBA", (200, 200), (0, 0, 0, 0)))
|
||||
circleThisPanel.circleOuter = Img(
|
||||
uv_size=(1, 1),
|
||||
box_size=(1, 1),
|
||||
parent_point=(0.5, 0.5),
|
||||
point=(0.5, 0.5),
|
||||
img=Shape.circular(
|
||||
radius=200,
|
||||
fill=Color.WHITE,
|
||||
width=outline_width,
|
||||
outline=line.color,
|
||||
),
|
||||
)
|
||||
circleThisPanel.circleOuter.circleInner = Img(
|
||||
uv_size=(1, 1),
|
||||
box_size=(0.7, 0.7),
|
||||
parent_point=(0.5, 0.5),
|
||||
point=(0.5, 0.5),
|
||||
img=Shape.circular(
|
||||
radius=200,
|
||||
fill=line.color,
|
||||
width=0,
|
||||
outline=line.color,
|
||||
),
|
||||
)
|
||||
|
||||
circleThisPanel.export("a.png", alpha=True)
|
||||
circleThis = circleThisPanel.base_img
|
||||
|
||||
circlePassed = Shape.circular(
|
||||
radius=200,
|
||||
fill=Color.WHITE,
|
||||
width=outline_width,
|
||||
outline=Color.GREY,
|
||||
)
|
||||
|
||||
arrival = False
|
||||
distance = 1 / (len(line.stations) - 1)
|
||||
for i, sta in enumerate(line.stations):
|
||||
box_size = (1.618, 1.618)
|
||||
if sta.identifier == station.identifier:
|
||||
arrival = True
|
||||
baseCanvas.linePanel.recLine.__dict__["station_{}".format(sta.identifier)] = Img(
|
||||
uv_size=(1, 1),
|
||||
box_size=(1.8, 1.8),
|
||||
parent_point=(distance * i, 0.5),
|
||||
point=(0.5, 0.5),
|
||||
img=circleThis,
|
||||
keep_ratio=True
|
||||
)
|
||||
continue
|
||||
if arrival:
|
||||
# 后方站绘制
|
||||
baseCanvas.linePanel.recLine.__dict__["station_{}".format(sta.identifier)] = Img(
|
||||
uv_size=(1, 1),
|
||||
box_size=box_size,
|
||||
parent_point=(distance * i, 0.5),
|
||||
point=(0.5, 0.5),
|
||||
img=circleForward,
|
||||
keep_ratio=True
|
||||
)
|
||||
else:
|
||||
# 前方站绘制
|
||||
baseCanvas.linePanel.recLine.__dict__["station_{}".format(sta.identifier)] = Img(
|
||||
uv_size=(1, 1),
|
||||
box_size=box_size,
|
||||
parent_point=(distance * i, 0.5),
|
||||
point=(0.5, 0.5),
|
||||
img=circlePassed,
|
||||
keep_ratio=True
|
||||
)
|
||||
return baseCanvas
|
||||
|
||||
|
||||
def generate_platform_sign(name: str, aliases: List[str], lineInfo: List[Line], entranceIdentifier: str, ratio: tuple[int | float, int | float],
|
||||
reso: int = resolution
|
||||
):
|
||||
pass
|
||||
|
||||
# def main():
|
||||
# generate_entrance_sign(
|
||||
# "璧山",
|
||||
# aliases=["Bishan"],
|
||||
# lineInfo=[
|
||||
#
|
||||
# Line(identifier="2", chineseName="1号线", englishName="Line 1", color=Color.RED, stations=[]),
|
||||
# Line(identifier="3", chineseName="27号线", englishName="Line 27", color="#685bc7", stations=[]),
|
||||
# Line(identifier="1", chineseName="璧铜线", englishName="BT Line", color="#685BC7", stations=[]),
|
||||
# ],
|
||||
# entranceIdentifier="1",
|
||||
# ratio=(8, 1)
|
||||
# )
|
||||
#
|
||||
#
|
||||
# main()
|
@ -1,125 +1,125 @@
|
||||
import nonebot
|
||||
from nonebot import on_message, require
|
||||
from nonebot.plugin import PluginMetadata
|
||||
|
||||
from liteyuki.utils.base.data import Database, LiteModel
|
||||
from liteyuki.utils.base.ly_typing import T_Bot, T_MessageEvent
|
||||
from liteyuki.utils.message.message import MarkdownMessage as md
|
||||
|
||||
require("nonebot_plugin_alconna")
|
||||
from nonebot_plugin_alconna import on_alconna
|
||||
from arclet.alconna import Arparma, Alconna, Args, Option, Subcommand
|
||||
|
||||
|
||||
class Node(LiteModel):
|
||||
TABLE_NAME: str = "node"
|
||||
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):
|
||||
TABLE_NAME: str = "push"
|
||||
source: Node = Node()
|
||||
target: Node = Node()
|
||||
inde: int = 0
|
||||
|
||||
|
||||
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.where_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.where_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.where_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.where_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 line in str(event.message).split("\n"):
|
||||
msg_formatted += f"**{line.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 md.send_md(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,
|
||||
}
|
||||
)
|
||||
import nonebot
|
||||
from nonebot import on_message, require
|
||||
from nonebot.plugin import PluginMetadata
|
||||
|
||||
from src.utils.base.data import Database, LiteModel
|
||||
from src.utils.base.ly_typing import T_Bot, T_MessageEvent
|
||||
from src.utils.message.message import MarkdownMessage as md
|
||||
|
||||
require("nonebot_plugin_alconna")
|
||||
from nonebot_plugin_alconna import on_alconna
|
||||
from arclet.alconna import Arparma, Alconna, Args, Option, Subcommand
|
||||
|
||||
|
||||
class Node(LiteModel):
|
||||
TABLE_NAME: str = "node"
|
||||
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):
|
||||
TABLE_NAME: str = "push"
|
||||
source: Node = Node()
|
||||
target: Node = Node()
|
||||
inde: int = 0
|
||||
|
||||
|
||||
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.where_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.where_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.where_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.where_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 line in str(event.message).split("\n"):
|
||||
msg_formatted += f"**{line.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 md.send_md(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,52 +1,52 @@
|
||||
from nonebot import on_command, require
|
||||
from nonebot.adapters.onebot.v11 import MessageSegment
|
||||
from nonebot.params import CommandArg
|
||||
from nonebot.permission import SUPERUSER
|
||||
from nonebot.plugin import PluginMetadata
|
||||
|
||||
from liteyuki.utils.base.ly_typing import T_Bot, T_MessageEvent, v11
|
||||
from liteyuki.utils.message.message import MarkdownMessage as md, broadcast_to_superusers
|
||||
from liteyuki.utils.message.html_tool import *
|
||||
|
||||
md_test = on_command("mdts", permission=SUPERUSER)
|
||||
btn_test = on_command("btnts", permission=SUPERUSER)
|
||||
latex_test = on_command("latex", permission=SUPERUSER)
|
||||
|
||||
|
||||
@md_test.handle()
|
||||
async def _(bot: T_Bot, event: T_MessageEvent, arg: v11.Message = CommandArg()):
|
||||
await md.send_md(
|
||||
v11.utils.unescape(str(arg)),
|
||||
bot,
|
||||
message_type=event.message_type,
|
||||
session_id=event.user_id if event.message_type == "private" else event.group_id
|
||||
)
|
||||
|
||||
|
||||
@btn_test.handle()
|
||||
async def _(bot: T_Bot, event: T_MessageEvent, arg: v11.Message = CommandArg()):
|
||||
await md.send_btn(
|
||||
str(arg),
|
||||
bot,
|
||||
message_type=event.message_type,
|
||||
session_id=event.user_id if event.message_type == "private" else event.group_id
|
||||
)
|
||||
|
||||
|
||||
@latex_test.handle()
|
||||
async def _(bot: T_Bot, event: T_MessageEvent, arg: v11.Message = CommandArg()):
|
||||
latex_text = f"$${v11.utils.unescape(str(arg))}$$"
|
||||
img = await md_to_pic(latex_text)
|
||||
await bot.send(event=event, message=MessageSegment.image(img))
|
||||
|
||||
|
||||
__author__ = "snowykami"
|
||||
__plugin_meta__ = PluginMetadata(
|
||||
name="轻雪Markdown测试",
|
||||
description="用于测试Markdown的插件",
|
||||
usage="",
|
||||
homepage="https://github.com/snowykami/LiteyukiBot",
|
||||
extra={
|
||||
"liteyuki": True,
|
||||
}
|
||||
)
|
||||
from nonebot import on_command, require
|
||||
from nonebot.adapters.onebot.v11 import MessageSegment
|
||||
from nonebot.params import CommandArg
|
||||
from nonebot.permission import SUPERUSER
|
||||
from nonebot.plugin import PluginMetadata
|
||||
|
||||
from src.utils.base.ly_typing import T_Bot, T_MessageEvent, v11
|
||||
from src.utils.message.message import MarkdownMessage as md, broadcast_to_superusers
|
||||
from src.utils.message.html_tool import *
|
||||
|
||||
md_test = on_command("mdts", permission=SUPERUSER)
|
||||
btn_test = on_command("btnts", permission=SUPERUSER)
|
||||
latex_test = on_command("latex", permission=SUPERUSER)
|
||||
|
||||
|
||||
@md_test.handle()
|
||||
async def _(bot: T_Bot, event: T_MessageEvent, arg: v11.Message = CommandArg()):
|
||||
await md.send_md(
|
||||
v11.utils.unescape(str(arg)),
|
||||
bot,
|
||||
message_type=event.message_type,
|
||||
session_id=event.user_id if event.message_type == "private" else event.group_id
|
||||
)
|
||||
|
||||
|
||||
@btn_test.handle()
|
||||
async def _(bot: T_Bot, event: T_MessageEvent, arg: v11.Message = CommandArg()):
|
||||
await md.send_btn(
|
||||
str(arg),
|
||||
bot,
|
||||
message_type=event.message_type,
|
||||
session_id=event.user_id if event.message_type == "private" else event.group_id
|
||||
)
|
||||
|
||||
|
||||
@latex_test.handle()
|
||||
async def _(bot: T_Bot, event: T_MessageEvent, arg: v11.Message = CommandArg()):
|
||||
latex_text = f"$${v11.utils.unescape(str(arg))}$$"
|
||||
img = await md_to_pic(latex_text)
|
||||
await bot.send(event=event, message=MessageSegment.image(img))
|
||||
|
||||
|
||||
__author__ = "snowykami"
|
||||
__plugin_meta__ = PluginMetadata(
|
||||
name="轻雪Markdown测试",
|
||||
description="用于测试Markdown的插件",
|
||||
usage="",
|
||||
homepage="https://github.com/snowykami/LiteyukiBot",
|
||||
extra={
|
||||
"liteyuki": True,
|
||||
}
|
||||
)
|
@ -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,15 +1,15 @@
|
||||
from nonebot.plugin import PluginMetadata
|
||||
from .minesweeper import *
|
||||
|
||||
__plugin_meta__ = PluginMetadata(
|
||||
name="轻雪小游戏",
|
||||
description="内置了一些小游戏",
|
||||
usage="",
|
||||
type="application",
|
||||
homepage="https://github.com/snowykami/LiteyukiBot",
|
||||
extra={
|
||||
"liteyuki": True,
|
||||
"toggleable" : True,
|
||||
"default_enable" : True,
|
||||
}
|
||||
)
|
||||
from nonebot.plugin import PluginMetadata
|
||||
from .minesweeper import *
|
||||
|
||||
__plugin_meta__ = PluginMetadata(
|
||||
name="轻雪小游戏",
|
||||
description="内置了一些小游戏",
|
||||
usage="",
|
||||
type="application",
|
||||
homepage="https://github.com/snowykami/LiteyukiBot",
|
||||
extra={
|
||||
"liteyuki": True,
|
||||
"toggleable" : True,
|
||||
"default_enable" : True,
|
||||
}
|
||||
)
|
@ -1,169 +1,169 @@
|
||||
import random
|
||||
from pydantic import BaseModel
|
||||
from liteyuki.utils.message.message import MarkdownMessage as md
|
||||
|
||||
class Dot(BaseModel):
|
||||
row: int
|
||||
col: int
|
||||
mask: bool = True
|
||||
value: int = 0
|
||||
flagged: bool = False
|
||||
|
||||
|
||||
class Minesweeper:
|
||||
# 0-8: number of mines around, 9: mine, -1: undefined
|
||||
NUMS = "⓪①②③④⑤⑥⑦⑧🅑⑩⑪⑫⑬⑭⑮⑯⑰⑱⑲⑳"
|
||||
MASK = "🅜"
|
||||
FLAG = "🅕"
|
||||
MINE = "🅑"
|
||||
|
||||
def __init__(self, rows, cols, num_mines, session_type, session_id):
|
||||
assert rows > 0 and cols > 0 and 0 < num_mines < rows * cols
|
||||
self.session_type = session_type
|
||||
self.session_id = session_id
|
||||
self.rows = rows
|
||||
self.cols = cols
|
||||
self.num_mines = num_mines
|
||||
self.board: list[list[Dot]] = [[Dot(row=i, col=j) for j in range(cols)] for i in range(rows)]
|
||||
self.is_first = True
|
||||
|
||||
def reveal(self, row, col) -> bool:
|
||||
"""
|
||||
展开
|
||||
Args:
|
||||
row:
|
||||
col:
|
||||
|
||||
Returns:
|
||||
游戏是否继续
|
||||
|
||||
"""
|
||||
|
||||
if self.is_first:
|
||||
# 第一次展开,生成地雷
|
||||
self.generate_board(self.board[row][col])
|
||||
self.is_first = False
|
||||
|
||||
if self.board[row][col].value == 9:
|
||||
self.board[row][col].mask = False
|
||||
return False
|
||||
|
||||
if not self.board[row][col].mask:
|
||||
return True
|
||||
|
||||
self.board[row][col].mask = False
|
||||
|
||||
if self.board[row][col].value == 0:
|
||||
self.reveal_neighbors(row, col)
|
||||
return True
|
||||
|
||||
def is_win(self) -> bool:
|
||||
"""
|
||||
是否胜利
|
||||
Returns:
|
||||
"""
|
||||
for row in range(self.rows):
|
||||
for col in range(self.cols):
|
||||
if self.board[row][col].mask and self.board[row][col].value != 9:
|
||||
return False
|
||||
return True
|
||||
|
||||
def generate_board(self, first_dot: Dot):
|
||||
"""
|
||||
避开第一个点,生成地雷
|
||||
Args:
|
||||
first_dot: 第一个点
|
||||
|
||||
Returns:
|
||||
|
||||
"""
|
||||
generate_count = 0
|
||||
while generate_count < self.num_mines:
|
||||
row = random.randint(0, self.rows - 1)
|
||||
col = random.randint(0, self.cols - 1)
|
||||
if self.board[row][col].value == 9 or (row, col) == (first_dot.row, first_dot.col):
|
||||
continue
|
||||
self.board[row][col] = Dot(row=row, col=col, mask=True, value=9)
|
||||
generate_count += 1
|
||||
|
||||
for row in range(self.rows):
|
||||
for col in range(self.cols):
|
||||
if self.board[row][col].value != 9:
|
||||
self.board[row][col].value = self.count_adjacent_mines(row, col)
|
||||
|
||||
def count_adjacent_mines(self, row, col):
|
||||
"""
|
||||
计算周围地雷数量
|
||||
Args:
|
||||
row:
|
||||
col:
|
||||
|
||||
Returns:
|
||||
|
||||
"""
|
||||
count = 0
|
||||
for r in range(max(0, row - 1), min(self.rows, row + 2)):
|
||||
for c in range(max(0, col - 1), min(self.cols, col + 2)):
|
||||
if self.board[r][c].value == 9:
|
||||
count += 1
|
||||
return count
|
||||
|
||||
def reveal_neighbors(self, row, col):
|
||||
"""
|
||||
递归展开,使用深度优先搜索
|
||||
Args:
|
||||
row:
|
||||
col:
|
||||
|
||||
Returns:
|
||||
|
||||
"""
|
||||
for r in range(max(0, row - 1), min(self.rows, row + 2)):
|
||||
for c in range(max(0, col - 1), min(self.cols, col + 2)):
|
||||
if self.board[r][c].mask:
|
||||
self.board[r][c].mask = False
|
||||
if self.board[r][c].value == 0:
|
||||
self.reveal_neighbors(r, c)
|
||||
|
||||
def mark(self, row, col) -> bool:
|
||||
"""
|
||||
标记
|
||||
Args:
|
||||
row:
|
||||
col:
|
||||
Returns:
|
||||
是否标记成功,如果已经展开则无法标记
|
||||
"""
|
||||
if self.board[row][col].mask:
|
||||
self.board[row][col].flagged = not self.board[row][col].flagged
|
||||
return self.board[row][col].flagged
|
||||
|
||||
def board_markdown(self) -> str:
|
||||
"""
|
||||
打印地雷板
|
||||
Returns:
|
||||
"""
|
||||
dis = " "
|
||||
start = "> " if self.cols >= 10 else ""
|
||||
text = start + self.NUMS[0] + dis*2
|
||||
# 横向两个雷之间的间隔字符
|
||||
# 生成横向索引
|
||||
for i in range(self.cols):
|
||||
text += f"{self.NUMS[i]}" + dis
|
||||
text += "\n\n"
|
||||
for i, row in enumerate(self.board):
|
||||
text += start + f"{self.NUMS[i]}" + dis*2
|
||||
print([d.value for d in row])
|
||||
for dot in row:
|
||||
if dot.mask and not dot.flagged:
|
||||
text += md.btn_cmd(self.MASK, f"minesweeper reveal {dot.row} {dot.col}")
|
||||
elif dot.flagged:
|
||||
text += md.btn_cmd(self.FLAG, f"minesweeper mark {dot.row} {dot.col}")
|
||||
else:
|
||||
text += self.NUMS[dot.value]
|
||||
text += dis
|
||||
text += "\n"
|
||||
btn_mark = md.btn_cmd("标记", f"minesweeper mark ", enter=False)
|
||||
btn_end = md.btn_cmd("结束", "minesweeper end", enter=True)
|
||||
text += f" {btn_mark} {btn_end}"
|
||||
return text
|
||||
import random
|
||||
from pydantic import BaseModel
|
||||
from src.utils.message.message import MarkdownMessage as md
|
||||
|
||||
class Dot(BaseModel):
|
||||
row: int
|
||||
col: int
|
||||
mask: bool = True
|
||||
value: int = 0
|
||||
flagged: bool = False
|
||||
|
||||
|
||||
class Minesweeper:
|
||||
# 0-8: number of mines around, 9: mine, -1: undefined
|
||||
NUMS = "⓪①②③④⑤⑥⑦⑧🅑⑩⑪⑫⑬⑭⑮⑯⑰⑱⑲⑳"
|
||||
MASK = "🅜"
|
||||
FLAG = "🅕"
|
||||
MINE = "🅑"
|
||||
|
||||
def __init__(self, rows, cols, num_mines, session_type, session_id):
|
||||
assert rows > 0 and cols > 0 and 0 < num_mines < rows * cols
|
||||
self.session_type = session_type
|
||||
self.session_id = session_id
|
||||
self.rows = rows
|
||||
self.cols = cols
|
||||
self.num_mines = num_mines
|
||||
self.board: list[list[Dot]] = [[Dot(row=i, col=j) for j in range(cols)] for i in range(rows)]
|
||||
self.is_first = True
|
||||
|
||||
def reveal(self, row, col) -> bool:
|
||||
"""
|
||||
展开
|
||||
Args:
|
||||
row:
|
||||
col:
|
||||
|
||||
Returns:
|
||||
游戏是否继续
|
||||
|
||||
"""
|
||||
|
||||
if self.is_first:
|
||||
# 第一次展开,生成地雷
|
||||
self.generate_board(self.board[row][col])
|
||||
self.is_first = False
|
||||
|
||||
if self.board[row][col].value == 9:
|
||||
self.board[row][col].mask = False
|
||||
return False
|
||||
|
||||
if not self.board[row][col].mask:
|
||||
return True
|
||||
|
||||
self.board[row][col].mask = False
|
||||
|
||||
if self.board[row][col].value == 0:
|
||||
self.reveal_neighbors(row, col)
|
||||
return True
|
||||
|
||||
def is_win(self) -> bool:
|
||||
"""
|
||||
是否胜利
|
||||
Returns:
|
||||
"""
|
||||
for row in range(self.rows):
|
||||
for col in range(self.cols):
|
||||
if self.board[row][col].mask and self.board[row][col].value != 9:
|
||||
return False
|
||||
return True
|
||||
|
||||
def generate_board(self, first_dot: Dot):
|
||||
"""
|
||||
避开第一个点,生成地雷
|
||||
Args:
|
||||
first_dot: 第一个点
|
||||
|
||||
Returns:
|
||||
|
||||
"""
|
||||
generate_count = 0
|
||||
while generate_count < self.num_mines:
|
||||
row = random.randint(0, self.rows - 1)
|
||||
col = random.randint(0, self.cols - 1)
|
||||
if self.board[row][col].value == 9 or (row, col) == (first_dot.row, first_dot.col):
|
||||
continue
|
||||
self.board[row][col] = Dot(row=row, col=col, mask=True, value=9)
|
||||
generate_count += 1
|
||||
|
||||
for row in range(self.rows):
|
||||
for col in range(self.cols):
|
||||
if self.board[row][col].value != 9:
|
||||
self.board[row][col].value = self.count_adjacent_mines(row, col)
|
||||
|
||||
def count_adjacent_mines(self, row, col):
|
||||
"""
|
||||
计算周围地雷数量
|
||||
Args:
|
||||
row:
|
||||
col:
|
||||
|
||||
Returns:
|
||||
|
||||
"""
|
||||
count = 0
|
||||
for r in range(max(0, row - 1), min(self.rows, row + 2)):
|
||||
for c in range(max(0, col - 1), min(self.cols, col + 2)):
|
||||
if self.board[r][c].value == 9:
|
||||
count += 1
|
||||
return count
|
||||
|
||||
def reveal_neighbors(self, row, col):
|
||||
"""
|
||||
递归展开,使用深度优先搜索
|
||||
Args:
|
||||
row:
|
||||
col:
|
||||
|
||||
Returns:
|
||||
|
||||
"""
|
||||
for r in range(max(0, row - 1), min(self.rows, row + 2)):
|
||||
for c in range(max(0, col - 1), min(self.cols, col + 2)):
|
||||
if self.board[r][c].mask:
|
||||
self.board[r][c].mask = False
|
||||
if self.board[r][c].value == 0:
|
||||
self.reveal_neighbors(r, c)
|
||||
|
||||
def mark(self, row, col) -> bool:
|
||||
"""
|
||||
标记
|
||||
Args:
|
||||
row:
|
||||
col:
|
||||
Returns:
|
||||
是否标记成功,如果已经展开则无法标记
|
||||
"""
|
||||
if self.board[row][col].mask:
|
||||
self.board[row][col].flagged = not self.board[row][col].flagged
|
||||
return self.board[row][col].flagged
|
||||
|
||||
def board_markdown(self) -> str:
|
||||
"""
|
||||
打印地雷板
|
||||
Returns:
|
||||
"""
|
||||
dis = " "
|
||||
start = "> " if self.cols >= 10 else ""
|
||||
text = start + self.NUMS[0] + dis*2
|
||||
# 横向两个雷之间的间隔字符
|
||||
# 生成横向索引
|
||||
for i in range(self.cols):
|
||||
text += f"{self.NUMS[i]}" + dis
|
||||
text += "\n\n"
|
||||
for i, row in enumerate(self.board):
|
||||
text += start + f"{self.NUMS[i]}" + dis*2
|
||||
print([d.value for d in row])
|
||||
for dot in row:
|
||||
if dot.mask and not dot.flagged:
|
||||
text += md.btn_cmd(self.MASK, f"minesweeper reveal {dot.row} {dot.col}")
|
||||
elif dot.flagged:
|
||||
text += md.btn_cmd(self.FLAG, f"minesweeper mark {dot.row} {dot.col}")
|
||||
else:
|
||||
text += self.NUMS[dot.value]
|
||||
text += dis
|
||||
text += "\n"
|
||||
btn_mark = md.btn_cmd("标记", f"minesweeper mark ", enter=False)
|
||||
btn_end = md.btn_cmd("结束", "minesweeper end", enter=True)
|
||||
text += f" {btn_mark} {btn_end}"
|
||||
return text
|
@ -1,103 +1,103 @@
|
||||
from nonebot import require
|
||||
|
||||
from liteyuki.utils.base.ly_typing import T_Bot, T_MessageEvent
|
||||
from liteyuki.utils.message.message import MarkdownMessage as md
|
||||
|
||||
require("nonebot_plugin_alconna")
|
||||
from .game import Minesweeper
|
||||
|
||||
from nonebot_plugin_alconna import Alconna, on_alconna, Subcommand, Args, Arparma
|
||||
|
||||
minesweeper = on_alconna(
|
||||
aliases={"扫雷"},
|
||||
command=Alconna(
|
||||
"minesweeper",
|
||||
Subcommand(
|
||||
"start",
|
||||
Args["row", int, 8]["col", int, 8]["mines", int, 10],
|
||||
alias=["开始"],
|
||||
|
||||
),
|
||||
Subcommand(
|
||||
"end",
|
||||
alias=["结束"]
|
||||
),
|
||||
Subcommand(
|
||||
"reveal",
|
||||
Args["row", int]["col", int],
|
||||
alias=["展开"]
|
||||
|
||||
),
|
||||
Subcommand(
|
||||
"mark",
|
||||
Args["row", int]["col", int],
|
||||
alias=["标记"]
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
minesweeper_cache: list[Minesweeper] = []
|
||||
|
||||
|
||||
def get_minesweeper_cache(event: T_MessageEvent) -> Minesweeper | None:
|
||||
for i in minesweeper_cache:
|
||||
if i.session_type == event.message_type:
|
||||
if i.session_id == event.user_id or i.session_id == event.group_id:
|
||||
return i
|
||||
return None
|
||||
|
||||
|
||||
@minesweeper.handle()
|
||||
async def _(event: T_MessageEvent, result: Arparma, bot: T_Bot):
|
||||
game = get_minesweeper_cache(event)
|
||||
if result.subcommands.get("start"):
|
||||
if game:
|
||||
await minesweeper.finish("当前会话不能同时进行多个扫雷游戏")
|
||||
else:
|
||||
try:
|
||||
new_game = Minesweeper(
|
||||
rows=result.subcommands["start"].args["row"],
|
||||
cols=result.subcommands["start"].args["col"],
|
||||
num_mines=result.subcommands["start"].args["mines"],
|
||||
session_type=event.message_type,
|
||||
session_id=event.user_id if event.message_type == "private" else event.group_id,
|
||||
)
|
||||
minesweeper_cache.append(new_game)
|
||||
await minesweeper.send("游戏开始")
|
||||
await md.send_md(new_game.board_markdown(), bot, event=event)
|
||||
except AssertionError:
|
||||
await minesweeper.finish("参数错误")
|
||||
elif result.subcommands.get("end"):
|
||||
if game:
|
||||
minesweeper_cache.remove(game)
|
||||
await minesweeper.finish("游戏结束")
|
||||
else:
|
||||
await minesweeper.finish("当前没有扫雷游戏")
|
||||
elif result.subcommands.get("reveal"):
|
||||
if not game:
|
||||
await minesweeper.finish("当前没有扫雷游戏")
|
||||
else:
|
||||
row = result.subcommands["reveal"].args["row"]
|
||||
col = result.subcommands["reveal"].args["col"]
|
||||
if not (0 <= row < game.rows and 0 <= col < game.cols):
|
||||
await minesweeper.finish("参数错误")
|
||||
if not game.reveal(row, col):
|
||||
minesweeper_cache.remove(game)
|
||||
await md.send_md(game.board_markdown(), bot, event=event)
|
||||
await minesweeper.finish("游戏结束")
|
||||
await md.send_md(game.board_markdown(), bot, event=event)
|
||||
if game.is_win():
|
||||
minesweeper_cache.remove(game)
|
||||
await minesweeper.finish("游戏胜利")
|
||||
elif result.subcommands.get("mark"):
|
||||
if not game:
|
||||
await minesweeper.finish("当前没有扫雷游戏")
|
||||
else:
|
||||
row = result.subcommands["mark"].args["row"]
|
||||
col = result.subcommands["mark"].args["col"]
|
||||
if not (0 <= row < game.rows and 0 <= col < game.cols):
|
||||
await minesweeper.finish("参数错误")
|
||||
game.board[row][col].flagged = not game.board[row][col].flagged
|
||||
await md.send_md(game.board_markdown(), bot, event=event)
|
||||
else:
|
||||
await minesweeper.finish("参数错误")
|
||||
from nonebot import require
|
||||
|
||||
from src.utils.base.ly_typing import T_Bot, T_MessageEvent
|
||||
from src.utils.message.message import MarkdownMessage as md
|
||||
|
||||
require("nonebot_plugin_alconna")
|
||||
from .game import Minesweeper
|
||||
|
||||
from nonebot_plugin_alconna import Alconna, on_alconna, Subcommand, Args, Arparma
|
||||
|
||||
minesweeper = on_alconna(
|
||||
aliases={"扫雷"},
|
||||
command=Alconna(
|
||||
"minesweeper",
|
||||
Subcommand(
|
||||
"start",
|
||||
Args["row", int, 8]["col", int, 8]["mines", int, 10],
|
||||
alias=["开始"],
|
||||
|
||||
),
|
||||
Subcommand(
|
||||
"end",
|
||||
alias=["结束"]
|
||||
),
|
||||
Subcommand(
|
||||
"reveal",
|
||||
Args["row", int]["col", int],
|
||||
alias=["展开"]
|
||||
|
||||
),
|
||||
Subcommand(
|
||||
"mark",
|
||||
Args["row", int]["col", int],
|
||||
alias=["标记"]
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
minesweeper_cache: list[Minesweeper] = []
|
||||
|
||||
|
||||
def get_minesweeper_cache(event: T_MessageEvent) -> Minesweeper | None:
|
||||
for i in minesweeper_cache:
|
||||
if i.session_type == event.message_type:
|
||||
if i.session_id == event.user_id or i.session_id == event.group_id:
|
||||
return i
|
||||
return None
|
||||
|
||||
|
||||
@minesweeper.handle()
|
||||
async def _(event: T_MessageEvent, result: Arparma, bot: T_Bot):
|
||||
game = get_minesweeper_cache(event)
|
||||
if result.subcommands.get("start"):
|
||||
if game:
|
||||
await minesweeper.finish("当前会话不能同时进行多个扫雷游戏")
|
||||
else:
|
||||
try:
|
||||
new_game = Minesweeper(
|
||||
rows=result.subcommands["start"].args["row"],
|
||||
cols=result.subcommands["start"].args["col"],
|
||||
num_mines=result.subcommands["start"].args["mines"],
|
||||
session_type=event.message_type,
|
||||
session_id=event.user_id if event.message_type == "private" else event.group_id,
|
||||
)
|
||||
minesweeper_cache.append(new_game)
|
||||
await minesweeper.send("游戏开始")
|
||||
await md.send_md(new_game.board_markdown(), bot, event=event)
|
||||
except AssertionError:
|
||||
await minesweeper.finish("参数错误")
|
||||
elif result.subcommands.get("end"):
|
||||
if game:
|
||||
minesweeper_cache.remove(game)
|
||||
await minesweeper.finish("游戏结束")
|
||||
else:
|
||||
await minesweeper.finish("当前没有扫雷游戏")
|
||||
elif result.subcommands.get("reveal"):
|
||||
if not game:
|
||||
await minesweeper.finish("当前没有扫雷游戏")
|
||||
else:
|
||||
row = result.subcommands["reveal"].args["row"]
|
||||
col = result.subcommands["reveal"].args["col"]
|
||||
if not (0 <= row < game.rows and 0 <= col < game.cols):
|
||||
await minesweeper.finish("参数错误")
|
||||
if not game.reveal(row, col):
|
||||
minesweeper_cache.remove(game)
|
||||
await md.send_md(game.board_markdown(), bot, event=event)
|
||||
await minesweeper.finish("游戏结束")
|
||||
await md.send_md(game.board_markdown(), bot, event=event)
|
||||
if game.is_win():
|
||||
minesweeper_cache.remove(game)
|
||||
await minesweeper.finish("游戏胜利")
|
||||
elif result.subcommands.get("mark"):
|
||||
if not game:
|
||||
await minesweeper.finish("当前没有扫雷游戏")
|
||||
else:
|
||||
row = result.subcommands["mark"].args["row"]
|
||||
col = result.subcommands["mark"].args["col"]
|
||||
if not (0 <= row < game.rows and 0 <= col < game.cols):
|
||||
await minesweeper.finish("参数错误")
|
||||
game.board[row][col].flagged = not game.board[row][col].flagged
|
||||
await md.send_md(game.board_markdown(), bot, event=event)
|
||||
else:
|
||||
await minesweeper.finish("参数错误")
|
@ -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,22 +1,22 @@
|
||||
from nonebot.plugin import PluginMetadata
|
||||
from .npm import *
|
||||
from .rpm import *
|
||||
|
||||
__author__ = "snowykami"
|
||||
__plugin_meta__ = PluginMetadata(
|
||||
name="轻雪包管理器",
|
||||
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" : True,
|
||||
}
|
||||
)
|
||||
from nonebot.plugin import PluginMetadata
|
||||
from .npm import *
|
||||
from .rpm import *
|
||||
|
||||
__author__ = "snowykami"
|
||||
__plugin_meta__ = PluginMetadata(
|
||||
name="轻雪包管理器",
|
||||
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" : True,
|
||||
}
|
||||
)
|
@ -1,256 +1,256 @@
|
||||
import json
|
||||
from typing import Optional
|
||||
|
||||
import aiofiles
|
||||
import nonebot.plugin
|
||||
from nonebot.adapters import satori
|
||||
|
||||
from liteyuki.utils import event as event_utils
|
||||
from liteyuki.utils.base.data import LiteModel
|
||||
from liteyuki.utils.base.data_manager import GlobalPlugin, Group, User, group_db, plugin_db, user_db
|
||||
from liteyuki.utils.base.ly_typing import T_MessageEvent
|
||||
|
||||
__group_data = {} # 群数据缓存, {group_id: Group}
|
||||
__user_data = {} # 用户数据缓存, {user_id: User}
|
||||
__default_enable = {} # 插件默认启用状态缓存, {plugin_name: bool} static
|
||||
__global_enable = {} # 插件全局启用状态缓存, {plugin_name: bool} dynamic
|
||||
|
||||
|
||||
class PluginTag(LiteModel):
|
||||
label: str
|
||||
color: str = '#000000'
|
||||
|
||||
|
||||
class StorePlugin(LiteModel):
|
||||
name: str
|
||||
desc: str
|
||||
module_name: str # 插件商店中的模块名不等于本地的模块名,前者是文件夹名,后者是点分割模块名
|
||||
project_link: str = ""
|
||||
homepage: str = ""
|
||||
author: str = ""
|
||||
type: str | None = None
|
||||
version: str | None = ""
|
||||
time: str = ""
|
||||
tags: list[PluginTag] = []
|
||||
is_official: bool = False
|
||||
|
||||
|
||||
def get_plugin_exist(plugin_name: str) -> bool:
|
||||
"""
|
||||
获取插件是否存在于加载列表
|
||||
Args:
|
||||
plugin_name:
|
||||
|
||||
Returns:
|
||||
|
||||
"""
|
||||
for plugin in nonebot.plugin.get_loaded_plugins():
|
||||
if plugin.name == plugin_name:
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
async def get_store_plugin(plugin_name: str) -> Optional[StorePlugin]:
|
||||
"""
|
||||
获取插件信息
|
||||
|
||||
Args:
|
||||
plugin_name (str): 插件模块名
|
||||
|
||||
Returns:
|
||||
Optional[StorePlugin]: 插件信息
|
||||
"""
|
||||
async with aiofiles.open("data/liteyuki/plugins.json", "r", encoding="utf-8") as f:
|
||||
plugins: list[StorePlugin] = [StorePlugin(**pobj) for pobj in json.loads(await f.read())]
|
||||
for plugin in plugins:
|
||||
if plugin.module_name == plugin_name:
|
||||
return plugin
|
||||
return None
|
||||
|
||||
|
||||
def get_plugin_default_enable(plugin_name: str) -> bool:
|
||||
"""
|
||||
获取插件默认启用状态,由插件定义,不存在则默认为启用,优先从缓存中获取
|
||||
|
||||
Args:
|
||||
plugin_name (str): 插件模块名
|
||||
|
||||
Returns:
|
||||
bool: 插件默认状态
|
||||
"""
|
||||
if plugin_name not in __default_enable:
|
||||
plug = nonebot.plugin.get_plugin(plugin_name)
|
||||
default_enable = (plug.metadata.extra.get("default_enable", True) if plug.metadata else True) if plug else True
|
||||
__default_enable[plugin_name] = default_enable
|
||||
|
||||
return __default_enable[plugin_name]
|
||||
|
||||
|
||||
def get_plugin_session_enable(event: T_MessageEvent, plugin_name: str) -> bool:
|
||||
"""
|
||||
获取插件当前会话启用状态
|
||||
|
||||
Args:
|
||||
event: 会话事件
|
||||
plugin_name (str): 插件模块名
|
||||
|
||||
Returns:
|
||||
bool: 插件当前状态
|
||||
"""
|
||||
if isinstance(event, satori.event.Event):
|
||||
if event.guild is not None:
|
||||
message_type = "group"
|
||||
else:
|
||||
message_type = "private"
|
||||
else:
|
||||
message_type = event.message_type
|
||||
if message_type == "group":
|
||||
group_id = str(event.guild.id if isinstance(event, satori.event.Event) else event.group_id)
|
||||
if group_id not in __group_data:
|
||||
group: Group = group_db.where_one(Group(), "group_id = ?", group_id, default=Group(group_id=group_id))
|
||||
__group_data[str(group_id)] = group
|
||||
|
||||
session = __group_data[group_id]
|
||||
else:
|
||||
# session: User = user_db.first(User(), "user_id = ?", event.user_id, default=User(user_id=str(event.user_id)))
|
||||
user_id = str(event.user.id if isinstance(event, satori.event.Event) else event.user_id)
|
||||
if user_id not in __user_data:
|
||||
user: User = user_db.where_one(User(), "user_id = ?", user_id, default=User(user_id=user_id))
|
||||
__user_data[user_id] = user
|
||||
session = __user_data[user_id]
|
||||
# 默认停用插件在启用列表内表示启用
|
||||
# 默认停用插件不在启用列表内表示停用
|
||||
# 默认启用插件在停用列表内表示停用
|
||||
# 默认启用插件不在停用列表内表示启用
|
||||
default_enable = get_plugin_default_enable(plugin_name)
|
||||
if default_enable:
|
||||
return plugin_name not in session.disabled_plugins
|
||||
else:
|
||||
return plugin_name in session.enabled_plugins
|
||||
|
||||
|
||||
def set_plugin_session_enable(event: T_MessageEvent, plugin_name: str, enable: bool):
|
||||
"""
|
||||
设置插件会话启用状态,同时更新数据库和缓存
|
||||
Args:
|
||||
event:
|
||||
plugin_name:
|
||||
enable:
|
||||
|
||||
Returns:
|
||||
|
||||
"""
|
||||
if event_utils.get_message_type(event) == "group":
|
||||
session: Group = group_db.where_one(Group(), "group_id = ?", str(event_utils.get_group_id(event)),
|
||||
default=Group(group_id=str(event_utils.get_group_id(event))))
|
||||
else:
|
||||
session: User = user_db.where_one(User(), "user_id = ?", str(event_utils.get_user_id(event)),
|
||||
default=User(user_id=str(event_utils.get_user_id(event))))
|
||||
print(session)
|
||||
default_enable = get_plugin_default_enable(plugin_name)
|
||||
if default_enable:
|
||||
if enable:
|
||||
session.disabled_plugins.remove(plugin_name)
|
||||
else:
|
||||
session.disabled_plugins.append(plugin_name)
|
||||
else:
|
||||
if enable:
|
||||
session.enabled_plugins.append(plugin_name)
|
||||
else:
|
||||
session.enabled_plugins.remove(plugin_name)
|
||||
|
||||
if event_utils.get_message_type(event) == "group":
|
||||
__group_data[str(event_utils.get_group_id(event))] = session
|
||||
group_db.save(session)
|
||||
else:
|
||||
__user_data[str(event_utils.get_user_id(event))] = session
|
||||
user_db.save(session)
|
||||
|
||||
|
||||
def get_plugin_global_enable(plugin_name: str) -> bool:
|
||||
"""
|
||||
获取插件全局启用状态, 优先从缓存中获取
|
||||
Args:
|
||||
plugin_name:
|
||||
|
||||
Returns:
|
||||
|
||||
"""
|
||||
if plugin_name not in __global_enable:
|
||||
plugin = plugin_db.where_one(
|
||||
GlobalPlugin(),
|
||||
"module_name = ?",
|
||||
plugin_name,
|
||||
default=GlobalPlugin(module_name=plugin_name, enabled=True))
|
||||
__global_enable[plugin_name] = plugin.enabled
|
||||
|
||||
return __global_enable[plugin_name]
|
||||
|
||||
|
||||
def set_plugin_global_enable(plugin_name: str, enable: bool):
|
||||
"""
|
||||
设置插件全局启用状态,同时更新数据库和缓存
|
||||
Args:
|
||||
plugin_name:
|
||||
enable:
|
||||
|
||||
Returns:
|
||||
|
||||
"""
|
||||
plugin = plugin_db.where_one(
|
||||
GlobalPlugin(),
|
||||
"module_name = ?",
|
||||
plugin_name,
|
||||
default=GlobalPlugin(module_name=plugin_name, enabled=True))
|
||||
plugin.enabled = enable
|
||||
|
||||
plugin_db.save(plugin)
|
||||
__global_enable[plugin_name] = enable
|
||||
|
||||
|
||||
def get_plugin_can_be_toggle(plugin_name: str) -> bool:
|
||||
"""
|
||||
获取插件是否可以被启用/停用
|
||||
|
||||
Args:
|
||||
plugin_name (str): 插件模块名
|
||||
|
||||
Returns:
|
||||
bool: 插件是否可以被启用/停用
|
||||
"""
|
||||
plug = nonebot.plugin.get_plugin(plugin_name)
|
||||
return plug.metadata.extra.get("toggleable", True) if plug and plug.metadata else True
|
||||
|
||||
|
||||
def get_group_enable(group_id: str) -> bool:
|
||||
"""
|
||||
获取群组是否启用插机器人
|
||||
|
||||
Args:
|
||||
group_id (str): 群组ID
|
||||
|
||||
Returns:
|
||||
bool: 群组是否启用插件
|
||||
"""
|
||||
group_id = str(group_id)
|
||||
if group_id not in __group_data:
|
||||
group: Group = group_db.where_one(Group(), "group_id = ?", group_id, default=Group(group_id=group_id))
|
||||
__group_data[group_id] = group
|
||||
|
||||
return __group_data[group_id].enable
|
||||
|
||||
|
||||
def set_group_enable(group_id: str, enable: bool):
|
||||
"""
|
||||
设置群组是否启用插机器人
|
||||
|
||||
Args:
|
||||
group_id (str): 群组ID
|
||||
enable (bool): 是否启用
|
||||
"""
|
||||
group_id = str(group_id)
|
||||
group: Group = group_db.where_one(Group(), "group_id = ?", group_id, default=Group(group_id=group_id))
|
||||
group.enable = enable
|
||||
|
||||
__group_data[group_id] = group
|
||||
group_db.save(group)
|
||||
import json
|
||||
from typing import Optional
|
||||
|
||||
import aiofiles
|
||||
import nonebot.plugin
|
||||
from nonebot.adapters import satori
|
||||
|
||||
from src.utils import event as event_utils
|
||||
from src.utils.base.data import LiteModel
|
||||
from src.utils.base.data_manager import GlobalPlugin, Group, User, group_db, plugin_db, user_db
|
||||
from src.utils.base.ly_typing import T_MessageEvent
|
||||
|
||||
__group_data = {} # 群数据缓存, {group_id: Group}
|
||||
__user_data = {} # 用户数据缓存, {user_id: User}
|
||||
__default_enable = {} # 插件默认启用状态缓存, {plugin_name: bool} static
|
||||
__global_enable = {} # 插件全局启用状态缓存, {plugin_name: bool} dynamic
|
||||
|
||||
|
||||
class PluginTag(LiteModel):
|
||||
label: str
|
||||
color: str = '#000000'
|
||||
|
||||
|
||||
class StorePlugin(LiteModel):
|
||||
name: str
|
||||
desc: str
|
||||
module_name: str # 插件商店中的模块名不等于本地的模块名,前者是文件夹名,后者是点分割模块名
|
||||
project_link: str = ""
|
||||
homepage: str = ""
|
||||
author: str = ""
|
||||
type: str | None = None
|
||||
version: str | None = ""
|
||||
time: str = ""
|
||||
tags: list[PluginTag] = []
|
||||
is_official: bool = False
|
||||
|
||||
|
||||
def get_plugin_exist(plugin_name: str) -> bool:
|
||||
"""
|
||||
获取插件是否存在于加载列表
|
||||
Args:
|
||||
plugin_name:
|
||||
|
||||
Returns:
|
||||
|
||||
"""
|
||||
for plugin in nonebot.plugin.get_loaded_plugins():
|
||||
if plugin.name == plugin_name:
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
async def get_store_plugin(plugin_name: str) -> Optional[StorePlugin]:
|
||||
"""
|
||||
获取插件信息
|
||||
|
||||
Args:
|
||||
plugin_name (str): 插件模块名
|
||||
|
||||
Returns:
|
||||
Optional[StorePlugin]: 插件信息
|
||||
"""
|
||||
async with aiofiles.open("data/liteyuki/plugins.json", "r", encoding="utf-8") as f:
|
||||
plugins: list[StorePlugin] = [StorePlugin(**pobj) for pobj in json.loads(await f.read())]
|
||||
for plugin in plugins:
|
||||
if plugin.module_name == plugin_name:
|
||||
return plugin
|
||||
return None
|
||||
|
||||
|
||||
def get_plugin_default_enable(plugin_name: str) -> bool:
|
||||
"""
|
||||
获取插件默认启用状态,由插件定义,不存在则默认为启用,优先从缓存中获取
|
||||
|
||||
Args:
|
||||
plugin_name (str): 插件模块名
|
||||
|
||||
Returns:
|
||||
bool: 插件默认状态
|
||||
"""
|
||||
if plugin_name not in __default_enable:
|
||||
plug = nonebot.plugin.get_plugin(plugin_name)
|
||||
default_enable = (plug.metadata.extra.get("default_enable", True) if plug.metadata else True) if plug else True
|
||||
__default_enable[plugin_name] = default_enable
|
||||
|
||||
return __default_enable[plugin_name]
|
||||
|
||||
|
||||
def get_plugin_session_enable(event: T_MessageEvent, plugin_name: str) -> bool:
|
||||
"""
|
||||
获取插件当前会话启用状态
|
||||
|
||||
Args:
|
||||
event: 会话事件
|
||||
plugin_name (str): 插件模块名
|
||||
|
||||
Returns:
|
||||
bool: 插件当前状态
|
||||
"""
|
||||
if isinstance(event, satori.event.Event):
|
||||
if event.guild is not None:
|
||||
message_type = "group"
|
||||
else:
|
||||
message_type = "private"
|
||||
else:
|
||||
message_type = event.message_type
|
||||
if message_type == "group":
|
||||
group_id = str(event.guild.id if isinstance(event, satori.event.Event) else event.group_id)
|
||||
if group_id not in __group_data:
|
||||
group: Group = group_db.where_one(Group(), "group_id = ?", group_id, default=Group(group_id=group_id))
|
||||
__group_data[str(group_id)] = group
|
||||
|
||||
session = __group_data[group_id]
|
||||
else:
|
||||
# session: User = user_db.first(User(), "user_id = ?", event.user_id, default=User(user_id=str(event.user_id)))
|
||||
user_id = str(event.user.id if isinstance(event, satori.event.Event) else event.user_id)
|
||||
if user_id not in __user_data:
|
||||
user: User = user_db.where_one(User(), "user_id = ?", user_id, default=User(user_id=user_id))
|
||||
__user_data[user_id] = user
|
||||
session = __user_data[user_id]
|
||||
# 默认停用插件在启用列表内表示启用
|
||||
# 默认停用插件不在启用列表内表示停用
|
||||
# 默认启用插件在停用列表内表示停用
|
||||
# 默认启用插件不在停用列表内表示启用
|
||||
default_enable = get_plugin_default_enable(plugin_name)
|
||||
if default_enable:
|
||||
return plugin_name not in session.disabled_plugins
|
||||
else:
|
||||
return plugin_name in session.enabled_plugins
|
||||
|
||||
|
||||
def set_plugin_session_enable(event: T_MessageEvent, plugin_name: str, enable: bool):
|
||||
"""
|
||||
设置插件会话启用状态,同时更新数据库和缓存
|
||||
Args:
|
||||
event:
|
||||
plugin_name:
|
||||
enable:
|
||||
|
||||
Returns:
|
||||
|
||||
"""
|
||||
if event_utils.get_message_type(event) == "group":
|
||||
session: Group = group_db.where_one(Group(), "group_id = ?", str(event_utils.get_group_id(event)),
|
||||
default=Group(group_id=str(event_utils.get_group_id(event))))
|
||||
else:
|
||||
session: User = user_db.where_one(User(), "user_id = ?", str(event_utils.get_user_id(event)),
|
||||
default=User(user_id=str(event_utils.get_user_id(event))))
|
||||
print(session)
|
||||
default_enable = get_plugin_default_enable(plugin_name)
|
||||
if default_enable:
|
||||
if enable:
|
||||
session.disabled_plugins.remove(plugin_name)
|
||||
else:
|
||||
session.disabled_plugins.append(plugin_name)
|
||||
else:
|
||||
if enable:
|
||||
session.enabled_plugins.append(plugin_name)
|
||||
else:
|
||||
session.enabled_plugins.remove(plugin_name)
|
||||
|
||||
if event_utils.get_message_type(event) == "group":
|
||||
__group_data[str(event_utils.get_group_id(event))] = session
|
||||
group_db.save(session)
|
||||
else:
|
||||
__user_data[str(event_utils.get_user_id(event))] = session
|
||||
user_db.save(session)
|
||||
|
||||
|
||||
def get_plugin_global_enable(plugin_name: str) -> bool:
|
||||
"""
|
||||
获取插件全局启用状态, 优先从缓存中获取
|
||||
Args:
|
||||
plugin_name:
|
||||
|
||||
Returns:
|
||||
|
||||
"""
|
||||
if plugin_name not in __global_enable:
|
||||
plugin = plugin_db.where_one(
|
||||
GlobalPlugin(),
|
||||
"module_name = ?",
|
||||
plugin_name,
|
||||
default=GlobalPlugin(module_name=plugin_name, enabled=True))
|
||||
__global_enable[plugin_name] = plugin.enabled
|
||||
|
||||
return __global_enable[plugin_name]
|
||||
|
||||
|
||||
def set_plugin_global_enable(plugin_name: str, enable: bool):
|
||||
"""
|
||||
设置插件全局启用状态,同时更新数据库和缓存
|
||||
Args:
|
||||
plugin_name:
|
||||
enable:
|
||||
|
||||
Returns:
|
||||
|
||||
"""
|
||||
plugin = plugin_db.where_one(
|
||||
GlobalPlugin(),
|
||||
"module_name = ?",
|
||||
plugin_name,
|
||||
default=GlobalPlugin(module_name=plugin_name, enabled=True))
|
||||
plugin.enabled = enable
|
||||
|
||||
plugin_db.save(plugin)
|
||||
__global_enable[plugin_name] = enable
|
||||
|
||||
|
||||
def get_plugin_can_be_toggle(plugin_name: str) -> bool:
|
||||
"""
|
||||
获取插件是否可以被启用/停用
|
||||
|
||||
Args:
|
||||
plugin_name (str): 插件模块名
|
||||
|
||||
Returns:
|
||||
bool: 插件是否可以被启用/停用
|
||||
"""
|
||||
plug = nonebot.plugin.get_plugin(plugin_name)
|
||||
return plug.metadata.extra.get("toggleable", True) if plug and plug.metadata else True
|
||||
|
||||
|
||||
def get_group_enable(group_id: str) -> bool:
|
||||
"""
|
||||
获取群组是否启用插机器人
|
||||
|
||||
Args:
|
||||
group_id (str): 群组ID
|
||||
|
||||
Returns:
|
||||
bool: 群组是否启用插件
|
||||
"""
|
||||
group_id = str(group_id)
|
||||
if group_id not in __group_data:
|
||||
group: Group = group_db.where_one(Group(), "group_id = ?", group_id, default=Group(group_id=group_id))
|
||||
__group_data[group_id] = group
|
||||
|
||||
return __group_data[group_id].enable
|
||||
|
||||
|
||||
def set_group_enable(group_id: str, enable: bool):
|
||||
"""
|
||||
设置群组是否启用插机器人
|
||||
|
||||
Args:
|
||||
group_id (str): 群组ID
|
||||
enable (bool): 是否启用
|
||||
"""
|
||||
group_id = str(group_id)
|
||||
group: Group = group_db.where_one(Group(), "group_id = ?", group_id, default=Group(group_id=group_id))
|
||||
group.enable = enable
|
||||
|
||||
__group_data[group_id] = group
|
||||
group_db.save(group)
|
File diff suppressed because it is too large
Load Diff
@ -1,186 +1,186 @@
|
||||
# 轻雪资源包管理器
|
||||
import os
|
||||
import zipfile
|
||||
import yaml
|
||||
from nonebot import require
|
||||
from nonebot.internal.matcher import Matcher
|
||||
from nonebot.permission import SUPERUSER
|
||||
|
||||
from liteyuki.utils.base.language import get_user_lang
|
||||
from liteyuki.utils.base.ly_typing import T_Bot, T_MessageEvent
|
||||
from liteyuki.utils.message.message import MarkdownMessage as md
|
||||
from liteyuki.utils.base.resource import (ResourceMetadata, add_resource_pack, change_priority, check_exist, check_status, get_loaded_resource_packs, get_resource_metadata, load_resources, remove_resource_pack)
|
||||
|
||||
require("nonebot_plugin_alconna")
|
||||
from nonebot_plugin_alconna import Alconna, Args, on_alconna, Arparma, Subcommand
|
||||
|
||||
|
||||
@on_alconna(
|
||||
aliases={"资源包"},
|
||||
command=Alconna(
|
||||
"rpm",
|
||||
Subcommand(
|
||||
"list",
|
||||
Args["page", int, 1]["num", int, 10],
|
||||
alias=["ls", "列表", "列出"],
|
||||
),
|
||||
Subcommand(
|
||||
"load",
|
||||
Args["name", str],
|
||||
alias=["安装"],
|
||||
),
|
||||
Subcommand(
|
||||
"unload",
|
||||
Args["name", str],
|
||||
alias=["卸载"],
|
||||
),
|
||||
Subcommand(
|
||||
"up",
|
||||
Args["name", str],
|
||||
alias=["上移"],
|
||||
),
|
||||
Subcommand(
|
||||
"down",
|
||||
Args["name", str],
|
||||
alias=["下移"],
|
||||
),
|
||||
Subcommand(
|
||||
"top",
|
||||
Args["name", str],
|
||||
alias=["置顶"],
|
||||
),
|
||||
Subcommand(
|
||||
"reload",
|
||||
alias=["重载"],
|
||||
),
|
||||
),
|
||||
permission=SUPERUSER
|
||||
).handle()
|
||||
async def _(bot: T_Bot, event: T_MessageEvent, result: Arparma, matcher: Matcher):
|
||||
ulang = get_user_lang(str(event.user_id))
|
||||
reply = ""
|
||||
send_as_md = False
|
||||
if result.subcommands.get("list"):
|
||||
send_as_md = True
|
||||
loaded_rps = get_loaded_resource_packs()
|
||||
reply += f"{ulang.get('liteyuki.loaded_resources', NUM=len(loaded_rps))}\n"
|
||||
for rp in loaded_rps:
|
||||
btn_unload = md.btn_cmd(
|
||||
ulang.get("npm.uninstall"),
|
||||
f"rpm unload {rp.folder}"
|
||||
)
|
||||
btn_move_up = md.btn_cmd(
|
||||
ulang.get("rpm.move_up"),
|
||||
f"rpm up {rp.folder}"
|
||||
)
|
||||
btn_move_down = md.btn_cmd(
|
||||
ulang.get("rpm.move_down"),
|
||||
f"rpm down {rp.folder}"
|
||||
)
|
||||
btn_move_top = md.btn_cmd(
|
||||
ulang.get("rpm.move_top"),
|
||||
f"rpm top {rp.folder}"
|
||||
)
|
||||
# 添加新行
|
||||
reply += (f"\n**{md.escape(rp.name)}**({md.escape(rp.folder)})\n\n"
|
||||
f"> {btn_move_up} {btn_move_down} {btn_move_top} {btn_unload}\n\n***")
|
||||
reply += f"\n\n{ulang.get('liteyuki.unloaded_resources')}\n"
|
||||
loaded_folders = [rp.folder for rp in get_loaded_resource_packs()]
|
||||
# 遍历resources文件夹,获取未加载的资源包
|
||||
for folder in os.listdir("resources"):
|
||||
if folder not in loaded_folders:
|
||||
if os.path.exists(os.path.join("resources", folder, "metadata.yml")):
|
||||
metadata = ResourceMetadata(
|
||||
**yaml.load(
|
||||
open(
|
||||
os.path.join("resources", folder, "metadata.yml"),
|
||||
encoding="utf-8"
|
||||
),
|
||||
Loader=yaml.FullLoader
|
||||
)
|
||||
)
|
||||
metadata.folder = folder
|
||||
metadata.path = os.path.join("resources", folder)
|
||||
btn_load = md.btn_cmd(
|
||||
ulang.get("npm.install"),
|
||||
f"rpm load {metadata.folder}"
|
||||
)
|
||||
# 添加新行
|
||||
reply += (f"\n**{md.escape(metadata.name)}**({md.escape(metadata.folder)})\n\n"
|
||||
f"> {btn_load}\n\n***")
|
||||
elif os.path.isfile(os.path.join("resources", folder)) and folder.endswith(".zip"):
|
||||
# zip文件
|
||||
# 临时解压并读取metadata.yml
|
||||
with zipfile.ZipFile(os.path.join("resources", folder), "r") as zip_ref:
|
||||
with zip_ref.open("metadata.yml") as f:
|
||||
metadata = ResourceMetadata(
|
||||
**yaml.load(f, Loader=yaml.FullLoader)
|
||||
)
|
||||
btn_load = md.btn_cmd(
|
||||
ulang.get("npm.install"),
|
||||
f"rpm load {folder}"
|
||||
)
|
||||
# 添加新行
|
||||
reply += (f"\n**{md.escape(metadata.name)}**({md.escape(folder)})\n\n"
|
||||
f"> {btn_load}\n\n***")
|
||||
elif result.subcommands.get("load") or result.subcommands.get("unload"):
|
||||
load = result.subcommands.get("load") is not None
|
||||
rp_name = result.args.get("name")
|
||||
r = False # 操作结果
|
||||
if check_exist(rp_name):
|
||||
if load != check_status(rp_name):
|
||||
# 状态不同
|
||||
if load:
|
||||
r = add_resource_pack(rp_name)
|
||||
else:
|
||||
r = remove_resource_pack(rp_name)
|
||||
rp_meta = get_resource_metadata(rp_name)
|
||||
reply += ulang.get(
|
||||
f"liteyuki.{'load' if load else 'unload'}_resource_{'success' if r else 'failed'}",
|
||||
NAME=rp_meta.name
|
||||
)
|
||||
else:
|
||||
# 重复操作
|
||||
reply += ulang.get(f"liteyuki.resource_already_{'load' if load else 'unload'}ed", NAME=rp_name)
|
||||
else:
|
||||
reply += ulang.get("liteyuki.resource_not_found", NAME=rp_name)
|
||||
if r:
|
||||
btn_reload = md.btn_cmd(
|
||||
ulang.get("liteyuki.reload_resources"),
|
||||
f"rpm reload"
|
||||
)
|
||||
reply += "\n" + ulang.get("liteyuki.need_reload", BTN=btn_reload)
|
||||
elif result.subcommands.get("up") or result.subcommands.get("down") or result.subcommands.get("top"):
|
||||
rp_name = result.args.get("name")
|
||||
if result.subcommands.get("up"):
|
||||
delta = -1
|
||||
elif result.subcommands.get("down"):
|
||||
delta = 1
|
||||
else:
|
||||
delta = 0
|
||||
if check_exist(rp_name):
|
||||
if check_status(rp_name):
|
||||
r = change_priority(rp_name, delta)
|
||||
reply += ulang.get(f"liteyuki.change_priority_{'success' if r else 'failed'}", NAME=rp_name)
|
||||
if r:
|
||||
btn_reload = md.btn_cmd(
|
||||
ulang.get("liteyuki.reload_resources"),
|
||||
f"rpm reload"
|
||||
)
|
||||
reply += "\n" + ulang.get("liteyuki.need_reload", BTN=btn_reload)
|
||||
else:
|
||||
reply += ulang.get("liteyuki.resource_not_found", NAME=rp_name)
|
||||
else:
|
||||
reply += ulang.get("liteyuki.resource_not_found", NAME=rp_name)
|
||||
elif result.subcommands.get("reload"):
|
||||
load_resources()
|
||||
reply = ulang.get(
|
||||
"liteyuki.reload_resources_success",
|
||||
NUM=len(get_loaded_resource_packs())
|
||||
)
|
||||
else:
|
||||
pass
|
||||
if send_as_md:
|
||||
await md.send_md(reply, bot, event=event)
|
||||
else:
|
||||
await matcher.finish(reply)
|
||||
# 轻雪资源包管理器
|
||||
import os
|
||||
import zipfile
|
||||
import yaml
|
||||
from nonebot import require
|
||||
from nonebot.internal.matcher import Matcher
|
||||
from nonebot.permission import SUPERUSER
|
||||
|
||||
from src.utils.base.language import get_user_lang
|
||||
from src.utils.base.ly_typing import T_Bot, T_MessageEvent
|
||||
from src.utils.message.message import MarkdownMessage as md
|
||||
from src.utils.base.resource import (ResourceMetadata, add_resource_pack, change_priority, check_exist, check_status, get_loaded_resource_packs, get_resource_metadata, load_resources, remove_resource_pack)
|
||||
|
||||
require("nonebot_plugin_alconna")
|
||||
from nonebot_plugin_alconna import Alconna, Args, on_alconna, Arparma, Subcommand
|
||||
|
||||
|
||||
@on_alconna(
|
||||
aliases={"资源包"},
|
||||
command=Alconna(
|
||||
"rpm",
|
||||
Subcommand(
|
||||
"list",
|
||||
Args["page", int, 1]["num", int, 10],
|
||||
alias=["ls", "列表", "列出"],
|
||||
),
|
||||
Subcommand(
|
||||
"load",
|
||||
Args["name", str],
|
||||
alias=["安装"],
|
||||
),
|
||||
Subcommand(
|
||||
"unload",
|
||||
Args["name", str],
|
||||
alias=["卸载"],
|
||||
),
|
||||
Subcommand(
|
||||
"up",
|
||||
Args["name", str],
|
||||
alias=["上移"],
|
||||
),
|
||||
Subcommand(
|
||||
"down",
|
||||
Args["name", str],
|
||||
alias=["下移"],
|
||||
),
|
||||
Subcommand(
|
||||
"top",
|
||||
Args["name", str],
|
||||
alias=["置顶"],
|
||||
),
|
||||
Subcommand(
|
||||
"reload",
|
||||
alias=["重载"],
|
||||
),
|
||||
),
|
||||
permission=SUPERUSER
|
||||
).handle()
|
||||
async def _(bot: T_Bot, event: T_MessageEvent, result: Arparma, matcher: Matcher):
|
||||
ulang = get_user_lang(str(event.user_id))
|
||||
reply = ""
|
||||
send_as_md = False
|
||||
if result.subcommands.get("list"):
|
||||
send_as_md = True
|
||||
loaded_rps = get_loaded_resource_packs()
|
||||
reply += f"{ulang.get('liteyuki.loaded_resources', NUM=len(loaded_rps))}\n"
|
||||
for rp in loaded_rps:
|
||||
btn_unload = md.btn_cmd(
|
||||
ulang.get("npm.uninstall"),
|
||||
f"rpm unload {rp.folder}"
|
||||
)
|
||||
btn_move_up = md.btn_cmd(
|
||||
ulang.get("rpm.move_up"),
|
||||
f"rpm up {rp.folder}"
|
||||
)
|
||||
btn_move_down = md.btn_cmd(
|
||||
ulang.get("rpm.move_down"),
|
||||
f"rpm down {rp.folder}"
|
||||
)
|
||||
btn_move_top = md.btn_cmd(
|
||||
ulang.get("rpm.move_top"),
|
||||
f"rpm top {rp.folder}"
|
||||
)
|
||||
# 添加新行
|
||||
reply += (f"\n**{md.escape(rp.name)}**({md.escape(rp.folder)})\n\n"
|
||||
f"> {btn_move_up} {btn_move_down} {btn_move_top} {btn_unload}\n\n***")
|
||||
reply += f"\n\n{ulang.get('liteyuki.unloaded_resources')}\n"
|
||||
loaded_folders = [rp.folder for rp in get_loaded_resource_packs()]
|
||||
# 遍历resources文件夹,获取未加载的资源包
|
||||
for folder in os.listdir("resources"):
|
||||
if folder not in loaded_folders:
|
||||
if os.path.exists(os.path.join("resources", folder, "metadata.yml")):
|
||||
metadata = ResourceMetadata(
|
||||
**yaml.load(
|
||||
open(
|
||||
os.path.join("resources", folder, "metadata.yml"),
|
||||
encoding="utf-8"
|
||||
),
|
||||
Loader=yaml.FullLoader
|
||||
)
|
||||
)
|
||||
metadata.folder = folder
|
||||
metadata.path = os.path.join("resources", folder)
|
||||
btn_load = md.btn_cmd(
|
||||
ulang.get("npm.install"),
|
||||
f"rpm load {metadata.folder}"
|
||||
)
|
||||
# 添加新行
|
||||
reply += (f"\n**{md.escape(metadata.name)}**({md.escape(metadata.folder)})\n\n"
|
||||
f"> {btn_load}\n\n***")
|
||||
elif os.path.isfile(os.path.join("resources", folder)) and folder.endswith(".zip"):
|
||||
# zip文件
|
||||
# 临时解压并读取metadata.yml
|
||||
with zipfile.ZipFile(os.path.join("resources", folder), "r") as zip_ref:
|
||||
with zip_ref.open("metadata.yml") as f:
|
||||
metadata = ResourceMetadata(
|
||||
**yaml.load(f, Loader=yaml.FullLoader)
|
||||
)
|
||||
btn_load = md.btn_cmd(
|
||||
ulang.get("npm.install"),
|
||||
f"rpm load {folder}"
|
||||
)
|
||||
# 添加新行
|
||||
reply += (f"\n**{md.escape(metadata.name)}**({md.escape(folder)})\n\n"
|
||||
f"> {btn_load}\n\n***")
|
||||
elif result.subcommands.get("load") or result.subcommands.get("unload"):
|
||||
load = result.subcommands.get("load") is not None
|
||||
rp_name = result.args.get("name")
|
||||
r = False # 操作结果
|
||||
if check_exist(rp_name):
|
||||
if load != check_status(rp_name):
|
||||
# 状态不同
|
||||
if load:
|
||||
r = add_resource_pack(rp_name)
|
||||
else:
|
||||
r = remove_resource_pack(rp_name)
|
||||
rp_meta = get_resource_metadata(rp_name)
|
||||
reply += ulang.get(
|
||||
f"liteyuki.{'load' if load else 'unload'}_resource_{'success' if r else 'failed'}",
|
||||
NAME=rp_meta.name
|
||||
)
|
||||
else:
|
||||
# 重复操作
|
||||
reply += ulang.get(f"liteyuki.resource_already_{'load' if load else 'unload'}ed", NAME=rp_name)
|
||||
else:
|
||||
reply += ulang.get("liteyuki.resource_not_found", NAME=rp_name)
|
||||
if r:
|
||||
btn_reload = md.btn_cmd(
|
||||
ulang.get("liteyuki.reload_resources"),
|
||||
f"rpm reload"
|
||||
)
|
||||
reply += "\n" + ulang.get("liteyuki.need_reload", BTN=btn_reload)
|
||||
elif result.subcommands.get("up") or result.subcommands.get("down") or result.subcommands.get("top"):
|
||||
rp_name = result.args.get("name")
|
||||
if result.subcommands.get("up"):
|
||||
delta = -1
|
||||
elif result.subcommands.get("down"):
|
||||
delta = 1
|
||||
else:
|
||||
delta = 0
|
||||
if check_exist(rp_name):
|
||||
if check_status(rp_name):
|
||||
r = change_priority(rp_name, delta)
|
||||
reply += ulang.get(f"liteyuki.change_priority_{'success' if r else 'failed'}", NAME=rp_name)
|
||||
if r:
|
||||
btn_reload = md.btn_cmd(
|
||||
ulang.get("liteyuki.reload_resources"),
|
||||
f"rpm reload"
|
||||
)
|
||||
reply += "\n" + ulang.get("liteyuki.need_reload", BTN=btn_reload)
|
||||
else:
|
||||
reply += ulang.get("liteyuki.resource_not_found", NAME=rp_name)
|
||||
else:
|
||||
reply += ulang.get("liteyuki.resource_not_found", NAME=rp_name)
|
||||
elif result.subcommands.get("reload"):
|
||||
load_resources()
|
||||
reply = ulang.get(
|
||||
"liteyuki.reload_resources_success",
|
||||
NUM=len(get_loaded_resource_packs())
|
||||
)
|
||||
else:
|
||||
pass
|
||||
if send_as_md:
|
||||
await md.send_md(reply, bot, event=event)
|
||||
else:
|
||||
await matcher.finish(reply)
|
@ -1,16 +1,16 @@
|
||||
from nonebot.plugin import PluginMetadata
|
||||
from .auto_update import *
|
||||
|
||||
__author__ = "expliyh"
|
||||
__plugin_meta__ = PluginMetadata(
|
||||
name="Satori 用户数据自动更新(临时措施)",
|
||||
description="",
|
||||
usage="",
|
||||
type="application",
|
||||
homepage="https://github.com/snowykami/LiteyukiBot",
|
||||
extra={
|
||||
"liteyuki": True,
|
||||
"toggleable" : True,
|
||||
"default_enable" : True,
|
||||
}
|
||||
)
|
||||
from nonebot.plugin import PluginMetadata
|
||||
from .auto_update import *
|
||||
|
||||
__author__ = "expliyh"
|
||||
__plugin_meta__ = PluginMetadata(
|
||||
name="Satori 用户数据自动更新(临时措施)",
|
||||
description="",
|
||||
usage="",
|
||||
type="application",
|
||||
homepage="https://github.com/snowykami/LiteyukiBot",
|
||||
extra={
|
||||
"liteyuki": True,
|
||||
"toggleable" : True,
|
||||
"default_enable" : True,
|
||||
}
|
||||
)
|
@ -1,21 +1,21 @@
|
||||
import nonebot
|
||||
|
||||
from nonebot.message import event_preprocessor
|
||||
# from nonebot_plugin_alconna.typings import Event
|
||||
from liteyuki.utils.base.ly_typing import T_MessageEvent
|
||||
from liteyuki.utils import satori_utils
|
||||
from nonebot.adapters import satori
|
||||
from nonebot_plugin_alconna.typings import Event
|
||||
from liteyuki.plugins.liteyuki_status.counter_for_satori import satori_counter
|
||||
|
||||
|
||||
@event_preprocessor
|
||||
async def pre_handle(event: Event):
|
||||
if isinstance(event, satori.MessageEvent):
|
||||
if event.user.id == event.self_id:
|
||||
satori_counter.msg_sent += 1
|
||||
else:
|
||||
satori_counter.msg_received += 1
|
||||
if event.user.name is not None:
|
||||
if await satori_utils.user_infos.put(event.user):
|
||||
nonebot.logger.info(f"Satori user {event.user.name}<{event.user.id}> updated")
|
||||
import nonebot
|
||||
|
||||
from nonebot.message import event_preprocessor
|
||||
# from nonebot_plugin_alconna.typings import Event
|
||||
from src.utils.base.ly_typing import T_MessageEvent
|
||||
from src.utils import satori_utils
|
||||
from nonebot.adapters import satori
|
||||
from nonebot_plugin_alconna.typings import Event
|
||||
from src.plugins.liteyuki_status.counter_for_satori import satori_counter
|
||||
|
||||
|
||||
@event_preprocessor
|
||||
async def pre_handle(event: Event):
|
||||
if isinstance(event, satori.MessageEvent):
|
||||
if event.user.id == event.self_id:
|
||||
satori_counter.msg_sent += 1
|
||||
else:
|
||||
satori_counter.msg_received += 1
|
||||
if event.user.name is not None:
|
||||
if await satori_utils.user_infos.put(event.user):
|
||||
nonebot.logger.info(f"Satori user {event.user.name}<{event.user.id}> updated")
|
@ -1,163 +1,163 @@
|
||||
import datetime
|
||||
import time
|
||||
|
||||
import aiohttp
|
||||
from nonebot import require
|
||||
from nonebot.plugin import PluginMetadata
|
||||
|
||||
from liteyuki.utils.base.config import get_config
|
||||
from liteyuki.utils.base.data import Database, LiteModel
|
||||
from liteyuki.utils.base.resource import get_path
|
||||
from liteyuki.utils.message.html_tool import template2image
|
||||
|
||||
require("nonebot_plugin_alconna")
|
||||
require("nonebot_plugin_apscheduler")
|
||||
from nonebot_plugin_apscheduler import scheduler
|
||||
from nonebot_plugin_alconna import Alconna, AlconnaResult, CommandResult, Subcommand, UniMessage, on_alconna, Args
|
||||
|
||||
__author__ = "snowykami"
|
||||
__plugin_meta__ = PluginMetadata(
|
||||
name="签名服务器状态",
|
||||
description="适用于ntqq的签名状态查看",
|
||||
usage=(
|
||||
"sign count 查看当前签名数\n"
|
||||
"sign data 查看签名数变化\n"
|
||||
"sign chart [limit] 查看签名数变化图表\n"
|
||||
),
|
||||
type="application",
|
||||
homepage="https://github.com/snowykami/LiteyukiBot",
|
||||
extra={
|
||||
"liteyuki" : True,
|
||||
"toggleable" : True,
|
||||
"default_enable": True,
|
||||
}
|
||||
)
|
||||
|
||||
SIGN_COUNT_URLS: dict[str, str] = get_config("sign_count_urls", None)
|
||||
SIGN_COUNT_DURATION = get_config("sign_count_duration", 10)
|
||||
|
||||
|
||||
class SignCount(LiteModel):
|
||||
TABLE_NAME: str = "sign_count"
|
||||
time: float = 0.0
|
||||
count: int = 0
|
||||
sid: str = ""
|
||||
|
||||
|
||||
sign_db = Database("data/liteyuki/ntqq_sign.ldb")
|
||||
sign_db.auto_migrate(SignCount())
|
||||
|
||||
sign_status = on_alconna(Alconna(
|
||||
"sign",
|
||||
Subcommand(
|
||||
"chart",
|
||||
Args["limit", int, 10000]
|
||||
),
|
||||
Subcommand(
|
||||
"count"
|
||||
),
|
||||
Subcommand(
|
||||
"data"
|
||||
)
|
||||
))
|
||||
|
||||
cache_img: bytes = None
|
||||
|
||||
|
||||
@sign_status.assign("count")
|
||||
async def _():
|
||||
reply = "Current sign count:"
|
||||
for name, count in (await get_now_sign()).items():
|
||||
reply += f"\n{name}: {count[1]}"
|
||||
await sign_status.send(reply)
|
||||
|
||||
|
||||
@sign_status.assign("data")
|
||||
async def _():
|
||||
query_stamp = [1, 5, 10, 15]
|
||||
|
||||
reply = "QPS from last " + ", ".join([str(i) for i in query_stamp]) + "mins"
|
||||
for name, url in SIGN_COUNT_URLS.items():
|
||||
count_data = []
|
||||
for stamp in query_stamp:
|
||||
count_rows = sign_db.where_all(SignCount(), "sid = ? and time > ?", url, time.time() - 60 * stamp)
|
||||
if len(count_rows) < 2:
|
||||
count_data.append(-1)
|
||||
else:
|
||||
count_data.append((count_rows[-1].count - count_rows[0].count)/(stamp*60))
|
||||
reply += f"\n{name}: " + ", ".join([f"{i:.1f}" for i in count_data])
|
||||
await sign_status.send(reply)
|
||||
|
||||
|
||||
@sign_status.assign("chart")
|
||||
async def _(arp: CommandResult = AlconnaResult()):
|
||||
limit = arp.result.subcommands.get("chart").args.get("limit")
|
||||
if limit == 10000:
|
||||
if cache_img:
|
||||
await sign_status.send(UniMessage.image(raw=cache_img))
|
||||
return
|
||||
img = await generate_chart(limit)
|
||||
await sign_status.send(UniMessage.image(raw=img))
|
||||
|
||||
|
||||
@scheduler.scheduled_job("interval", seconds=SIGN_COUNT_DURATION, next_run_time=datetime.datetime.now())
|
||||
async def update_sign_count():
|
||||
global cache_img
|
||||
if not SIGN_COUNT_URLS:
|
||||
return
|
||||
data = await get_now_sign()
|
||||
for name, count in data.items():
|
||||
await save_sign_count(count[0], count[1], SIGN_COUNT_URLS[name])
|
||||
|
||||
cache_img = await generate_chart(10000)
|
||||
|
||||
|
||||
async def get_now_sign() -> dict[str, tuple[float, int]]:
|
||||
"""
|
||||
Get the sign count and the time of the latest sign
|
||||
Returns:
|
||||
tuple[float, int] | None: (time, count)
|
||||
"""
|
||||
data = {}
|
||||
now = time.time()
|
||||
async with aiohttp.ClientSession() as client:
|
||||
for name, url in SIGN_COUNT_URLS.items():
|
||||
async with client.get(url) as resp:
|
||||
count = (await resp.json())["count"]
|
||||
data[name] = (now, count)
|
||||
return data
|
||||
|
||||
|
||||
async def save_sign_count(timestamp: float, count: int, sid: str):
|
||||
"""
|
||||
Save the sign count to the database
|
||||
Args:
|
||||
sid: the sign id, use url as the id
|
||||
count:
|
||||
timestamp (float): the time of the sign count (int): the count of the sign
|
||||
"""
|
||||
sign_db.save(SignCount(time=timestamp, count=count, sid=sid))
|
||||
|
||||
|
||||
async def generate_chart(limit):
|
||||
data = []
|
||||
for name, url in SIGN_COUNT_URLS.items():
|
||||
count_rows = sign_db.where_all(SignCount(), "sid = ? ORDER BY id DESC LIMIT ?", url, limit)
|
||||
count_rows.reverse()
|
||||
data.append(
|
||||
{
|
||||
"name" : name,
|
||||
# "data": [[row.time, row.count] for row in count_rows]
|
||||
"times" : [row.time for row in count_rows],
|
||||
"counts": [row.count for row in count_rows]
|
||||
}
|
||||
)
|
||||
|
||||
img = await template2image(
|
||||
template=get_path("templates/sign_status.html"),
|
||||
templates={
|
||||
"data": data
|
||||
},
|
||||
)
|
||||
|
||||
return img
|
||||
import datetime
|
||||
import time
|
||||
|
||||
import aiohttp
|
||||
from nonebot import require
|
||||
from nonebot.plugin import PluginMetadata
|
||||
|
||||
from src.utils.base.config import get_config
|
||||
from src.utils.base.data import Database, LiteModel
|
||||
from src.utils.base.resource import get_path
|
||||
from src.utils.message.html_tool import template2image
|
||||
|
||||
require("nonebot_plugin_alconna")
|
||||
require("nonebot_plugin_apscheduler")
|
||||
from nonebot_plugin_apscheduler import scheduler
|
||||
from nonebot_plugin_alconna import Alconna, AlconnaResult, CommandResult, Subcommand, UniMessage, on_alconna, Args
|
||||
|
||||
__author__ = "snowykami"
|
||||
__plugin_meta__ = PluginMetadata(
|
||||
name="签名服务器状态",
|
||||
description="适用于ntqq的签名状态查看",
|
||||
usage=(
|
||||
"sign count 查看当前签名数\n"
|
||||
"sign data 查看签名数变化\n"
|
||||
"sign chart [limit] 查看签名数变化图表\n"
|
||||
),
|
||||
type="application",
|
||||
homepage="https://github.com/snowykami/LiteyukiBot",
|
||||
extra={
|
||||
"liteyuki" : True,
|
||||
"toggleable" : True,
|
||||
"default_enable": True,
|
||||
}
|
||||
)
|
||||
|
||||
SIGN_COUNT_URLS: dict[str, str] = get_config("sign_count_urls", None)
|
||||
SIGN_COUNT_DURATION = get_config("sign_count_duration", 10)
|
||||
|
||||
|
||||
class SignCount(LiteModel):
|
||||
TABLE_NAME: str = "sign_count"
|
||||
time: float = 0.0
|
||||
count: int = 0
|
||||
sid: str = ""
|
||||
|
||||
|
||||
sign_db = Database("data/liteyuki/ntqq_sign.ldb")
|
||||
sign_db.auto_migrate(SignCount())
|
||||
|
||||
sign_status = on_alconna(Alconna(
|
||||
"sign",
|
||||
Subcommand(
|
||||
"chart",
|
||||
Args["limit", int, 10000]
|
||||
),
|
||||
Subcommand(
|
||||
"count"
|
||||
),
|
||||
Subcommand(
|
||||
"data"
|
||||
)
|
||||
))
|
||||
|
||||
cache_img: bytes = None
|
||||
|
||||
|
||||
@sign_status.assign("count")
|
||||
async def _():
|
||||
reply = "Current sign count:"
|
||||
for name, count in (await get_now_sign()).items():
|
||||
reply += f"\n{name}: {count[1]}"
|
||||
await sign_status.send(reply)
|
||||
|
||||
|
||||
@sign_status.assign("data")
|
||||
async def _():
|
||||
query_stamp = [1, 5, 10, 15]
|
||||
|
||||
reply = "QPS from last " + ", ".join([str(i) for i in query_stamp]) + "mins"
|
||||
for name, url in SIGN_COUNT_URLS.items():
|
||||
count_data = []
|
||||
for stamp in query_stamp:
|
||||
count_rows = sign_db.where_all(SignCount(), "sid = ? and time > ?", url, time.time() - 60 * stamp)
|
||||
if len(count_rows) < 2:
|
||||
count_data.append(-1)
|
||||
else:
|
||||
count_data.append((count_rows[-1].count - count_rows[0].count)/(stamp*60))
|
||||
reply += f"\n{name}: " + ", ".join([f"{i:.1f}" for i in count_data])
|
||||
await sign_status.send(reply)
|
||||
|
||||
|
||||
@sign_status.assign("chart")
|
||||
async def _(arp: CommandResult = AlconnaResult()):
|
||||
limit = arp.result.subcommands.get("chart").args.get("limit")
|
||||
if limit == 10000:
|
||||
if cache_img:
|
||||
await sign_status.send(UniMessage.image(raw=cache_img))
|
||||
return
|
||||
img = await generate_chart(limit)
|
||||
await sign_status.send(UniMessage.image(raw=img))
|
||||
|
||||
|
||||
@scheduler.scheduled_job("interval", seconds=SIGN_COUNT_DURATION, next_run_time=datetime.datetime.now())
|
||||
async def update_sign_count():
|
||||
global cache_img
|
||||
if not SIGN_COUNT_URLS:
|
||||
return
|
||||
data = await get_now_sign()
|
||||
for name, count in data.items():
|
||||
await save_sign_count(count[0], count[1], SIGN_COUNT_URLS[name])
|
||||
|
||||
cache_img = await generate_chart(10000)
|
||||
|
||||
|
||||
async def get_now_sign() -> dict[str, tuple[float, int]]:
|
||||
"""
|
||||
Get the sign count and the time of the latest sign
|
||||
Returns:
|
||||
tuple[float, int] | None: (time, count)
|
||||
"""
|
||||
data = {}
|
||||
now = time.time()
|
||||
async with aiohttp.ClientSession() as client:
|
||||
for name, url in SIGN_COUNT_URLS.items():
|
||||
async with client.get(url) as resp:
|
||||
count = (await resp.json())["count"]
|
||||
data[name] = (now, count)
|
||||
return data
|
||||
|
||||
|
||||
async def save_sign_count(timestamp: float, count: int, sid: str):
|
||||
"""
|
||||
Save the sign count to the database
|
||||
Args:
|
||||
sid: the sign id, use url as the id
|
||||
count:
|
||||
timestamp (float): the time of the sign count (int): the count of the sign
|
||||
"""
|
||||
sign_db.save(SignCount(time=timestamp, count=count, sid=sid))
|
||||
|
||||
|
||||
async def generate_chart(limit):
|
||||
data = []
|
||||
for name, url in SIGN_COUNT_URLS.items():
|
||||
count_rows = sign_db.where_all(SignCount(), "sid = ? ORDER BY id DESC LIMIT ?", url, limit)
|
||||
count_rows.reverse()
|
||||
data.append(
|
||||
{
|
||||
"name" : name,
|
||||
# "data": [[row.time, row.count] for row in count_rows]
|
||||
"times" : [row.time for row in count_rows],
|
||||
"counts": [row.count for row in count_rows]
|
||||
}
|
||||
)
|
||||
|
||||
img = await template2image(
|
||||
template=get_path("templates/sign_status.html"),
|
||||
templates={
|
||||
"data": data
|
||||
},
|
||||
)
|
||||
|
||||
return img
|
@ -1,18 +1,18 @@
|
||||
from nonebot.plugin import PluginMetadata
|
||||
from .monitors import *
|
||||
from .matchers import *
|
||||
|
||||
|
||||
__author__ = "snowykami"
|
||||
__plugin_meta__ = PluginMetadata(
|
||||
name="轻雪智障回复",
|
||||
description="",
|
||||
usage="",
|
||||
type="application",
|
||||
homepage="https://github.com/snowykami/LiteyukiBot",
|
||||
extra={
|
||||
"liteyuki": True,
|
||||
"toggleable" : True,
|
||||
"default_enable" : True,
|
||||
}
|
||||
from nonebot.plugin import PluginMetadata
|
||||
from .monitors import *
|
||||
from .matchers import *
|
||||
|
||||
|
||||
__author__ = "snowykami"
|
||||
__plugin_meta__ = PluginMetadata(
|
||||
name="轻雪智障回复",
|
||||
description="",
|
||||
usage="",
|
||||
type="application",
|
||||
homepage="https://github.com/snowykami/LiteyukiBot",
|
||||
extra={
|
||||
"liteyuki": True,
|
||||
"toggleable" : True,
|
||||
"default_enable" : True,
|
||||
}
|
||||
)
|
@ -1,106 +1,106 @@
|
||||
import asyncio
|
||||
import random
|
||||
|
||||
import nonebot
|
||||
from nonebot import Bot, on_message, get_driver, require
|
||||
from nonebot.internal.matcher import Matcher
|
||||
from nonebot.permission import SUPERUSER
|
||||
from nonebot.rule import to_me
|
||||
from nonebot.typing import T_State
|
||||
|
||||
from liteyuki.utils.base.ly_typing import T_MessageEvent
|
||||
from .utils import get_keywords
|
||||
from liteyuki.utils.base.word_bank import get_reply
|
||||
from liteyuki.utils.event import get_message_type
|
||||
from liteyuki.utils.base.permission import GROUP_ADMIN, GROUP_OWNER
|
||||
from liteyuki.utils.base.data_manager import group_db, Group
|
||||
|
||||
require("nonebot_plugin_alconna")
|
||||
from nonebot_plugin_alconna import on_alconna, Alconna, Args, Arparma
|
||||
|
||||
nicknames = set()
|
||||
driver = get_driver()
|
||||
group_reply_probability: dict[str, float] = {
|
||||
}
|
||||
default_reply_probability = 0.05
|
||||
cut_probability = 0.4 # 分几句话的概率
|
||||
|
||||
|
||||
@on_alconna(
|
||||
Alconna(
|
||||
"set-reply-probability",
|
||||
Args["probability", float, default_reply_probability],
|
||||
),
|
||||
aliases={"设置回复概率"},
|
||||
permission=SUPERUSER | GROUP_ADMIN | GROUP_OWNER,
|
||||
).handle()
|
||||
async def _(result: Arparma, event: T_MessageEvent, matcher: Matcher):
|
||||
# 修改内存和数据库的概率值
|
||||
if get_message_type(event) == "group":
|
||||
group_id = event.group_id
|
||||
probability = result.main_args.get("probability")
|
||||
# 保存到数据库
|
||||
group: Group = group_db.where_one(Group(), "group_id = ?", group_id, default=Group(group_id=str(group_id)))
|
||||
group.config["reply_probability"] = probability
|
||||
group_db.save(group)
|
||||
|
||||
await matcher.send(f"已将群组{group_id}的回复概率设置为{probability}")
|
||||
return
|
||||
|
||||
|
||||
@group_db.on_save
|
||||
def _(model: Group):
|
||||
"""
|
||||
在数据库更新时,更新内存中的回复概率
|
||||
Args:
|
||||
model:
|
||||
|
||||
Returns:
|
||||
|
||||
"""
|
||||
group_reply_probability[model.group_id] = model.config.get("reply_probability", default_reply_probability)
|
||||
|
||||
|
||||
@driver.on_bot_connect
|
||||
async def _(bot: Bot):
|
||||
global nicknames
|
||||
nicknames.update(bot.config.nickname)
|
||||
# 从数据库加载群组的回复概率
|
||||
groups = group_db.where_all(Group(), default=[])
|
||||
for group in groups:
|
||||
group_reply_probability[group.group_id] = group.config.get("reply_probability", default_reply_probability)
|
||||
|
||||
|
||||
@on_message(priority=100).handle()
|
||||
async def _(event: T_MessageEvent, bot: Bot, state: T_State, matcher: Matcher):
|
||||
kws = await get_keywords(event.message.extract_plain_text())
|
||||
|
||||
tome = False
|
||||
if await to_me()(event=event, bot=bot, state=state):
|
||||
tome = True
|
||||
else:
|
||||
for kw in kws:
|
||||
if kw in nicknames:
|
||||
tome = True
|
||||
break
|
||||
|
||||
# 回复概率
|
||||
message_type = get_message_type(event)
|
||||
if tome or message_type == "private":
|
||||
p = 1.0
|
||||
else:
|
||||
p = group_reply_probability.get(event.group_id, default_reply_probability)
|
||||
|
||||
if random.random() < p:
|
||||
if reply := get_reply(kws):
|
||||
if random.random() < cut_probability:
|
||||
reply = reply.replace("。", "||").replace(",", "||").replace("!", "||").replace("?", "||")
|
||||
replies = reply.split("||")
|
||||
for r in replies:
|
||||
if r: # 防止空字符串
|
||||
await asyncio.sleep(random.random() * 2)
|
||||
await matcher.send(r)
|
||||
else:
|
||||
await asyncio.sleep(random.random() * 3)
|
||||
await matcher.send(reply)
|
||||
return
|
||||
import asyncio
|
||||
import random
|
||||
|
||||
import nonebot
|
||||
from nonebot import Bot, on_message, get_driver, require
|
||||
from nonebot.internal.matcher import Matcher
|
||||
from nonebot.permission import SUPERUSER
|
||||
from nonebot.rule import to_me
|
||||
from nonebot.typing import T_State
|
||||
|
||||
from src.utils.base.ly_typing import T_MessageEvent
|
||||
from .utils import get_keywords
|
||||
from src.utils.base.word_bank import get_reply
|
||||
from src.utils.event import get_message_type
|
||||
from src.utils.base.permission import GROUP_ADMIN, GROUP_OWNER
|
||||
from src.utils.base.data_manager import group_db, Group
|
||||
|
||||
require("nonebot_plugin_alconna")
|
||||
from nonebot_plugin_alconna import on_alconna, Alconna, Args, Arparma
|
||||
|
||||
nicknames = set()
|
||||
driver = get_driver()
|
||||
group_reply_probability: dict[str, float] = {
|
||||
}
|
||||
default_reply_probability = 0.05
|
||||
cut_probability = 0.4 # 分几句话的概率
|
||||
|
||||
|
||||
@on_alconna(
|
||||
Alconna(
|
||||
"set-reply-probability",
|
||||
Args["probability", float, default_reply_probability],
|
||||
),
|
||||
aliases={"设置回复概率"},
|
||||
permission=SUPERUSER | GROUP_ADMIN | GROUP_OWNER,
|
||||
).handle()
|
||||
async def _(result: Arparma, event: T_MessageEvent, matcher: Matcher):
|
||||
# 修改内存和数据库的概率值
|
||||
if get_message_type(event) == "group":
|
||||
group_id = event.group_id
|
||||
probability = result.main_args.get("probability")
|
||||
# 保存到数据库
|
||||
group: Group = group_db.where_one(Group(), "group_id = ?", group_id, default=Group(group_id=str(group_id)))
|
||||
group.config["reply_probability"] = probability
|
||||
group_db.save(group)
|
||||
|
||||
await matcher.send(f"已将群组{group_id}的回复概率设置为{probability}")
|
||||
return
|
||||
|
||||
|
||||
@group_db.on_save
|
||||
def _(model: Group):
|
||||
"""
|
||||
在数据库更新时,更新内存中的回复概率
|
||||
Args:
|
||||
model:
|
||||
|
||||
Returns:
|
||||
|
||||
"""
|
||||
group_reply_probability[model.group_id] = model.config.get("reply_probability", default_reply_probability)
|
||||
|
||||
|
||||
@driver.on_bot_connect
|
||||
async def _(bot: Bot):
|
||||
global nicknames
|
||||
nicknames.update(bot.config.nickname)
|
||||
# 从数据库加载群组的回复概率
|
||||
groups = group_db.where_all(Group(), default=[])
|
||||
for group in groups:
|
||||
group_reply_probability[group.group_id] = group.config.get("reply_probability", default_reply_probability)
|
||||
|
||||
|
||||
@on_message(priority=100).handle()
|
||||
async def _(event: T_MessageEvent, bot: Bot, state: T_State, matcher: Matcher):
|
||||
kws = await get_keywords(event.message.extract_plain_text())
|
||||
|
||||
tome = False
|
||||
if await to_me()(event=event, bot=bot, state=state):
|
||||
tome = True
|
||||
else:
|
||||
for kw in kws:
|
||||
if kw in nicknames:
|
||||
tome = True
|
||||
break
|
||||
|
||||
# 回复概率
|
||||
message_type = get_message_type(event)
|
||||
if tome or message_type == "private":
|
||||
p = 1.0
|
||||
else:
|
||||
p = group_reply_probability.get(event.group_id, default_reply_probability)
|
||||
|
||||
if random.random() < p:
|
||||
if reply := get_reply(kws):
|
||||
if random.random() < cut_probability:
|
||||
reply = reply.replace("。", "||").replace(",", "||").replace("!", "||").replace("?", "||")
|
||||
replies = reply.split("||")
|
||||
for r in replies:
|
||||
if r: # 防止空字符串
|
||||
await asyncio.sleep(random.random() * 2)
|
||||
await matcher.send(r)
|
||||
else:
|
||||
await asyncio.sleep(random.random() * 3)
|
||||
await matcher.send(reply)
|
||||
return
|
@ -1,13 +1,13 @@
|
||||
from jieba import lcut
|
||||
from nonebot.utils import run_sync
|
||||
|
||||
|
||||
@run_sync
|
||||
def get_keywords(text: str) -> list[str, ...]:
|
||||
"""
|
||||
获取关键词
|
||||
Args:
|
||||
text: 文本
|
||||
Returns:
|
||||
"""
|
||||
return lcut(text)
|
||||
from jieba import lcut
|
||||
from nonebot.utils import run_sync
|
||||
|
||||
|
||||
@run_sync
|
||||
def get_keywords(text: str) -> list[str, ...]:
|
||||
"""
|
||||
获取关键词
|
||||
Args:
|
||||
text: 文本
|
||||
Returns:
|
||||
"""
|
||||
return lcut(text)
|
@ -1,29 +1,29 @@
|
||||
from nonebot.plugin import PluginMetadata
|
||||
from .stat_matchers import *
|
||||
from .stat_monitors import *
|
||||
from .stat_restful_api import *
|
||||
|
||||
__author__ = "snowykami"
|
||||
__plugin_meta__ = PluginMetadata(
|
||||
name="统计信息",
|
||||
description="统计机器人的信息,包括消息、群聊等,支持排名、图表等功能",
|
||||
usage=(
|
||||
"```\nstatistic message 查看统计消息\n"
|
||||
"可选参数:\n"
|
||||
" -g|--group [group_id] 指定群聊\n"
|
||||
" -u|--user [user_id] 指定用户\n"
|
||||
" -d|--duration [duration] 指定时长\n"
|
||||
" -p|--period [period] 指定次数统计周期\n"
|
||||
" -b|--bot [bot_id] 指定机器人\n"
|
||||
"命令别名:\n"
|
||||
" statistic|stat message|msg|m\n"
|
||||
"```"
|
||||
),
|
||||
type="application",
|
||||
homepage="https://github.com/snowykami/LiteyukiBot",
|
||||
extra={
|
||||
"liteyuki" : True,
|
||||
"toggleable" : False,
|
||||
"default_enable": True,
|
||||
}
|
||||
)
|
||||
from nonebot.plugin import PluginMetadata
|
||||
from .stat_matchers import *
|
||||
from .stat_monitors import *
|
||||
from .stat_restful_api import *
|
||||
|
||||
__author__ = "snowykami"
|
||||
__plugin_meta__ = PluginMetadata(
|
||||
name="统计信息",
|
||||
description="统计机器人的信息,包括消息、群聊等,支持排名、图表等功能",
|
||||
usage=(
|
||||
"```\nstatistic message 查看统计消息\n"
|
||||
"可选参数:\n"
|
||||
" -g|--group [group_id] 指定群聊\n"
|
||||
" -u|--user [user_id] 指定用户\n"
|
||||
" -d|--duration [duration] 指定时长\n"
|
||||
" -p|--period [period] 指定次数统计周期\n"
|
||||
" -b|--bot [bot_id] 指定机器人\n"
|
||||
"命令别名:\n"
|
||||
" statistic|stat message|msg|m\n"
|
||||
"```"
|
||||
),
|
||||
type="application",
|
||||
homepage="https://github.com/snowykami/LiteyukiBot",
|
||||
extra={
|
||||
"liteyuki" : True,
|
||||
"toggleable" : False,
|
||||
"default_enable": True,
|
||||
}
|
||||
)
|
@ -1,21 +1,21 @@
|
||||
from liteyuki.utils.base.data import Database, LiteModel
|
||||
|
||||
|
||||
class MessageEventModel(LiteModel):
|
||||
TABLE_NAME: str = "message_event"
|
||||
time: int = 0
|
||||
|
||||
bot_id: str = ""
|
||||
adapter: str = ""
|
||||
|
||||
user_id: str = ""
|
||||
group_id: str = ""
|
||||
|
||||
message_id: str = ""
|
||||
message: list = []
|
||||
message_text: str = ""
|
||||
message_type: str = ""
|
||||
|
||||
|
||||
msg_db = Database("data/liteyuki/msg.ldb")
|
||||
from src.utils.base.data import Database, LiteModel
|
||||
|
||||
|
||||
class MessageEventModel(LiteModel):
|
||||
TABLE_NAME: str = "message_event"
|
||||
time: int = 0
|
||||
|
||||
bot_id: str = ""
|
||||
adapter: str = ""
|
||||
|
||||
user_id: str = ""
|
||||
group_id: str = ""
|
||||
|
||||
message_id: str = ""
|
||||
message: list = []
|
||||
message_text: str = ""
|
||||
message_type: str = ""
|
||||
|
||||
|
||||
msg_db = Database("data/liteyuki/msg.ldb")
|
||||
msg_db.auto_migrate(MessageEventModel())
|
@ -1,172 +1,172 @@
|
||||
import time
|
||||
from typing import Any
|
||||
|
||||
from collections import Counter
|
||||
|
||||
from nonebot import Bot
|
||||
|
||||
from liteyuki.utils.message.html_tool import template2image
|
||||
from .common import MessageEventModel, msg_db
|
||||
from liteyuki.utils.base.language import Language
|
||||
from liteyuki.utils.base.resource import get_path
|
||||
from liteyuki.utils.message.string_tool import convert_seconds_to_time
|
||||
from ...utils.external.logo import get_group_icon, get_user_icon
|
||||
|
||||
|
||||
async def count_msg_by_bot_id(bot_id: str) -> int:
|
||||
condition = " AND bot_id = ?"
|
||||
condition_args = [bot_id]
|
||||
|
||||
msg_rows = msg_db.where_all(
|
||||
MessageEventModel(),
|
||||
condition,
|
||||
*condition_args
|
||||
)
|
||||
|
||||
return len(msg_rows)
|
||||
|
||||
|
||||
async def get_stat_msg_image(
|
||||
duration: int,
|
||||
period: int,
|
||||
group_id: str = None,
|
||||
bot_id: str = None,
|
||||
user_id: str = None,
|
||||
ulang: Language = Language()
|
||||
) -> bytes:
|
||||
"""
|
||||
获取统计消息
|
||||
Args:
|
||||
user_id:
|
||||
ulang:
|
||||
bot_id:
|
||||
group_id:
|
||||
duration: 统计时间,单位秒
|
||||
period: 统计周期,单位秒
|
||||
|
||||
Returns:
|
||||
tuple: [int,], [int,] 两个列表,分别为周期中心时间戳和消息数量
|
||||
"""
|
||||
now = int(time.time())
|
||||
start_time = (now - duration)
|
||||
|
||||
condition = "time > ?"
|
||||
condition_args = [start_time]
|
||||
|
||||
if group_id:
|
||||
condition += " AND group_id = ?"
|
||||
condition_args.append(group_id)
|
||||
if bot_id:
|
||||
condition += " AND bot_id = ?"
|
||||
condition_args.append(bot_id)
|
||||
|
||||
if user_id:
|
||||
condition += " AND user_id = ?"
|
||||
condition_args.append(user_id)
|
||||
|
||||
msg_rows = msg_db.where_all(
|
||||
MessageEventModel(),
|
||||
condition,
|
||||
*condition_args
|
||||
)
|
||||
timestamps = []
|
||||
msg_count = []
|
||||
msg_rows.sort(key=lambda x: x.time)
|
||||
|
||||
start_time = max(msg_rows[0].time, start_time)
|
||||
|
||||
for i in range(start_time, now, period):
|
||||
timestamps.append(i + period // 2)
|
||||
msg_count.append(0)
|
||||
|
||||
for msg in msg_rows:
|
||||
period_start_time = start_time + (msg.time - start_time) // period * period
|
||||
period_center_time = period_start_time + period // 2
|
||||
index = timestamps.index(period_center_time)
|
||||
msg_count[index] += 1
|
||||
|
||||
templates = {
|
||||
"data": [
|
||||
{
|
||||
"name" : ulang.get("stat.message")
|
||||
+ f" Period {convert_seconds_to_time(period)}" + f" Duration {convert_seconds_to_time(duration)}"
|
||||
+ (f" Group {group_id}" if group_id else "") + (f" Bot {bot_id}" if bot_id else "") + (
|
||||
f" User {user_id}" if user_id else ""),
|
||||
"times" : timestamps,
|
||||
"counts": msg_count
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
return await template2image(get_path("templates/stat_msg.html"), templates)
|
||||
|
||||
|
||||
async def get_stat_rank_image(
|
||||
rank_type: str,
|
||||
limit: dict[str, Any],
|
||||
ulang: Language = Language(),
|
||||
bot: Bot = None,
|
||||
) -> bytes:
|
||||
if rank_type == "user":
|
||||
condition = "user_id != ''"
|
||||
condition_args = []
|
||||
else:
|
||||
condition = "group_id != ''"
|
||||
condition_args = []
|
||||
|
||||
for k, v in limit.items():
|
||||
match k:
|
||||
case "user_id":
|
||||
condition += " AND user_id = ?"
|
||||
condition_args.append(v)
|
||||
case "group_id":
|
||||
condition += " AND group_id = ?"
|
||||
condition_args.append(v)
|
||||
case "bot_id":
|
||||
condition += " AND bot_id = ?"
|
||||
condition_args.append(v)
|
||||
case "duration":
|
||||
condition += " AND time > ?"
|
||||
condition_args.append(v)
|
||||
|
||||
msg_rows = msg_db.where_all(
|
||||
MessageEventModel(),
|
||||
condition,
|
||||
*condition_args
|
||||
)
|
||||
|
||||
"""
|
||||
{
|
||||
name: string, # user name or group name
|
||||
count: int, # message count
|
||||
icon: string # icon url
|
||||
}
|
||||
"""
|
||||
|
||||
if rank_type == "user":
|
||||
ranking_counter = Counter([msg.user_id for msg in msg_rows])
|
||||
else:
|
||||
ranking_counter = Counter([msg.group_id for msg in msg_rows])
|
||||
sorted_data = sorted(ranking_counter.items(), key=lambda x: x[1], reverse=True)
|
||||
|
||||
ranking: list[dict[str, Any]] = [
|
||||
{
|
||||
"name" : _[0],
|
||||
"count": _[1],
|
||||
"icon" : await (get_group_icon(platform="qq", group_id=_[0]) if rank_type == "group" else get_user_icon(
|
||||
platform="qq", user_id=_[0]
|
||||
))
|
||||
}
|
||||
for _ in sorted_data[0:min(len(sorted_data), limit["rank"])]
|
||||
]
|
||||
|
||||
templates = {
|
||||
"data":
|
||||
{
|
||||
"name" : ulang.get("stat.rank") + f" Type {rank_type}" + f" Limit {limit}",
|
||||
"ranking": ranking
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return await template2image(get_path("templates/stat_rank.html"), templates, debug=True)
|
||||
import time
|
||||
from typing import Any
|
||||
|
||||
from collections import Counter
|
||||
|
||||
from nonebot import Bot
|
||||
|
||||
from src.utils.message.html_tool import template2image
|
||||
from .common import MessageEventModel, msg_db
|
||||
from src.utils.base.language import Language
|
||||
from src.utils.base.resource import get_path
|
||||
from src.utils.message.string_tool import convert_seconds_to_time
|
||||
from ...utils.external.logo import get_group_icon, get_user_icon
|
||||
|
||||
|
||||
async def count_msg_by_bot_id(bot_id: str) -> int:
|
||||
condition = " AND bot_id = ?"
|
||||
condition_args = [bot_id]
|
||||
|
||||
msg_rows = msg_db.where_all(
|
||||
MessageEventModel(),
|
||||
condition,
|
||||
*condition_args
|
||||
)
|
||||
|
||||
return len(msg_rows)
|
||||
|
||||
|
||||
async def get_stat_msg_image(
|
||||
duration: int,
|
||||
period: int,
|
||||
group_id: str = None,
|
||||
bot_id: str = None,
|
||||
user_id: str = None,
|
||||
ulang: Language = Language()
|
||||
) -> bytes:
|
||||
"""
|
||||
获取统计消息
|
||||
Args:
|
||||
user_id:
|
||||
ulang:
|
||||
bot_id:
|
||||
group_id:
|
||||
duration: 统计时间,单位秒
|
||||
period: 统计周期,单位秒
|
||||
|
||||
Returns:
|
||||
tuple: [int,], [int,] 两个列表,分别为周期中心时间戳和消息数量
|
||||
"""
|
||||
now = int(time.time())
|
||||
start_time = (now - duration)
|
||||
|
||||
condition = "time > ?"
|
||||
condition_args = [start_time]
|
||||
|
||||
if group_id:
|
||||
condition += " AND group_id = ?"
|
||||
condition_args.append(group_id)
|
||||
if bot_id:
|
||||
condition += " AND bot_id = ?"
|
||||
condition_args.append(bot_id)
|
||||
|
||||
if user_id:
|
||||
condition += " AND user_id = ?"
|
||||
condition_args.append(user_id)
|
||||
|
||||
msg_rows = msg_db.where_all(
|
||||
MessageEventModel(),
|
||||
condition,
|
||||
*condition_args
|
||||
)
|
||||
timestamps = []
|
||||
msg_count = []
|
||||
msg_rows.sort(key=lambda x: x.time)
|
||||
|
||||
start_time = max(msg_rows[0].time, start_time)
|
||||
|
||||
for i in range(start_time, now, period):
|
||||
timestamps.append(i + period // 2)
|
||||
msg_count.append(0)
|
||||
|
||||
for msg in msg_rows:
|
||||
period_start_time = start_time + (msg.time - start_time) // period * period
|
||||
period_center_time = period_start_time + period // 2
|
||||
index = timestamps.index(period_center_time)
|
||||
msg_count[index] += 1
|
||||
|
||||
templates = {
|
||||
"data": [
|
||||
{
|
||||
"name" : ulang.get("stat.message")
|
||||
+ f" Period {convert_seconds_to_time(period)}" + f" Duration {convert_seconds_to_time(duration)}"
|
||||
+ (f" Group {group_id}" if group_id else "") + (f" Bot {bot_id}" if bot_id else "") + (
|
||||
f" User {user_id}" if user_id else ""),
|
||||
"times" : timestamps,
|
||||
"counts": msg_count
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
return await template2image(get_path("templates/stat_msg.html"), templates)
|
||||
|
||||
|
||||
async def get_stat_rank_image(
|
||||
rank_type: str,
|
||||
limit: dict[str, Any],
|
||||
ulang: Language = Language(),
|
||||
bot: Bot = None,
|
||||
) -> bytes:
|
||||
if rank_type == "user":
|
||||
condition = "user_id != ''"
|
||||
condition_args = []
|
||||
else:
|
||||
condition = "group_id != ''"
|
||||
condition_args = []
|
||||
|
||||
for k, v in limit.items():
|
||||
match k:
|
||||
case "user_id":
|
||||
condition += " AND user_id = ?"
|
||||
condition_args.append(v)
|
||||
case "group_id":
|
||||
condition += " AND group_id = ?"
|
||||
condition_args.append(v)
|
||||
case "bot_id":
|
||||
condition += " AND bot_id = ?"
|
||||
condition_args.append(v)
|
||||
case "duration":
|
||||
condition += " AND time > ?"
|
||||
condition_args.append(v)
|
||||
|
||||
msg_rows = msg_db.where_all(
|
||||
MessageEventModel(),
|
||||
condition,
|
||||
*condition_args
|
||||
)
|
||||
|
||||
"""
|
||||
{
|
||||
name: string, # user name or group name
|
||||
count: int, # message count
|
||||
icon: string # icon url
|
||||
}
|
||||
"""
|
||||
|
||||
if rank_type == "user":
|
||||
ranking_counter = Counter([msg.user_id for msg in msg_rows])
|
||||
else:
|
||||
ranking_counter = Counter([msg.group_id for msg in msg_rows])
|
||||
sorted_data = sorted(ranking_counter.items(), key=lambda x: x[1], reverse=True)
|
||||
|
||||
ranking: list[dict[str, Any]] = [
|
||||
{
|
||||
"name" : _[0],
|
||||
"count": _[1],
|
||||
"icon" : await (get_group_icon(platform="qq", group_id=_[0]) if rank_type == "group" else get_user_icon(
|
||||
platform="qq", user_id=_[0]
|
||||
))
|
||||
}
|
||||
for _ in sorted_data[0:min(len(sorted_data), limit["rank"])]
|
||||
]
|
||||
|
||||
templates = {
|
||||
"data":
|
||||
{
|
||||
"name" : ulang.get("stat.rank") + f" Type {rank_type}" + f" Limit {limit}",
|
||||
"ranking": ranking
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return await template2image(get_path("templates/stat_rank.html"), templates, debug=True)
|
@ -1,135 +1,135 @@
|
||||
from nonebot import Bot, require
|
||||
from liteyuki.utils.message.string_tool import convert_duration, convert_time_to_seconds
|
||||
from .stat_api import *
|
||||
from liteyuki.utils import event as event_utils
|
||||
from liteyuki.utils.base.language import Language
|
||||
from liteyuki.utils.base.ly_typing import T_MessageEvent
|
||||
|
||||
require("nonebot_plugin_alconna")
|
||||
|
||||
from nonebot_plugin_alconna import (
|
||||
UniMessage,
|
||||
on_alconna,
|
||||
Alconna,
|
||||
Args,
|
||||
Subcommand,
|
||||
Arparma,
|
||||
Option,
|
||||
MultiVar
|
||||
)
|
||||
|
||||
stat_msg = on_alconna(
|
||||
Alconna(
|
||||
"statistic",
|
||||
Subcommand(
|
||||
"message",
|
||||
# Args["duration", str, "2d"]["period", str, "60s"], # 默认为1天
|
||||
Option(
|
||||
"-d|--duration",
|
||||
Args["duration", str, "2d"],
|
||||
help_text="统计时间",
|
||||
),
|
||||
Option(
|
||||
"-p|--period",
|
||||
Args["period", str, "60s"],
|
||||
help_text="统计周期",
|
||||
),
|
||||
Option(
|
||||
"-b|--bot", # 生成图表
|
||||
Args["bot_id", str, "current"],
|
||||
help_text="是否指定机器人",
|
||||
),
|
||||
Option(
|
||||
"-g|--group",
|
||||
Args["group_id", str, "current"],
|
||||
help_text="指定群组"
|
||||
),
|
||||
Option(
|
||||
"-u|--user",
|
||||
Args["user_id", str, "current"],
|
||||
help_text="指定用户"
|
||||
),
|
||||
alias={"msg", "m"},
|
||||
help_text="查看统计次数内的消息"
|
||||
),
|
||||
Subcommand(
|
||||
"rank",
|
||||
Option(
|
||||
"-u|--user",
|
||||
help_text="以用户为指标",
|
||||
),
|
||||
Option(
|
||||
"-g|--group",
|
||||
help_text="以群组为指标",
|
||||
),
|
||||
Option(
|
||||
"-l|--limit",
|
||||
Args["limit", MultiVar(str)],
|
||||
help_text="限制参数,使用key=val格式",
|
||||
),
|
||||
Option(
|
||||
"-d|--duration",
|
||||
Args["duration", str, "1d"],
|
||||
help_text="统计时间",
|
||||
),
|
||||
Option(
|
||||
"-r|--rank",
|
||||
Args["rank", int, 20],
|
||||
help_text="指定排名",
|
||||
),
|
||||
alias={"r"},
|
||||
)
|
||||
),
|
||||
aliases={"stat"}
|
||||
)
|
||||
|
||||
|
||||
@stat_msg.assign("message")
|
||||
async def _(result: Arparma, event: T_MessageEvent, bot: Bot):
|
||||
ulang = Language(event_utils.get_user_id(event))
|
||||
try:
|
||||
duration = convert_time_to_seconds(result.other_args.get("duration", "2d")) # 秒数
|
||||
period = convert_time_to_seconds(result.other_args.get("period", "1m"))
|
||||
except BaseException as e:
|
||||
await stat_msg.send(ulang.get("liteyuki.invalid_command", TEXT=str(e.__str__())))
|
||||
return
|
||||
|
||||
group_id = result.other_args.get("group_id")
|
||||
bot_id = result.other_args.get("bot_id")
|
||||
user_id = result.other_args.get("user_id")
|
||||
|
||||
if group_id in ["current", "c"]:
|
||||
group_id = str(event_utils.get_group_id(event))
|
||||
|
||||
if group_id in ["all", "a"]:
|
||||
group_id = "all"
|
||||
|
||||
if bot_id in ["current", "c"]:
|
||||
bot_id = str(bot.self_id)
|
||||
|
||||
if user_id in ["current", "c"]:
|
||||
user_id = str(event_utils.get_user_id(event))
|
||||
|
||||
img = await get_stat_msg_image(duration=duration, period=period, group_id=group_id, bot_id=bot_id, user_id=user_id, ulang=ulang)
|
||||
await stat_msg.send(UniMessage.image(raw=img))
|
||||
|
||||
|
||||
@stat_msg.assign("rank")
|
||||
async def _(result: Arparma, event: T_MessageEvent, bot: Bot):
|
||||
ulang = Language(event_utils.get_user_id(event))
|
||||
rank_type = "user"
|
||||
duration = convert_time_to_seconds(result.other_args.get("duration", "1d"))
|
||||
print(result)
|
||||
if result.subcommands.get("rank").options.get("user"):
|
||||
rank_type = "user"
|
||||
elif result.subcommands.get("rank").options.get("group"):
|
||||
rank_type = "group"
|
||||
|
||||
limit = result.other_args.get("limit", {})
|
||||
if limit:
|
||||
limit = dict([i.split("=") for i in limit])
|
||||
limit["duration"] = time.time() - duration # 起始时间戳
|
||||
limit["rank"] = result.other_args.get("rank", 20)
|
||||
|
||||
img = await get_stat_rank_image(rank_type=rank_type, limit=limit, ulang=ulang)
|
||||
await stat_msg.send(UniMessage.image(raw=img))
|
||||
from nonebot import Bot, require
|
||||
from src.utils.message.string_tool import convert_duration, convert_time_to_seconds
|
||||
from .data_source import *
|
||||
from src.utils import event as event_utils
|
||||
from src.utils.base.language import Language
|
||||
from src.utils.base.ly_typing import T_MessageEvent
|
||||
|
||||
require("nonebot_plugin_alconna")
|
||||
|
||||
from nonebot_plugin_alconna import (
|
||||
UniMessage,
|
||||
on_alconna,
|
||||
Alconna,
|
||||
Args,
|
||||
Subcommand,
|
||||
Arparma,
|
||||
Option,
|
||||
MultiVar
|
||||
)
|
||||
|
||||
stat_msg = on_alconna(
|
||||
Alconna(
|
||||
"statistic",
|
||||
Subcommand(
|
||||
"message",
|
||||
# Args["duration", str, "2d"]["period", str, "60s"], # 默认为1天
|
||||
Option(
|
||||
"-d|--duration",
|
||||
Args["duration", str, "2d"],
|
||||
help_text="统计时间",
|
||||
),
|
||||
Option(
|
||||
"-p|--period",
|
||||
Args["period", str, "60s"],
|
||||
help_text="统计周期",
|
||||
),
|
||||
Option(
|
||||
"-b|--bot", # 生成图表
|
||||
Args["bot_id", str, "current"],
|
||||
help_text="是否指定机器人",
|
||||
),
|
||||
Option(
|
||||
"-g|--group",
|
||||
Args["group_id", str, "current"],
|
||||
help_text="指定群组"
|
||||
),
|
||||
Option(
|
||||
"-u|--user",
|
||||
Args["user_id", str, "current"],
|
||||
help_text="指定用户"
|
||||
),
|
||||
alias={"msg", "m"},
|
||||
help_text="查看统计次数内的消息"
|
||||
),
|
||||
Subcommand(
|
||||
"rank",
|
||||
Option(
|
||||
"-u|--user",
|
||||
help_text="以用户为指标",
|
||||
),
|
||||
Option(
|
||||
"-g|--group",
|
||||
help_text="以群组为指标",
|
||||
),
|
||||
Option(
|
||||
"-l|--limit",
|
||||
Args["limit", MultiVar(str)],
|
||||
help_text="限制参数,使用key=val格式",
|
||||
),
|
||||
Option(
|
||||
"-d|--duration",
|
||||
Args["duration", str, "1d"],
|
||||
help_text="统计时间",
|
||||
),
|
||||
Option(
|
||||
"-r|--rank",
|
||||
Args["rank", int, 20],
|
||||
help_text="指定排名",
|
||||
),
|
||||
alias={"r"},
|
||||
)
|
||||
),
|
||||
aliases={"stat"}
|
||||
)
|
||||
|
||||
|
||||
@stat_msg.assign("message")
|
||||
async def _(result: Arparma, event: T_MessageEvent, bot: Bot):
|
||||
ulang = Language(event_utils.get_user_id(event))
|
||||
try:
|
||||
duration = convert_time_to_seconds(result.other_args.get("duration", "2d")) # 秒数
|
||||
period = convert_time_to_seconds(result.other_args.get("period", "1m"))
|
||||
except BaseException as e:
|
||||
await stat_msg.send(ulang.get("liteyuki.invalid_command", TEXT=str(e.__str__())))
|
||||
return
|
||||
|
||||
group_id = result.other_args.get("group_id")
|
||||
bot_id = result.other_args.get("bot_id")
|
||||
user_id = result.other_args.get("user_id")
|
||||
|
||||
if group_id in ["current", "c"]:
|
||||
group_id = str(event_utils.get_group_id(event))
|
||||
|
||||
if group_id in ["all", "a"]:
|
||||
group_id = "all"
|
||||
|
||||
if bot_id in ["current", "c"]:
|
||||
bot_id = str(bot.self_id)
|
||||
|
||||
if user_id in ["current", "c"]:
|
||||
user_id = str(event_utils.get_user_id(event))
|
||||
|
||||
img = await get_stat_msg_image(duration=duration, period=period, group_id=group_id, bot_id=bot_id, user_id=user_id, ulang=ulang)
|
||||
await stat_msg.send(UniMessage.image(raw=img))
|
||||
|
||||
|
||||
@stat_msg.assign("rank")
|
||||
async def _(result: Arparma, event: T_MessageEvent, bot: Bot):
|
||||
ulang = Language(event_utils.get_user_id(event))
|
||||
rank_type = "user"
|
||||
duration = convert_time_to_seconds(result.other_args.get("duration", "1d"))
|
||||
print(result)
|
||||
if result.subcommands.get("rank").options.get("user"):
|
||||
rank_type = "user"
|
||||
elif result.subcommands.get("rank").options.get("group"):
|
||||
rank_type = "group"
|
||||
|
||||
limit = result.other_args.get("limit", {})
|
||||
if limit:
|
||||
limit = dict([i.split("=") for i in limit])
|
||||
limit["duration"] = time.time() - duration # 起始时间戳
|
||||
limit["rank"] = result.other_args.get("rank", 20)
|
||||
|
||||
img = await get_stat_rank_image(rank_type=rank_type, limit=limit, ulang=ulang)
|
||||
await stat_msg.send(UniMessage.image(raw=img))
|
@ -1,92 +1,92 @@
|
||||
import time
|
||||
|
||||
from nonebot import require
|
||||
from nonebot.message import event_postprocessor
|
||||
|
||||
from liteyuki.utils.base.data import Database, LiteModel
|
||||
from liteyuki.utils.base.ly_typing import v11, v12, satori
|
||||
|
||||
from liteyuki.utils.base.ly_typing import T_Bot, T_MessageEvent
|
||||
|
||||
from .common import MessageEventModel, msg_db
|
||||
from liteyuki.utils import event as event_utils
|
||||
|
||||
require("nonebot_plugin_alconna")
|
||||
|
||||
|
||||
async def general_event_monitor(bot: T_Bot, event: T_MessageEvent):
|
||||
print("POST PROCESS")
|
||||
# if isinstance(bot, satori.Bot):
|
||||
# print("POST PROCESS SATORI EVENT")
|
||||
# return await satori_event_monitor(bot, event)
|
||||
# elif isinstance(bot, v11.Bot):
|
||||
# print("POST PROCESS V11 EVENT")
|
||||
# return await onebot_v11_event_monitor(bot, event)
|
||||
|
||||
|
||||
@event_postprocessor
|
||||
async def onebot_v11_event_monitor(bot: v11.Bot, event: v11.MessageEvent):
|
||||
if event.message_type == "group":
|
||||
event: v11.GroupMessageEvent
|
||||
group_id = str(event.group_id)
|
||||
else:
|
||||
group_id = ""
|
||||
mem = MessageEventModel(
|
||||
time=int(time.time()),
|
||||
bot_id=bot.self_id,
|
||||
adapter="onebot.v11",
|
||||
group_id=group_id,
|
||||
user_id=str(event.user_id),
|
||||
|
||||
message_id=str(event.message_id),
|
||||
|
||||
message=[ms.__dict__ for ms in event.message],
|
||||
message_text=event.raw_message,
|
||||
message_type=event.message_type,
|
||||
)
|
||||
msg_db.save(mem)
|
||||
|
||||
|
||||
@event_postprocessor
|
||||
async def onebot_v12_event_monitor(bot: v12.Bot, event: v12.MessageEvent):
|
||||
if event.message_type == "group":
|
||||
event: v12.GroupMessageEvent
|
||||
group_id = str(event.group_id)
|
||||
else:
|
||||
group_id = ""
|
||||
mem = MessageEventModel(
|
||||
time=int(time.time()),
|
||||
bot_id=bot.self_id,
|
||||
adapter="onebot.v12",
|
||||
group_id=group_id,
|
||||
user_id=str(event.user_id),
|
||||
|
||||
message_id=[ms.__dict__ for ms in event.message],
|
||||
|
||||
message=event.message,
|
||||
message_text=event.raw_message,
|
||||
message_type=event.message_type,
|
||||
)
|
||||
msg_db.save(mem)
|
||||
|
||||
|
||||
@event_postprocessor
|
||||
async def satori_event_monitor(bot: satori.Bot, event: satori.MessageEvent):
|
||||
if event.guild is not None:
|
||||
event: satori.MessageEvent
|
||||
group_id = str(event.guild.id)
|
||||
else:
|
||||
group_id = ""
|
||||
|
||||
mem = MessageEventModel(
|
||||
time=int(time.time()),
|
||||
bot_id=bot.self_id,
|
||||
adapter="satori",
|
||||
group_id=group_id,
|
||||
user_id=str(event.user.id),
|
||||
message_id=[ms.__str__() for ms in event.message],
|
||||
message=event.message,
|
||||
message_text=event.message.content,
|
||||
message_type=event_utils.get_message_type(event),
|
||||
)
|
||||
msg_db.save(mem)
|
||||
import time
|
||||
|
||||
from nonebot import require
|
||||
from nonebot.message import event_postprocessor
|
||||
|
||||
from src.utils.base.data import Database, LiteModel
|
||||
from src.utils.base.ly_typing import v11, v12, satori
|
||||
|
||||
from src.utils.base.ly_typing import T_Bot, T_MessageEvent
|
||||
|
||||
from .common import MessageEventModel, msg_db
|
||||
from src.utils import event as event_utils
|
||||
|
||||
require("nonebot_plugin_alconna")
|
||||
|
||||
|
||||
async def general_event_monitor(bot: T_Bot, event: T_MessageEvent):
|
||||
print("POST PROCESS")
|
||||
# if isinstance(bot, satori.Bot):
|
||||
# print("POST PROCESS SATORI EVENT")
|
||||
# return await satori_event_monitor(bot, event)
|
||||
# elif isinstance(bot, v11.Bot):
|
||||
# print("POST PROCESS V11 EVENT")
|
||||
# return await onebot_v11_event_monitor(bot, event)
|
||||
|
||||
|
||||
@event_postprocessor
|
||||
async def onebot_v11_event_monitor(bot: v11.Bot, event: v11.MessageEvent):
|
||||
if event.message_type == "group":
|
||||
event: v11.GroupMessageEvent
|
||||
group_id = str(event.group_id)
|
||||
else:
|
||||
group_id = ""
|
||||
mem = MessageEventModel(
|
||||
time=int(time.time()),
|
||||
bot_id=bot.self_id,
|
||||
adapter="onebot.v11",
|
||||
group_id=group_id,
|
||||
user_id=str(event.user_id),
|
||||
|
||||
message_id=str(event.message_id),
|
||||
|
||||
message=[ms.__dict__ for ms in event.message],
|
||||
message_text=event.raw_message,
|
||||
message_type=event.message_type,
|
||||
)
|
||||
msg_db.save(mem)
|
||||
|
||||
|
||||
@event_postprocessor
|
||||
async def onebot_v12_event_monitor(bot: v12.Bot, event: v12.MessageEvent):
|
||||
if event.message_type == "group":
|
||||
event: v12.GroupMessageEvent
|
||||
group_id = str(event.group_id)
|
||||
else:
|
||||
group_id = ""
|
||||
mem = MessageEventModel(
|
||||
time=int(time.time()),
|
||||
bot_id=bot.self_id,
|
||||
adapter="onebot.v12",
|
||||
group_id=group_id,
|
||||
user_id=str(event.user_id),
|
||||
|
||||
message_id=[ms.__dict__ for ms in event.message],
|
||||
|
||||
message=event.message,
|
||||
message_text=event.raw_message,
|
||||
message_type=event.message_type,
|
||||
)
|
||||
msg_db.save(mem)
|
||||
|
||||
|
||||
@event_postprocessor
|
||||
async def satori_event_monitor(bot: satori.Bot, event: satori.MessageEvent):
|
||||
if event.guild is not None:
|
||||
event: satori.MessageEvent
|
||||
group_id = str(event.guild.id)
|
||||
else:
|
||||
group_id = ""
|
||||
|
||||
mem = MessageEventModel(
|
||||
time=int(time.time()),
|
||||
bot_id=bot.self_id,
|
||||
adapter="satori",
|
||||
group_id=group_id,
|
||||
user_id=str(event.user.id),
|
||||
message_id=[ms.__str__() for ms in event.message],
|
||||
message=event.message,
|
||||
message_text=event.message.content,
|
||||
message_type=event_utils.get_message_type(event),
|
||||
)
|
||||
msg_db.save(mem)
|
21
src/plugins/liteyuki_statistics/word_cloud/LICENSE
Normal file
21
src/plugins/liteyuki_statistics/word_cloud/LICENSE
Normal file
@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2022 hemengyang
|
||||
|
||||
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.
|
107
src/plugins/liteyuki_statistics/word_cloud/data_source.py
Normal file
107
src/plugins/liteyuki_statistics/word_cloud/data_source.py
Normal file
@ -0,0 +1,107 @@
|
||||
import asyncio
|
||||
import concurrent.futures
|
||||
import contextlib
|
||||
import re
|
||||
from functools import partial
|
||||
from io import BytesIO
|
||||
from random import choice
|
||||
from typing import Optional
|
||||
|
||||
import jieba
|
||||
import jieba.analyse
|
||||
import numpy as np
|
||||
from emoji import replace_emoji
|
||||
from PIL import Image
|
||||
from wordcloud import WordCloud
|
||||
|
||||
from .config import global_config, plugin_config
|
||||
|
||||
|
||||
def pre_precess(msg: str) -> str:
|
||||
"""对消息进行预处理"""
|
||||
# 去除网址
|
||||
# https://stackoverflow.com/a/17773849/9212748
|
||||
url_regex = re.compile(
|
||||
r"(https?:\/\/(?:www\.|(?!www))[a-zA-Z0-9][a-zA-Z0-9-]+[a-zA-Z0-9]\.[^\s]{2,}|www\.[a-zA-Z0-9][a-zA-Z0-9-]"
|
||||
r"+[a-zA-Z0-9]\.[^\s]{2,}|https?:\/\/(?:www\.|(?!www))[a-zA-Z0-9]+\.[^\s]{2,}|www\.[a-zA-Z0-9]+\.[^\s]{2,})"
|
||||
)
|
||||
msg = url_regex.sub("", msg)
|
||||
|
||||
# 去除 \u200b
|
||||
msg = re.sub(r"\u200b", "", msg)
|
||||
|
||||
# 去除 emoji
|
||||
# https://github.com/carpedm20/emoji
|
||||
msg = replace_emoji(msg)
|
||||
|
||||
return msg
|
||||
|
||||
|
||||
def analyse_message(msg: str) -> dict[str, float]:
|
||||
"""分析消息
|
||||
|
||||
分词,并统计词频
|
||||
"""
|
||||
# 设置停用词表
|
||||
if plugin_config.wordcloud_stopwords_path:
|
||||
jieba.analyse.set_stop_words(plugin_config.wordcloud_stopwords_path)
|
||||
# 加载用户词典
|
||||
if plugin_config.wordcloud_userdict_path:
|
||||
jieba.load_userdict(str(plugin_config.wordcloud_userdict_path))
|
||||
# 基于 TF-IDF 算法的关键词抽取
|
||||
# 返回所有关键词,因为设置了数量其实也只是 tags[:topK],不如交给词云库处理
|
||||
words = jieba.analyse.extract_tags(msg, topK=0, withWeight=True)
|
||||
return dict(words)
|
||||
|
||||
|
||||
def get_mask(key: str):
|
||||
"""获取 mask"""
|
||||
mask_path = plugin_config.get_mask_path(key)
|
||||
if mask_path.exists():
|
||||
return np.array(Image.open(mask_path))
|
||||
# 如果指定 mask 文件不存在,则尝试默认 mask
|
||||
default_mask_path = plugin_config.get_mask_path()
|
||||
if default_mask_path.exists():
|
||||
return np.array(Image.open(default_mask_path))
|
||||
|
||||
|
||||
def _get_wordcloud(messages: list[str], mask_key: str) -> Optional[bytes]:
|
||||
# 过滤掉命令
|
||||
command_start = tuple(i for i in global_config.command_start if i)
|
||||
message = " ".join(m for m in messages if not m.startswith(command_start))
|
||||
# 预处理
|
||||
message = pre_precess(message)
|
||||
# 分析消息。分词,并统计词频
|
||||
frequency = analyse_message(message)
|
||||
# 词云参数
|
||||
wordcloud_options = {}
|
||||
wordcloud_options.update(plugin_config.wordcloud_options)
|
||||
wordcloud_options.setdefault("font_path", str(plugin_config.wordcloud_font_path))
|
||||
wordcloud_options.setdefault("width", plugin_config.wordcloud_width)
|
||||
wordcloud_options.setdefault("height", plugin_config.wordcloud_height)
|
||||
wordcloud_options.setdefault(
|
||||
"background_color", plugin_config.wordcloud_background_color
|
||||
)
|
||||
# 如果 colormap 是列表,则随机选择一个
|
||||
colormap = (
|
||||
plugin_config.wordcloud_colormap
|
||||
if isinstance(plugin_config.wordcloud_colormap, str)
|
||||
else choice(plugin_config.wordcloud_colormap)
|
||||
)
|
||||
wordcloud_options.setdefault("colormap", colormap)
|
||||
wordcloud_options.setdefault("mask", get_mask(mask_key))
|
||||
with contextlib.suppress(ValueError):
|
||||
wordcloud = WordCloud(**wordcloud_options)
|
||||
image = wordcloud.generate_from_frequencies(frequency).to_image()
|
||||
image_bytes = BytesIO()
|
||||
image.save(image_bytes, format="PNG")
|
||||
return image_bytes.getvalue()
|
||||
|
||||
|
||||
async def get_wordcloud(messages: list[str], mask_key: str) -> Optional[bytes]:
|
||||
loop = asyncio.get_running_loop()
|
||||
pfunc = partial(_get_wordcloud, messages, mask_key)
|
||||
# 虽然不知道具体是哪里泄漏了,但是通过每次关闭线程池可以避免这个问题
|
||||
# https://github.com/he0119/nonebot-plugin-wordcloud/issues/99
|
||||
with concurrent.futures.ThreadPoolExecutor() as pool:
|
||||
return await loop.run_in_executor(pool, pfunc)
|
@ -1,24 +1,24 @@
|
||||
from nonebot.plugin import PluginMetadata
|
||||
from .status import *
|
||||
|
||||
__author__ = "snowykami"
|
||||
__plugin_meta__ = PluginMetadata(
|
||||
name="状态查看器",
|
||||
description="",
|
||||
usage=(
|
||||
"MARKDOWN### 状态查看器\n"
|
||||
"查看机器人的状态\n"
|
||||
"### 用法\n"
|
||||
"- `/status` 查看基本情况\n"
|
||||
"- `/status memory` 查看内存使用情况\n"
|
||||
"- `/status process` 查看进程情况\n"
|
||||
),
|
||||
type="application",
|
||||
homepage="https://github.com/snowykami/LiteyukiBot",
|
||||
extra={
|
||||
"liteyuki": True,
|
||||
"toggleable" : False,
|
||||
"default_enable" : True,
|
||||
}
|
||||
)
|
||||
|
||||
from nonebot.plugin import PluginMetadata
|
||||
from .status import *
|
||||
|
||||
__author__ = "snowykami"
|
||||
__plugin_meta__ = PluginMetadata(
|
||||
name="状态查看器",
|
||||
description="",
|
||||
usage=(
|
||||
"MARKDOWN### 状态查看器\n"
|
||||
"查看机器人的状态\n"
|
||||
"### 用法\n"
|
||||
"- `/status` 查看基本情况\n"
|
||||
"- `/status memory` 查看内存使用情况\n"
|
||||
"- `/status process` 查看进程情况\n"
|
||||
),
|
||||
type="application",
|
||||
homepage="https://github.com/snowykami/LiteyukiBot",
|
||||
extra={
|
||||
"liteyuki": True,
|
||||
"toggleable" : False,
|
||||
"default_enable" : True,
|
||||
}
|
||||
)
|
||||
|
@ -1,278 +1,278 @@
|
||||
import platform
|
||||
import time
|
||||
|
||||
import nonebot
|
||||
import psutil
|
||||
from cpuinfo import cpuinfo
|
||||
from nonebot import require
|
||||
from nonebot.adapters import satori
|
||||
|
||||
from liteyuki.utils import __NAME__, __VERSION__
|
||||
from liteyuki.utils.base.config import get_config
|
||||
from liteyuki.utils.base.data_manager import TempConfig, common_db
|
||||
from liteyuki.utils.base.language import Language
|
||||
from liteyuki.utils.base.resource import get_loaded_resource_packs, get_path
|
||||
from liteyuki.utils.message.html_tool import template2image
|
||||
from liteyuki.utils import satori_utils
|
||||
from .counter_for_satori import satori_counter
|
||||
from git import Repo
|
||||
|
||||
require("nonebot_plugin_apscheduler")
|
||||
from nonebot_plugin_apscheduler import scheduler
|
||||
|
||||
commit_hash = Repo(".").head.commit.hexsha
|
||||
|
||||
protocol_names = {
|
||||
0: "iPad",
|
||||
1: "Android Phone",
|
||||
2: "Android Watch",
|
||||
3: "Mac",
|
||||
5: "iPad",
|
||||
6: "Android Pad",
|
||||
}
|
||||
|
||||
"""
|
||||
Universal Interface
|
||||
data
|
||||
- bot
|
||||
- name: str
|
||||
icon: str
|
||||
id: int
|
||||
protocol_name: str
|
||||
groups: int
|
||||
friends: int
|
||||
message_sent: int
|
||||
message_received: int
|
||||
app_name: str
|
||||
- hardware
|
||||
- cpu
|
||||
- percent: float
|
||||
- name: str
|
||||
- mem
|
||||
- percent: float
|
||||
- total: int
|
||||
- used: int
|
||||
- free: int
|
||||
- swap
|
||||
- percent: float
|
||||
- total: int
|
||||
- used: int
|
||||
- free: int
|
||||
- disk: list
|
||||
- name: str
|
||||
- percent: float
|
||||
- total: int
|
||||
"""
|
||||
status_card_cache = {} # lang -> bytes
|
||||
|
||||
|
||||
# 60s刷新一次
|
||||
@scheduler.scheduled_job("cron", second="*/40")
|
||||
async def refresh_status_card():
|
||||
nonebot.logger.debug("Refreshing status card cache...")
|
||||
global status_card_cache
|
||||
bot_data = await get_bots_data()
|
||||
hardware_data = await get_hardware_data()
|
||||
liteyuki_data = await get_liteyuki_data()
|
||||
for lang in status_card_cache.keys():
|
||||
status_card_cache[lang] = await generate_status_card(
|
||||
bot_data,
|
||||
hardware_data,
|
||||
liteyuki_data,
|
||||
lang=lang,
|
||||
use_cache=False
|
||||
)
|
||||
|
||||
|
||||
async def generate_status_card(bot: dict, hardware: dict, liteyuki: dict, lang="zh-CN", bot_id="0",
|
||||
use_cache=False
|
||||
) -> bytes:
|
||||
if not use_cache:
|
||||
return await template2image(
|
||||
get_path("templates/status.html", abs_path=True),
|
||||
{
|
||||
"data": {
|
||||
"bot" : bot,
|
||||
"hardware" : hardware,
|
||||
"liteyuki" : liteyuki,
|
||||
"localization": get_local_data(lang)
|
||||
}
|
||||
},
|
||||
)
|
||||
else:
|
||||
if lang not in status_card_cache:
|
||||
status_card_cache[lang] = await generate_status_card(bot, hardware, liteyuki, lang=lang, bot_id=bot_id)
|
||||
return status_card_cache[lang]
|
||||
|
||||
|
||||
def get_local_data(lang_code) -> dict:
|
||||
lang = Language(lang_code)
|
||||
return {
|
||||
"friends" : lang.get("status.friends"),
|
||||
"groups" : lang.get("status.groups"),
|
||||
"plugins" : lang.get("status.plugins"),
|
||||
"bots" : lang.get("status.bots"),
|
||||
"message_sent" : lang.get("status.message_sent"),
|
||||
"message_received": lang.get("status.message_received"),
|
||||
"cpu" : lang.get("status.cpu"),
|
||||
"memory" : lang.get("status.memory"),
|
||||
"swap" : lang.get("status.swap"),
|
||||
"disk" : lang.get("status.disk"),
|
||||
|
||||
"usage" : lang.get("status.usage"),
|
||||
"total" : lang.get("status.total"),
|
||||
"used" : lang.get("status.used"),
|
||||
"free" : lang.get("status.free"),
|
||||
|
||||
"days" : lang.get("status.days"),
|
||||
"hours" : lang.get("status.hours"),
|
||||
"minutes" : lang.get("status.minutes"),
|
||||
"seconds" : lang.get("status.seconds"),
|
||||
"runtime" : lang.get("status.runtime"),
|
||||
"threads" : lang.get("status.threads"),
|
||||
"cores" : lang.get("status.cores"),
|
||||
"process" : lang.get("status.process"),
|
||||
"resources" : lang.get("status.resources"),
|
||||
"description" : lang.get("status.description"),
|
||||
}
|
||||
|
||||
|
||||
async def get_bots_data(self_id: str = "0") -> dict:
|
||||
"""获取当前所有机器人数据
|
||||
Returns:
|
||||
"""
|
||||
result = {
|
||||
"self_id": self_id,
|
||||
"bots" : [],
|
||||
}
|
||||
for bot_id, bot in nonebot.get_bots().items():
|
||||
groups = 0
|
||||
friends = 0
|
||||
status = {}
|
||||
bot_name = bot_id
|
||||
version_info = {}
|
||||
if isinstance(bot, satori.Bot):
|
||||
try:
|
||||
bot_name = (await satori_utils.user_infos.get(bot.self_id)).name
|
||||
groups = str(await satori_utils.count_groups(bot))
|
||||
friends = str(await satori_utils.count_friends(bot))
|
||||
status = {}
|
||||
version_info = await bot.get_version_info()
|
||||
except Exception:
|
||||
pass
|
||||
else:
|
||||
try:
|
||||
# API fetch
|
||||
bot_name = (await bot.get_login_info())["nickname"]
|
||||
groups = len(await bot.get_group_list())
|
||||
friends = len(await bot.get_friend_list())
|
||||
status = await bot.get_status()
|
||||
version_info = await bot.get_version_info()
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
statistics = status.get("stat", {})
|
||||
app_name = version_info.get("app_name", "UnknownImplementation")
|
||||
if app_name in ["Lagrange.OneBot", "LLOneBot", "Shamrock", "NapCat.Onebot"]:
|
||||
icon = f"https://q.qlogo.cn/g?b=qq&nk={bot_id}&s=640"
|
||||
elif isinstance(bot, satori.Bot):
|
||||
app_name = "Satori"
|
||||
icon = (await bot.login_get()).user.avatar
|
||||
else:
|
||||
icon = None
|
||||
bot_data = {
|
||||
"name" : bot_name,
|
||||
"icon" : icon,
|
||||
"id" : bot_id,
|
||||
"protocol_name" : protocol_names.get(version_info.get("protocol_name"), "Online"),
|
||||
"groups" : groups,
|
||||
"friends" : friends,
|
||||
"message_sent" : satori_counter.msg_sent if isinstance(bot, satori.Bot) else statistics.get("message_sent", 0),
|
||||
"message_received": satori_counter.msg_received if isinstance(bot, satori.Bot) else statistics.get("message_received", 0),
|
||||
"app_name" : app_name
|
||||
}
|
||||
result["bots"].append(bot_data)
|
||||
|
||||
return result
|
||||
|
||||
|
||||
async def get_hardware_data() -> dict:
|
||||
mem = psutil.virtual_memory()
|
||||
all_processes = psutil.Process().children(recursive=True)
|
||||
all_processes.append(psutil.Process())
|
||||
|
||||
mem_used_process = 0
|
||||
process_mem = {}
|
||||
for process in all_processes:
|
||||
try:
|
||||
ps_name = process.name().replace(".exe", "")
|
||||
if ps_name not in process_mem:
|
||||
process_mem[ps_name] = 0
|
||||
process_mem[ps_name] += process.memory_info().rss
|
||||
mem_used_process += process.memory_info().rss
|
||||
except Exception:
|
||||
pass
|
||||
swap = psutil.swap_memory()
|
||||
cpu_brand_raw = cpuinfo.get_cpu_info().get("brand_raw", "Unknown")
|
||||
if "AMD" in cpu_brand_raw:
|
||||
brand = "AMD"
|
||||
elif "Intel" in cpu_brand_raw:
|
||||
brand = "Intel"
|
||||
else:
|
||||
brand = "Unknown"
|
||||
result = {
|
||||
"cpu" : {
|
||||
"percent": psutil.cpu_percent(),
|
||||
"name" : f"{brand} {cpuinfo.get_cpu_info().get('arch', 'Unknown')}",
|
||||
"cores" : psutil.cpu_count(logical=False),
|
||||
"threads": psutil.cpu_count(logical=True),
|
||||
"freq" : psutil.cpu_freq().current # MHz
|
||||
},
|
||||
"memory": {
|
||||
"percent" : mem.percent,
|
||||
"total" : mem.total,
|
||||
"used" : mem.used,
|
||||
"free" : mem.free,
|
||||
"usedProcess": mem_used_process,
|
||||
},
|
||||
"swap" : {
|
||||
"percent": swap.percent,
|
||||
"total" : swap.total,
|
||||
"used" : swap.used,
|
||||
"free" : swap.free
|
||||
},
|
||||
"disk" : [],
|
||||
}
|
||||
|
||||
for disk in psutil.disk_partitions(all=True):
|
||||
try:
|
||||
disk_usage = psutil.disk_usage(disk.mountpoint)
|
||||
if disk_usage.total == 0:
|
||||
continue # 虚拟磁盘
|
||||
result["disk"].append({
|
||||
"name" : disk.mountpoint,
|
||||
"percent": disk_usage.percent,
|
||||
"total" : disk_usage.total,
|
||||
"used" : disk_usage.used,
|
||||
"free" : disk_usage.free
|
||||
})
|
||||
except:
|
||||
pass
|
||||
|
||||
return result
|
||||
|
||||
|
||||
async def get_liteyuki_data() -> dict:
|
||||
temp_data: TempConfig = common_db.where_one(TempConfig(), default=TempConfig())
|
||||
result = {
|
||||
"name" : list(get_config("nickname", [__NAME__]))[0],
|
||||
"version" : f"{__VERSION__}{'-' + commit_hash[:7] if (commit_hash and len(commit_hash) > 8) else ''}",
|
||||
"plugins" : len(nonebot.get_loaded_plugins()),
|
||||
"resources": len(get_loaded_resource_packs()),
|
||||
"nonebot" : f"{nonebot.__version__}",
|
||||
"python" : f"{platform.python_implementation()} {platform.python_version()}",
|
||||
"system" : f"{platform.system()} {platform.release()}",
|
||||
"runtime" : time.time() - temp_data.data.get("start_time", time.time()), # 运行时间秒数
|
||||
"bots" : len(nonebot.get_bots())
|
||||
}
|
||||
return result
|
||||
import platform
|
||||
import time
|
||||
|
||||
import nonebot
|
||||
import psutil
|
||||
from cpuinfo import cpuinfo
|
||||
from nonebot import require
|
||||
from nonebot.adapters import satori
|
||||
|
||||
from src.utils import __NAME__, __VERSION__
|
||||
from src.utils.base.config import get_config
|
||||
from src.utils.base.data_manager import TempConfig, common_db
|
||||
from src.utils.base.language import Language
|
||||
from src.utils.base.resource import get_loaded_resource_packs, get_path
|
||||
from src.utils.message.html_tool import template2image
|
||||
from src.utils import satori_utils
|
||||
from .counter_for_satori import satori_counter
|
||||
from git import Repo
|
||||
|
||||
require("nonebot_plugin_apscheduler")
|
||||
from nonebot_plugin_apscheduler import scheduler
|
||||
|
||||
commit_hash = Repo(".").head.commit.hexsha
|
||||
|
||||
protocol_names = {
|
||||
0: "iPad",
|
||||
1: "Android Phone",
|
||||
2: "Android Watch",
|
||||
3: "Mac",
|
||||
5: "iPad",
|
||||
6: "Android Pad",
|
||||
}
|
||||
|
||||
"""
|
||||
Universal Interface
|
||||
data
|
||||
- bot
|
||||
- name: str
|
||||
icon: str
|
||||
id: int
|
||||
protocol_name: str
|
||||
groups: int
|
||||
friends: int
|
||||
message_sent: int
|
||||
message_received: int
|
||||
app_name: str
|
||||
- hardware
|
||||
- cpu
|
||||
- percent: float
|
||||
- name: str
|
||||
- mem
|
||||
- percent: float
|
||||
- total: int
|
||||
- used: int
|
||||
- free: int
|
||||
- swap
|
||||
- percent: float
|
||||
- total: int
|
||||
- used: int
|
||||
- free: int
|
||||
- disk: list
|
||||
- name: str
|
||||
- percent: float
|
||||
- total: int
|
||||
"""
|
||||
status_card_cache = {} # lang -> bytes
|
||||
|
||||
|
||||
# 60s刷新一次
|
||||
@scheduler.scheduled_job("cron", second="*/40")
|
||||
async def refresh_status_card():
|
||||
nonebot.logger.debug("Refreshing status card cache...")
|
||||
global status_card_cache
|
||||
bot_data = await get_bots_data()
|
||||
hardware_data = await get_hardware_data()
|
||||
liteyuki_data = await get_liteyuki_data()
|
||||
for lang in status_card_cache.keys():
|
||||
status_card_cache[lang] = await generate_status_card(
|
||||
bot_data,
|
||||
hardware_data,
|
||||
liteyuki_data,
|
||||
lang=lang,
|
||||
use_cache=False
|
||||
)
|
||||
|
||||
|
||||
async def generate_status_card(bot: dict, hardware: dict, liteyuki: dict, lang="zh-CN", bot_id="0",
|
||||
use_cache=False
|
||||
) -> bytes:
|
||||
if not use_cache:
|
||||
return await template2image(
|
||||
get_path("templates/status.html", abs_path=True),
|
||||
{
|
||||
"data": {
|
||||
"bot" : bot,
|
||||
"hardware" : hardware,
|
||||
"liteyuki" : liteyuki,
|
||||
"localization": get_local_data(lang)
|
||||
}
|
||||
},
|
||||
)
|
||||
else:
|
||||
if lang not in status_card_cache:
|
||||
status_card_cache[lang] = await generate_status_card(bot, hardware, liteyuki, lang=lang, bot_id=bot_id)
|
||||
return status_card_cache[lang]
|
||||
|
||||
|
||||
def get_local_data(lang_code) -> dict:
|
||||
lang = Language(lang_code)
|
||||
return {
|
||||
"friends" : lang.get("status.friends"),
|
||||
"groups" : lang.get("status.groups"),
|
||||
"plugins" : lang.get("status.plugins"),
|
||||
"bots" : lang.get("status.bots"),
|
||||
"message_sent" : lang.get("status.message_sent"),
|
||||
"message_received": lang.get("status.message_received"),
|
||||
"cpu" : lang.get("status.cpu"),
|
||||
"memory" : lang.get("status.memory"),
|
||||
"swap" : lang.get("status.swap"),
|
||||
"disk" : lang.get("status.disk"),
|
||||
|
||||
"usage" : lang.get("status.usage"),
|
||||
"total" : lang.get("status.total"),
|
||||
"used" : lang.get("status.used"),
|
||||
"free" : lang.get("status.free"),
|
||||
|
||||
"days" : lang.get("status.days"),
|
||||
"hours" : lang.get("status.hours"),
|
||||
"minutes" : lang.get("status.minutes"),
|
||||
"seconds" : lang.get("status.seconds"),
|
||||
"runtime" : lang.get("status.runtime"),
|
||||
"threads" : lang.get("status.threads"),
|
||||
"cores" : lang.get("status.cores"),
|
||||
"process" : lang.get("status.process"),
|
||||
"resources" : lang.get("status.resources"),
|
||||
"description" : lang.get("status.description"),
|
||||
}
|
||||
|
||||
|
||||
async def get_bots_data(self_id: str = "0") -> dict:
|
||||
"""获取当前所有机器人数据
|
||||
Returns:
|
||||
"""
|
||||
result = {
|
||||
"self_id": self_id,
|
||||
"bots" : [],
|
||||
}
|
||||
for bot_id, bot in nonebot.get_bots().items():
|
||||
groups = 0
|
||||
friends = 0
|
||||
status = {}
|
||||
bot_name = bot_id
|
||||
version_info = {}
|
||||
if isinstance(bot, satori.Bot):
|
||||
try:
|
||||
bot_name = (await satori_utils.user_infos.get(bot.self_id)).name
|
||||
groups = str(await satori_utils.count_groups(bot))
|
||||
friends = str(await satori_utils.count_friends(bot))
|
||||
status = {}
|
||||
version_info = await bot.get_version_info()
|
||||
except Exception:
|
||||
pass
|
||||
else:
|
||||
try:
|
||||
# API fetch
|
||||
bot_name = (await bot.get_login_info())["nickname"]
|
||||
groups = len(await bot.get_group_list())
|
||||
friends = len(await bot.get_friend_list())
|
||||
status = await bot.get_status()
|
||||
version_info = await bot.get_version_info()
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
statistics = status.get("stat", {})
|
||||
app_name = version_info.get("app_name", "UnknownImplementation")
|
||||
if app_name in ["Lagrange.OneBot", "LLOneBot", "Shamrock", "NapCat.Onebot"]:
|
||||
icon = f"https://q.qlogo.cn/g?b=qq&nk={bot_id}&s=640"
|
||||
elif isinstance(bot, satori.Bot):
|
||||
app_name = "Satori"
|
||||
icon = (await bot.login_get()).user.avatar
|
||||
else:
|
||||
icon = None
|
||||
bot_data = {
|
||||
"name" : bot_name,
|
||||
"icon" : icon,
|
||||
"id" : bot_id,
|
||||
"protocol_name" : protocol_names.get(version_info.get("protocol_name"), "Online"),
|
||||
"groups" : groups,
|
||||
"friends" : friends,
|
||||
"message_sent" : satori_counter.msg_sent if isinstance(bot, satori.Bot) else statistics.get("message_sent", 0),
|
||||
"message_received": satori_counter.msg_received if isinstance(bot, satori.Bot) else statistics.get("message_received", 0),
|
||||
"app_name" : app_name
|
||||
}
|
||||
result["bots"].append(bot_data)
|
||||
|
||||
return result
|
||||
|
||||
|
||||
async def get_hardware_data() -> dict:
|
||||
mem = psutil.virtual_memory()
|
||||
all_processes = psutil.Process().children(recursive=True)
|
||||
all_processes.append(psutil.Process())
|
||||
|
||||
mem_used_process = 0
|
||||
process_mem = {}
|
||||
for process in all_processes:
|
||||
try:
|
||||
ps_name = process.name().replace(".exe", "")
|
||||
if ps_name not in process_mem:
|
||||
process_mem[ps_name] = 0
|
||||
process_mem[ps_name] += process.memory_info().rss
|
||||
mem_used_process += process.memory_info().rss
|
||||
except Exception:
|
||||
pass
|
||||
swap = psutil.swap_memory()
|
||||
cpu_brand_raw = cpuinfo.get_cpu_info().get("brand_raw", "Unknown")
|
||||
if "AMD" in cpu_brand_raw:
|
||||
brand = "AMD"
|
||||
elif "Intel" in cpu_brand_raw:
|
||||
brand = "Intel"
|
||||
else:
|
||||
brand = "Unknown"
|
||||
result = {
|
||||
"cpu" : {
|
||||
"percent": psutil.cpu_percent(),
|
||||
"name" : f"{brand} {cpuinfo.get_cpu_info().get('arch', 'Unknown')}",
|
||||
"cores" : psutil.cpu_count(logical=False),
|
||||
"threads": psutil.cpu_count(logical=True),
|
||||
"freq" : psutil.cpu_freq().current # MHz
|
||||
},
|
||||
"memory": {
|
||||
"percent" : mem.percent,
|
||||
"total" : mem.total,
|
||||
"used" : mem.used,
|
||||
"free" : mem.free,
|
||||
"usedProcess": mem_used_process,
|
||||
},
|
||||
"swap" : {
|
||||
"percent": swap.percent,
|
||||
"total" : swap.total,
|
||||
"used" : swap.used,
|
||||
"free" : swap.free
|
||||
},
|
||||
"disk" : [],
|
||||
}
|
||||
|
||||
for disk in psutil.disk_partitions(all=True):
|
||||
try:
|
||||
disk_usage = psutil.disk_usage(disk.mountpoint)
|
||||
if disk_usage.total == 0:
|
||||
continue # 虚拟磁盘
|
||||
result["disk"].append({
|
||||
"name" : disk.mountpoint,
|
||||
"percent": disk_usage.percent,
|
||||
"total" : disk_usage.total,
|
||||
"used" : disk_usage.used,
|
||||
"free" : disk_usage.free
|
||||
})
|
||||
except:
|
||||
pass
|
||||
|
||||
return result
|
||||
|
||||
|
||||
async def get_liteyuki_data() -> dict:
|
||||
temp_data: TempConfig = common_db.where_one(TempConfig(), default=TempConfig())
|
||||
result = {
|
||||
"name" : list(get_config("nickname", [__NAME__]))[0],
|
||||
"version" : f"{__VERSION__}{'-' + commit_hash[:7] if (commit_hash and len(commit_hash) > 8) else ''}",
|
||||
"plugins" : len(nonebot.get_loaded_plugins()),
|
||||
"resources": len(get_loaded_resource_packs()),
|
||||
"nonebot" : f"{nonebot.__version__}",
|
||||
"python" : f"{platform.python_implementation()} {platform.python_version()}",
|
||||
"system" : f"{platform.system()} {platform.release()}",
|
||||
"runtime" : time.time() - temp_data.data.get("start_time", time.time()), # 运行时间秒数
|
||||
"bots" : len(nonebot.get_bots())
|
||||
}
|
||||
return result
|
@ -1,10 +1,10 @@
|
||||
class SatoriCounter:
|
||||
msg_sent: int
|
||||
msg_received: int
|
||||
|
||||
def __init__(self):
|
||||
self.msg_sent = 0
|
||||
self.msg_received = 0
|
||||
|
||||
|
||||
satori_counter = SatoriCounter()
|
||||
class SatoriCounter:
|
||||
msg_sent: int
|
||||
msg_received: int
|
||||
|
||||
def __init__(self):
|
||||
self.msg_sent = 0
|
||||
self.msg_received = 0
|
||||
|
||||
|
||||
satori_counter = SatoriCounter()
|
@ -1,49 +1,49 @@
|
||||
from liteyuki.utils import event as event_utils
|
||||
from liteyuki.utils.base.language import get_user_lang
|
||||
from liteyuki.utils.base.ly_typing import T_Bot, T_MessageEvent
|
||||
from .api import *
|
||||
|
||||
require("nonebot_plugin_alconna")
|
||||
from nonebot_plugin_alconna import on_alconna, Alconna, Subcommand, UniMessage
|
||||
|
||||
status_alc = on_alconna(
|
||||
aliases={"状态"},
|
||||
command=Alconna(
|
||||
"status",
|
||||
Subcommand(
|
||||
"memory",
|
||||
alias={"mem", "m", "内存"},
|
||||
),
|
||||
Subcommand(
|
||||
"process",
|
||||
alias={"proc", "p", "进程"},
|
||||
)
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
@status_alc.handle()
|
||||
async def _(event: T_MessageEvent, bot: T_Bot):
|
||||
ulang = get_user_lang(event_utils.get_user_id(event))
|
||||
if ulang.lang_code in status_card_cache:
|
||||
image = status_card_cache[ulang.lang_code]
|
||||
else:
|
||||
image = await generate_status_card(
|
||||
bot=await get_bots_data(),
|
||||
hardware=await get_hardware_data(),
|
||||
liteyuki=await get_liteyuki_data(),
|
||||
lang=ulang.lang_code,
|
||||
bot_id=bot.self_id,
|
||||
use_cache=True
|
||||
)
|
||||
await status_alc.finish(UniMessage.image(raw=image))
|
||||
|
||||
|
||||
@status_alc.assign("memory")
|
||||
async def _():
|
||||
print("memory")
|
||||
|
||||
|
||||
@status_alc.assign("process")
|
||||
async def _():
|
||||
print("process")
|
||||
from src.utils import event as event_utils
|
||||
from src.utils.base.language import get_user_lang
|
||||
from src.utils.base.ly_typing import T_Bot, T_MessageEvent
|
||||
from .api import *
|
||||
|
||||
require("nonebot_plugin_alconna")
|
||||
from nonebot_plugin_alconna import on_alconna, Alconna, Subcommand, UniMessage
|
||||
|
||||
status_alc = on_alconna(
|
||||
aliases={"状态"},
|
||||
command=Alconna(
|
||||
"status",
|
||||
Subcommand(
|
||||
"memory",
|
||||
alias={"mem", "m", "内存"},
|
||||
),
|
||||
Subcommand(
|
||||
"process",
|
||||
alias={"proc", "p", "进程"},
|
||||
)
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
@status_alc.handle()
|
||||
async def _(event: T_MessageEvent, bot: T_Bot):
|
||||
ulang = get_user_lang(event_utils.get_user_id(event))
|
||||
if ulang.lang_code in status_card_cache:
|
||||
image = status_card_cache[ulang.lang_code]
|
||||
else:
|
||||
image = await generate_status_card(
|
||||
bot=await get_bots_data(),
|
||||
hardware=await get_hardware_data(),
|
||||
liteyuki=await get_liteyuki_data(),
|
||||
lang=ulang.lang_code,
|
||||
bot_id=bot.self_id,
|
||||
use_cache=True
|
||||
)
|
||||
await status_alc.finish(UniMessage.image(raw=image))
|
||||
|
||||
|
||||
@status_alc.assign("memory")
|
||||
async def _():
|
||||
print("memory")
|
||||
|
||||
|
||||
@status_alc.assign("process")
|
||||
async def _():
|
||||
print("process")
|
@ -1,17 +1,17 @@
|
||||
from nonebot.plugin import PluginMetadata
|
||||
from .api import *
|
||||
|
||||
__author__ = "snowykami"
|
||||
__plugin_meta__ = PluginMetadata(
|
||||
name="联合黑名单(测试中...)",
|
||||
description="",
|
||||
usage="",
|
||||
type="application",
|
||||
homepage="https://github.com/snowykami/LiteyukiBot",
|
||||
extra={
|
||||
"liteyuki": True,
|
||||
"toggleable" : True,
|
||||
"default_enable" : True,
|
||||
}
|
||||
)
|
||||
|
||||
from nonebot.plugin import PluginMetadata
|
||||
from .api import *
|
||||
|
||||
__author__ = "snowykami"
|
||||
__plugin_meta__ = PluginMetadata(
|
||||
name="联合黑名单(测试中...)",
|
||||
description="",
|
||||
usage="",
|
||||
type="application",
|
||||
homepage="https://github.com/snowykami/LiteyukiBot",
|
||||
extra={
|
||||
"liteyuki": True,
|
||||
"toggleable" : True,
|
||||
"default_enable" : True,
|
||||
}
|
||||
)
|
||||
|
@ -1,59 +1,59 @@
|
||||
import datetime
|
||||
|
||||
import aiohttp
|
||||
import httpx
|
||||
import nonebot
|
||||
from nonebot import require
|
||||
from nonebot.exception import IgnoredException
|
||||
from nonebot.message import event_preprocessor
|
||||
from nonebot_plugin_alconna.typings import Event
|
||||
|
||||
require("nonebot_plugin_apscheduler")
|
||||
|
||||
from nonebot_plugin_apscheduler import scheduler
|
||||
|
||||
blacklist_data: dict[str, set[str]] = {}
|
||||
blacklist: set[str] = set()
|
||||
|
||||
|
||||
@scheduler.scheduled_job("interval", minutes=10, next_run_time=datetime.datetime.now())
|
||||
async def update_blacklist():
|
||||
await request_for_blacklist()
|
||||
|
||||
|
||||
async def request_for_blacklist():
|
||||
global blacklist
|
||||
urls = [
|
||||
"https://cdn.liteyuki.icu/static/ubl/"
|
||||
]
|
||||
|
||||
platforms = [
|
||||
"qq"
|
||||
]
|
||||
|
||||
for plat in platforms:
|
||||
for url in urls:
|
||||
url += f"{plat}.txt"
|
||||
async with aiohttp.ClientSession() as client:
|
||||
resp = await client.get(url)
|
||||
blacklist_data[plat] = set((await resp.text()).splitlines())
|
||||
blacklist = get_uni_set()
|
||||
nonebot.logger.info("blacklists updated")
|
||||
|
||||
|
||||
def get_uni_set() -> set:
|
||||
s = set()
|
||||
for new_set in blacklist_data.values():
|
||||
s.update(new_set)
|
||||
return s
|
||||
|
||||
|
||||
@event_preprocessor
|
||||
async def pre_handle(event: Event):
|
||||
try:
|
||||
user_id = str(event.get_user_id())
|
||||
except:
|
||||
return
|
||||
|
||||
if user_id in get_uni_set():
|
||||
raise IgnoredException("UserId in blacklist")
|
||||
import datetime
|
||||
|
||||
import aiohttp
|
||||
import httpx
|
||||
import nonebot
|
||||
from nonebot import require
|
||||
from nonebot.exception import IgnoredException
|
||||
from nonebot.message import event_preprocessor
|
||||
from nonebot_plugin_alconna.typings import Event
|
||||
|
||||
require("nonebot_plugin_apscheduler")
|
||||
|
||||
from nonebot_plugin_apscheduler import scheduler
|
||||
|
||||
blacklist_data: dict[str, set[str]] = {}
|
||||
blacklist: set[str] = set()
|
||||
|
||||
|
||||
@scheduler.scheduled_job("interval", minutes=10, next_run_time=datetime.datetime.now())
|
||||
async def update_blacklist():
|
||||
await request_for_blacklist()
|
||||
|
||||
|
||||
async def request_for_blacklist():
|
||||
global blacklist
|
||||
urls = [
|
||||
"https://cdn.liteyuki.icu/static/ubl/"
|
||||
]
|
||||
|
||||
platforms = [
|
||||
"qq"
|
||||
]
|
||||
|
||||
for plat in platforms:
|
||||
for url in urls:
|
||||
url += f"{plat}.txt"
|
||||
async with aiohttp.ClientSession() as client:
|
||||
resp = await client.get(url)
|
||||
blacklist_data[plat] = set((await resp.text()).splitlines())
|
||||
blacklist = get_uni_set()
|
||||
nonebot.logger.info("blacklists updated")
|
||||
|
||||
|
||||
def get_uni_set() -> set:
|
||||
s = set()
|
||||
for new_set in blacklist_data.values():
|
||||
s.update(new_set)
|
||||
return s
|
||||
|
||||
|
||||
@event_preprocessor
|
||||
async def pre_handle(event: Event):
|
||||
try:
|
||||
user_id = str(event.get_user_id())
|
||||
except:
|
||||
return
|
||||
|
||||
if user_id in get_uni_set():
|
||||
raise IgnoredException("UserId in blacklist")
|
@ -1,16 +1,16 @@
|
||||
from nonebot.plugin import PluginMetadata
|
||||
|
||||
from .profile_manager import *
|
||||
|
||||
__author__ = "snowykami"
|
||||
__plugin_meta__ = PluginMetadata(
|
||||
name="轻雪用户管理",
|
||||
description="用户管理插件",
|
||||
usage="",
|
||||
homepage="https://github.com/snowykami/LiteyukiBot",
|
||||
extra={
|
||||
"liteyuki" : True,
|
||||
"toggleable" : False,
|
||||
"default_enable": True,
|
||||
}
|
||||
)
|
||||
from nonebot.plugin import PluginMetadata
|
||||
|
||||
from .profile_manager import *
|
||||
|
||||
__author__ = "snowykami"
|
||||
__plugin_meta__ = PluginMetadata(
|
||||
name="轻雪用户管理",
|
||||
description="用户管理插件",
|
||||
usage="",
|
||||
homepage="https://github.com/snowykami/LiteyukiBot",
|
||||
extra={
|
||||
"liteyuki" : True,
|
||||
"toggleable" : False,
|
||||
"default_enable": True,
|
||||
}
|
||||
)
|
@ -1,23 +1,23 @@
|
||||
representative_timezones_list = [
|
||||
"Etc/GMT+12", # 国际日期变更线西
|
||||
"Pacific/Honolulu", # 夏威夷标准时间
|
||||
"America/Anchorage", # 阿拉斯加标准时间
|
||||
"America/Los_Angeles", # 美国太平洋标准时间
|
||||
"America/Denver", # 美国山地标准时间
|
||||
"America/Chicago", # 美国中部标准时间
|
||||
"America/New_York", # 美国东部标准时间
|
||||
"Europe/London", # 英国标准时间
|
||||
"Europe/Paris", # 中欧标准时间
|
||||
"Europe/Moscow", # 莫斯科标准时间
|
||||
"Asia/Dubai", # 阿联酋标准时间
|
||||
"Asia/Kolkata", # 印度标准时间
|
||||
"Asia/Shanghai", # 中国标准时间
|
||||
"Asia/Hong_Kong", # 中国香港标准时间
|
||||
"Asia/Chongqing", # 中国重庆标准时间
|
||||
"Asia/Macau", # 中国澳门标准时间
|
||||
"Asia/Taipei", # 中国台湾标准时间
|
||||
"Asia/Tokyo", # 日本标准时间
|
||||
"Australia/Sydney", # 澳大利亚东部标准时间
|
||||
"Pacific/Auckland" # 新西兰标准时间
|
||||
]
|
||||
representative_timezones_list = [
|
||||
"Etc/GMT+12", # 国际日期变更线西
|
||||
"Pacific/Honolulu", # 夏威夷标准时间
|
||||
"America/Anchorage", # 阿拉斯加标准时间
|
||||
"America/Los_Angeles", # 美国太平洋标准时间
|
||||
"America/Denver", # 美国山地标准时间
|
||||
"America/Chicago", # 美国中部标准时间
|
||||
"America/New_York", # 美国东部标准时间
|
||||
"Europe/London", # 英国标准时间
|
||||
"Europe/Paris", # 中欧标准时间
|
||||
"Europe/Moscow", # 莫斯科标准时间
|
||||
"Asia/Dubai", # 阿联酋标准时间
|
||||
"Asia/Kolkata", # 印度标准时间
|
||||
"Asia/Shanghai", # 中国标准时间
|
||||
"Asia/Hong_Kong", # 中国香港标准时间
|
||||
"Asia/Chongqing", # 中国重庆标准时间
|
||||
"Asia/Macau", # 中国澳门标准时间
|
||||
"Asia/Taipei", # 中国台湾标准时间
|
||||
"Asia/Tokyo", # 日本标准时间
|
||||
"Australia/Sydney", # 澳大利亚东部标准时间
|
||||
"Pacific/Auckland" # 新西兰标准时间
|
||||
]
|
||||
representative_timezones_list.sort()
|
@ -1,150 +1,150 @@
|
||||
from typing import Optional
|
||||
|
||||
import pytz
|
||||
from nonebot import require
|
||||
|
||||
from liteyuki.utils.base.data import LiteModel, Database
|
||||
from liteyuki.utils.base.data_manager import User, user_db, group_db
|
||||
from liteyuki.utils.base.language import Language, change_user_lang, get_all_lang, get_user_lang
|
||||
from liteyuki.utils.base.ly_typing import T_Bot, T_MessageEvent
|
||||
from liteyuki.utils.message.message import MarkdownMessage as md
|
||||
from .const import representative_timezones_list
|
||||
from liteyuki.utils import event as event_utils
|
||||
|
||||
require("nonebot_plugin_alconna")
|
||||
from nonebot_plugin_alconna import Alconna, Args, Arparma, Subcommand, on_alconna
|
||||
|
||||
profile_alc = on_alconna(
|
||||
Alconna(
|
||||
"profile",
|
||||
Subcommand(
|
||||
"set",
|
||||
Args["key", str]["value", str, None],
|
||||
alias=["s", "设置"],
|
||||
),
|
||||
Subcommand(
|
||||
"get",
|
||||
Args["key", str],
|
||||
alias=["g", "查询"],
|
||||
),
|
||||
),
|
||||
aliases={"用户信息"}
|
||||
)
|
||||
|
||||
|
||||
# json储存
|
||||
class Profile(LiteModel):
|
||||
lang: str = "zh-CN"
|
||||
nickname: str = ""
|
||||
timezone: str = "Asia/Shanghai"
|
||||
location: str = ""
|
||||
|
||||
|
||||
@profile_alc.handle()
|
||||
async def _(result: Arparma, event: T_MessageEvent, bot: T_Bot):
|
||||
user: User = user_db.where_one(User(), "user_id = ?", event_utils.get_user_id(event),
|
||||
default=User(user_id=str(event_utils.get_user_id(event))))
|
||||
ulang = get_user_lang(str(event_utils.get_user_id(event)))
|
||||
if result.subcommands.get("set"):
|
||||
if result.subcommands["set"].args.get("value"):
|
||||
# 对合法性进行校验后设置
|
||||
r = set_profile(result.args["key"], result.args["value"], str(event_utils.get_user_id(event)))
|
||||
if r:
|
||||
user.profile[result.args["key"]] = result.args["value"]
|
||||
user_db.save(user) # 数据库保存
|
||||
await profile_alc.finish(
|
||||
ulang.get(
|
||||
"user.profile.set_success",
|
||||
ATTR=ulang.get(f"user.profile.{result.args['key']}"),
|
||||
VALUE=result.args["value"]
|
||||
)
|
||||
)
|
||||
else:
|
||||
await profile_alc.finish(ulang.get("user.profile.set_failed", ATTR=ulang.get(f"user.profile.{result.args['key']}")))
|
||||
else:
|
||||
# 未输入值,尝试呼出菜单
|
||||
menu = get_profile_menu(result.args["key"], ulang)
|
||||
if menu:
|
||||
await md.send_md(menu, bot, event=event)
|
||||
else:
|
||||
await profile_alc.finish(ulang.get("user.profile.input_value", ATTR=ulang.get(f"user.profile.{result.args['key']}")))
|
||||
|
||||
user.profile[result.args["key"]] = result.args["value"]
|
||||
|
||||
elif result.subcommands.get("get"):
|
||||
if result.args["key"] in user.profile:
|
||||
await profile_alc.finish(user.profile[result.args["key"]])
|
||||
else:
|
||||
await profile_alc.finish("无此键值")
|
||||
else:
|
||||
profile = Profile(**user.profile)
|
||||
|
||||
for k, v in user.profile.items():
|
||||
profile.__setattr__(k, v)
|
||||
|
||||
reply = f"# {ulang.get('user.profile.info')}\n***\n"
|
||||
|
||||
hidden_attr = ["id", "TABLE_NAME"]
|
||||
enter_attr = ["lang", "timezone"]
|
||||
|
||||
for key in sorted(profile.dict().keys()):
|
||||
if key in hidden_attr:
|
||||
continue
|
||||
val = profile.dict()[key]
|
||||
key_text = ulang.get(f"user.profile.{key}")
|
||||
btn_set = md.btn_cmd(ulang.get("user.profile.edit"), f"profile set {key}",
|
||||
enter=True if key in enter_attr else False)
|
||||
reply += (f"\n**{key_text}** **{val}**\n"
|
||||
f"\n> {ulang.get(f'user.profile.{key}.desc')}"
|
||||
f"\n> {btn_set} \n\n***\n")
|
||||
await md.send_md(reply, bot, event=event)
|
||||
|
||||
|
||||
def get_profile_menu(key: str, ulang: Language) -> Optional[str]:
|
||||
"""获取属性的markdown菜单
|
||||
Args:
|
||||
ulang: 用户语言
|
||||
key: 属性键
|
||||
|
||||
Returns:
|
||||
|
||||
"""
|
||||
setting_name = ulang.get(f"user.profile.{key}")
|
||||
|
||||
no_menu = ["id", "nickname", "location"]
|
||||
|
||||
if key in no_menu:
|
||||
return None
|
||||
|
||||
reply = f"**{setting_name} {ulang.get('user.profile.settings')}**\n***\n"
|
||||
if key == "lang":
|
||||
for lang_code, lang_name in get_all_lang().items():
|
||||
btn_set_lang = md.btn_cmd(f"{lang_name}({lang_code})", f"profile set {key} {lang_code}")
|
||||
reply += f"\n{btn_set_lang}\n***\n"
|
||||
elif key == "timezone":
|
||||
for tz in representative_timezones_list:
|
||||
btn_set_tz = md.btn_cmd(tz, f"profile set {key} {tz}")
|
||||
reply += f"{btn_set_tz}\n***\n"
|
||||
return reply
|
||||
|
||||
|
||||
def set_profile(key: str, value: str, user_id: str) -> bool:
|
||||
"""设置属性,使用if分支对每一个合法性进行检查
|
||||
Args:
|
||||
user_id:
|
||||
key:
|
||||
value:
|
||||
|
||||
Returns:
|
||||
是否成功设置,输入合法性不通过返回False
|
||||
|
||||
"""
|
||||
if key == "lang":
|
||||
if value in get_all_lang():
|
||||
change_user_lang(user_id, value)
|
||||
return True
|
||||
elif key == "timezone":
|
||||
if value in pytz.all_timezones:
|
||||
return True
|
||||
elif key == "nickname":
|
||||
return True
|
||||
from typing import Optional
|
||||
|
||||
import pytz
|
||||
from nonebot import require
|
||||
|
||||
from src.utils.base.data import LiteModel, Database
|
||||
from src.utils.base.data_manager import User, user_db, group_db
|
||||
from src.utils.base.language import Language, change_user_lang, get_all_lang, get_user_lang
|
||||
from src.utils.base.ly_typing import T_Bot, T_MessageEvent
|
||||
from src.utils.message.message import MarkdownMessage as md
|
||||
from .const import representative_timezones_list
|
||||
from src.utils import event as event_utils
|
||||
|
||||
require("nonebot_plugin_alconna")
|
||||
from nonebot_plugin_alconna import Alconna, Args, Arparma, Subcommand, on_alconna
|
||||
|
||||
profile_alc = on_alconna(
|
||||
Alconna(
|
||||
"profile",
|
||||
Subcommand(
|
||||
"set",
|
||||
Args["key", str]["value", str, None],
|
||||
alias=["s", "设置"],
|
||||
),
|
||||
Subcommand(
|
||||
"get",
|
||||
Args["key", str],
|
||||
alias=["g", "查询"],
|
||||
),
|
||||
),
|
||||
aliases={"用户信息"}
|
||||
)
|
||||
|
||||
|
||||
# json储存
|
||||
class Profile(LiteModel):
|
||||
lang: str = "zh-CN"
|
||||
nickname: str = ""
|
||||
timezone: str = "Asia/Shanghai"
|
||||
location: str = ""
|
||||
|
||||
|
||||
@profile_alc.handle()
|
||||
async def _(result: Arparma, event: T_MessageEvent, bot: T_Bot):
|
||||
user: User = user_db.where_one(User(), "user_id = ?", event_utils.get_user_id(event),
|
||||
default=User(user_id=str(event_utils.get_user_id(event))))
|
||||
ulang = get_user_lang(str(event_utils.get_user_id(event)))
|
||||
if result.subcommands.get("set"):
|
||||
if result.subcommands["set"].args.get("value"):
|
||||
# 对合法性进行校验后设置
|
||||
r = set_profile(result.args["key"], result.args["value"], str(event_utils.get_user_id(event)))
|
||||
if r:
|
||||
user.profile[result.args["key"]] = result.args["value"]
|
||||
user_db.save(user) # 数据库保存
|
||||
await profile_alc.finish(
|
||||
ulang.get(
|
||||
"user.profile.set_success",
|
||||
ATTR=ulang.get(f"user.profile.{result.args['key']}"),
|
||||
VALUE=result.args["value"]
|
||||
)
|
||||
)
|
||||
else:
|
||||
await profile_alc.finish(ulang.get("user.profile.set_failed", ATTR=ulang.get(f"user.profile.{result.args['key']}")))
|
||||
else:
|
||||
# 未输入值,尝试呼出菜单
|
||||
menu = get_profile_menu(result.args["key"], ulang)
|
||||
if menu:
|
||||
await md.send_md(menu, bot, event=event)
|
||||
else:
|
||||
await profile_alc.finish(ulang.get("user.profile.input_value", ATTR=ulang.get(f"user.profile.{result.args['key']}")))
|
||||
|
||||
user.profile[result.args["key"]] = result.args["value"]
|
||||
|
||||
elif result.subcommands.get("get"):
|
||||
if result.args["key"] in user.profile:
|
||||
await profile_alc.finish(user.profile[result.args["key"]])
|
||||
else:
|
||||
await profile_alc.finish("无此键值")
|
||||
else:
|
||||
profile = Profile(**user.profile)
|
||||
|
||||
for k, v in user.profile.items():
|
||||
profile.__setattr__(k, v)
|
||||
|
||||
reply = f"# {ulang.get('user.profile.info')}\n***\n"
|
||||
|
||||
hidden_attr = ["id", "TABLE_NAME"]
|
||||
enter_attr = ["lang", "timezone"]
|
||||
|
||||
for key in sorted(profile.dict().keys()):
|
||||
if key in hidden_attr:
|
||||
continue
|
||||
val = profile.dict()[key]
|
||||
key_text = ulang.get(f"user.profile.{key}")
|
||||
btn_set = md.btn_cmd(ulang.get("user.profile.edit"), f"profile set {key}",
|
||||
enter=True if key in enter_attr else False)
|
||||
reply += (f"\n**{key_text}** **{val}**\n"
|
||||
f"\n> {ulang.get(f'user.profile.{key}.desc')}"
|
||||
f"\n> {btn_set} \n\n***\n")
|
||||
await md.send_md(reply, bot, event=event)
|
||||
|
||||
|
||||
def get_profile_menu(key: str, ulang: Language) -> Optional[str]:
|
||||
"""获取属性的markdown菜单
|
||||
Args:
|
||||
ulang: 用户语言
|
||||
key: 属性键
|
||||
|
||||
Returns:
|
||||
|
||||
"""
|
||||
setting_name = ulang.get(f"user.profile.{key}")
|
||||
|
||||
no_menu = ["id", "nickname", "location"]
|
||||
|
||||
if key in no_menu:
|
||||
return None
|
||||
|
||||
reply = f"**{setting_name} {ulang.get('user.profile.settings')}**\n***\n"
|
||||
if key == "lang":
|
||||
for lang_code, lang_name in get_all_lang().items():
|
||||
btn_set_lang = md.btn_cmd(f"{lang_name}({lang_code})", f"profile set {key} {lang_code}")
|
||||
reply += f"\n{btn_set_lang}\n***\n"
|
||||
elif key == "timezone":
|
||||
for tz in representative_timezones_list:
|
||||
btn_set_tz = md.btn_cmd(tz, f"profile set {key} {tz}")
|
||||
reply += f"{btn_set_tz}\n***\n"
|
||||
return reply
|
||||
|
||||
|
||||
def set_profile(key: str, value: str, user_id: str) -> bool:
|
||||
"""设置属性,使用if分支对每一个合法性进行检查
|
||||
Args:
|
||||
user_id:
|
||||
key:
|
||||
value:
|
||||
|
||||
Returns:
|
||||
是否成功设置,输入合法性不通过返回False
|
||||
|
||||
"""
|
||||
if key == "lang":
|
||||
if value in get_all_lang():
|
||||
change_user_lang(user_id, value)
|
||||
return True
|
||||
elif key == "timezone":
|
||||
if value in pytz.all_timezones:
|
||||
return True
|
||||
elif key == "nickname":
|
||||
return True
|
@ -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 liteyuki.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 liteyuki.utils.base.config import get_config
|
||||
from liteyuki.utils.base.ly_typing import T_MessageEvent
|
||||
|
||||
from .qw_api import *
|
||||
from liteyuki.utils.base.data_manager import User, user_db
|
||||
from liteyuki.utils.base.language import Language, get_user_lang
|
||||
from liteyuki.utils.base.resource import get_path
|
||||
from liteyuki.utils.message.html_tool import template2image
|
||||
from liteyuki.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,21 +1,21 @@
|
||||
from nonebot.plugin import PluginMetadata
|
||||
|
||||
from .main import *
|
||||
|
||||
__author__ = "snowykami"
|
||||
__plugin_meta__ = PluginMetadata(
|
||||
name="网页监控面板",
|
||||
description="网页监控面板,用于查看机器人的状态和信息",
|
||||
usage=(
|
||||
"访问 127.0.0.1:port 查看机器人的状态信息\n"
|
||||
"stat msg -g|--group [group_id] 查看群的统计信息,不带参数为全群\n"
|
||||
"配置项:custom_domain,自定义域名,通常对外用,内网无需"
|
||||
),
|
||||
type="application",
|
||||
homepage="https://github.com/snowykami/LiteyukiBot",
|
||||
extra={
|
||||
"liteyuki" : True,
|
||||
"toggleable" : False,
|
||||
"default_enable": True,
|
||||
}
|
||||
)
|
||||
from nonebot.plugin import PluginMetadata
|
||||
|
||||
from .main import *
|
||||
|
||||
__author__ = "snowykami"
|
||||
__plugin_meta__ = PluginMetadata(
|
||||
name="网页监控面板",
|
||||
description="网页监控面板,用于查看机器人的状态和信息",
|
||||
usage=(
|
||||
"访问 127.0.0.1:port 查看机器人的状态信息\n"
|
||||
"stat msg -g|--group [group_id] 查看群的统计信息,不带参数为全群\n"
|
||||
"配置项:custom_domain,自定义域名,通常对外用,内网无需"
|
||||
),
|
||||
type="application",
|
||||
homepage="https://github.com/snowykami/LiteyukiBot",
|
||||
extra={
|
||||
"liteyuki" : True,
|
||||
"toggleable" : False,
|
||||
"default_enable": True,
|
||||
}
|
||||
)
|
@ -1,4 +1,4 @@
|
||||
from fastapi import FastAPI
|
||||
from nonebot import get_app
|
||||
|
||||
from fastapi import FastAPI
|
||||
from nonebot import get_app
|
||||
|
||||
app: FastAPI = get_app()
|
@ -1,10 +1,10 @@
|
||||
from fastapi import FastAPI
|
||||
from nonebot import get_app
|
||||
from .restful_api import *
|
||||
|
||||
|
||||
@app.get("/ping")
|
||||
async def root():
|
||||
return {
|
||||
"message": "pong"
|
||||
}
|
||||
from fastapi import FastAPI
|
||||
from nonebot import get_app
|
||||
from .restful_api import *
|
||||
|
||||
|
||||
@app.get("/ping")
|
||||
async def root():
|
||||
return {
|
||||
"message": "pong"
|
||||
}
|
@ -1,24 +1,24 @@
|
||||
from fastapi import FastAPI, APIRouter
|
||||
from .common import *
|
||||
|
||||
device_info_router = APIRouter(prefix="/api/device-info")
|
||||
bot_info_router = APIRouter(prefix="/api/bot-info")
|
||||
|
||||
|
||||
@device_info_router.get("/")
|
||||
async def device_info():
|
||||
print("Hello Device Info")
|
||||
return {
|
||||
"message": "Hello Device Info"
|
||||
}
|
||||
|
||||
|
||||
@bot_info_router.get("/")
|
||||
async def bot_info():
|
||||
return {
|
||||
"message": "Hello Bot Info"
|
||||
}
|
||||
|
||||
|
||||
app.include_router(device_info_router)
|
||||
app.include_router(bot_info_router)
|
||||
from fastapi import FastAPI, APIRouter
|
||||
from .common import *
|
||||
|
||||
device_info_router = APIRouter(prefix="/api/device-info")
|
||||
bot_info_router = APIRouter(prefix="/api/bot-info")
|
||||
|
||||
|
||||
@device_info_router.get("/")
|
||||
async def device_info():
|
||||
print("Hello Device Info")
|
||||
return {
|
||||
"message": "Hello Device Info"
|
||||
}
|
||||
|
||||
|
||||
@bot_info_router.get("/")
|
||||
async def bot_info():
|
||||
return {
|
||||
"message": "Hello Bot Info"
|
||||
}
|
||||
|
||||
|
||||
app.include_router(device_info_router)
|
||||
app.include_router(bot_info_router)
|
@ -1,3 +1,3 @@
|
||||
name: Sign Status
|
||||
description: for Lagrange
|
||||
name: Sign Status
|
||||
description: for Lagrange
|
||||
version: 2024.4.26
|
@ -1,4 +1,4 @@
|
||||
.sign-chart {
|
||||
height: 400px;
|
||||
background-color: rgba(255, 255, 255, 0.7);
|
||||
.sign-chart {
|
||||
height: 400px;
|
||||
background-color: rgba(255, 255, 255, 0.7);
|
||||
}
|
@ -1,75 +1,75 @@
|
||||
// 数据类型声明
|
||||
// import * as echarts from 'echarts';
|
||||
|
||||
let data = JSON.parse(document.getElementById("data").innerText) // object
|
||||
const signChartDivTemplate = document.importNode(document.getElementById("sign-chart-template").content, true)
|
||||
data.forEach((item) => {
|
||||
let signChartDiv = signChartDivTemplate.cloneNode(true)
|
||||
let chartID = item["name"]
|
||||
// 初始化ECharts实例
|
||||
// 设置id
|
||||
signChartDiv.querySelector(".sign-chart").id = chartID
|
||||
document.body.appendChild(signChartDiv)
|
||||
|
||||
let signChart = echarts.init(document.getElementById(chartID))
|
||||
let timeCount = []
|
||||
|
||||
item["counts"].forEach((count, index) => {
|
||||
// 计算平均值,index - 1的count + index的count + index + 1的count /3
|
||||
if (index > 0) {
|
||||
timeCount.push((item["counts"][index] - item["counts"][index - 1]) / (60*(item["times"][index] - item["times"][index - 1])))
|
||||
}
|
||||
})
|
||||
|
||||
console.log(timeCount)
|
||||
|
||||
signChart.setOption(
|
||||
{
|
||||
animation: false,
|
||||
title: {
|
||||
text: item["name"],
|
||||
textStyle: {
|
||||
color: '#000000' // 设置标题文本颜色为红色
|
||||
}
|
||||
},
|
||||
xAxis: {
|
||||
type: 'category',
|
||||
data: item["times"].map(timestampToTime),
|
||||
},
|
||||
yAxis: [
|
||||
{
|
||||
type: 'value',
|
||||
min: Math.min(...item["counts"]),
|
||||
},
|
||||
{
|
||||
type: 'value',
|
||||
min: Math.min(...timeCount),
|
||||
}
|
||||
],
|
||||
series: [
|
||||
{
|
||||
data: item["counts"],
|
||||
type: 'line',
|
||||
yAxisIndex: 0
|
||||
},
|
||||
{
|
||||
data: timeCount,
|
||||
type: 'line',
|
||||
yAxisIndex: 1
|
||||
}
|
||||
]
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
|
||||
function timestampToTime(timestamp) {
|
||||
let date = new Date(timestamp * 1000)
|
||||
let Y = date.getFullYear() + '-'
|
||||
let M = (date.getMonth() + 1 < 10 ? '0' + (date.getMonth() + 1) : date.getMonth() + 1) + '-'
|
||||
let D = date.getDate() + ' '
|
||||
let h = date.getHours() + ':'
|
||||
let m = date.getMinutes() + ':'
|
||||
let s = date.getSeconds()
|
||||
return M + D + h + m + s
|
||||
// 数据类型声明
|
||||
// import * as echarts from 'echarts';
|
||||
|
||||
let data = JSON.parse(document.getElementById("data").innerText) // object
|
||||
const signChartDivTemplate = document.importNode(document.getElementById("sign-chart-template").content, true)
|
||||
data.forEach((item) => {
|
||||
let signChartDiv = signChartDivTemplate.cloneNode(true)
|
||||
let chartID = item["name"]
|
||||
// 初始化ECharts实例
|
||||
// 设置id
|
||||
signChartDiv.querySelector(".sign-chart").id = chartID
|
||||
document.body.appendChild(signChartDiv)
|
||||
|
||||
let signChart = echarts.init(document.getElementById(chartID))
|
||||
let timeCount = []
|
||||
|
||||
item["counts"].forEach((count, index) => {
|
||||
// 计算平均值,index - 1的count + index的count + index + 1的count /3
|
||||
if (index > 0) {
|
||||
timeCount.push((item["counts"][index] - item["counts"][index - 1]) / (60*(item["times"][index] - item["times"][index - 1])))
|
||||
}
|
||||
})
|
||||
|
||||
console.log(timeCount)
|
||||
|
||||
signChart.setOption(
|
||||
{
|
||||
animation: false,
|
||||
title: {
|
||||
text: item["name"],
|
||||
textStyle: {
|
||||
color: '#000000' // 设置标题文本颜色为红色
|
||||
}
|
||||
},
|
||||
xAxis: {
|
||||
type: 'category',
|
||||
data: item["times"].map(timestampToTime),
|
||||
},
|
||||
yAxis: [
|
||||
{
|
||||
type: 'value',
|
||||
min: Math.min(...item["counts"]),
|
||||
},
|
||||
{
|
||||
type: 'value',
|
||||
min: Math.min(...timeCount),
|
||||
}
|
||||
],
|
||||
series: [
|
||||
{
|
||||
data: item["counts"],
|
||||
type: 'line',
|
||||
yAxisIndex: 0
|
||||
},
|
||||
{
|
||||
data: timeCount,
|
||||
type: 'line',
|
||||
yAxisIndex: 1
|
||||
}
|
||||
]
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
|
||||
function timestampToTime(timestamp) {
|
||||
let date = new Date(timestamp * 1000)
|
||||
let Y = date.getFullYear() + '-'
|
||||
let M = (date.getMonth() + 1 < 10 ? '0' + (date.getMonth() + 1) : date.getMonth() + 1) + '-'
|
||||
let D = date.getDate() + ' '
|
||||
let h = date.getHours() + ':'
|
||||
let m = date.getMinutes() + ':'
|
||||
let s = date.getSeconds()
|
||||
return M + D + h + m + s
|
||||
}
|
@ -1,22 +1,22 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh" xmlns="http://www.w3.org/1999/html">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Liteyuki Status</title>
|
||||
<link rel="stylesheet" href="./css/card.css">
|
||||
<link rel="stylesheet" href="./css/fonts.css">
|
||||
<link rel="stylesheet" href="./css/sign_status.css">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
<template id="sign-chart-template">
|
||||
<div class="info-box sign-chart">
|
||||
</div>
|
||||
</template>
|
||||
<div class="data-storage" id="data">{{ data | tojson }}</div>
|
||||
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/echarts/5.5.0/echarts.min.js"></script>
|
||||
<script src="./js/sign_status.js"></script>
|
||||
<script src="./js/card.js"></script>
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh" xmlns="http://www.w3.org/1999/html">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Liteyuki Status</title>
|
||||
<link rel="stylesheet" href="./css/card.css">
|
||||
<link rel="stylesheet" href="./css/fonts.css">
|
||||
<link rel="stylesheet" href="./css/sign_status.css">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
<template id="sign-chart-template">
|
||||
<div class="info-box sign-chart">
|
||||
</div>
|
||||
</template>
|
||||
<div class="data-storage" id="data">{{ data | tojson }}</div>
|
||||
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/echarts/5.5.0/echarts.min.js"></script>
|
||||
<script src="./js/sign_status.js"></script>
|
||||
<script src="./js/card.js"></script>
|
||||
</body>
|
@ -1,3 +1,3 @@
|
||||
crt.station=Station(s)
|
||||
crt.hour=Hour(s)
|
||||
crt.station=Station(s)
|
||||
crt.hour=Hour(s)
|
||||
crt.minute=Min(s)
|
@ -1,3 +1,3 @@
|
||||
crt.station=站
|
||||
crt.hour=小时
|
||||
crt.station=站
|
||||
crt.hour=小时
|
||||
crt.minute=分钟
|
@ -1,3 +1,3 @@
|
||||
name: CRT相关资源包
|
||||
description: For Liteyuki CRT utils
|
||||
name: CRT相关资源包
|
||||
description: For Liteyuki CRT utils
|
||||
version: 2024.4.26
|
@ -1,161 +1,161 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>CRT 线路图</title>
|
||||
<link rel="stylesheet" href="./css/card.css">
|
||||
<link rel="stylesheet" href="./css/fonts.css">
|
||||
</head>
|
||||
<style>
|
||||
|
||||
:root {
|
||||
--color-primary: #f00;
|
||||
--color-secondary: #fff;
|
||||
--sub-text-color: #aaa;
|
||||
}
|
||||
|
||||
.segment {
|
||||
display: flex;
|
||||
background-color: #0d1117;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.line-icon {
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
.vertical-bar {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
width: 20px;
|
||||
height: 100%;
|
||||
border-radius: 50px;
|
||||
background-color: #f00;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.station-dot {
|
||||
width: 15px;
|
||||
height: 15px;
|
||||
aspect-ratio: 1/1;
|
||||
border-radius: 50%;
|
||||
background-color: #fff;
|
||||
}
|
||||
|
||||
/* 第一个点在bar顶端,第二个在底部*/
|
||||
.station-dot:first-child {
|
||||
margin-top: 2px;
|
||||
margin-bottom: auto;
|
||||
}
|
||||
|
||||
.station-dot:last-child {
|
||||
margin-top: auto;
|
||||
margin-bottom: 2px;
|
||||
}
|
||||
|
||||
.transfer-info {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.station-name {
|
||||
font-size: 16px;
|
||||
color: var(--color-secondary);
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
.end-station {
|
||||
margin-top: auto;
|
||||
}
|
||||
|
||||
.line-info {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.line-name {
|
||||
padding: 3px;
|
||||
border-radius: 50px;
|
||||
background-color: #f00;
|
||||
color: var(--color-secondary);
|
||||
font-size: 10px;
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
.line-direction {
|
||||
font-size: 12px;
|
||||
color: var(--sub-text-color);
|
||||
}
|
||||
|
||||
.station-info {
|
||||
align-items: center;
|
||||
font-size: 10px;
|
||||
color: var(--sub-text-color);
|
||||
margin: 10px 0;
|
||||
}
|
||||
|
||||
.start-station {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.segment-index {
|
||||
font-size: 12px;
|
||||
color: var(--sub-text-color);
|
||||
}
|
||||
</style>
|
||||
<body>
|
||||
<div class="data-storage" id="data">{{ data | tojson }}</div>
|
||||
|
||||
<!---->
|
||||
|
||||
<template id="segment-template">
|
||||
<div class="segment">
|
||||
<div class="line-icon">
|
||||
<!-- 竖条-->
|
||||
<div class="vertical-bar">
|
||||
<div class="station-dot"></div>
|
||||
<div class="station-dot"></div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<div class="transfer-info">
|
||||
<div class="start-station">
|
||||
<div class="station-name start-station-name">
|
||||
下北泽站
|
||||
</div>
|
||||
<div class="segment-index">
|
||||
第1段
|
||||
</div>
|
||||
</div>
|
||||
<div class="line-info">
|
||||
<div class="line-name">
|
||||
轨道交通环线外环
|
||||
</div>
|
||||
<div class="line-direction">
|
||||
沙坪坝方向
|
||||
</div>
|
||||
</div>
|
||||
<div class="station-info">
|
||||
5站(14分钟)
|
||||
</div>
|
||||
<div class="end-station">
|
||||
<div class="station-name end-station-name">
|
||||
新桥站
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<template id="route-template">
|
||||
<div class="info-box route-info" id="route-info">
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
<script src="./js/card.js"></script>
|
||||
<script src="./js/crt_route.js"></script>
|
||||
</body>
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>CRT 线路图</title>
|
||||
<link rel="stylesheet" href="./css/card.css">
|
||||
<link rel="stylesheet" href="./css/fonts.css">
|
||||
</head>
|
||||
<style>
|
||||
|
||||
:root {
|
||||
--color-primary: #f00;
|
||||
--color-secondary: #fff;
|
||||
--sub-text-color: #aaa;
|
||||
}
|
||||
|
||||
.segment {
|
||||
display: flex;
|
||||
background-color: #0d1117;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.line-icon {
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
.vertical-bar {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
width: 20px;
|
||||
height: 100%;
|
||||
border-radius: 50px;
|
||||
background-color: #f00;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.station-dot {
|
||||
width: 15px;
|
||||
height: 15px;
|
||||
aspect-ratio: 1/1;
|
||||
border-radius: 50%;
|
||||
background-color: #fff;
|
||||
}
|
||||
|
||||
/* 第一个点在bar顶端,第二个在底部*/
|
||||
.station-dot:first-child {
|
||||
margin-top: 2px;
|
||||
margin-bottom: auto;
|
||||
}
|
||||
|
||||
.station-dot:last-child {
|
||||
margin-top: auto;
|
||||
margin-bottom: 2px;
|
||||
}
|
||||
|
||||
.transfer-info {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.station-name {
|
||||
font-size: 16px;
|
||||
color: var(--color-secondary);
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
.end-station {
|
||||
margin-top: auto;
|
||||
}
|
||||
|
||||
.line-info {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.line-name {
|
||||
padding: 3px;
|
||||
border-radius: 50px;
|
||||
background-color: #f00;
|
||||
color: var(--color-secondary);
|
||||
font-size: 10px;
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
.line-direction {
|
||||
font-size: 12px;
|
||||
color: var(--sub-text-color);
|
||||
}
|
||||
|
||||
.station-info {
|
||||
align-items: center;
|
||||
font-size: 10px;
|
||||
color: var(--sub-text-color);
|
||||
margin: 10px 0;
|
||||
}
|
||||
|
||||
.start-station {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.segment-index {
|
||||
font-size: 12px;
|
||||
color: var(--sub-text-color);
|
||||
}
|
||||
</style>
|
||||
<body>
|
||||
<div class="data-storage" id="data">{{ data | tojson }}</div>
|
||||
|
||||
<!---->
|
||||
|
||||
<template id="segment-template">
|
||||
<div class="segment">
|
||||
<div class="line-icon">
|
||||
<!-- 竖条-->
|
||||
<div class="vertical-bar">
|
||||
<div class="station-dot"></div>
|
||||
<div class="station-dot"></div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<div class="transfer-info">
|
||||
<div class="start-station">
|
||||
<div class="station-name start-station-name">
|
||||
下北泽站
|
||||
</div>
|
||||
<div class="segment-index">
|
||||
第1段
|
||||
</div>
|
||||
</div>
|
||||
<div class="line-info">
|
||||
<div class="line-name">
|
||||
轨道交通环线外环
|
||||
</div>
|
||||
<div class="line-direction">
|
||||
沙坪坝方向
|
||||
</div>
|
||||
</div>
|
||||
<div class="station-info">
|
||||
5站(14分钟)
|
||||
</div>
|
||||
<div class="end-station">
|
||||
<div class="station-name end-station-name">
|
||||
新桥站
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<template id="route-template">
|
||||
<div class="info-box route-info" id="route-info">
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
<script src="./js/card.js"></script>
|
||||
<script src="./js/crt_route.js"></script>
|
||||
</body>
|
||||
</html>
|
@ -1,35 +1,35 @@
|
||||
// Copyright (c) 2024 SnowyKami Liteyuki Studio All Rights Reserved.
|
||||
|
||||
|
||||
/**
|
||||
* @type {{
|
||||
* results: Array<{
|
||||
* abstracts: string,
|
||||
* createdDt: string,
|
||||
* endStaName: string,
|
||||
* startStaName: string,
|
||||
* isValid: boolean,
|
||||
* needTimeScope: number,
|
||||
* needTransferTimes: number,
|
||||
* price: number,
|
||||
* skipGenerateSequence: boolean,
|
||||
* transferLines: string,
|
||||
* transferLinesColor: string,
|
||||
* transferStaDerict: string,
|
||||
* transferStaNames: string,
|
||||
* }>
|
||||
* }}
|
||||
*/
|
||||
|
||||
const data = JSON.parse(document.getElementById("data").innerText);
|
||||
const results = data["result"];
|
||||
const route_template = document.importNode(document.getElementById("route-template").content, true)
|
||||
|
||||
results.forEach(
|
||||
(item, index) => {
|
||||
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
|
||||
// Copyright (c) 2024 SnowyKami Liteyuki Studio All Rights Reserved.
|
||||
|
||||
|
||||
/**
|
||||
* @type {{
|
||||
* results: Array<{
|
||||
* abstracts: string,
|
||||
* createdDt: string,
|
||||
* endStaName: string,
|
||||
* startStaName: string,
|
||||
* isValid: boolean,
|
||||
* needTimeScope: number,
|
||||
* needTransferTimes: number,
|
||||
* price: number,
|
||||
* skipGenerateSequence: boolean,
|
||||
* transferLines: string,
|
||||
* transferLinesColor: string,
|
||||
* transferStaDerict: string,
|
||||
* transferStaNames: string,
|
||||
* }>
|
||||
* }}
|
||||
*/
|
||||
|
||||
const data = JSON.parse(document.getElementById("data").innerText);
|
||||
const results = data["result"];
|
||||
const route_template = document.importNode(document.getElementById("route-template").content, true)
|
||||
|
||||
results.forEach(
|
||||
(item, index) => {
|
||||
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
|
@ -1,2 +1,2 @@
|
||||
stat.message=统计消息
|
||||
stat.message=统计消息
|
||||
stat.rank=发言排名
|
@ -1,3 +1,3 @@
|
||||
name: 轻雪统计信息附件
|
||||
description: For Liteyuki statistic
|
||||
name: 轻雪统计信息附件
|
||||
description: For Liteyuki statistic
|
||||
version: 2024.4.26
|
@ -1,4 +1,4 @@
|
||||
.sign-chart {
|
||||
height: 400px;
|
||||
background-color: rgba(255, 255, 255, 0.7);
|
||||
.sign-chart {
|
||||
height: 400px;
|
||||
background-color: rgba(255, 255, 255, 0.7);
|
||||
}
|
@ -1,54 +1,54 @@
|
||||
// 数据类型声明
|
||||
// import * as echarts from 'echarts';
|
||||
|
||||
let data = JSON.parse(document.getElementById("data").innerText) // object
|
||||
const signChartDivTemplate = document.importNode(document.getElementById("sign-chart-template").content, true)
|
||||
data.forEach((item) => {
|
||||
let signChartDiv = signChartDivTemplate.cloneNode(true)
|
||||
let chartID = item["name"]
|
||||
// 初始化ECharts实例
|
||||
// 设置id
|
||||
signChartDiv.querySelector(".sign-chart").id = chartID
|
||||
document.body.appendChild(signChartDiv)
|
||||
|
||||
let signChart = echarts.init(document.getElementById(chartID))
|
||||
|
||||
signChart.setOption(
|
||||
{
|
||||
animation: false,
|
||||
title: {
|
||||
text: item["name"],
|
||||
textStyle: {
|
||||
color: '#000000' // 设置标题文本颜色为红色
|
||||
}
|
||||
},
|
||||
xAxis: {
|
||||
type: 'category',
|
||||
data: item["times"].map(timestampToTime),
|
||||
},
|
||||
yAxis: {
|
||||
type: 'value',
|
||||
min: Math.min(...item["counts"]),
|
||||
},
|
||||
|
||||
series: [
|
||||
{
|
||||
data: item["counts"],
|
||||
type: 'line',
|
||||
}
|
||||
]
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
|
||||
function timestampToTime(timestamp) {
|
||||
let date = new Date(timestamp * 1000)
|
||||
let Y = date.getFullYear() + '-'
|
||||
let M = (date.getMonth() + 1 < 10 ? '0' + (date.getMonth() + 1) : date.getMonth() + 1) + '-'
|
||||
let D = date.getDate() + ' '
|
||||
let h = date.getHours() + ':'
|
||||
let m = date.getMinutes() + ':'
|
||||
let s = date.getSeconds()
|
||||
return M + D + h + m + s
|
||||
// 数据类型声明
|
||||
// import * as echarts from 'echarts';
|
||||
|
||||
let data = JSON.parse(document.getElementById("data").innerText) // object
|
||||
const signChartDivTemplate = document.importNode(document.getElementById("sign-chart-template").content, true)
|
||||
data.forEach((item) => {
|
||||
let signChartDiv = signChartDivTemplate.cloneNode(true)
|
||||
let chartID = item["name"]
|
||||
// 初始化ECharts实例
|
||||
// 设置id
|
||||
signChartDiv.querySelector(".sign-chart").id = chartID
|
||||
document.body.appendChild(signChartDiv)
|
||||
|
||||
let signChart = echarts.init(document.getElementById(chartID))
|
||||
|
||||
signChart.setOption(
|
||||
{
|
||||
animation: false,
|
||||
title: {
|
||||
text: item["name"],
|
||||
textStyle: {
|
||||
color: '#000000' // 设置标题文本颜色为红色
|
||||
}
|
||||
},
|
||||
xAxis: {
|
||||
type: 'category',
|
||||
data: item["times"].map(timestampToTime),
|
||||
},
|
||||
yAxis: {
|
||||
type: 'value',
|
||||
min: Math.min(...item["counts"]),
|
||||
},
|
||||
|
||||
series: [
|
||||
{
|
||||
data: item["counts"],
|
||||
type: 'line',
|
||||
}
|
||||
]
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
|
||||
function timestampToTime(timestamp) {
|
||||
let date = new Date(timestamp * 1000)
|
||||
let Y = date.getFullYear() + '-'
|
||||
let M = (date.getMonth() + 1 < 10 ? '0' + (date.getMonth() + 1) : date.getMonth() + 1) + '-'
|
||||
let D = date.getDate() + ' '
|
||||
let h = date.getHours() + ':'
|
||||
let m = date.getMinutes() + ':'
|
||||
let s = date.getSeconds()
|
||||
return M + D + h + m + s
|
||||
}
|
@ -1,25 +1,25 @@
|
||||
let data = JSON.parse(document.getElementById("data").innerText) // object
|
||||
|
||||
const rowDiv = document.importNode(document.getElementById("row-template").content, true)
|
||||
|
||||
function randomHideChar(str) {
|
||||
// 随机隐藏6位以上字符串的中间连续四位字符,用*代替
|
||||
if (str.length <= 6) {
|
||||
return str
|
||||
}
|
||||
let start = Math.floor(str.length / 2) - 2
|
||||
return str.slice(0, start) + "****" + str.slice(start + 4)
|
||||
}
|
||||
data["ranking"].forEach((item) => {
|
||||
let row = rowDiv.cloneNode(true)
|
||||
let rowID = item["name"]
|
||||
let rowIconSrc = item["icon"]
|
||||
let rowCount = item["count"]
|
||||
|
||||
row.querySelector(".row-name").innerText = randomHideChar(rowID)
|
||||
row.querySelector(".row-icon").src = rowIconSrc
|
||||
row.querySelector(".row-count").innerText = rowCount
|
||||
|
||||
document.body.appendChild(row)
|
||||
})
|
||||
|
||||
let data = JSON.parse(document.getElementById("data").innerText) // object
|
||||
|
||||
const rowDiv = document.importNode(document.getElementById("row-template").content, true)
|
||||
|
||||
function randomHideChar(str) {
|
||||
// 随机隐藏6位以上字符串的中间连续四位字符,用*代替
|
||||
if (str.length <= 6) {
|
||||
return str
|
||||
}
|
||||
let start = Math.floor(str.length / 2) - 2
|
||||
return str.slice(0, start) + "****" + str.slice(start + 4)
|
||||
}
|
||||
data["ranking"].forEach((item) => {
|
||||
let row = rowDiv.cloneNode(true)
|
||||
let rowID = item["name"]
|
||||
let rowIconSrc = item["icon"]
|
||||
let rowCount = item["count"]
|
||||
|
||||
row.querySelector(".row-name").innerText = randomHideChar(rowID)
|
||||
row.querySelector(".row-icon").src = rowIconSrc
|
||||
row.querySelector(".row-count").innerText = rowCount
|
||||
|
||||
document.body.appendChild(row)
|
||||
})
|
||||
|
@ -1,22 +1,22 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh" xmlns="http://www.w3.org/1999/html">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Liteyuki Stats Message</title>
|
||||
<link rel="stylesheet" href="./css/card.css">
|
||||
<link rel="stylesheet" href="./css/fonts.css">
|
||||
<link rel="stylesheet" href="./css/stat_msg.css">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
<template id="sign-chart-template">
|
||||
<div class="info-box sign-chart">
|
||||
</div>
|
||||
</template>
|
||||
<div class="data-storage" id="data">{{ data | tojson }}</div>
|
||||
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/echarts/5.5.0/echarts.min.js"></script>
|
||||
<script src="./js/stat_msg.js"></script>
|
||||
<script src="./js/card.js"></script>
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh" xmlns="http://www.w3.org/1999/html">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Liteyuki Stats Message</title>
|
||||
<link rel="stylesheet" href="./css/card.css">
|
||||
<link rel="stylesheet" href="./css/fonts.css">
|
||||
<link rel="stylesheet" href="./css/stat_msg.css">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
<template id="sign-chart-template">
|
||||
<div class="info-box sign-chart">
|
||||
</div>
|
||||
</template>
|
||||
<div class="data-storage" id="data">{{ data | tojson }}</div>
|
||||
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/echarts/5.5.0/echarts.min.js"></script>
|
||||
<script src="./js/stat_msg.js"></script>
|
||||
<script src="./js/card.js"></script>
|
||||
</body>
|
@ -1,54 +1,54 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh" xmlns="http://www.w3.org/1999/html">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Liteyuki Stats Message</title>
|
||||
<link rel="stylesheet" href="./css/card.css">
|
||||
<link rel="stylesheet" href="./css/fonts.css">
|
||||
<link rel="stylesheet" href="./css/stat_rank.css">
|
||||
<style>
|
||||
.row {
|
||||
height: 100px;
|
||||
display: flex;
|
||||
background-color: rgba(255, 255, 255, 0.9);
|
||||
border-radius: 100px;
|
||||
margin-bottom: 10px;
|
||||
padding-right: 10px;
|
||||
}
|
||||
|
||||
.row-name {
|
||||
font-size: 40px;
|
||||
align-content: center;
|
||||
width: 100px;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.row-icon {
|
||||
border-radius: 50%;
|
||||
margin-right: auto;
|
||||
}
|
||||
|
||||
.row-count {
|
||||
align-content: center;
|
||||
font-size: 40px;
|
||||
/* 靠右*/
|
||||
margin-left: auto;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
<template id="row-template">
|
||||
<div class="row">
|
||||
<img src="./img/arrow-up.svg" alt="up" class="row-icon">
|
||||
<div class="row-name"></div>
|
||||
<div class="row-count"></div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<div class="data-storage" id="data">{{ data | tojson }}</div>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/echarts/5.5.0/echarts.min.js"></script>
|
||||
<script src="./js/stat_rank.js"></script>
|
||||
<script src="./js/card.js"></script>
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh" xmlns="http://www.w3.org/1999/html">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Liteyuki Stats Message</title>
|
||||
<link rel="stylesheet" href="./css/card.css">
|
||||
<link rel="stylesheet" href="./css/fonts.css">
|
||||
<link rel="stylesheet" href="./css/stat_rank.css">
|
||||
<style>
|
||||
.row {
|
||||
height: 100px;
|
||||
display: flex;
|
||||
background-color: rgba(255, 255, 255, 0.9);
|
||||
border-radius: 100px;
|
||||
margin-bottom: 10px;
|
||||
padding-right: 10px;
|
||||
}
|
||||
|
||||
.row-name {
|
||||
font-size: 40px;
|
||||
align-content: center;
|
||||
width: 100px;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.row-icon {
|
||||
border-radius: 50%;
|
||||
margin-right: auto;
|
||||
}
|
||||
|
||||
.row-count {
|
||||
align-content: center;
|
||||
font-size: 40px;
|
||||
/* 靠右*/
|
||||
margin-left: auto;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
<template id="row-template">
|
||||
<div class="row">
|
||||
<img src="./img/arrow-up.svg" alt="up" class="row-icon">
|
||||
<div class="row-name"></div>
|
||||
<div class="row-count"></div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<div class="data-storage" id="data">{{ data | tojson }}</div>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/echarts/5.5.0/echarts.min.js"></script>
|
||||
<script src="./js/stat_rank.js"></script>
|
||||
<script src="./js/card.js"></script>
|
||||
</body>
|
@ -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
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user