Ⓜ️手动从旧梦 81a191f merge
1
.gitignore
vendored
@ -29,6 +29,7 @@ src/plugins/trimo_plugin_msctconverter/MusicPreview/assets/wav
|
||||
|
||||
test.py
|
||||
line_count.py
|
||||
mypy.ini
|
||||
|
||||
# nuitka
|
||||
main.build/
|
||||
|
5
docs/.gitignore
vendored
@ -1,5 +0,0 @@
|
||||
|
||||
node_modules/
|
||||
./.vuepress/.cache/
|
||||
./.vuepress/.temp/
|
||||
./.vuepress/dist/
|
@ -1,13 +0,0 @@
|
||||
import {defineClientConfig} from "vuepress/client";
|
||||
import resourceStoreComp from "./components/ResStore.vue";
|
||||
import pluginStoreComp from "./components/PluginStore.vue";
|
||||
//导入element-plus
|
||||
import ElementPlus from 'element-plus';
|
||||
|
||||
export default defineClientConfig({
|
||||
enhance: ({app, router, siteData}) => {
|
||||
app.component("resourceStoreComp", resourceStoreComp);
|
||||
app.component("pluginStoreComp", pluginStoreComp);
|
||||
app.use(ElementPlus);
|
||||
},
|
||||
});
|
@ -1,126 +0,0 @@
|
||||
<template>
|
||||
<div class="item-card">
|
||||
<div class="item-name">{{ props.item.name }}</div>
|
||||
<div class="item-description">{{ props.item.desc }}</div>
|
||||
<div class="item-bar">
|
||||
<!-- 三个可点击svg,一个github,一个下载,一个可点击"https://github.com/{{ username }}.png?size=80"个人头像配上id-->
|
||||
<a :href=props.item.homepage class="btn">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" viewBox="0 0 16 16">
|
||||
<path fill="currentColor"
|
||||
d="m7.775 3.275l1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0a.751.751 0 0 1 .018-1.042a.751.751 0 0 1 1.042-.018a1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018a.751.751 0 0 1-.018-1.042m-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018a.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0a.751.751 0 0 1-.018 1.042a.751.751 0 0 1-1.042.018a1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83"/>
|
||||
</svg>
|
||||
</a>
|
||||
|
||||
<!-- <button class="copy-btn btn"><div @click="copyToClipboard">安装</div></button> 点击后把安装命令写入剪贴板-->
|
||||
<button class="btn copy-btn" @click="copyToClipboard">复制安装命令</button>
|
||||
|
||||
<div class="btn">
|
||||
<a class="author-info" :href="`https://github.com/${props.item.author }`">
|
||||
<img class="icon avatar" :src="`https://github.com/${ props.item.author }.png?size=80`" alt="">
|
||||
<div class="author-name">{{ props.item.author }}</div>
|
||||
</a>
|
||||
</div>
|
||||
<!-- 复制键,复制安装命令,npm install props.item.module_name-->
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import {defineProps, onMounted} from 'vue'
|
||||
import Clipboard from 'clipboard'
|
||||
// 复制安装命令按钮
|
||||
|
||||
// 构建复制成功和失败的提示
|
||||
const props = defineProps({
|
||||
item: Object
|
||||
})
|
||||
|
||||
const copyToClipboard = () => {
|
||||
const clipboard = new Clipboard('.copy-btn', {
|
||||
text: () => `npm install ${props.item.module_name}`
|
||||
})
|
||||
clipboard.on('success', () => {
|
||||
})
|
||||
clipboard.on('error', () => {
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
// 复制到剪贴板的函数
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.item-card {
|
||||
position: relative;
|
||||
border-radius: 15px;
|
||||
background-color: #00000011;
|
||||
height: 160px;
|
||||
padding: 16px;
|
||||
margin: 10px;
|
||||
box-sizing: border-box;
|
||||
transition: background 0.3s ease;
|
||||
}
|
||||
|
||||
.btn {
|
||||
margin-right: 15px;
|
||||
}
|
||||
|
||||
button {
|
||||
background-color: #00000000;
|
||||
border: none;
|
||||
}
|
||||
|
||||
.copy-btn {
|
||||
cursor: pointer;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.copy-btn:hover {
|
||||
color: #111;
|
||||
}
|
||||
|
||||
.item-name {
|
||||
color: #111;
|
||||
font-size: 20px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.item-description {
|
||||
color: #333;
|
||||
font-size: 12px;
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
|
||||
.icon {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
color: $themeColor;
|
||||
}
|
||||
|
||||
.author-info {
|
||||
display: flex;
|
||||
justify-content: left;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.author-name {
|
||||
font-size: 15px;
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
.avatar {
|
||||
border-radius: 50%;
|
||||
margin: 0 10px;
|
||||
}
|
||||
|
||||
.item-bar {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
height: 50px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
box-sizing: border-box;
|
||||
justify-content: space-between;
|
||||
color: #00000055;
|
||||
}
|
||||
</style>
|
@ -1,62 +0,0 @@
|
||||
<script setup lang="ts">
|
||||
import {computed, ref} from 'vue'
|
||||
import ItemCard from './PluginItemCard.vue'
|
||||
|
||||
|
||||
let filteredItems = computed(() => {
|
||||
if (!search.value) {
|
||||
return items.value
|
||||
}
|
||||
return items.value.filter(item =>
|
||||
item.name.toLowerCase().includes(search.value.toLowerCase()) ||
|
||||
item.desc.toLowerCase().includes(search.value.toLowerCase()) ||
|
||||
item.author.toLowerCase().includes(search.value.toLowerCase()) ||
|
||||
item.module_name.toLowerCase().includes(search.value.toLowerCase())
|
||||
)
|
||||
})
|
||||
// 插件商店Nonebot
|
||||
let items = ref([])
|
||||
let search = ref('')
|
||||
// 从官方拉取
|
||||
fetch("/assets/plugins.json")
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
items.value = data
|
||||
})
|
||||
.catch(error => console.error(error))
|
||||
|
||||
//追加
|
||||
fetch('https://registry.nonebot.dev/plugins.json')
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
items.value = items.value.concat(data)
|
||||
})
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="market">
|
||||
<h1>插件商店</h1>
|
||||
<p>内容来自<a href="https://nonebot.dev/store/plugins">NoneBot插件商店</a>和轻雪商店,在此仅作引用,具体请访问NoneBot插件商店</p>
|
||||
<!-- 搜索框-->
|
||||
<div class="search-box-div"><input class="item-search-box" type="text" placeholder="搜索插件" v-model="search"/></div>
|
||||
<div class="items">
|
||||
<!-- 使用filteredItems来布局商品 -->
|
||||
<ItemCard v-for="item in filteredItems" :key="item.id" :item="item"/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
h1 {
|
||||
color: #00a6ff;
|
||||
text-align: center;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.items {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(260px, 1fr));
|
||||
gap: 10px;
|
||||
}
|
||||
</style>
|
@ -1,90 +0,0 @@
|
||||
<template>
|
||||
<div class="item-card">
|
||||
<div class="item-name">{{ props.item.name }}</div>
|
||||
<div class="item-description">{{ props.item.description }}</div>
|
||||
<div class="item-bar">
|
||||
<!-- 三个可点击svg,一个github,一个下载,一个可点击"https://github.com/{{ username }}.png?size=80"个人头像配上id-->
|
||||
<a :href=props.item.link class="">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" viewBox="0 0 16 16">
|
||||
<path fill="currentColor"
|
||||
d="m7.775 3.275l1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0a.751.751 0 0 1 .018-1.042a.751.751 0 0 1 1.042-.018a1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018a.751.751 0 0 1-.018-1.042m-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018a.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0a.751.751 0 0 1-.018 1.042a.751.751 0 0 1-1.042.018a1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83"/>
|
||||
</svg>
|
||||
</a>
|
||||
<div><a class="author-info" :href="`https://github.com/${props.item.author }`">
|
||||
<img class="icon avatar" :src="`https://github.com/${ props.item.author }.png?size=80`" alt="">
|
||||
<div class="author-name">{{ props.item.author }}</div>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import {defineProps} from 'vue'
|
||||
|
||||
const props = defineProps({
|
||||
item: Object
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.item-card {
|
||||
position: relative;
|
||||
border-radius: 15px;
|
||||
background-color: #00000011;
|
||||
height: 160px;
|
||||
padding: 16px;
|
||||
margin: 10px;
|
||||
box-sizing: border-box;
|
||||
transition: background 0.3s ease;
|
||||
}
|
||||
|
||||
.item-card:hover {
|
||||
border: 2px solid $themeColor;
|
||||
}
|
||||
|
||||
.item-name {
|
||||
color: $themeColor;
|
||||
font-size: 20px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.item-description {
|
||||
color: #333;
|
||||
font-size: 15px;
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
|
||||
.icon {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
color: $themeColor;
|
||||
}
|
||||
|
||||
.author-info {
|
||||
display: flex;
|
||||
justify-content: left;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.author-name {
|
||||
font-size: 15px;
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
.avatar {
|
||||
border-radius: 50%;
|
||||
margin: 0 10px;
|
||||
}
|
||||
|
||||
.item-bar {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
height: 50px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
box-sizing: border-box;
|
||||
justify-content: space-between;
|
||||
color: #00000055;
|
||||
}
|
||||
</style>
|
@ -1,53 +0,0 @@
|
||||
<script setup lang="ts">
|
||||
import {computed, ref} from 'vue'
|
||||
import ItemCard from './ResItemCard.vue'
|
||||
import * as url from "node:url";
|
||||
|
||||
// 从public/assets/resources.json加载插件
|
||||
let filteredItems = computed(() => {
|
||||
if (!search.value) {
|
||||
return items.value.reverse()
|
||||
}
|
||||
return items.value.filter(item =>
|
||||
item.name.toLowerCase().includes(search.value.toLowerCase()) ||
|
||||
item.description.toLowerCase().includes(search.value.toLowerCase()) ||
|
||||
item.author.toLowerCase().includes(search.value.toLowerCase())
|
||||
).reverse()
|
||||
})
|
||||
// 插件商店Nonebot
|
||||
let items = ref([])
|
||||
let search = ref('')
|
||||
fetch("/assets/resources.json")
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
items.value = data
|
||||
})
|
||||
.catch(error => console.error(error))
|
||||
// 列表倒序
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="market">
|
||||
<h1>主题/资源商店</h1>
|
||||
<div class="search-box-div"><input class="item-search-box" type="text" placeholder="搜索资源" v-model="search" /></div>
|
||||
<div class="items">
|
||||
<!-- 使用filteredItems来布局商品 -->
|
||||
<ItemCard v-for="item in filteredItems" :key="item.id" :item="item"/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
h1 {
|
||||
color: #00a6ff;
|
||||
text-align: center;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.items {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(260px, 1fr));
|
||||
gap: 10px;
|
||||
}
|
||||
</style>
|
@ -1,30 +0,0 @@
|
||||
import {defineUserConfig} from "vuepress";
|
||||
import theme from "./theme.js";
|
||||
import viteBundler from "@vuepress/bundler-vite";
|
||||
|
||||
export default defineUserConfig({
|
||||
base: "/",
|
||||
|
||||
lang: "zh-CN",
|
||||
title: "LiteyukiBot 轻雪机器人",
|
||||
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'}],
|
||||
|
||||
[
|
||||
"meta",
|
||||
{
|
||||
name: "viewport",
|
||||
content: "width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no",
|
||||
},
|
||||
],
|
||||
],
|
||||
|
||||
theme,
|
||||
// 和 PWA 一起启用
|
||||
// shouldPrefetch: false,
|
||||
});
|
@ -1,20 +0,0 @@
|
||||
import {navbar} from "vuepress-theme-hope";
|
||||
|
||||
export default navbar([
|
||||
"/",
|
||||
{
|
||||
text: "项目部署",
|
||||
link: "/deployment/",
|
||||
prefix: "deployment/",
|
||||
},
|
||||
{
|
||||
text: "使用手册",
|
||||
link: "/usage/",
|
||||
prefix: "usage/",
|
||||
},
|
||||
{
|
||||
text: "商店",
|
||||
link: "/store/resource",
|
||||
prefix: "store/",
|
||||
}
|
||||
]);
|
Before Width: | Height: | Size: 3.5 KiB |
Before Width: | Height: | Size: 4.6 KiB |
Before Width: | Height: | Size: 13 KiB |
Before Width: | Height: | Size: 4.6 KiB |
Before Width: | Height: | Size: 13 KiB |
Before Width: | Height: | Size: 2.2 KiB |
Before Width: | Height: | Size: 3.6 KiB |
Before Width: | Height: | Size: 26 KiB |
Before Width: | Height: | Size: 9.8 KiB |
@ -1 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="200" height="200" class="icon" viewBox="0 0 1024 1024"><path fill="#FDD7AD" d="M512 0 335.448 88.272l-70.616 35.312-70.624 35.312-176.552 88.28v529.648L512 1024l494.344-247.176V247.176z"/><path fill="#CBB292" d="m759.176 370.76-70.624 35.304-494.344-247.168 70.624-35.312zM512 494.344V1024L17.656 776.824V247.176z"/><path fill="#7F6E5D" d="M1006.344 247.168v529.656L512 1024V494.344l176.552-88.28v70.624l141.24-70.624v-70.616z"/><path fill="#7F5B53" d="M829.792 335.448v70.624L688.56 476.68v-70.624z"/><path fill="#CBB292" d="m829.792 335.448-70.624 35.312-494.344-247.176 70.624-35.312z"/><path fill="#2C3E50" d="m682.52 550.32 157.032-78.512a17.656 17.656 0 0 1 25.552 15.792v9.32a52.96 52.96 0 0 1-29.28 47.376L678.8 622.8a17.656 17.656 0 0 1-25.552-15.792v-9.312a52.96 52.96 0 0 1 29.28-47.376z"/></svg>
|
Before Width: | Height: | Size: 854 B |
@ -1 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="1024" height="1024"><defs><linearGradient id="a" x1="522.593" x2="522.593" y1="-70.302" y2="-335.937" gradientUnits="userSpaceOnUse" spreadMethod="pad"><stop offset="0" stop-color="#fe5d5a" stop-opacity=".1"/><stop offset=".908" stop-color="#ef1220" stop-opacity=".5"/></linearGradient><linearGradient id="b" x1="107.12" x2="935.038" y1="-373.67" y2="-373.67" gradientUnits="userSpaceOnUse" spreadMethod="pad"><stop offset="0" stop-color="#ff5e59"/><stop offset="1" stop-color="#f01422"/></linearGradient><linearGradient id="c" x1="519.405" x2="519.405" y1="-195.547" y2="-726.816" gradientUnits="userSpaceOnUse" spreadMethod="pad"><stop offset="0" stop-color="#ffe2e2"/><stop offset=".888" stop-color="#ff8e8e"/></linearGradient><linearGradient id="d" x1="191.5" x2="483.9" y1="-564.9" y2="-564.9" gradientUnits="userSpaceOnUse" spreadMethod="pad"><stop offset="0" stop-color="#e92700" stop-opacity=".3"/><stop offset=".013" stop-color="#ef1220" stop-opacity=".2"/></linearGradient><linearGradient id="e" x1="403.502" x2="253.121" y1="-847.32" y2="-586.853" gradientUnits="userSpaceOnUse" spreadMethod="pad"><stop offset="0" stop-color="#ff5e59"/><stop offset=".201" stop-color="#f01422"/></linearGradient><linearGradient id="f" x1="330.485" x2="330.485" y1="-801.787" y2="-625.789" gradientUnits="userSpaceOnUse" spreadMethod="pad"><stop offset="0" stop-color="#ff5e59"/><stop offset=".201" stop-color="#f01422"/></linearGradient><linearGradient id="g" x1="397.351" x2="256.845" y1="-647.231" y2="-890.596" gradientUnits="userSpaceOnUse" spreadMethod="pad"><stop offset="0" stop-color="#ffa6a6"/><stop offset=".908" stop-color="#ff6b5d"/></linearGradient></defs><path fill="url(#a)" d="M501.2 662.3 327.6 763.8c-13.9 8.1-14.2 28.1-.5 36.7l179.1 97.7c10.9 5.9 24.1 5.9 34.9-.1l177-97.9c13.6-8.5 13.4-28.3-.3-36.5l-168.4-101c-14.8-9-33.3-9.1-48.2-.4Z"/><path fill="#f63037" d="m110.2 525.7-3.1 77.6 57.5 18.5L184 519.4Z"/><path fill="url(#b)" d="m476.6 363.5-328 154.6c-21 42.7-55.4 65.4-35.5 103.5 4.2 8 9.4 14.4 15.4 18.1l358.2 195.5c21.8 11.9 48.1 11.8 69.8-.2l354-195.8c27.2-16.9 34.8-90.3 7.3-106.8L573 364.1c-29.7-17.8-66.6-18-96.4-.6Z"/><path fill="url(#c)" d="M476.6 298.7 129.4 501.6c-27.8 16.3-28.4 56.3-1 73.3l358.2 195.5c21.8 11.9 48.1 11.8 69.8-.2l354-195.8c27.2-16.9 26.9-56.6-.6-73.1L573 299.3c-29.7-17.8-66.6-18-96.4-.6Z"/><path fill="#ff8989" fill-opacity=".31" d="m481.2 387.8 39.4 123.4c1.1 3.4 4 6 7.6 6.6l173.4 30.4-33-118.3c-.9-3.3-3.6-5.8-7-6.5l-180.4-35.6ZM327 499.2l40.4 101.1L496.7 525c2.5-1.5 3.7-4.5 2.7-7.3l-36-106.8-127.6 65c-8.6 4.3-12.4 14.4-8.8 23.3ZM523.8 540.5l-140.3 77.2L567.2 659c3.2.7 6.6.1 9.3-1.6l134.6-85-174.7-33.8c-4.3-1-8.7-.3-12.6 1.9Z"/><path fill="url(#d)" d="M483.9 406.1c0 35.46-65.46 64.2-146.2 64.2s-146.2-28.74-146.2-64.2c0-35.46 65.46-64.2 146.2-64.2s146.2 28.74 146.2 64.2Z"/><path fill="url(#e)" d="m254.2 188.4-123 83.1c-1.8 1.3-2.6 3.6-1.8 5.7l39.1 110.6c.6 1.7 2 2.9 3.8 3.2l221.8 40.5c1.3.3 2.7-.1 3.7-.8l131.7-93.6c1.9-1.4 2.6-3.9 1.7-6.1l-49.4-107c-.6-1.5-2.1-2.6-3.7-2.8l-220.3-33.5c-1.3-.2-2.6.1-3.6.7Z"/><path fill="url(#f)" d="m528.6 274.5 3 59.1-205 65.6-177.2-72.7-20-49.2 1.9-54.1Z"/><path fill="url(#g)" d="m250.6 138-112.3 76c-6 4.1-8.5 11.7-6.1 18.5l34.2 96.6c1.9 5.4 6.6 9.3 12.1 10.4l211 38.5c4.3.7 8.6-.2 12.1-2.7l120.5-85.5c6.3-4.4 8.4-12.7 5.3-19.7l-43.1-93.5c-2.2-4.9-6.8-8.3-12.1-9.1L262 135.6c-4-.7-8 .2-11.4 2.4Z"/><path fill="#fff" d="m419.8 252.8-79-11-29-57.7c-3.8-7.6-13.2-10.7-20.8-6.9-7.6 3.8-10.7 13.2-6.9 20.8l26.6 52.9-61.8 42.2c-7.1 4.8-8.9 14.5-4.1 21.5 3 4.4 7.9 6.8 12.8 6.8 3 0 6-.9 8.7-2.7l68-46.4 81.1 11.2c.7.1 1.4.1 2.1.1 7.6 0 14.3-5.6 15.3-13.4 1.4-8.4-4.5-16.2-13-17.4Z"/></svg>
|
Before Width: | Height: | Size: 3.6 KiB |
@ -1 +0,0 @@
|
||||
<svg width="98" height="96" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" clip-rule="evenodd" d="M48.854 0C21.839 0 0 22 0 49.217c0 21.756 13.993 40.172 33.405 46.69 2.427.49 3.316-1.059 3.316-2.362 0-1.141-.08-5.052-.08-9.127-13.59 2.934-16.42-5.867-16.42-5.867-2.184-5.704-5.42-7.17-5.42-7.17-4.448-3.015.324-3.015.324-3.015 4.934.326 7.523 5.052 7.523 5.052 4.367 7.496 11.404 5.378 14.235 4.074.404-3.178 1.699-5.378 3.074-6.6-10.839-1.141-22.243-5.378-22.243-24.283 0-5.378 1.94-9.778 5.014-13.2-.485-1.222-2.184-6.275.486-13.038 0 0 4.125-1.304 13.426 5.052a46.97 46.97 0 0 1 12.214-1.63c4.125 0 8.33.571 12.213 1.63 9.302-6.356 13.427-5.052 13.427-5.052 2.67 6.763.97 11.816.485 13.038 3.155 3.422 5.015 7.822 5.015 13.2 0 18.905-11.404 23.06-22.324 24.283 1.78 1.548 3.316 4.481 3.316 9.126 0 6.6-.08 11.897-.08 13.526 0 1.304.89 2.853 3.316 2.364 19.412-6.52 33.405-24.935 33.405-46.691C97.707 22 75.788 0 48.854 0z" fill="#24292f"/></svg>
|
Before Width: | Height: | Size: 963 B |
@ -1 +0,0 @@
|
||||
<svg width="98" height="96" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" clip-rule="evenodd" d="M48.854 0C21.839 0 0 22 0 49.217c0 21.756 13.993 40.172 33.405 46.69 2.427.49 3.316-1.059 3.316-2.362 0-1.141-.08-5.052-.08-9.127-13.59 2.934-16.42-5.867-16.42-5.867-2.184-5.704-5.42-7.17-5.42-7.17-4.448-3.015.324-3.015.324-3.015 4.934.326 7.523 5.052 7.523 5.052 4.367 7.496 11.404 5.378 14.235 4.074.404-3.178 1.699-5.378 3.074-6.6-10.839-1.141-22.243-5.378-22.243-24.283 0-5.378 1.94-9.778 5.014-13.2-.485-1.222-2.184-6.275.486-13.038 0 0 4.125-1.304 13.426 5.052a46.97 46.97 0 0 1 12.214-1.63c4.125 0 8.33.571 12.213 1.63 9.302-6.356 13.427-5.052 13.427-5.052 2.67 6.763.97 11.816.485 13.038 3.155 3.422 5.015 7.822 5.015 13.2 0 18.905-11.404 23.06-22.324 24.283 1.78 1.548 3.316 4.481 3.316 9.126 0 6.6-.08 11.897-.08 13.526 0 1.304.89 2.853 3.316 2.364 19.412-6.52 33.405-24.935 33.405-46.691C97.707 22 75.788 0 48.854 0z" fill="#fff"/></svg>
|
Before Width: | Height: | Size: 960 B |
Before Width: | Height: | Size: 9.0 KiB |
@ -1 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="200" height="200" class="icon" viewBox="0 0 1536 1024"><path fill="#1296db" d="M1425.067.256H110.933A110.933 110.933 0 0 0 0 110.848v723.627a110.933 110.933 0 0 0 110.933 110.933h1314.39c61.269 0 110.933-49.75 110.677-110.677V110.848A110.933 110.933 0 0 0 1425.067.256z" class="selected" data-spm-anchor-id="a313x.7781069.0.i4"/><path fill="#FFF" d="M664.747 723.797V435.883L517.12 620.373l-147.456-184.49v288l-148.053-67.158V221.781h147.626l147.627 184.576 147.541-184.576h147.627v565.76z"/><path d="M1024 0h426.667A85.333 85.333 0 0 1 1536 85.333v768a85.333 85.333 0 0 1-85.333 85.334H1024V0z" opacity=".1"/><path fill="#FFF" d="m1256.96 731.307-170.667-216.491h113.75V304.64h113.749v210.176h113.835z" opacity=".5"/></svg>
|
Before Width: | Height: | Size: 771 B |
@ -1,23 +0,0 @@
|
||||
[
|
||||
{
|
||||
"module_name": "nonebot_plugin_status",
|
||||
"project_link": "nonebot-plugin-status",
|
||||
"name": "测试",
|
||||
"desc": "测试",
|
||||
"author": "snowykami",
|
||||
"homepage": "https://github.com/nonebot/plugin-status",
|
||||
"tags": [
|
||||
{
|
||||
"label": "server",
|
||||
"color": "#aeeaa8"
|
||||
}
|
||||
],
|
||||
"is_official": true,
|
||||
"type": "application",
|
||||
"supported_adapters": null,
|
||||
"valid": true,
|
||||
"version": "0.8.1",
|
||||
"time": "2024-03-04T06:57:10.250823Z",
|
||||
"skip_test": false
|
||||
}
|
||||
]
|
@ -1,56 +0,0 @@
|
||||
[
|
||||
{
|
||||
"name": "KawaiiStatus",
|
||||
"author": "SnowyKami",
|
||||
"description": "可爱的状态卡片,仿照koishi的制作",
|
||||
"link": "https://cdn.liteyuki.icu/static/lrp/KawaiiStatus.zip"
|
||||
},
|
||||
{
|
||||
"name": "MiSans字体包",
|
||||
"author": "SnowyKami",
|
||||
"description": "小米官方字体MiSans",
|
||||
"link": "https://cdn.liteyuki.icu/static/lrp/MiSansFonts.zip"
|
||||
},
|
||||
{
|
||||
"name": "MapleMono字体包",
|
||||
"author": "SnowyKami",
|
||||
"description": "适用于字母的字体包",
|
||||
"link": "https://cdn.liteyuki.icu/static/lrp/MapleMonoFonts.zip"
|
||||
},
|
||||
{
|
||||
"name": "野兽先辈主题HomoTheme",
|
||||
"author": "SnowyKami",
|
||||
"description": "野兽先辈主题包,114514!",
|
||||
"link": "https://cdn.liteyuki.icu/static/lrp/HomoTheme.zip"
|
||||
},
|
||||
{
|
||||
"name": "自定义设备信息",
|
||||
"author": "SnowyKami",
|
||||
"description": "自定义服务端的设备信息,自行修改使用",
|
||||
"link": "https://cdn.liteyuki.icu/static/lrp/custom-device.zip"
|
||||
},
|
||||
{
|
||||
"name": "轻雪傲娇系词库",
|
||||
"author": "SnowyKami",
|
||||
"description": "使用https://github.com/Kyomotoi/AnimeThesaurus的词库",
|
||||
"link": "https://cdn.liteyuki.icu/static/lrp/liteyuki_words_aojiao.zip"
|
||||
},
|
||||
{
|
||||
"name": "轻雪可爱系词库",
|
||||
"author": "SnowyKami",
|
||||
"description": "使用https://github.com/Kyomotoi/AnimeThesaurus的词库",
|
||||
"link": "https://cdn.liteyuki.icu/static/lrp/liteyuki_words_kawaii.zip"
|
||||
},
|
||||
{
|
||||
"name": "轻雪Kakyo语言包",
|
||||
"author": "Nanaloveyuki",
|
||||
"description": "Liteyuki Bot的语言包,用于提供多种语言的翻译。",
|
||||
"link": "https://github.com/Nanaloveyuki/liteyuki-langpack/releases/download/KakyoVer/Kakyo-pack.zip"
|
||||
},
|
||||
{
|
||||
"name": "更多背景模板包",
|
||||
"author": "snowykami",
|
||||
"description": "自定义各种卡片的背景",
|
||||
"link": "https://cdn.liteyuki.icu/static/lrp/morebg.zip"
|
||||
}
|
||||
]
|
Before Width: | Height: | Size: 66 KiB |
@ -1,291 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 1366 768">
|
||||
<defs>
|
||||
<style>
|
||||
.cls-1 {
|
||||
fill: url(#_未命名的渐变_8);
|
||||
}
|
||||
|
||||
.cls-1, .cls-2, .cls-3, .cls-4, .cls-5, .cls-6, .cls-7, .cls-8, .cls-9, .cls-10, .cls-11, .cls-12, .cls-13, .cls-14, .cls-15, .cls-16, .cls-17, .cls-18, .cls-19, .cls-20, .cls-21, .cls-22, .cls-23, .cls-24, .cls-25, .cls-26, .cls-27, .cls-28, .cls-29, .cls-30, .cls-31, .cls-32, .cls-33, .cls-34 {
|
||||
stroke-width: 0px;
|
||||
}
|
||||
|
||||
.cls-2 {
|
||||
fill: url(#_未命名的渐变_9-3);
|
||||
}
|
||||
|
||||
.cls-3 {
|
||||
fill: url(#_未命名的渐变_11);
|
||||
}
|
||||
|
||||
.cls-4 {
|
||||
fill: url(#_未命名的渐变_11-4);
|
||||
}
|
||||
|
||||
.cls-5 {
|
||||
fill: url(#_未命名的渐变_5);
|
||||
}
|
||||
|
||||
.cls-6 {
|
||||
fill: url(#_未命名的渐变_55);
|
||||
}
|
||||
|
||||
.cls-7 {
|
||||
fill: url(#_未命名的渐变_11-5);
|
||||
}
|
||||
|
||||
.cls-8 {
|
||||
fill: url(#_未命名的渐变_65);
|
||||
}
|
||||
|
||||
.cls-9 {
|
||||
fill: url(#_未命名的渐变_3-2);
|
||||
}
|
||||
|
||||
.cls-10 {
|
||||
fill: url(#_未命名的渐变_7-3);
|
||||
}
|
||||
|
||||
.cls-11 {
|
||||
fill: url(#_未命名的渐变_7);
|
||||
}
|
||||
|
||||
.cls-12 {
|
||||
fill: url(#_未命名的渐变_58);
|
||||
}
|
||||
|
||||
.cls-13 {
|
||||
fill: url(#_未命名的渐变_3-3);
|
||||
}
|
||||
|
||||
.cls-14 {
|
||||
fill: url(#_未命名的渐变_67);
|
||||
}
|
||||
|
||||
.cls-15 {
|
||||
fill: url(#_未命名的渐变_11-2);
|
||||
}
|
||||
|
||||
.cls-16 {
|
||||
fill: url(#_未命名的渐变_72);
|
||||
}
|
||||
|
||||
.cls-17 {
|
||||
fill: url(#_未命名的渐变_71);
|
||||
}
|
||||
|
||||
.cls-18, .cls-35 {
|
||||
fill: #fff;
|
||||
}
|
||||
|
||||
.cls-19 {
|
||||
fill: url(#_未命名的渐变_4);
|
||||
}
|
||||
|
||||
.cls-20 {
|
||||
fill: url(#_未命名的渐变_3);
|
||||
}
|
||||
|
||||
.cls-21 {
|
||||
fill: url(#_未命名的渐变_240);
|
||||
}
|
||||
|
||||
.cls-22 {
|
||||
fill: url(#_未命名的渐变_9-2);
|
||||
}
|
||||
|
||||
.cls-23 {
|
||||
fill: url(#_未命名的渐变_6);
|
||||
}
|
||||
|
||||
.cls-24 {
|
||||
fill: url(#_未命名的渐变_9-4);
|
||||
}
|
||||
|
||||
.cls-25 {
|
||||
fill: url(#_未命名的渐变_66);
|
||||
}
|
||||
|
||||
.cls-26 {
|
||||
fill: url(#_未命名的渐变_7-2);
|
||||
}
|
||||
|
||||
.cls-35 {
|
||||
stroke: #fff;
|
||||
stroke-miterlimit: 10;
|
||||
}
|
||||
|
||||
.cls-27 {
|
||||
fill: url(#_未命名的渐变_9);
|
||||
}
|
||||
|
||||
.cls-28 {
|
||||
fill: url(#_未命名的渐变_9-5);
|
||||
}
|
||||
|
||||
.cls-29 {
|
||||
fill: url(#_未命名的渐变_68);
|
||||
}
|
||||
|
||||
.cls-30 {
|
||||
fill: url(#_未命名的渐变_11-3);
|
||||
}
|
||||
|
||||
.cls-31 {
|
||||
fill: url(#_未命名的渐变_69);
|
||||
}
|
||||
|
||||
.cls-32 {
|
||||
fill: #9cf;
|
||||
}
|
||||
|
||||
.cls-33 {
|
||||
fill: url(#_未命名的渐变_58-2);
|
||||
}
|
||||
|
||||
.cls-34 {
|
||||
fill: url(#_未命名的渐变_4-2);
|
||||
}
|
||||
</style>
|
||||
<linearGradient id="_未命名的渐变_11" data-name="未命名的渐变 11" x1="445.89" y1="235.02" x2="528.25" y2="235.02" gradientUnits="userSpaceOnUse">
|
||||
<stop offset="0" stop-color="#73fff1"/>
|
||||
<stop offset="1" stop-color="#5cefff"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="_未命名的渐变_11-2" data-name="未命名的渐变 11" x1="549.98" y1="292.99" x2="584.05" y2="292.99" xlink:href="#_未命名的渐变_11"/>
|
||||
<linearGradient id="_未命名的渐变_11-3" data-name="未命名的渐变 11" x1="553" y1="212" x2="585" y2="212" xlink:href="#_未命名的渐变_11"/>
|
||||
<linearGradient id="_未命名的渐变_11-4" data-name="未命名的渐变 11" x1="609.82" y1="260.3" x2="698.34" y2="260.3" xlink:href="#_未命名的渐变_11"/>
|
||||
<linearGradient id="_未命名的渐变_11-5" data-name="未命名的渐变 11" x1="702.75" y1="282.24" x2="784.94" y2="282.24" xlink:href="#_未命名的渐变_11"/>
|
||||
<linearGradient id="_未命名的渐变_240" data-name="未命名的渐变 240" x1="835" y1="294" x2="960" y2="294" gradientUnits="userSpaceOnUse">
|
||||
<stop offset="0" stop-color="#eadeff"/>
|
||||
<stop offset=".28" stop-color="#e0efff"/>
|
||||
<stop offset=".43" stop-color="#d9ecff"/>
|
||||
<stop offset=".59" stop-color="#aedbff"/>
|
||||
<stop offset="1" stop-color="#00d7ff"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="_未命名的渐变_9" data-name="未命名的渐变 9" x1="664.92" y1="424.34" x2="766.76" y2="424.34" gradientUnits="userSpaceOnUse">
|
||||
<stop offset="0" stop-color="#2ebbff"/>
|
||||
<stop offset="1" stop-color="#006bff"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="_未命名的渐变_9-2" data-name="未命名的渐变 9" x1="761.74" y1="467.72" x2="842.05" y2="467.72" xlink:href="#_未命名的渐变_9"/>
|
||||
<linearGradient id="_未命名的渐变_9-3" data-name="未命名的渐变 9" x1="951.96" y1="394" x2="977.04" y2="394" xlink:href="#_未命名的渐变_9"/>
|
||||
<linearGradient id="_未命名的渐变_9-4" data-name="未命名的渐变 9" x1="952.19" y1="456.86" x2="977.56" y2="456.86" xlink:href="#_未命名的渐变_9"/>
|
||||
<linearGradient id="_未命名的渐变_9-5" data-name="未命名的渐变 9" x1="848.96" y1="437.15" x2="934.09" y2="437.15" xlink:href="#_未命名的渐变_9"/>
|
||||
<linearGradient id="_未命名的渐变_4" data-name="未命名的渐变 4" x1="418.01" y1="402.81" x2="470.25" y2="402.81" gradientUnits="userSpaceOnUse">
|
||||
<stop offset="0" stop-color="#6445ff"/>
|
||||
<stop offset="1" stop-color="#5c86ff"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="_未命名的渐变_4-2" data-name="未命名的渐变 4" x1="445.81" y1="464.45" x2="466.06" y2="464.45" xlink:href="#_未命名的渐变_4"/>
|
||||
<linearGradient id="_未命名的渐变_72" data-name="未命名的渐变 72" x1="634.1" y1="393.61" x2="649.62" y2="390.81" gradientTransform="translate(-320.36 273.4) rotate(-11.09) scale(1.44 .77)" gradientUnits="userSpaceOnUse">
|
||||
<stop offset="0" stop-color="#c4aeff"/>
|
||||
<stop offset=".43" stop-color="#ce9fff"/>
|
||||
<stop offset=".55" stop-color="#af8eff"/>
|
||||
<stop offset=".72" stop-color="#7d74ff"/>
|
||||
<stop offset=".9" stop-color="#6333ff"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="_未命名的渐变_58" data-name="未命名的渐变 58" x1="620.28" y1="400.01" x2="636.02" y2="397.18" gradientUnits="userSpaceOnUse">
|
||||
<stop offset="0" stop-color="#b1ccff"/>
|
||||
<stop offset=".34" stop-color="#b8c0ff"/>
|
||||
<stop offset=".94" stop-color="#cba2ff"/>
|
||||
<stop offset="1" stop-color="#ce9fff"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="_未命名的渐变_3" data-name="未命名的渐变 3" x1="469.91" y1="455" x2="478" y2="455" gradientUnits="userSpaceOnUse">
|
||||
<stop offset="0" stop-color="#5c86ff"/>
|
||||
<stop offset="1" stop-color="#64a2ff"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="_未命名的渐变_3-2" data-name="未命名的渐变 3" x1="470.95" y1="475.89" x2="477.01" y2="475.89" xlink:href="#_未命名的渐变_3"/>
|
||||
<linearGradient id="_未命名的渐变_65" data-name="未命名的渐变 65" x1="485.09" y1="467.16" x2="502.74" y2="469.91" gradientTransform="translate(-109.75 153.43) rotate(-15.93)" gradientUnits="userSpaceOnUse">
|
||||
<stop offset=".11" stop-color="#64a2ff"/>
|
||||
<stop offset=".28" stop-color="#62a6ff"/>
|
||||
<stop offset="1" stop-color="#5cb5ff"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="_未命名的渐变_66" data-name="未命名的渐变 66" x1="506.01" y1="471.79" x2="532.2" y2="471.17" gradientTransform="translate(117.47 -103.02) rotate(12.81)" gradientUnits="userSpaceOnUse">
|
||||
<stop offset="0" stop-color="#5cb5ff"/>
|
||||
<stop offset="1" stop-color="#74e0ff"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="_未命名的渐变_68" data-name="未命名的渐变 68" x1="559.81" y1="469.17" x2="583.24" y2="468.62" gradientUnits="userSpaceOnUse">
|
||||
<stop offset="0" stop-color="#9cd1ff"/>
|
||||
<stop offset="1" stop-color="#bae0ff"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="_未命名的渐变_71" data-name="未命名的渐变 71" x1="584.93" y1="460.83" x2="609.05" y2="460.83" gradientUnits="userSpaceOnUse">
|
||||
<stop offset="0" stop-color="#a6d3ff"/>
|
||||
<stop offset=".7" stop-color="#b1ccff"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="_未命名的渐变_58-2" data-name="未命名的渐变 58" x1="605.79" y1="473.79" x2="643.63" y2="466.98" xlink:href="#_未命名的渐变_58"/>
|
||||
<linearGradient id="_未命名的渐变_3-3" data-name="未命名的渐变 3" x1="479.91" y1="397.6" x2="514.17" y2="397.6" xlink:href="#_未命名的渐变_3"/>
|
||||
<linearGradient id="_未命名的渐变_5" data-name="未命名的渐变 5" x1="525.59" y1="402.28" x2="558.08" y2="402.28" gradientUnits="userSpaceOnUse">
|
||||
<stop offset="0" stop-color="#64a2ff"/>
|
||||
<stop offset="1" stop-color="#5cc9ff"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="_未命名的渐变_69" data-name="未命名的渐变 69" x1="583.43" y1="398.41" x2="604.15" y2="398.41" gradientUnits="userSpaceOnUse">
|
||||
<stop offset="0" stop-color="#84c9ff"/>
|
||||
<stop offset=".42" stop-color="#b1ccff"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="_未命名的渐变_67" data-name="未命名的渐变 67" x1="531.83" y1="464.83" x2="558.02" y2="464.22" gradientTransform="translate(-86.67 132.78) rotate(-12.37)" gradientUnits="userSpaceOnUse">
|
||||
<stop offset="0" stop-color="#74e0ff"/>
|
||||
<stop offset=".56" stop-color="#8dd6ff"/>
|
||||
<stop offset="1" stop-color="#9cd1ff"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="_未命名的渐变_6" data-name="未命名的渐变 6" x1="531" y1="411" x2="539" y2="411" gradientUnits="userSpaceOnUse">
|
||||
<stop offset="0" stop-color="#64a2ff"/>
|
||||
<stop offset="1" stop-color="#5cbfff"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="_未命名的渐变_7" data-name="未命名的渐变 7" x1="553.98" y1="380.5" x2="559" y2="380.5" gradientUnits="userSpaceOnUse">
|
||||
<stop offset="0" stop-color="#64ccff"/>
|
||||
<stop offset="1" stop-color="#84c9ff"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="_未命名的渐变_7-2" data-name="未命名的渐变 7" x1="546.98" y1="383.5" x2="552" y2="383.5" xlink:href="#_未命名的渐变_7"/>
|
||||
<linearGradient id="_未命名的渐变_7-3" data-name="未命名的渐变 7" y1="404.39" x2="561.98" y2="404.39" xlink:href="#_未命名的渐变_7"/>
|
||||
<linearGradient id="_未命名的渐变_8" data-name="未命名的渐变 8" x1="578.42" y1="391.26" x2="586.42" y2="391.26" gradientUnits="userSpaceOnUse">
|
||||
<stop offset=".01" stop-color="#84c9ff"/>
|
||||
<stop offset="1" stop-color="#b1ccff"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="_未命名的渐变_55" data-name="未命名的渐变 55" x1="623.35" y1="385.26" x2="623.75" y2="385.26" gradientUnits="userSpaceOnUse">
|
||||
<stop offset="0" stop-color="#b1c9ff"/>
|
||||
<stop offset=".42" stop-color="#b1ccff"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<g id="_图层_3" data-name="图层 3">
|
||||
<path class="cls-32" d="m447,485c11.04,8.56,30.35.82,33,0,14.37-4.43,15.38-15.33,26-15,10.44.33,13.16,10.98,24,10,6.48-.59,7.04-4.53,16-6,8-1.31,8.66,1.66,17,1,12.76-1.01,15.73-8.33,24-6,8.42,4.47,11.58,6.51,22,5,0,0,11.3-1.75,32-10,1.42-9.07,4.72,6.12,11.99,12,10.1,9.09,19.95-.4,31,8,10.86,8.26,13.5,26.66,13,31-1.65,4.39,6.37,10.16,6,10-4.41-.77-20.72-10.82-25-11-6.8-1.26-15.53-2.26-24-5-17.1-5.24-15.17-10.82-34-10-7.53.04-6.69-.47-12,1-2.54-.23-9.74.73-13-1-18.29-11.87-9.94,11.78-36,3-9.47,1.86-14.72.65-18.02-1.12-2.21-1.19-4.61-5.33-6.74-4.61-.57.19-.74.58-2.24,1.74-1.2.93-2.27,1.58-3,2-.91.36-1.91.7-3,1-2.93.8-5.59,1.02-7.75,1.02-.74,0-1.87-.01-3.25-.02-3.97,0-3.61.03-4,0-2.87-.21-3.75-.93-7-1-.42,0-.77,0-1,0h-6c-3,.22-5.39.65-7,1-1.72.37-3.36.82-10,3-3.07,1.01-4.59,1.59-6,2-1.81.53-5.13,1.49-9,2-5.07.67-9,.3-12,0-6.54-.65-11.56-2.18-14-3-4.6-1.55-26.12-13.37-20-10,0,0,0,0,0,0-.03-.02-20.88-13.37-28-32-1.09-2.85-2-6-2-6-.52-1.8-1.44-5.03-2-9-.44-3.12-1.34-9.53,0-10,1.69-.59,6.23,8.51,7.27,10.48,5.15,9.76,18.12,22.54,52.73,35.52Z"/>
|
||||
<path class="cls-32" d="m633,394c-13.63,7.43-2.93,57.29,2,57,1.82-.11,1.13-6.97,8-16,3-3.94,3.81-3.37,5-6,.88-3.8,2.06-11.66-2-19-.61-1.11-1.29-2.11-2-3-.31-.36-.8-1.04-1-2-.06-.32-.19-.91,0-1,.26-.13.82.87,2,2,.84.81,1.71,1.61,3,2,1.06.32,1.94.17,3,0,1.38-.23,2.37-.69,3-1,1.31-.64,2.25-1.09,3-2,1.84-2.22,1.14-5.41,1-6-.33-1.36-.69-1.29-2-4-.87-1.8-.57-1.55-2-5-.49-1.19-1-2.79-2-6-.12-.39-.37-1.22,0-2,0,0,.29-.6,1-1,1.98-1.11,11.6.55,15,1,2.66.35,13.92,1.61,15.83,2.95.42.3.8.64,1.17,1.05,2.43,2.73.93,4.31,3,9,3.83,8.69,13.16,12.57,19,15,5.09,2.12,8.54,2.5,13,3,6.22.69,7.53-.22,8-1,.74-1.23-.27-2.7-1-5-1.53-4.78-.3-8.09,0-12,.51-6.64-1.77-13.74-4-17-9.26-13.54-41.29,4.77-71-2-1.28-.29-9.89-2.3-12,1-1.87,2.94,2.99,7.55,1,12-1.49,3.33-5.5,3.63-8,5Z"/>
|
||||
<path class="cls-32" d="m683,228c-22.42,19.49,74.7,203.11,32,245-9.08,8.91-26.9,13.94-28,28-.65,8.33,4.78,18.3,13,23,13.88,7.94,28.83,2.05,33-1,2.68-1.96,4-4,4-4,1.44-2.22,1.56-3.91,3-8,.76-2.17,1.5-3.9,2-5,.43-1.13.95-2.86,1-5,.04-1.88-.32-2.29-.25-4.13.04-1.01.22-5.22,2.25-5.87.27-.09,1.3-.35,3,1-.17-.41-.33-.87-.21-.95.24-.16,1.16,1.42,2.21,1.95,2.87,1.44,7.56-.07,8-2-10.79-5.85-14.4-6.84-15-6-.79,1.11,3.49,5.56,3,6-.37.33-3.05-1.93-3-2,.05-.07,3.08,1.97,4,3,.08.09.44.51,1,1,.23.2.43.36.55.46.1.12.26.31.45.54,2.9,3.62,4.2,7.01,4.62,8.05.92,2.28,2.67,5.77,6.38,10.95,15.05,16.54,34.83,19.18,54,9,16.33,3.81,7.85,10.54,33,5,25.9-4.5,50.39,1.98,49-25-1.26-10.66-5.86-19.02,0-10,8.68,14.17,21.69,39.51,43,27,6.44-4.99,5.9-9.29,8-16,1.22-1.48,4.53-4.38,6-4,.01.08.16,1-.41,1.5-.6.53-1.8.43-2.59-.5,2.97,10.52,12.31,17.02,21,16,7.93-.94,13.74-10.13,14.27-11.06.54-.96.98-1.97.98-1.97s.41-.95.75-1.97c1.19-3.64,1.46-7.4,1.46-7.4.88-11.86.98-15.27.98-15.27.35-11.02.5-9.78.67-17.66.19-8.34-.04-7.03.13-13.62,0,0,.55-21.79-3.25-26.05-.48-.54-1-1-1-1-1.02-.91-1.79-1.28-3-2,0,0-1-.67-3-2-1-.67-2.08-1.22-3-2-1.25-1.06-1.85-1.94-3-2-.39-.02-.52.07-1,0-1.2-.18-1.99-1.02-2-1-.02.03.5.41,3,2,.1-.62,1.27-2.67,2-3,5.54-4.98,5.58-5.38,8-10,6.12-15.03-2.92-19.21-14-26-1.23-1.16-1.87-.9-3-1-.59-2.25-2.17-3.18-3-6-1.01-2.71-2.29-10.67-2-12,.57-5.41,2.39-8.84,4-11,2.1-2.82,3.85-3.49,6-7,1.22-2,2.84-4.65,3-8,.25-5.38-3.39-9.63-8-15-2.47-2.87-5.06-5.17-6-6-2.42-2.13-4.22-3.42-5-4-5.14-3.8-8.31-10.94-9.7-14.15-5.32-12.26-7.9-23.86-8.3-32.85-1.17-26.5-38.54-51.77-63-35-7.04,5.65-2.81,10.14-11,18-10.42,10-22.57,7.8-34,13-5.71,3.43-10.18.22-16-2-9.95-3.09-18.5-13.48-24-22-1.52-1.57-3.98-3.73-7.79-5.64-30.14-4.8-47.97-7.45-51.21-7.36-.78.02-4.27.18-7.57-1.74-7.85-9.54-30.21,6.06-40.43,13.74Z"/>
|
||||
<path class="cls-32" d="m667,174c-4.66.36-8.11,1.64-10.79,3.57-13.35,9.62-7.67,24.47-21.35,37.56-3.88,3.71-4.86,2.87-33.86,12.87-.06.02-2.46.85-4,0-.23-.13-.75-.41-1-1-.23-.54-.15-1.16-.12-1.33.02-.16.09-.51.12-.67h0c1.24-1.71,8.11-11.55,4.97-21.02-3.76-11.34-20.93-19.28-39.97-12.98-12.11,2.27-15.97.27-17.33-1.85-1.94-3.02.84-6.86,5.33-19.16,3.86-10.58,5.89-16.13,6-22,.1-5.49-1.43-13.73-10-24-23.21-14.8-47-13.43-56-2-1.46,1.85-5.64,7.92-4,20-3.3,4.02-9.31,1.14-20,4-10.63,1.96-20.69,9.46-13,23,5.63,9.91,94.53,145.36,192.09,115.49,50.17-15.36,72.52-65.2,72.91-66.49.03-.1.51-1.68,0-2-.15-.1-.3-.03-1,0h-3c-1.55,0-3.44-.77-4-1-.5-.2-1.8-.75-5-3-1.04-.73-2.42-1.73-4-3-3.1-.72-4.4-1.96-5-3-.3-.52-.38-.9-1-2-1.21-2.14-1.61-1.92-2-3-.34-.93-.04-1.09,0-5,0-.35,0-.06,0-2,0-3.6.02-3.67,0-4-.12-1.85-.9-3.16-2-5-2.63-4.41-6.25-6.57-7-7-1.67-.96-2.99-1.38-5-2-2.56-.79-6.28-1.69-11-2Z"/>
|
||||
</g>
|
||||
<g id="_背景" data-name="背景">
|
||||
<path class="cls-18" d="m572,182c-16.42-3.08-26.21,8.1-31,4-7.63-6.54,18.33-33.97,9-55-6.88-15.51-30.12-21.13-45-21-3.33.03-16.49.44-31,9-30.76,18.15-37.62,55.27-56,109-20.03,58.55-43.71,77.3-45,119-.4,13.04.88,31.37,1,33,2.91,40.64,9.7,47.97,12,64,.27,1.85.98,7.34,5,12,.83.96,1.59,1.64,2.01,2,.47.48,1.15,1.18,1.99,2,10.84,10.63,27.33,20.18,27.33,20.18,17.89,10.12,26.84,15.18,35.66,13.82,3.19-.49,6.88-1.68,16.62-3.27,3.46-.56,6.46-.79,12.39-1.73,3.95-.63,7.36-.89,10-1,3.64-.15,6.36-.03,7,0,4.66.22,8.01.84,9,1,3.81.61,10.14.32,21-4,3.96.13,4.67,2.49,9,4,4.59,1.6,5.73-.37,15,0,16.7,2.72,12.4-.95,25-4,5.48-.56,5.78,1.97,14,3,9.86,2.45,36.35-8.51,48,1,6.44,5.27,9.1,4.24,16,6,7.05,3.78,15.85,4.35,23,8,18.2,11.17,34.14,20.96,51,2,5.59-6.98,3.83-12.43,8-14,7.13-2.81,31.59,30.21,58,23,15.36-6.57,13.31-4.51,28,1,5.68,1.04,23.32-3.84,33-4,12.6-.87,15.51.92,21-2,11.5-6.32,8.35-12.82,9-21,6.52,3,17.82,31.94,43,18,6.28-4.14,7.41-10.27,11-10,3.81.29,3.43,7.28,9,11,7.22,4.82,20.84,1.79,26-6,1.88-2.84,1.95-5.28,2-16,.06-12.86,2.5-34.29,1-57-.27-4.14-.62-6.12-2-8-1.81-2.47-4.68-3.81-7.73-8.57-.11-.18-.21-.33-.27-.43,0-.08-.02-.19-.02-.33.03-1.6,1.77-2.75,2.36-3.17,1.19-.85,2.8-2.4,4.66-5.5,10.77-33.99-34.41-21.61-20-59,3.12-6.89,6.55-6.37,8-12,2.32-9.04-4.5-18.26-8-23-5.99-8.1-8.37-6.45-17-17-2.93-3.58-6.84-8.36-9.5-15.5-2.32-6.24-2.48-11.05-2.5-12.5,0-.42,0-.77,0-1,.13-4.73-.28-14.34-6-24-8.66-14.64-25.27-22.26-40-22-22.75.41-39.01,19.59-41,22-4.52,5.48-5.53,9.19-11,12-8.17,4.19-19.82,3.24-29-1-1.04-.48-1.78-.88-2-1-13.03-7.1-12.62-18.04-23-25-.78-.52-2.86-1.85-9.5-3.93-18.42-5.77-40.67-7.51-55.5-8.07-1.93.07-4.76-.05-8-1-3.67-1.08-6.36-2.77-8-4-5.34-3.78-6.82-2.84-9-5-3.59-3.56.14-10.39-3-17-2.84-5.97-10.15-7.96-14-9-2.47-.67-11.2-2.68-20,2-1.88,1-6.08,3.3-9,8-2.37,3.82-1.56,5.46-4,14-2.08,7.28-3.18,10.97-6,13-1.17.85-5.1,3.27-13,1-3.07-.18-7.45,0-12,2-8.86,3.88-12,11.82-14,11-2.79-1.15,5.52-15.64,0-28-5.58-12.5-22.22-15.85-23-16Z"/>
|
||||
<g id="_主体" data-name="主体">
|
||||
<path id="lite-l" class="cls-3" d="m516,141c.03-1.1-.07-2.97-1-5-.89-1.93-2.11-3.1-2.75-3.7-.4-.38-1.57-1.42-3.37-2.29-1.11-.53-2.12-.83-2.88-1.01-.48,0-1.16,0-1.98.04-1.4.09-4.59.32-7.8,1.81-3.86,1.79-6.04,4.59-7.21,6.15-1.03,1.36-.93,1.77-4,8-1.08,2.19-1.79,3.57-2,4-5.9,11.79-12,35-12,35-7.65,29.08-7.7,25.76-12,44-2.8,11.87-4.71,21.49-6,28-.61,3.07-2.21,11.25-4,22,0,0-1.57,9.87-3,30-.1,1.34-.46,7.05,1,14,.85,4.05,3,8,3,8h0c2.28,2.98,4.52,4.83,5.99,5.89,11.06,8,23.32,4.35,57.01,4.11,3.06-.02,5.53-.01,7,0,1.02-.28,2.51-.84,4-2,.52-.41,1.27-1.06,2-2,.45-.58,1.59-1.91,2-4,.21-1.08.43-2.87,0-5-.36-1.17-.7-2.18-1-3-.3-.83-.53-1.39-1-2-.36-.46-.73-.79-1-1-.24-.23-.76-.76-1-1-1.73-1.73-1.87-1.87-2-2-1.25-1.2-2.82-1.65-4-2-5.84-1.71-23-2-23-2-4.12-.07-10.14-.62-17.53-2.99,1.73-17.19,5.56-49.39,13.31-81.46,4.95-20.51,4.35-18.09,8.22-30.55,5.85-18.81,8.77-28.22,11-34,0,0,3.24-8.41,4.63-15.69.44-2.32.84-4.8.84-4.8.18-1.17.35-2.34.53-3.5Z"/>
|
||||
<path id="lite-i-1" class="cls-15" d="m569,240c4.11.31,6.91,2.96,8,4,6.83,6.48,6.93,17.68,7,26,0,.06,0,.03,0,7,0,14.32,0,21.48,0,22,.04,13.27.66,35.34-4,42-1.29,1.84-3,3-3,3-2.97,2.01-6.17,1.99-8,2-1.45,0-7.74-.12-13-5-6.34-5.87-6.06-13.87-6-15,0-.07,0-9.93,0-10,0-38.04,0-37.85,0-38-.02-1.29-.01-3.53,0-8,.03-9.9.15-12.06,1-15,1.28-4.42,3.15-6.95,4-8,1.1-1.36,2.19-2.34,3-3,.7-.58,1.7-1.32,3-2,.98-.52,4.28-2.28,8-2Z"/>
|
||||
<path id="lite-i-1-1" class="cls-30" d="m566.78,196.07c8.85-.96,18.71,7.52,18.2,17.09-.5,9.28-10.62,16.63-19.33,14.46-6.93-1.73-13.7-9.65-12.51-18.4,1.06-7.79,7.93-12.52,13.65-13.14Z"/>
|
||||
<path id="lite-t" class="cls-4" d="m643,219c-5.46,4.9-13.26-2.45-24,3-1.76.89-7.94,4.02-9,10-.72,4.08.94,7.61,1.38,8.43.48.92,2.17,4.11,5.62,5.57,1.09.46,1.2.26,6,1,5.06.78,4.17.82,6,1,2.02.19,4,.68,6,1,.54.09,1.55.25,2,1,.42.71.13,1.6,0,2-.61,1.93-.44,4.05-1,6-1.01,3.51-.87,8.39-1,13-.29,10.62-1.82,12.94-2,20-.11,4.19-.42,18.97,9,31,1.49,1.9,10.06,12.84,21,12,1.28-.1,5.89-.45,9-4,4.48-5.11,2.13-12.61,2-13-1.5-4.54-4.19-4.73-8-10-4-5.53-5.15-11-6-15-1.67-7.89-.83-14.05,0-20,1.22-8.76,3.02-10.14,4-16,.13-.81.46-2.94,2-5,0,0,.85-1.13,2-2,2.17-1.64,8.68-.49,12.72-.42,3.25.06,5.06.09,7.28-.58,1.17-.35,8.31-2.66,10-9,1.37-5.14-1.51-11.55-6-14-3.8-2.07-12.48-2.26-15-3-1.03-.3-1.9-.93-2-1-.47-.34-.82-.6-1-1-.56-1.23.89-2.87,1-3,.54-.96,1.27-2.32,2-4,1.51-3.46,2.99-6.85,3-11,0-1.91.01-6.19-3-10-3.3-4.17-8.02-4.99-9.42-5.24-.92-.16-6.37-1-11.58,2.24-1.61,1-4.15,3-7,9-5.75,12.08-1.75,17.18-6,21Z"/>
|
||||
<path id="lite-e" class="cls-7" d="m735,276c-.58.39-5.22,3.65-5,8,.19,3.92,4.21,6.49,5,7,5.42,3.48,9.31.51,25-1,10.15-.98,16.08-.47,21-5,1.89-1.74,2.72-3.39,3-4,2.32-5.01-.29-10.09-1-14-1.83-10.01-12.11-17.03-15-19-1.39-.95-8.43-5.63-19-7-4.47-.58-13.46-1.75-23,3-8.94,4.45-13.31,11.6-16,16-5.84,9.56-6.79,18.48-7,21-.4,4.87-1.09,14.86,5,25,4.96,8.26,11.87,12.39,15,14,8.24,4.25,15.6,4.12,23,4,9.99-.17,16.58-2.61,23-5,2-.74,5.17-2.02,8-5,1.68-1.77,4.08-4.29,4-8-.07-3.36-2.16-6.53-5-8-2.95-1.53-5.9-.81-9,0-6.07,1.58-6.81,4.01-12,6-6.49,2.49-12.52,1.46-15,1-3.77-.69-9.81-1.81-14-7-3.24-4.01-3.72-8.41-4-11-.26-2.43-1.1-10.07,4-17,.43-.59,7.21-9.48,18-9,1.43.06,9.65.59,14,7,1.16,1.71,2.94,4.33,2,6-2.22,3.93-15.76-4.29-25,2Z"/>
|
||||
<path id="logo-snow" class="cls-21" d="m875,272l13.33,22,9.67-15-8-20,4-6,6,2,9,13,7-11,9,1,3,7-11,13,17,2,4,8-5,4-22-4-13.5,16.8,27.5,5.2,11-14,7,1,3,7-9,11,19,3,4,7-4,5-18-3,7,14-4,4-8-1-10-20-40-8-2-2-20-37-23-4-3-7,4-5,16,3-9-15,4-6,7,1,9,16,9-10,7,2,3,6-10,14Z"/>
|
||||
<path id="yuki-y" class="cls-27" d="m717,426c-18.61-18.39-62.05-50.01-50-71,2.85-4.5,7.34-9.45,17-7,15.19,5.99,14.64,39.18,45,55,8.67-18.92,10.28-33.18,10-43-.12-4.37-.74-12.15,4-16,.82-.55,5.22-3.4,10.85-2.15,3.86.85,6.25,3.17,7.15,4.15.5.6,1.28,1.58,2.09,2.87,8.99,14.33.03,32.06-11.49,69.45-4.1,13.3-6.65,21.58-9.6,33.68-5.81,23.86-8.17,40.38-20,51-11.03,9.91-22.32.03-23-9-2.89-15.02,23.13-56.98,18-68Z"/>
|
||||
<path id="yuki-u" class="cls-22" d="m776,433c-.54-.11-5.36-1.02-9,2-2.31,1.92-3.04,4.55-4,8-.74,2.66-.91,4.8-1,6-.59,8.25,0,15,0,15,1.18,13.55,1.77,20.33,3,23,1.04,2.27,4.07,8.71,11,13,6.21,3.85,12.31,3.91,21,4,.81,0,16.83.09,21-4,.22-.21,1.2-1.26,2-1,.93.3.69,2.05,2,4,0,0,.63.93,2,2,3.54,2.75,12.53,3.41,16-1,.62-.79,1.75-2.64,2-5,.07-.68.05-1.22.02-1.76-.13-2.75-.88-4.83-1.02-5.24-1.31-3.73-3.37-15.69-4-23-.58-6.71.42-5.3,0-19-.11-3.63-.12-9.19-3-15-1.36-2.75-2.63-4.09-4-5-2.95-1.96-6.82-2.22-10-1-.8.31-3.48,1.34-5,4-1.36,2.37-1.11,4.83-1,6,.66,6.98.45,14.01,1,21,.72,9.13.57,11.51-1,14-1.8,2.86-4.42,4.2-6,5-7.58,3.86-15,.82-17,0-2.54-1.04-3.49-2.11-4-3-.74-1.29-.88-2.78-1-4-.13-1.35-.05-2.34,0-3v-16c-.19-7.44-.28-11.16-2-14-.52-.85-3.02-4.98-8-6Z"/>
|
||||
<path id="yuki-i-1-1" class="cls-2" d="m964,382c-6.65.3-12.55,6.12-12,13,.53,6.56,6.7,11.28,13,11,6.65-.3,12.55-6.12,12-13-.53-6.56-6.7-11.28-13-11Z"/>
|
||||
<path id="yuki-i-1" class="cls-24" d="m964,413c-.8.03-3.41.23-6,2,0,0-1.73,1.18-3,3-3.44,4.92-2.32,14.54-2,25,.14,4.66,0,9.33,0,14,0,11.92,0,17.88,0,18-.51,8.41-1.54,11.05,0,16,.77,2.5,1.84,5.92,5,8,3.21,2.11,6.76,1.77,8.36,1.62.77-.07,5.65-.54,8.64-3.62,3.4-3.49,2.66-8.56,2-17-.09-1.11-1.18-15.43-1-28,.1-7.33-.76-14.71,0-22,.11-1.03.52-4.77-1-9-.64-1.78-1.55-4.19-4-6-2.89-2.13-6.05-2.04-7-2Z"/>
|
||||
<path id="yuki-k" class="cls-28" d="m918,409c-.26-.11-.66-.27-1.17-.44,0,0-4.46-1.53-9.91-1-4.47.44-18.59,11.34-31.93,31.43-.35-10.53-.23-21.87.56-33.9.37-5.55.85-10.92,1.44-16.1.26-1.2.52-2.94.41-5.03-.09-1.81-.25-4.96-2.27-8.04-.56-.85-2.9-4.42-7.52-5.51-5.61-1.33-10.01,2.08-10.62,2.58-3.44,2.76-4.45,6.67-5,9-3.6,15.32-3,27-3,27,.42,8.15,1.67,32.57,1,50-.22,5.66.05,11.33,0,17-.05,6.02-.13,9.05,1,13,1.45,5.07,4.08,8.75,6,11,1.19,1.11,3.93,3.36,8,4,3.99.63,7.79-.51,9.84-1.85,11.06-7.21,1.65-39.59,4.55-40.56.39-.13,1.19.88,2.61,3.4,8.89,11.22,14.99,15.39,30,29,7.7,10.13,23.45,3.59,22-8-.19-16.59-39.62-33.08-31.91-39.47,1.89-2.38,6-8.21,6.91-9.53,5.47-7.8,10.83-8.44,13-15,1.34-4.04.69-6.57.43-7.38-1.01-3.15-3.39-4.94-4.43-5.62Z"/>
|
||||
<path id="lite-jp-1" class="cls-19" d="m452,384c-1.75-1-2.61-.17-10,1-2.22.35-7.11,1.06-13,3-1.36.45-2.68.93-3,2-.49,1.65,1.56,4.27,4,5,.32.09.56.13.74.16,2.19.34,3.89-.56,5.26-1.16,1.96-.87,3.64-1.24,7-2,0,0,4.44-1,5,0,.02.04.04.07.04.07.07.18-.02.5-.04,2.93,0,.06,0,.71,0,2,0,5.01.09,5.71,0,7-.2,2.74-.3,4.11-1,5-1.25,1.58-3.23,1.52-7,2-5.62.72-4.41,1.27-10,2-7.19.94-9.49.07-11,2-1.32,1.68-1.32,4.57,0,6,1.74,1.88,5.46.88,9,0,7.35-1.82,19.55-4.84,29-6,4.81-.59,12-1.09,13-4,.57-1.65,0-3,0-3-.06-.14-.62-1.43-2-2-1.19-.49-2.36-.18-3,0-6.78,1.91-9.23,2.82-10,2-.45-.48-.17-1.39,0-2,1.77-6.29,1.22-12.36,1-14-.23-1.67-.84-6.18-4-8Z"/>
|
||||
<path id="liteecho-1" class="cls-34" d="m448,440c-1.02.34-1.72,1.18-2,2-.31.93-.06,1.78,0,2,.38,1.39.25,7.26,0,19-.12,5.74-.36,7.73,0,12,.21,2.49.58,5.11,2,8,.87,1.76,1.83,3.72,4,5,2.08,1.23,4.09,1.14,7,1,2.69-.13,6.51-.31,7-2,.37-1.27-1.18-3.23-3-4-2.78-1.18-4.84.97-7,0-2.17-.97-2.69-4.29-3-7-1.07-9.45-.9-24.04-1-30-.02-1.36-.12-5.21-2-6-.58-.25-1.37-.21-2,0Z"/>
|
||||
<path id="yuki-jp-4-1" class="cls-16" d="m636.06,390.95c-2.11.2-3.55,1.9-3.93,2.35-.57.68-.91,1.34-1.11,1.8,1.09,1.05,6.06,5.64,11.22,4.91,2.33-.33,5.1-1.81,5.34-2.63.03-.09.03-.17.03-.17,0-.29-.33-.71-1.89-1.83-1.33-.96-2.23-1.61-3.3-2.18-.69-.37-1.23-.6-1.68-.79-2.41-1.04-3.62-1.55-4.68-1.45Z"/>
|
||||
<path id="yuki-jp-4" class="cls-12" d="m621,375c.37-.49,1.34-1.8,3-2,2.33-.28,3.93,1.89,4,2,.33.46.44.81,1,3,.59,2.3.88,3.45,1,4,.33,1.56.71,3.58,1,6,.65,5.37.61,6.78,1,10,.79,6.47,1.28,10.5,3,15,.07.18,1.23,3.21,1,7-.08,1.29-.29,2.25-1,3-1,1.07-2.67,1.39-4,1-.58-.17-1.69-.65-3-3-1.53-2.75-1.78-5.26-2-7-.3-2.39-.86-5.79-2-10-1-6.33-2-12.67-3-19-.33-1.67-.67-3.33-1-5-.1-.53-.53-2.95,1-5Z"/>
|
||||
<path id="liteecho-i-1-1" class="cls-20" d="m474,451c-1.86-.04-3.59,1.27-4,3-.56,2.38,1.47,5.01,4,5,2.03-.01,4-1.72,4-4,0-2.25-1.92-3.96-4-4Z"/>
|
||||
<path id="liteecho-i-1" class="cls-9" d="m475,464c-.61-.3-1.38-.31-2,0-.14.07-.62.33-1,1-1.18,2.08-1.01,7.51-1,8,.16,5.45,0,9,0,9-.08,1.78-.22,3.7,1,5,.21.22.94,1,2,1,1.08,0,1.81-.8,2-1,1.05-1.14,1.02-2.72,1-4-.03-2.33.04-4.67,0-7-.05-3.31.17-8.95-1-11-.12-.2-.41-.71-1-1Z"/>
|
||||
<path id="liteecho-t" class="cls-8" d="m492.48,452.64c-.52.03-1.3.07-1.92.55-.25.19-.76.67-1.1,2.68-.15.92-.29,2.26-.14,3.91-1.17.01-3.52.16-5.32,1.22-.38.22-.77.51-1,1-.39.85-.08,1.91.41,2.59.74,1.01,1.99,1.19,3.59,1.41.85.12,1.57.12,2.05.1-.06.76-.12,1.85-.14,3.16-.01,1.22-.03,2.48.14,4.12.15,1.49.3,2.92.96,4.62.39,1.03.78,1.67,1,2,.25.39.74,1.08,1.48,1.76,1.12,1.05,2.67,2.5,4.52,2.24,1.2-.17,2.57-1.06,2.76-2.24.12-.76-.29-1.46-.55-1.92-.43-.76-.87-.99-1.51-1.65-.95-.97-1.44-1.93-1.78-2.61-.71-1.41-.9-2.61-1.1-3.85-.15-.97-.37-2.35-.14-4.12.09-.67.22-1.22.32-1.61.56,0,1.35-.04,2.29-.17,1.54-.21,2.32-.32,2.88-.82.8-.71,1.27-2.03.83-3.01-.13-.3-.38-.61-1-1-1.52-.94-3.22-1.02-4.36-.94.11-.49.27-1.25.41-2.2.49-3.18.14-3.81-.14-4.12-.26-.3-.59-.43-1.24-.69-.4-.16-1.18-.47-2.2-.41Z"/>
|
||||
<path id="liteecho-e" class="cls-25" d="m515.41,469.49c-1.24,0-1.82.11-2.17.53-.63.76-.29,2.24.31,3.15,1.51,2.31,5.53,2.25,7.8,1.77,1.4-.3,4.49-.94,5.98-3.77,1.7-3.21-.04-6.62-.4-7.27-1.86-3.4-5.47-4.29-7.14-4.7-3.37-.83-6.16.05-7.27.4-1.07.34-2.81.91-4.53,2.4-3.45,2.98-3.92,7.09-4,8-.27,3.05.7,5.35,1,6,.27.6,1.17,2.42,3,4,1.04.89,2.16,1.53,3,2,.99.56,1.75.89,2,1,1.02.44,1.88.81,3,1,1.17.2,2.07.1,3,0,.34-.04,1.85-.22,4-1,3.3-1.2,4.59-2.54,5-3,.47-.53.68-.9.71-1.34.06-1.15-1.21-2.31-2.48-2.62-.51-.12-.94-.08-1.23-.05-2.01.26-3.04,1.28-4.31,1.86-2.59,1.19-5.84.41-7.69-.86-1.16-.8-1.95-1.93-2-2-.26-.38-.69-1.07-1-2-.26-.77-.95-2.86,0-5,.82-1.85,2.41-2.69,3-3,2.34-1.23,5.06-1.05,7,0,.69.37,1.64.9,2,2,.29.87.26,2.23-.52,2.84-1,.8-2.36-.34-6.07-.36Z"/>
|
||||
<path id="liteecho-c" class="cls-29" d="m570,456c-1.16,0-4.12.04-7,2-1.54,1.05-2.49,2.26-3,3-1.62,2.38-1.87,4.7-2,6-.12,1.26-.36,4,1,7,2.14,4.72,6.61,6.52,8,7,1.04.36,4.93,1.64,9,0,.94-.38,2.74-1.13,4-3,.45-.66,1.99-2.94,1-5-.54-1.12-1.88-2.25-3-2-1.41.32-1.04,2.48-3,4-1.92,1.48-4.45,1.09-5,1-.54-.08-2.42-.41-4-2-2.4-2.42-2.04-5.66-2-6,.07-.51.43-3.37,3-5,2.25-1.43,5.33-1.38,7,0,.93.77.92,1.55,2,2,1.26.52,3.2.27,4-1,.72-1.15.17-2.56,0-3-.15-.38-.69-1.63-3-3-.98-.58-3.45-2.01-7-2Z"/>
|
||||
<path id="liteecho-h" class="cls-17" d="m590,439c-.92-.49-2.08-.44-3,0-1.8.87-1.99,2.91-2,3v9c0,4,.04,8,0,12-.03,3,.1,6,0,9-.1,3.04-.29,5.38,1,8,.45.91,1.42,2.89,3,3,1.63.11,2.9-1.85,3-2,1.44-2.28.03-4.1,0-9-.02-2.85,0-5.82,2-8,.37-.4,1.8-1.97,4-2,2.44-.04,3.91,1.89,4,2,.92,1.22,1,2.52,1,3-.33,1.67-.67,3.33-1,5-.41,1.12-.91,2.85-1,5-.09,2.3-.16,3.99,1,5,1.28,1.11,3.61,1.02,5,0,1.65-1.21,1.8-3.61,2-7,.08-1.27.04-2.31,0-3v-8c-.02-.84-.16-2.36-1-4-.51-1-1.73-2.93-4-4-3.65-1.72-7.65-.14-8,0-1.5.62-2.24,1.36-3,1-.71-.34-.94-1.4-1-4-.12-5.04.61-6.66,0-10-.56-3.06-1.54-3.76-2-4Z"/>
|
||||
<path id="liteecho-o" class="cls-33" d="m625,458c-4.27-.18-7.17,2.27-8,3-3.05,2.66-3.79,6-4,7-.18.88-1.21,5.84,2,10,2.08,2.69,4.82,3.62,6,4,1.29.42,4.46,1.39,8,0,2.95-1.16,4.51-3.31,5-4,1.43-2.02,1.81-3.97,2-5,.46-2.51.1-4.49,0-5-.67-3.35-2.52-5.47-3-6-.69-.76-3.46-3.8-8-4Z"/>
|
||||
<path id="liteecho-o-white" class="cls-35" d="m623.5,464.5c-2.49.24-3.75,2.55-4,3-.09.17-1.81,3.42,0,6,1.68,2.39,5.6,3.06,8,1,2.1-1.8,2.13-4.93,1-7-.84-1.54-2.7-3.22-5-3Z"/>
|
||||
<path id="lite-jp-2" class="cls-13" d="m481,384c-.71.63-1.32,1.81-1,3,.38,1.44,1.86,1.95,2,2,.77.25,1.44.12,2,0,4.88-1.04,5.31-1.31,6-1,1.18.54,1.54,2.08,2,4,.41,1.72.73,3.08,0,4-.65.81-2.11.89-5,1-1.25.05-2.29.03-3,0-.53.14-1.96.61-3,2-.4.54-1.21,1.61-1,3,.24,1.62,1.7,2.78,3,3,.44.08.79.03,1,0,4.59-.65,6.89-.98,7-1,2.55-.43,3.84-.64,4.45-.34,1.26.62,1.41,1.86,2.55,5.34,0,0,1.48,4.5,4,10,.8,1.75,1.53,3.13,2,4,.16.13,2.42,1.9,5,1,.16-.06,2.44-.88,3-3,.21-.78.1-1.42,0-2-.27-1.54-.96-2.26-2-4-.12-.21.03.04-2-4-.67-1.33-1.32-2.67-2-4-1.91-3.76-2.3-4.3-2-5,.2-.46.49-.53,4-2,1.13-.48,2.45-1.03,4-2,1.13-.71,1.71-1.21,2-2,.06-.17.35-1.03,0-2-.48-1.32-1.88-2.03-3-2-.31,0-.54.07-.66.11-2.2.63-4.77,1.67-5.34,1.89-2.21.88-3.31,1.31-4,1-.42-.19-.77-.54-2-4-1.21-3.42-1.11-3.77-1-4,.42-.85,1.37-.79,6-2,2.46-.64,3.57-1.02,4-2,.3-.68.14-1.39,0-2-.09-.4-.54-2.17-2-3-1.17-.66-2.4-.38-4,0-3.65.88-4.88,2.55-6,2-.55-.27-1.15-1.13-1-4-.42-2-.76-3-1-3h0c-.76-1.48-2.4-2.29-4-2-1.51.27-2.7,1.47-3,3-.17,2.45.41,4.01,1,5,.57.95,1.14,1.36,1,2-.24,1.14-2.45,1.64-4,2-2.38.55-3.11.2-4,1Z"/>
|
||||
<path id="yuki-jp-1" class="cls-5" d="m526,396c-.46,1.12-.62,2.69,0,4,.19.41.79,1.49,2,2,1.22.51,2.43.16,3,0,5-1.43,7.51-2.15,8-2,4.42,1.34,3.64,20.08,7,22,2.14,1.22,4,1,4,1,1.43-.17,3.03-.97,3.02-1,0,0,0,0-.02,0,.27-.2.65-.52,1-1,1.53-2.12.85-5.32-1-10-3.3-8.33-6.24-11.07-5-13,.71-1.1,1.84-.46,5.57-2.36,2.51-1.27,4.06-2.06,4.43-3.64.46-1.98-1.11-4.46-3-5-1.01-.29-2.23.21-4.65,1.22-3.32,1.39-3.58,2.02-4.35,1.78-2.25-.7-.9-6.34-4-8-1.22-.65-2.84-.54-4,0-.31.14-1.56.73-2,2-.24.7-.14,1.29,0,2,.51,2.66,2.08,3.57,2,5.99,0,0,0,0,0,0,.01,0,.02,0,.02,0,.01.1-10.37-.01-12.02,4Z"/>
|
||||
<path id="yuki-jp-2-3" class="cls-31" d="m603,386c-.23-.2-.95-.74-2-1-.58-.14-1.39-.34-2,0-.79.44-.86,1.6-1,3,0,0-.62,6.23-2,9-.43.86-.97,1.66-.97,1.66-.12.18-.24.35-.35.5-.2.27-.42.55-.68.84-1.47,1.68-5.93,4.2-5.93,4.2-3.23,1.82-3.66,2.04-4.07,2.8-.61,1.13-.9,2.9,0,4,.8.97,2.17.99,3,1,.7,0,3.05-.07,7.17-3.17,1.64-1.24,3.78-2.89,5.83-5.83,3.15-4.53,3.72-8.76,4-11,.52-4.2-.4-5.48-1-6Z"/>
|
||||
<path id="liteecho-e2" class="cls-14" d="m540.81,469.18c-1.13.52-1.6.87-1.74,1.41-.25.96.69,2.15,1.62,2.72,2.35,1.44,5.96-.31,7.81-1.71,1.14-.86,3.66-2.76,3.81-5.96.17-3.63-2.85-5.97-3.45-6.41-3.13-2.29-6.78-1.55-8.46-1.22-3.41.68-5.55,2.66-6.41,3.45-.83.76-2.15,2.02-3.08,4.1-1.85,4.16-.53,8.09-.22,8.94,1.05,2.88,2.91,4.54,3.46,5,.5.42,2.09,1.69,4.42,2.34,1.32.37,2.61.46,3.57.53,1.13.08,1.96.06,2.24.05,1.11-.04,2.04-.06,3.14-.37,1.14-.32,1.92-.79,2.71-1.28.29-.18,1.58-.98,3.19-2.61,2.48-2.49,3.07-4.25,3.25-4.84.2-.68.24-1.11.07-1.51-.43-1.07-2.08-1.58-3.36-1.31-.51.11-.88.32-1.13.48-1.71,1.09-2.2,2.45-3.11,3.52-1.84,2.18-5.11,2.86-7.32,2.49-1.39-.23-2.59-.92-2.66-.96-.4-.23-1.08-.67-1.76-1.38-.56-.59-2.08-2.19-2.13-4.52-.04-2.02,1.04-3.46,1.44-3.99,1.59-2.11,4.14-3.1,6.33-2.98.79.04,1.86.12,2.66.96.63.67,1.18,1.91.74,2.79-.57,1.15-2.28.69-5.65,2.26Z"/>
|
||||
</g>
|
||||
<circle class="cls-23" cx="535" cy="411" r="4"/>
|
||||
<ellipse class="cls-11" cx="556.49" cy="380.5" rx="2.51" ry="2.5"/>
|
||||
<ellipse class="cls-26" cx="549.49" cy="383.5" rx="2.51" ry="2.5"/>
|
||||
<circle class="cls-10" cx="557.98" cy="404.39" r="4"/>
|
||||
<circle class="cls-1" cx="582.42" cy="391.26" r="4"/>
|
||||
<circle class="cls-6" cx="590.42" cy="385.26" r="4"/>
|
||||
</g>
|
||||
</svg>
|
Before Width: | Height: | Size: 31 KiB |
@ -1,22 +0,0 @@
|
||||
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: #32383D'>" +
|
||||
" <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()
|
Before Width: | Height: | Size: 92 KiB |
@ -1 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="1200" height="1200" class="icon" viewBox="0 0 3280.944 2800"><path fill="#41b883" d="M1645.332 601.004h375.675L1081.82 2238.478 142.636 601.004h718.477l220.708 379.704 216.013-379.704z"/><path fill="#41b883" d="M142.636 601.004l939.185 1637.474 939.186-1637.474h-375.675l-563.51 982.484-568.208-982.484z"/><path fill="#35495e" d="M513.188 601.004l568.207 987.23 563.511-987.23h-347.498l-216.013 379.704-220.708-379.704zM1607.792 1311.83l594.678 2.293 187.353-316.325-598.662 2.292zM2198.506 1909.57C2867.436 732.7 2939.502 605.426 2937.874 603.78c-.715-.723 45.303-1.314 102.262-1.314s103.562.428 103.562.951c0 .523-208.57 367.978-463.491 816.567L2216.715 2235.6l-102.1.596-102.102.596z"/><path fill="#41b883" d="M1680.563 2233.328c0-1.34 168.208-298.145 440.375-777.048a4135645.775 4135645.775 0 00337.619-594.19l146.13-257.25 170.746-.04 170.747-.04-5.536 9.741c-3.044 5.358-43.727 77.302-90.407 159.875-85.356 150.992-337.562 595.163-656.602 1156.373l-172 302.559-170.536.588c-93.795.322-170.536.069-170.536-.567z"/><path fill="#35495e" d="M1429.783 1625.351l594.679 2.292 187.353-316.324-598.662 2.292z"/><path fill="#41b883" d="M1524.207 1464.903l608.285 6.877 173.746-320.909h-619.072z"/></svg>
|
Before Width: | Height: | Size: 1.2 KiB |
@ -1,25 +0,0 @@
|
||||
import {sidebar} from "vuepress-theme-hope";
|
||||
|
||||
export default sidebar({
|
||||
"/": [
|
||||
"",
|
||||
{
|
||||
text: "项目部署",
|
||||
icon: "laptop-code",
|
||||
prefix: "deployment/",
|
||||
children: "structure",
|
||||
},
|
||||
{
|
||||
text: "使用手册",
|
||||
icon: "book",
|
||||
prefix: "usage/",
|
||||
children: "structure",
|
||||
},
|
||||
{
|
||||
text: "商店",
|
||||
icon: "store",
|
||||
prefix: "store/",
|
||||
children: "structure",
|
||||
}
|
||||
],
|
||||
});
|
@ -1,7 +0,0 @@
|
||||
// you can change config here
|
||||
$colors: #c0392b, #d35400, #f39c12, #27ae60, #16a085, #2980b9, #8e44ad, #2c3e50,
|
||||
#7f8c8d !default;
|
||||
|
||||
body {
|
||||
overflow-x: hidden;
|
||||
}
|
@ -1,100 +0,0 @@
|
||||
// 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; /* 你想要的颜色 */
|
||||
line-height: 2;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: ColorTube;
|
||||
src: url("/assets/fonts/ColorTube.woff") format("woff")
|
||||
}
|
||||
|
||||
code {
|
||||
font-family: "Fira Code", monospace !important;
|
||||
}
|
||||
|
||||
.vp-hero-image {
|
||||
overflow: hidden;
|
||||
padding: -50px;
|
||||
}
|
||||
|
||||
#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;
|
||||
}
|
||||
|
||||
.item-search-box {
|
||||
border-radius: 100px;
|
||||
width: 80%;
|
||||
padding: 10px;
|
||||
margin: 10px 0;
|
||||
border: 1px solid #ccc;
|
||||
}
|
||||
|
||||
.search-box-div {
|
||||
display: flex;
|
||||
justify-content: space-around;
|
||||
margin: 10px 0;
|
||||
}
|
||||
|
||||
.item-search-box {
|
||||
width: 80%;
|
||||
}
|
@ -1,2 +0,0 @@
|
||||
// you can change colors here
|
||||
$theme-color: #00a6ff;
|
@ -1,192 +0,0 @@
|
||||
import {hopeTheme} from "vuepress-theme-hope";
|
||||
import navbar from "./navbar.js";
|
||||
import sidebar from "./sidebar.js";
|
||||
|
||||
export default hopeTheme({
|
||||
|
||||
hostname: "https://vuepress-theme-hope-docs-demo.netlify.app",
|
||||
|
||||
author: {
|
||||
name: "远野千束",
|
||||
url: "https://sfkm.me",
|
||||
},
|
||||
|
||||
iconAssets: "fontawesome-with-brands",
|
||||
|
||||
logo: "https://cdn.liteyuki.icu/static/img/liteyuki_icon_640.png",
|
||||
|
||||
repo: "https://github.com/LiteyukiStudio/LiteyukiBot",
|
||||
|
||||
docsDir: "docs",
|
||||
|
||||
// 导航栏
|
||||
navbar,
|
||||
|
||||
// 侧边栏
|
||||
sidebar,
|
||||
|
||||
// 页脚
|
||||
footer: "LiteyukiBot",
|
||||
displayFooter: true,
|
||||
|
||||
// 加密配置
|
||||
encrypt: {
|
||||
config: {
|
||||
"/demo/encrypt.html": ["1234"],
|
||||
},
|
||||
},
|
||||
|
||||
// 多语言配置
|
||||
metaLocales: {
|
||||
editLink: "在 GitHub 上编辑此页",
|
||||
},
|
||||
|
||||
// 如果想要实时查看任何改变,启用它。注: 这对更新性能有很大负面影响
|
||||
// hotReload: true,
|
||||
|
||||
// 在这里配置主题提供的插件
|
||||
plugins: {
|
||||
search: true,
|
||||
// search: true,
|
||||
comment: {
|
||||
provider: "Giscus",
|
||||
repo: "snowykami/LiteyukiBot",
|
||||
repoId: "R_kgDOHVNKpQ",
|
||||
category: "Announcements",
|
||||
categoryId: "DIC_kwDOHVNKpc4CeWxj",
|
||||
},
|
||||
|
||||
components: {
|
||||
components: ["Badge", "VPCard"],
|
||||
},
|
||||
|
||||
// 此处开启了很多功能用于演示,你应仅保留用到的功能。
|
||||
mdEnhance: {
|
||||
alert: true,
|
||||
align: true,
|
||||
attrs: true,
|
||||
codetabs: true,
|
||||
footnote: true,
|
||||
component: true,
|
||||
demo: true,
|
||||
figure: true,
|
||||
imgLazyload: true,
|
||||
imgSize: true,
|
||||
include: true,
|
||||
mark: true,
|
||||
stylize: [
|
||||
{
|
||||
matcher: "Recommended",
|
||||
replacer: ({tag}) => {
|
||||
if (tag === "em")
|
||||
return {
|
||||
tag: "Badge",
|
||||
attrs: {type: "tip"},
|
||||
content: "Recommended",
|
||||
};
|
||||
},
|
||||
},
|
||||
],
|
||||
sub: true,
|
||||
sup: true,
|
||||
tabs: true,
|
||||
vPre: true,
|
||||
|
||||
|
||||
// 在启用之前安装 chart.js
|
||||
// chart: true,
|
||||
|
||||
// insert component easily
|
||||
|
||||
// 在启用之前安装 echarts
|
||||
// echarts: true,
|
||||
|
||||
// 在启用之前安装 flowchart.ts
|
||||
// flowchart: true,
|
||||
|
||||
// gfm requires mathjax-full to provide tex support
|
||||
// gfm: true,
|
||||
|
||||
// 在启用之前安装 katex
|
||||
// katex: true,
|
||||
|
||||
// 在启用之前安装 mathjax-full
|
||||
// mathjax: true,
|
||||
|
||||
// 在启用之前安装 mermaid
|
||||
// mermaid: true,
|
||||
|
||||
// playground: {
|
||||
// presets: ["ts", "vue"],
|
||||
// },
|
||||
|
||||
// 在启用之前安装 reveal.js
|
||||
// revealJs: {
|
||||
// plugins: ["highlight", "math", "search", "notes", "zoom"],
|
||||
// },
|
||||
|
||||
// 在启用之前安装 @vue/repl
|
||||
// vuePlayground: true,
|
||||
|
||||
// install sandpack-vue3 before enabling it
|
||||
// sandpack: true,
|
||||
},
|
||||
|
||||
// 如果你需要 PWA。安装 @vuepress/plugin-pwa 并取消下方注释
|
||||
// pwa: {
|
||||
// favicon: "/favicon.ico",
|
||||
// cacheHTML: true,
|
||||
// cachePic: true,
|
||||
// appendBase: true,
|
||||
// apple: {
|
||||
// icon: "/assets/icon/apple-icon-152.png",
|
||||
// statusBarColor: "black",
|
||||
// },
|
||||
// msTile: {
|
||||
// image: "/assets/icon/ms-icon-144.png",
|
||||
// color: "#ffffff",
|
||||
// },
|
||||
// manifest: {
|
||||
// icons: [
|
||||
// {
|
||||
// src: "/assets/icon/chrome-mask-512.png",
|
||||
// sizes: "512x512",
|
||||
// purpose: "maskable",
|
||||
// type: "image/png",
|
||||
// },
|
||||
// {
|
||||
// src: "/assets/icon/chrome-mask-192.png",
|
||||
// sizes: "192x192",
|
||||
// purpose: "maskable",
|
||||
// type: "image/png",
|
||||
// },
|
||||
// {
|
||||
// src: "/assets/icon/chrome-512.png",
|
||||
// sizes: "512x512",
|
||||
// type: "image/png",
|
||||
// },
|
||||
// {
|
||||
// src: "/assets/icon/chrome-192.png",
|
||||
// sizes: "192x192",
|
||||
// type: "image/png",
|
||||
// },
|
||||
// ],
|
||||
// shortcuts: [
|
||||
// {
|
||||
// name: "Demo",
|
||||
// short_name: "Demo",
|
||||
// url: "/demo/",
|
||||
// icons: [
|
||||
// {
|
||||
// src: "/assets/icon/guide-maskable.png",
|
||||
// sizes: "192x192",
|
||||
// purpose: "maskable",
|
||||
// type: "image/png",
|
||||
// },
|
||||
// ],
|
||||
// },
|
||||
// ],
|
||||
// },
|
||||
// },
|
||||
},
|
||||
});
|
@ -1,82 +0,0 @@
|
||||
---
|
||||
home: true
|
||||
icon: home
|
||||
title: 首页
|
||||
heroImage: https://cdn.liteyuki.icu/static/svg/lylogo-full.svg
|
||||
heroImageDark: https://cdn.liteyuki.icu/static/svg/lylogo-full-dark.svg
|
||||
bgImage:
|
||||
bgImageDark:
|
||||
bgImageStyle:
|
||||
background-attachment: fixed
|
||||
heroText: LiteyukiBot
|
||||
tagline: LiteyukiBot 轻雪机器人,基于NoneBot2构建的综合应用型聊天机器人
|
||||
|
||||
actions:
|
||||
- text: 快速部署
|
||||
icon: rocket
|
||||
link: ./deployment/install.html
|
||||
type: primary
|
||||
|
||||
- text: 使用手册
|
||||
icon: book
|
||||
link: ./usage/basic_command.html
|
||||
|
||||
highlights:
|
||||
- header: 简洁至上
|
||||
image: /assets/image/layout.svg
|
||||
bgImage: https://theme-hope-assets.vuejs.press/bg/2-light.svg
|
||||
bgImageDark: https://theme-hope-assets.vuejs.press/bg/2-dark.svg
|
||||
bgImageStyle:
|
||||
background-repeat: repeat
|
||||
background-size: initial
|
||||
features:
|
||||
- title: 基于NoneBot2
|
||||
icon: robot
|
||||
details: 拥有良好的生态支持
|
||||
link: https://nonebot.dev/
|
||||
|
||||
- title: 便捷管理
|
||||
icon: plug
|
||||
details: 自带包管理器,便捷管理插件/资源包
|
||||
|
||||
- title: 主题支持
|
||||
icon: paint-brush
|
||||
details: 使用资源包对外观进行完全自定义
|
||||
link: https://bot.liteyuki.icu/usage/resource_pack.html
|
||||
|
||||
- title: 国际化
|
||||
icon: globe
|
||||
details: 通过资源包支持多种语言
|
||||
link: https://baike.baidu.com/item/i18n/6771940
|
||||
|
||||
- title: 简易使用
|
||||
icon: cog
|
||||
details: 无需繁琐前期过程,开箱即用
|
||||
link: https://bot.liteyuki.icu/deployment/config.html
|
||||
|
||||
- title: 超高性能
|
||||
icon: tachometer-alt
|
||||
details: 500个插件,2s内启动
|
||||
|
||||
- title: 滚动更新
|
||||
icon: cloud-download
|
||||
details: 让你的机器人保持最新提交
|
||||
|
||||
- title: 开源项目
|
||||
icon: code
|
||||
details: 项目遵循MIT LICENCE开源,欢迎各位的贡献
|
||||
|
||||
- header: 快速部署
|
||||
image: /assets/image/box.svg
|
||||
bgImage: https://theme-hope-assets.vuejs.press/bg/3-light.svg
|
||||
bgImageDark: https://theme-hope-assets.vuejs.press/bg/3-dark.svg
|
||||
highlights:
|
||||
- title: 安装 Git 和 Python3.10+
|
||||
- title: 使用 <code>git clone https://github.com/snowykami/LiteyukiBot --depth=1</code> 以克隆项目至本地。
|
||||
details: 如果无法连接到GitHub,可以使用 <code>git clone https://gitee.com/snowykami/LiteyukiBot --depth=1</code>。
|
||||
- title: 使用 <code>cd LiteyukiBot</code> 切换到项目目录。
|
||||
- title: 使用 <code>pip install -r requirements.txt</code> 安装项目依赖。
|
||||
details: 如果你有多个 Python 环境,请使用 <code>pythonx -m pip install -r requirements.txt</code>。
|
||||
- title: 使用 <code>python main.py</code> 启动项目。
|
||||
copyright: © 2021-2024 SnowyKami All Rights Reserved
|
||||
---
|
@ -1,86 +0,0 @@
|
||||
---
|
||||
home: true
|
||||
icon: home
|
||||
title: 首页
|
||||
heroImage: https://cdn.liteyuki.icu/static/img/logo.png
|
||||
bgImage:
|
||||
bgImageDark:
|
||||
bgImageStyle:
|
||||
background-attachment: fixed
|
||||
heroText: LiteyukiBot
|
||||
tagline: 轻雪机器人,一个以轻量和简洁为设计理念基于Nonebot2的OneBot标准聊天机器人
|
||||
|
||||
actions:
|
||||
- text: 快速部署
|
||||
icon: lightbulb
|
||||
link: ./deployment/install.html
|
||||
type: primary
|
||||
|
||||
- text: 使用手册
|
||||
icon: book
|
||||
link: ./usage/basic_command.html
|
||||
|
||||
highlights:
|
||||
|
||||
- header: 简洁至上
|
||||
image: /assets/image/layout.svg
|
||||
bgImage: https://theme-hope-assets.vuejs.press/bg/2-light.svg
|
||||
bgImageDark: https://theme-hope-assets.vuejs.press/bg/2-dark.svg
|
||||
bgImageStyle:
|
||||
background-repeat: repeat
|
||||
background-size: initial
|
||||
features:
|
||||
- title: 基于Nonebot2
|
||||
icon: robot
|
||||
details: 拥有良好的生态支持
|
||||
link: https://nonebot.dev/
|
||||
|
||||
- title: 便捷插件管理
|
||||
icon: plug
|
||||
details: 使用<code>包管理器</code>,无需命令行操作即可安装/卸载插件
|
||||
|
||||
- title: 人性化交互
|
||||
icon: mouse-pointer
|
||||
details: 新的点击交互模式,拒绝手打指令
|
||||
|
||||
- title: 主题支持
|
||||
icon: paint-brush
|
||||
details: 使用资源包对外观进行完全自定义
|
||||
link: https://bot.liteyuki.icu/usage/resource_pack.html
|
||||
|
||||
- title: 国际化
|
||||
icon: globe
|
||||
details: 通过资源包支持多种语言
|
||||
link: https://baike.baidu.com/item/i18n/6771940
|
||||
|
||||
- title: 简易配置
|
||||
icon: cog
|
||||
details: 无需繁琐前期过程,开箱即用
|
||||
link: https://bot.liteyuki.icu/deployment/config.html
|
||||
|
||||
- title: 高性能
|
||||
icon: tachometer-alt
|
||||
details: 500个插件,3s内启动
|
||||
|
||||
- title: 滚动更新
|
||||
icon: cloud-download
|
||||
details: 让你的机器人保持最新提交
|
||||
|
||||
- title: 开源
|
||||
icon: code
|
||||
details: 项目遵循MIT协议开源,欢迎各位的贡献
|
||||
|
||||
- header: 快速部署
|
||||
image: /assets/image/box.svg
|
||||
bgImage: https://theme-hope-assets.vuejs.press/bg/3-light.svg
|
||||
bgImageDark: https://theme-hope-assets.vuejs.press/bg/3-dark.svg
|
||||
highlights:
|
||||
- title: 安装 Git 和 Python3.10+
|
||||
- title: 使用 <code>git clone https://github.com/snowykami/LiteyukiBot</code> 以克隆项目至本地。
|
||||
details: 如果无法连接到GitHub,可以使用 <code>git clone https://gitee.com/snowykami/LiteyukiBot</code>。
|
||||
- title: 使用 <code>cd LiteyukiBot</code> 切换到项目目录。
|
||||
- title: 使用 <code>pip install -r requirements.txt</code> 安装项目依赖。
|
||||
details: 如果你有多个 Python 环境,请使用 <code>pythonx -m pip install -r requirements.txt</code>。
|
||||
- title: 使用 <code>python main.py</code> 启动项目。
|
||||
copyright: © 2021-2024 SnowyKami All Rights Reserved
|
||||
---
|
@ -1,8 +0,0 @@
|
||||
---
|
||||
title: 项目部署
|
||||
index: false
|
||||
icon: laptop-code
|
||||
category: 部署
|
||||
---
|
||||
|
||||
<Catalog />
|
@ -1,62 +0,0 @@
|
||||
---
|
||||
title: 配置
|
||||
icon: cog
|
||||
order: 2
|
||||
category: 使用指南
|
||||
tag:
|
||||
- 配置
|
||||
- 部署
|
||||
---
|
||||
|
||||
首次运行后生成`config.yml`,你可以修改配置项后重启轻雪,绝大多数情况下,你只需要修改`superusers`和`nickname`字段即可
|
||||
|
||||
## **基础配置项**
|
||||
|
||||
```yaml
|
||||
command_start: [ "/", "" ] # 指令前缀,若没有""空命令头,请开启alconna_use_command_start保证alconna解析正常
|
||||
host: 127.0.0.1 # 监听地址,默认为本机,若要接收外部请求请填写0.0.0.0
|
||||
port: 20216 # 绑定端口
|
||||
nickname: [ "liteyuki" ] # 机器人昵称列表
|
||||
superusers: [ "1919810" ] # 超级用户列表
|
||||
```
|
||||
|
||||
## **其他配置**
|
||||
|
||||
以下为默认值,如需自定义请手动添加
|
||||
|
||||
```yaml
|
||||
onebot_access_token: "" # 访问令牌,对公开放时建议设置
|
||||
default_language: "zh-CN" # 默认语言
|
||||
alconna_auto_completion: false # alconna是否自动补全指令,默认false,建议开启
|
||||
# 开发者选项
|
||||
allow_update: true # 是否允许更新
|
||||
log_level: "INFO" # 日志等级
|
||||
log_icon: true # 是否显示日志等级图标(某些控制台字体不可用)
|
||||
auto_report: true # 是否自动上报问题给轻雪服务器
|
||||
auto_update: true # 是否自动更新轻雪,每天4点检查更新
|
||||
debug: false # 轻雪调试,开启会自动重载Bot或者资源,其他插件自带的调试功能也将开启
|
||||
safe_mode: false # 安全模式,开启后将不会加载任何第三方插件
|
||||
# 其他Nonebot插件的配置项
|
||||
custom_config_1: "custom_value1"
|
||||
custom_config_2: "custom_value2"
|
||||
...
|
||||
```
|
||||
|
||||
> [!tip]
|
||||
> 如果要使用dotenv配置文件,请自行创建`.env.{ENVIRONMENT}`,并在`config.yml`中添加`environment:{ENVIRONMENT}`字段
|
||||
|
||||
## **OneBot实现端配置**
|
||||
|
||||
生产环境中推荐反向WebSocket
|
||||
不同的实现端给出的字段可能不同,但是基本上都是一样的,这里给出一个参考值
|
||||
|
||||
| 字段 | 参考值 | 说明 |
|
||||
|-------------|------------------------------------|----------------------------------|
|
||||
| 协议 | 反向WebSocket | 推荐使用反向ws协议进行通信,即轻雪作为服务端 |
|
||||
| 地址 | ws://127.0.0.1:20216/onebot/v11/ws | 地址取决于配置文件,本机默认为`127.0.0.1:20216` |
|
||||
| AccessToken | `""` | 如果你给轻雪配置了`AccessToken`,请在此填写相同的值 |
|
||||
|
||||
## **其他**
|
||||
|
||||
- 要使用其他通信方式请访问[OneBot Adapter](https://onebot.adapters.nonebot.dev/)获取详细信息
|
||||
- 轻雪不局限于OneBot适配器,你可以使用NoneBot2支持的任何适配器
|
@ -1,58 +0,0 @@
|
||||
---
|
||||
title: 答疑
|
||||
icon: question
|
||||
order: 3
|
||||
category: 使用指南
|
||||
tag:
|
||||
- 配置
|
||||
- 部署
|
||||
---
|
||||
|
||||
## **常见问题**
|
||||
|
||||
- 设备上Python环境太乱了,pip和python不对应怎么办?
|
||||
- 请使用`/path/to/python -m pip install -r requirements.txt`来安装依赖,
|
||||
然后用`/path/to/python main.py`来启动Bot,
|
||||
其中`/path/to/python`是你要用来运行Bot的可执行文件
|
||||
|
||||
- 为什么我启动后机器人没有反应?
|
||||
- 请检查配置文件的`command_start`或`superusers`,确认你有权限使用命令并按照正确的命令发送
|
||||
- 确认命令头没有和`nickname{}`冲突,例如一个命令是`help`,但是`Bot`昵称有一个`help`,那么将会被解析为nickname而不是命令
|
||||
|
||||
- 更新轻雪失败,报错`InvalidGitRepositoryError`
|
||||
- 请正确安装`Git`,并使用克隆而非直接下载的方式部署轻雪
|
||||
|
||||
- 怎么登录聊天平台,例如QQ?
|
||||
- 你有这个问题说明你不是很了解这个项目,本项目不负责实现登录功能,只负责处理和回应消息,登录功能由实现端(协议端)提供,
|
||||
实现端本身不负责处理响应逻辑,将消息按照OneBot标准处理好上报给轻雪
|
||||
你需要使用Onebot标准的实现端来连接到轻雪并将消息上报给轻雪,下面已经列出一些推荐的实现端
|
||||
- `Playwright`安装失败
|
||||
- 输入`playwright install`安装浏览器
|
||||
- 有的插件安装后报错无法启动
|
||||
- 请先查阅插件文档,确认插件必要配置项完好后,仍然出现问题,请联系插件作者或在安全模式`safe_mode: true`下启动轻雪,在安全模式下你可以使用`npm uninstall`卸载问题插件
|
||||
- 其他问题
|
||||
-
|
||||
加入QQ群[775840726](http://qm.qq.com/cgi-bin/qm/qr?_wv=1027&k=SzmDYbfR6jY94o9KFNon7AwelRyI6M_u&authKey=ygeBdEmdFNyCWuNR4w0M1M8%2B5oDg7k%2FDfN0tzBkYcnbB%2FGHNnlVEnCIGbdftsnn7&noverify=0&group_code=775840726)
|
||||
|
||||
## **推荐方案(QQ)**
|
||||
|
||||
1. [Lagrange.OneBot](https://github.com/KonataDev/Lagrange.Core),基于NTQQ的OneBot实现,目前Markdown消息支持Lagrange
|
||||
2. [LLOneBot](https://github.com/LLOneBot/LLOneBot),NTQQ的OneBot插件,需要安装NTQQ
|
||||
3. [OpenShamrock](https://github.com/whitechi73/OpenShamrock),基于Lsposed的OneBot11实现
|
||||
4. [TRSS-Yunzai](https://github.com/TimeRainStarSky/Yunzai),基于`node.js`,可使用`ws-plugin`进行通信
|
||||
5. [go-cqhttp](https://github.com/Mrs4s/go-cqhttp),`go`语言实现的OneBot11实现端,目前可用性较低
|
||||
6. [Gensokyo](https://github.com/Hoshinonyaruko/Gensokyo),基于 OneBot QQ官方机器人Api Golang 原生实现,需要官方机器人权限
|
||||
7. 人工实现的`Onebot`协议,自己整一个WebSocket客户端,看着QQ的消息,然后给轻雪传输数据
|
||||
|
||||
## **推荐方案(Minecraft)**
|
||||
|
||||
1. [MinecraftOneBot](https://github.com/snowykami/MinecraftOnebot),我们专门为Minecraft开发的服务器Bot,支持OneBotV11标准
|
||||
|
||||
使用其他项目连接请先自行查阅文档,若有困难请联系对应开发者而不是Liteyuki的开发者
|
||||
|
||||
## **鸣谢**
|
||||
|
||||
- [Nonebot2](https://nonebot.dev)提供的框架支持
|
||||
- [nonebot-plugin-htmlrender](https://github.com/kexue-z/nonebot-plugin-htmlrender/tree/master)提供的渲染功能
|
||||
- [nonebot-plugin-alconna](https://github.com/ArcletProject/nonebot-plugin-alconna)提供的命令解析功能
|
||||
- [MiSans](https://hyperos.mi.com/font/zh/),[MapleMono](https://gitee.com/mirrors/Maple-Mono)提供的字体,且遵守了相关字体开源协议
|
@ -1,60 +0,0 @@
|
||||
---
|
||||
title: 安装
|
||||
icon: download
|
||||
order: 1
|
||||
category: 使用指南
|
||||
tag:
|
||||
- 安装
|
||||
---
|
||||
|
||||
## **开始安装**
|
||||
|
||||
### **常规部署**
|
||||
|
||||
1. 安装 [`Git`](https://git-scm.com/download/) 和 [`Python3.10+`](https://www.python.org/downloads/release/python-31010/) 环境
|
||||
|
||||
```bash
|
||||
# 克隆项目到本地,轻雪使用Git进行版本管理,该步骤为必要项
|
||||
git clone https://github.com/snowykami/LiteyukiBot --depth=1
|
||||
# 切换到Bot目录下
|
||||
cd LiteyukiBot
|
||||
# 安装依赖
|
||||
pip install -r requirements.txt
|
||||
# 启动Bot
|
||||
python main.py
|
||||
```
|
||||
|
||||
> [!tip]
|
||||
> 推荐使用虚拟环境来运行轻雪,以避免依赖冲突,你可以使用`python -m venv venv`来创建虚拟环境,然后使用`venv\Scripts\activate`来激活虚拟环境
|
||||
|
||||
### **使用Docker构建镜像部署**
|
||||
|
||||
1. 安装 [`Docker`](https://docs.docker.com/get-docker/)
|
||||
2. 克隆项目 `git clone https://github.com/snowykami/LiteyukiBot`
|
||||
3. 进入轻雪目录 `cd LiteyukiBot`
|
||||
4. 构建镜像 `docker build -t liteyukibot .`
|
||||
5. 启动容器 `docker run -p 20216:20216 -v $(pwd):/liteyukibot -v $(pwd)/.cache:/root/.cache liteyukibot`
|
||||
|
||||
> [!tip]
|
||||
> Windows请使用项目绝对目录`/path/to/LiteyukiBot`代替`$(pwd)` <br>
|
||||
> 若你修改了端口号请将`20216:20216`中的`20216`替换为你的端口号
|
||||
|
||||
### **使用TRSS Scripts部署**
|
||||
[TRSS_Liteyuki轻雪机器人管理脚本](https://timerainstarsky.github.io/TRSS_Liteyuki/),该功能由TRSS提供支持,不是LiteyukiBot官方提供的功能,推荐使用`Arch Linux`
|
||||
|
||||
|
||||
## **设备要求**
|
||||
|
||||
- Windows系统版本最低`Windows10+`/`Windows Server 2019+`
|
||||
- Linux系统要支持Python3.10+,推荐`Ubuntu 20.04+`(~~别用你那b CentOS~~)
|
||||
- CPU: 至少`1vCPU`
|
||||
- 内存: Bot无其他插件会占用`300~500MB`,包括`chromium` `node`等进程,其他插件占用视具体插件而定,建议`1GB`以上
|
||||
- 硬盘: 至少`1GB`空间
|
||||
|
||||
> [!warning]
|
||||
> 如果设备上有多个环境,请使用`path/to/python -m pip install -r requirements.txt`来安装依赖,`path/to/python`为你的Python可执行文件路径
|
||||
|
||||
> [!warning]
|
||||
> 轻雪的更新功能依赖Git,如果你没有安装Git直接下载源代码运行,你将无法使用更新功能
|
||||
|
||||
#### 其他问题请移步至[答疑](/deployment/fandq)
|
107
docs/foolsday.md
@ -1,107 +0,0 @@
|
||||
---
|
||||
home: true
|
||||
icon: home
|
||||
title: 首页
|
||||
heroImage: https://cdn.liteyuki.icu/static/img/lykwi.png
|
||||
bgImage:
|
||||
bgImageDark:
|
||||
bgImageStyle:
|
||||
background-attachment: fixed
|
||||
heroText: HeavylavaBot666 # LiteyukiBot 6
|
||||
tagline: 重浆机器人,一个以笨重和复杂为设计理念基于Koishi114514的TwoBotv1919810标准聊天机器人,可用于雪地清扫,使用Typethon编写
|
||||
#tagline: 轻雪机器人,一个以轻量和简洁为设计理念基于Nonebot2的OneBot标准聊天机器人
|
||||
# 泰普森(X
|
||||
|
||||
actions:
|
||||
- text: 快速结束 # 快速开始
|
||||
icon: lightbulb
|
||||
link: ./deployment/install.html
|
||||
type: primary
|
||||
|
||||
- text: 奇怪的册子 # 使用手册
|
||||
icon: book
|
||||
link: ./usage/basic_command.html
|
||||
|
||||
#1. 安装 `Git` 和 `Python3.10+` 环境
|
||||
#2. 克隆项目 `git clone https://github.com/snowykami/LiteyukiBot` (无法连接可以用`https://gitee.com/snowykami/LiteyukiBot`)
|
||||
#3. 切换目录`cd LiteyukiBot`
|
||||
#4. 安装依赖`pip install -r requirements.txt`(如果多个Python环境请指定后安装`pythonx -m pip install -r requirements.txt`)
|
||||
#5. 启动`python main.py`
|
||||
|
||||
highlights:
|
||||
|
||||
- header: 简洁至下 # 简洁至上
|
||||
image: /assets/image/layout.svg
|
||||
bgImage: https://theme-hope-assets.vuejs.press/bg/2-light.svg
|
||||
bgImageDark: https://theme-hope-assets.vuejs.press/bg/2-dark.svg
|
||||
bgImageStyle:
|
||||
background-repeat: repeat
|
||||
background-size: initial
|
||||
features:
|
||||
- title: 基于Koishi.js233
|
||||
icon: robot
|
||||
details: 拥有辣鸡的生态支持
|
||||
link: https://nonebot.dev/
|
||||
|
||||
- title: 盲目插件管理
|
||||
icon: plug
|
||||
details: 基于nbshi使用<code>xmpn和bib</code>,让你无法安装/卸载插件
|
||||
|
||||
- title: 纯命令行
|
||||
icon: mouse-pointer
|
||||
details: 老的掉牙的交互模式,必须手打指令
|
||||
|
||||
- title: 猪蹄支持
|
||||
icon: paint-brush
|
||||
details: 支持多种烤猪蹄样式,丢弃烤箱,拥抱烧烤架,满足你的干饭需求
|
||||
|
||||
- title: 去国际化
|
||||
icon: globe
|
||||
details: 支持多种语言,包括i18n部分语言和自行扩展的语言代码
|
||||
link: https://baike.baidu.com/item/i18n/6771940
|
||||
|
||||
- title: 超难配置
|
||||
icon: cog
|
||||
details: 无需过多配置,开箱即用
|
||||
link: https://bot.liteyuki.icu/deployment/config.html
|
||||
|
||||
- title: 高占用
|
||||
icon: memory
|
||||
details: 使用更多的意义不明的依赖和资源
|
||||
|
||||
- title: 一个Bot标准
|
||||
icon: link
|
||||
details: 支持OneBotv11/12标准的四种通信协议
|
||||
link: https://onebot.dev/
|
||||
|
||||
- title: Alconna
|
||||
icon: link
|
||||
details: 使用Alconna实现低效命令解析
|
||||
link: https://github.com/nonebot/plugin-alconna
|
||||
|
||||
- title: 不准更新
|
||||
icon: cloud-download
|
||||
details: 要更新自己写新版本去
|
||||
|
||||
- title: 服务支持
|
||||
icon: server
|
||||
details: 内置重浆API,但随时可能提桶跑路
|
||||
|
||||
- title: 闭源
|
||||
icon: code
|
||||
details: 要源代码自己逆向去
|
||||
|
||||
- header: 慢速部署 # 快速部署
|
||||
image: /assets/image/box.svg
|
||||
bgImage: https://theme-hope-assets.vuejs.press/bg/3-light.svg
|
||||
bgImageDark: https://theme-hope-assets.vuejs.press/bg/3-dark.svg
|
||||
highlights:
|
||||
- title: 安装 winget 和 nothing.js # git & node.js+
|
||||
- title: 使用 <code>git clone https://github.com/snowykami/LiteyukiBot</code> 以克隆项目至FTP。 # 本地
|
||||
details: 如果无法连接到PoonHub,可以使用 <code>git clone https://gitee.com/snowykami/LiteyukiBot</code>。
|
||||
- title: 使用 <code>cd LiteyukiBot</code> 切换到项目目录。
|
||||
- title: 使用 <code>npm install -r requirements.txt</code> 安装项目依赖。
|
||||
details: 如果你有多个 nothing.js 环境,请使用 <code>pythonx -m npm install -r requirements.txt</code>。
|
||||
- title: 使用 <code>node main.py</code> 启动项目。
|
||||
copyright: © 2021-2024 SnowyKami All Rights Reserved
|
||||
---
|
@ -1,27 +0,0 @@
|
||||
{
|
||||
"name": "liteyuki",
|
||||
"version": "2.0.0",
|
||||
"description": "A project of vuepress-theme-hope",
|
||||
"license": "MIT",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"docs:build": "vuepress-vite build .",
|
||||
"docs:clean-dev": "vuepress-vite dev . --clean-cache",
|
||||
"docs:dev": "vuepress-vite dev .",
|
||||
"docs:update-package": "pnpm dlx vp-update"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@vuepress/bundler-vite": "2.0.0-rc.9",
|
||||
"@vuepress/plugin-search": "2.0.0-rc.24",
|
||||
"vue": "^3.4.21",
|
||||
"vuepress": "2.0.0-rc.9",
|
||||
"vuepress-plugin-search-pro": "2.0.0-rc.34",
|
||||
"vuepress-theme-hope": "2.0.0-rc.32"
|
||||
},
|
||||
"dependencies": {
|
||||
"clipboard": "^2.0.11",
|
||||
"element-plus": "^2.7.0",
|
||||
"element-ui": "^2.15.14",
|
||||
"vue-router": "^4.4.0"
|
||||
}
|
||||
}
|
3181
docs/pnpm-lock.yaml
@ -1,5 +0,0 @@
|
||||
---
|
||||
title: 资源商店
|
||||
icon: store
|
||||
index: false
|
||||
---
|
@ -1,8 +0,0 @@
|
||||
---
|
||||
title: 插件商店
|
||||
icon: plug
|
||||
order: 2
|
||||
category: 使用手册
|
||||
---
|
||||
|
||||
<pluginStoreComp />
|
@ -1,7 +0,0 @@
|
||||
---
|
||||
title: 资源商店
|
||||
icon: box
|
||||
order: 1
|
||||
category: 使用手册
|
||||
---
|
||||
<resourceStoreComp />
|
@ -1,15 +0,0 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"module": "NodeNext",
|
||||
"moduleResolution": "NodeNext",
|
||||
"target": "ES2022",
|
||||
"allowSyntheticDefaultImports": true
|
||||
},
|
||||
"include": [
|
||||
"./.vuepress/**/*.ts",
|
||||
"./.vuepress/**/*.vue"
|
||||
],
|
||||
"exclude": [
|
||||
"node_modules"
|
||||
]
|
||||
}
|
@ -1,8 +0,0 @@
|
||||
---
|
||||
title: 使用手册
|
||||
index: false
|
||||
icon: laptop-code
|
||||
category: 使用手册
|
||||
---
|
||||
|
||||
<Catalog />
|
@ -1,16 +0,0 @@
|
||||
---
|
||||
title: 用户协议
|
||||
icon: user-secret
|
||||
order: 3
|
||||
category: 使用手册
|
||||
---
|
||||
|
||||
1. 本项目遵循`MIT`协议,你可以自由使用,修改,分发,但是请保留原作者信息
|
||||
2. 你可以选择开启`auto_report`(默认开启),轻雪会收集以下内容
|
||||
- 运行环境的设备信息:CPU,内存,系统信息,Python信息
|
||||
- 插件信息(不含插件数据)
|
||||
- 部分异常信息,
|
||||
- 会话负载信息(不含隐私部分)
|
||||
以上内容仅用于项目的优化,不包含任何隐私信息,且通过安全的方式传输到轻雪的服务器,若你不希望提供这些信息,可以在配置文件中把`auto_report`设定为`false`
|
||||
3. 本项目不会收集用户的任何隐私信息,但请注意甄别第三方插件的安全性
|
||||
4. 使用此项目代表你已经同意以上协议
|
@ -1,124 +0,0 @@
|
||||
---
|
||||
title: 基础命令
|
||||
icon: comment
|
||||
order: 1
|
||||
category: 使用手册
|
||||
---
|
||||
|
||||
# 基础插件
|
||||
---
|
||||
|
||||
> [!tip]
|
||||
> **参数**:`<param>`为必填参数,`[option]`为可选参数。
|
||||
>
|
||||
> **命令别名**:配置了命令别名的命令可以使用别名代替原命令,例如`npm install ~`可以使用`插件 安装 ~`代替。
|
||||
|
||||
## **轻雪命令`liteyuki_command`**
|
||||
|
||||
|
||||
| 命令 | 说明 | 权限 | 举例 | 可用参数 |
|
||||
| :----------------------------------------: | :---------------------------------------------------------------------------------------------: | :----------------------------------------: | :---------------------------------------------------------: | :----------------------------------------------------------------------------------: |
|
||||
| `reload-liteyuki` | 重载轻雪 | 超级用户 | ❌ | ❌ |
|
||||
| `update-liteyuki` | 更新轻雪 | 超级用户 | ❌ | ❌ |
|
||||
| `liteecho` | 查看当前bot 版本 | 超级用户 | ❌ | ❌ |
|
||||
| `status` | 查看统计信息和状态 | 超级用户 | ❌ | ❌ |
|
||||
| `config set <key> value` | 添加配置项,若存在则会覆盖,输入值会被执行以转换为正确的类型,"10"和10是不一样的 | 超级用户 | `config set name 'liteyuki-bot'` | `<key>`: 若存在则覆盖, 若不存在则创建于`config.yml` ; `value`: yml格式的所有合法内容 |
|
||||
| `config get [key] ` | 查询配置项,不带key返回配置项列表,推荐私聊使用 | 超级用户 | `config get name` | `<key>`: 若存在则返回, 若不存在则返回空 |
|
||||
| `switch-image-mode ` | 在普通图片和Markdown大图之间切换,该功能需要commit:505468b及以后的Lagrange.OneBot,默认普通图片 | 超级用户 | `switch-image-mode` | ❌ |
|
||||
| `/api api_name [args] ` | 调用机器人API | 超级用户 | `/api get_group_member_list group_id=1234567` | `<args>`: 参数列表, 格式为onebot v11协议api, 可用%20代替空格 |
|
||||
| `/function function_name [args] [kwargs] ` | 调用机器人函数(`.lyfunction`语法) | 超级用户 | `/function send_group_msg group_id=1234567 message='hello'` | `<args>`和`<kwargs>`: 参数列表, api格式为onebot v11协议api |
|
||||
| group enable/disable [group_id] | 在群聊启用/停用机器人,group_id仅超级用户可用 | 超级用户,群聊仅群主、管理员、超级用户可用 | `group enable 1145141919810` | `<group_id>`: 群号 |
|
||||
| liteyuki-docs | 查看轻雪文档 | 所有人 | ❌ | ❌ |
|
||||
|
||||
|
||||
---
|
||||
### **命令别名**
|
||||
|
||||
|
||||
| 命令 | 别名 |
|
||||
| :---------------: | :----------------------------------: |
|
||||
| status | 状态 |
|
||||
| reload-liteyuki | 重启轻雪 |
|
||||
| update-liteyuki | 更新轻雪 |
|
||||
| reload-resources | 重载资源 |
|
||||
| config | 配置, `set` 设置 / `get` 查询 |
|
||||
| switch-image-mode | 切换图片模式 |
|
||||
| liteyuki-docs | 轻雪文档 |
|
||||
| group | 群聊, `enable` 启用 / `disable` 停用 |
|
||||
|
||||
|
||||
---
|
||||
|
||||
## **插件/包管理器 `liteyuki_pacman`**
|
||||
|
||||
- 插件管理
|
||||
|
||||
| 命令 | 说明 | 权限 |
|
||||
| :-----------------------------------------------------: | :----------------------------------------: | :----------------------------------------------: |
|
||||
| `npm update` | 更新插件商店索引 | 超级用户 |
|
||||
| `npm install <plugin_name>` | 安装插件 | 超级用户 |
|
||||
| `npm uninstall <plugin_name>` | 卸载插件 | 超级用户 |
|
||||
| `npm search <keywords...>` | 通过关键词搜索插件 | 超级用户 |
|
||||
| `npm enable-global/disable-global <plugin_name>` | 全局启用/停用插件 | 超级用户 |
|
||||
| `npm enable/disable <plugin_name> [--group <group_id>]` | 当前会话启用/停用插件 | 群聊仅群主、管理员、超级用户可用,私聊所有人可用 |
|
||||
| `npm list [page] [num]` | 列出所有插件 page为页数,num为每页显示数量 | 群聊仅群主、管理员、超级用户可用,私聊所有人可用 |
|
||||
| `help <plugin_name>` | 查看插件帮助 | 所有人 |
|
||||
|
||||
|
||||
- 资源包管理
|
||||
|
||||
| 命令 | 说明 | 权限 |
|
||||
| :----------------------: | :------------------------------------------: | :------: |
|
||||
| `rpm list [page] [num]` | 列出所有资源包 page为页数,num为每页显示数量 | 超级用户 |
|
||||
| `rpm load <pack_name>` | 加载资源包 | 超级用户 |
|
||||
| `rpm unload <pack_name>` | 卸载资源包 | 超级用户 |
|
||||
| `rpm change <pack_name>` | 修改优先级 | 超级用户 |
|
||||
| `rpm reload` | 重载所有资源包 | 超级用户 |
|
||||
|
||||
|
||||
### 命令别名
|
||||
|
||||
| 命令 | 别名 |
|
||||
| :--------------: | :------: |
|
||||
| `npm` | 插件管理 |
|
||||
| `update` | 更新 |
|
||||
| `install` | 安装 |
|
||||
| `uninstall` | 卸载 |
|
||||
| `search` | 搜索 |
|
||||
| `enable` | 启用 |
|
||||
| `disable` | 停用 |
|
||||
| `enable-global` | 全局启用 |
|
||||
| `disable-global` | 全局停用 |
|
||||
| `rpm` | 资源包 |
|
||||
| `load` | 加载 |
|
||||
| `unload` | 卸载 |
|
||||
| `change` | 更改 |
|
||||
| `reload` | 重载 |
|
||||
| `list` | 列表 |
|
||||
| `help` | 帮助 |
|
||||
|
||||
> [!warning]
|
||||
> 受限于NoneBot2钩子函数的依赖注入参数,插件停用只能阻断传入响应,对于主动推送的插件不生效,请阅读插件主页的说明。
|
||||
>
|
||||
|
||||
---
|
||||
|
||||
|
||||
## **用户管理`liteyuki_user`**
|
||||
|
||||
| 命令 | 说明 | 权限 |
|
||||
| :-------------------------: | :----------------------------: | :----: |
|
||||
| `profile` | 查看用户信息菜单 | 所有人 |
|
||||
| `profile set <key> [value]` | 设置用户信息或打开属性设置菜单 | 所有人 |
|
||||
| `profile get <key>` | 获取用户信息 | 所有人 |
|
||||
|
||||
|
||||
###命令别名
|
||||
|
||||
| 命令 | 别名 |
|
||||
| :-------: | :------: |
|
||||
| `profile` | 个人信息 |
|
||||
| `set` | 设置 |
|
||||
| `get` | 查询 |
|
||||
|
||||
|
@ -1,70 +0,0 @@
|
||||
---
|
||||
title: 功能命令
|
||||
icon: comment
|
||||
order: 2
|
||||
category: 使用手册
|
||||
---
|
||||
|
||||
## 功能插件命令
|
||||
|
||||
### **轻雪天气`liteyuki_weather`**
|
||||
|
||||
查询实时天气,支持绑定城市,支持中英文城市名,支持多个关键词查询。
|
||||
|
||||
配置项
|
||||
|
||||
```yaml
|
||||
weather_key: "" # 和风天气的天气key,会自动判断key版本
|
||||
```
|
||||
|
||||
命令
|
||||
|
||||
```shell
|
||||
weather <keywords...> # Keywords为城市名,支持中英文
|
||||
```
|
||||
查询目标地实时天气,例如:"天气 北京 海淀", "weather Tokyo Shinjuku"
|
||||
|
||||
```shell
|
||||
bind-city <keywords...> # Keywords为城市名,支持中英文
|
||||
```
|
||||
|
||||
绑定查询城市,个人全局生效
|
||||
|
||||
#### 命令别名
|
||||
|
||||
| 命令 | 别名 |
|
||||
| :-------: | :------- |
|
||||
| weather | 天气 |
|
||||
| bind-city | 绑定城市 |
|
||||
|
||||
---
|
||||
|
||||
### **统计信息`liteyuki_statistics`**
|
||||
|
||||
统计信息
|
||||
命令
|
||||
|
||||
```shell
|
||||
statistic message --duration <duration> --period <period> --group [current|group_id] --bot [current|bot_id]
|
||||
```
|
||||
|
||||
功能: 用于统计Bot接收到的消息, 统计周期为`period`, 统计时间范围为`duration`
|
||||
|
||||
| 参数 | 格式 |
|
||||
| :------: | :------------------------------------------------------------: |
|
||||
| duration | 使用通用日期简写: `1d`(天), `1h`(小时), `45m`(分钟), `14s`(秒) |
|
||||
| period | 使用通用日期简写: `1d`(天), `1h`(小时), `45m`(分钟), `14s`(秒) |
|
||||
| group | `current` (当前群聊) 或 `group_id` (QQ群号) |
|
||||
| bot | `current` (当前Bot) 或 `bot_id` |
|
||||
|
||||
#### 命令别名
|
||||
|
||||
| 命令 | 别名 |
|
||||
| :----------: | :---: |
|
||||
| `statistic` | stat |
|
||||
| `message` | m |
|
||||
| `--duration` | -d |
|
||||
| --period` | -p |
|
||||
| `--group` | -g |
|
||||
| `--bot` | -b |
|
||||
| `current` | c |
|
@ -1,45 +0,0 @@
|
||||
---
|
||||
title: 轻雪API
|
||||
icon: code
|
||||
order: 5
|
||||
category: 使用指南
|
||||
tag:
|
||||
- 配置
|
||||
- 部署
|
||||
---
|
||||
|
||||
## **轻雪API**
|
||||
|
||||
轻雪API是轻雪运行中部分服务的支持,由`go`语言编写,例如错误反馈,图床链接等,目前服务由轻雪服务器提供,用户无需额外部署
|
||||
|
||||
接口
|
||||
|
||||
- `url` `https://api.liteyuki.icu`
|
||||
|
||||
- `POST` `/register` 注册一个Bot
|
||||
- 参数
|
||||
- `name` `string` Bot名称
|
||||
- `version` `string` Bot版本
|
||||
- `version_id` `int` Bot版本ID
|
||||
- `python` `string` Python版本
|
||||
- `os` `string` 操作系统
|
||||
- 返回
|
||||
- `code` `int` 状态码
|
||||
- `liteyuki_id` `string` 轻雪ID
|
||||
|
||||
- `POST` `/bug_report` 上报错误
|
||||
- 参数
|
||||
- `liteyuki_id` `string` 轻雪ID
|
||||
- `content` `string` 错误信息
|
||||
- `device_info` `string` 设备信息
|
||||
- 返回
|
||||
- `code` `int` 状态码
|
||||
- `report_id` `string` 错误ID
|
||||
|
||||
- `POST` `/upload_image` 图床上传
|
||||
- 参数
|
||||
- `image` `file` 图片文件
|
||||
- `liteyuki_id` `string` 轻雪ID,用于鉴权
|
||||
- 返回
|
||||
- `code` `int` 状态码
|
||||
- `url` `string` 图床链接
|
@ -1,72 +0,0 @@
|
||||
---
|
||||
title: 轻雪函数
|
||||
icon: code
|
||||
order: 4
|
||||
category: 使用指南
|
||||
tag:
|
||||
- 配置
|
||||
---
|
||||
|
||||
## **轻雪函数**
|
||||
|
||||
轻雪函数 Liteyuki Function 是轻雪的一个功能,它允许你在轻雪中运行一些自定义的由数据驱动的命令,类似于Minecraft的mcfunction.
|
||||
|
||||
### **函数文件**
|
||||
|
||||
函数文件放在资源包的`functions`目录下,文件名以`.mcfunction` `.lyfunction` `.lyf`结尾,例如`test.mcfunction`,文件内容为一系列的命令,每行一个命令,支持单行注释`#`(编辑时的语法高亮可采取`shell`格式),例如:
|
||||
|
||||
```shell
|
||||
# 在发信器输出"hello world"
|
||||
cmd echo hello world
|
||||
|
||||
# 如果你想同时输出多行内容可以尝试换行符(Python格式)
|
||||
cmd echo hello world\nLiteyuki bot
|
||||
```
|
||||
|
||||
也支持句末注释,例如:
|
||||
```shell
|
||||
cmd echo hello world # 输出"hello world"
|
||||
```
|
||||
|
||||
### **命令文档**
|
||||
|
||||
```shell
|
||||
var <var1=value1> [var2=value2] ... # 定义变量
|
||||
cmd <command> # 在设备上执行命令
|
||||
api <api_name> [var=value...] # 调用Bot API
|
||||
function <func_name> # 调用函数,可递归
|
||||
sleep <time> # 异步等待,单位s
|
||||
nohup <command> # 使用新的task执行命令,即不等待
|
||||
end # 结束函数关键字,包括子task
|
||||
await # 等待所有异步任务结束,若函数中启动了其他task,需要在最后调用,否则task对象会被销毁
|
||||
```
|
||||
|
||||
|
||||
#### **示例**
|
||||
|
||||
```shell
|
||||
# 疯狂戳好友
|
||||
# 使用 /function poke user_id=123456 执行
|
||||
# 每隔0.2s戳两次,无限戳,会触发最大递归深度限制
|
||||
# 若要戳20s后停止,则需要删除await,添加sleep 20和end
|
||||
api friend_poke user_id=user_id
|
||||
api friend_poke user_id=user_id
|
||||
sleep 0.2
|
||||
nohup function poke
|
||||
await
|
||||
```
|
||||
|
||||
### **API**
|
||||
|
||||
理论上所有基于onebotv11的api都可调用,不同Adapter api也有差别.
|
||||
|
||||
[Onebot v11 API文档](https://283375.github.io/onebot_v11_vitepress/api/index.html)
|
||||
|
||||
### **结束关键字**
|
||||
|
||||
由于LiteyukiBot基于异步运行, 所以在编写lyfunction时也要注意异步的调用,避免出现"单线程走到底"的情况是效率提升的关键.
|
||||
|
||||
`await` 异步任务结束关键字,用于结束当前已完成function的执行
|
||||
|
||||
> [!warning]
|
||||
> 但若出现非单function的情况,有一个task任务没有完成而await被执行了,那么当前所有函数包的task都会被截停销毁
|
@ -1,42 +0,0 @@
|
||||
---
|
||||
title: 资源包
|
||||
icon: paint-brush
|
||||
order: 3
|
||||
category: 使用手册
|
||||
---
|
||||
|
||||
## 简介
|
||||
资源包,亦可根据用途称为主题包、字体包、语言包等,它允许你一定程度上自定义轻雪的外观,并且不用修改源代码
|
||||
- [资源/主题商店](/store/)提供了一些资源包供你选择,你也可以自己制作资源包
|
||||
- 资源包的制作很简单,如果你接触过`Minecraft`的资源包,那么你能够很快就上手,仅需按照原有路径进行文件替换即可,讲起打包成一个新的资源包。
|
||||
- 部分内容制作需要一点点前端基础,例如`html`,`css`
|
||||
- 轻雪原版资源包请查看`LiteyukiBot/liteyuki/resources`,可以在此基础上进行修改
|
||||
- 欢迎各位投稿资源包到轻雪资源商店
|
||||
|
||||
请注意,主题包中的html渲染使用Js来规定数据的渲染位置,请确保您所编写的html代码能被Bot解析,否则会导致渲染失败或渲染结果不理想/异常/错位等无法预料的事情发生。推荐在编写html时同时更改对应Js代码,以避免出现无法预料的问题。
|
||||
|
||||
---
|
||||
## 加载资源包
|
||||
- 资源包通常是以`.zip`格式压缩的,只需要将其解压到根目录`resources`目录下即可,注意不要嵌套文件夹,正常的路径应该是这样的
|
||||
```shell
|
||||
main.py
|
||||
resources
|
||||
└─resource_pack_1
|
||||
├─metadata.yml
|
||||
├─templates
|
||||
└───...
|
||||
└─resource_pack_2
|
||||
├─metadata.yml
|
||||
└─...
|
||||
```
|
||||
- 你自己制作的资源包也应该遵循这个规则,并且应该在`metadata.yml`中填写一些信息
|
||||
- 若没有`metadata.yml`文件,则该文件夹不会被识别为资源包
|
||||
```yaml
|
||||
name: "资源包名称"
|
||||
version: "1.0.0"
|
||||
description: "资源包描述"
|
||||
# 你可以自定义一些信息,但请保证以上三个字段
|
||||
...
|
||||
```
|
||||
- 资源包加载遵循一个优先级,即后加载的资源包会覆盖前面的资源包,例如,你在A包中定义了一个`index.html`文件,B包也定义了一个`index.html`文件,那么加载B包后,A包中的`index.html`文件会被覆盖
|
||||
- 对于不同资源包的不同文件,是可以相对引用的,例如你在A中定义了`templates/index.html`,在B中定义了`templates/style.css`,可以在A的`index.html`中用`./style.css`相对路径引用B中的css
|
@ -3,10 +3,19 @@ from liteyuki.bot import (
|
||||
get_bot
|
||||
)
|
||||
|
||||
# def get_bot_instance() -> LiteyukiBot | None:
|
||||
# """
|
||||
# 获取轻雪实例
|
||||
# Returns:
|
||||
# LiteyukiBot: 当前的轻雪实例
|
||||
# """
|
||||
# return _BOT_INSTANCE
|
||||
from liteyuki.comm import (
|
||||
Channel,
|
||||
chan,
|
||||
Event
|
||||
)
|
||||
|
||||
from liteyuki.plugin import (
|
||||
load_plugin,
|
||||
load_plugins
|
||||
)
|
||||
|
||||
from liteyuki.log import (
|
||||
logger,
|
||||
init_log
|
||||
|
||||
)
|
||||
|
@ -1,147 +1,137 @@
|
||||
import asyncio
|
||||
import multiprocessing
|
||||
from typing import Any, Coroutine, Optional
|
||||
import os
|
||||
import platform
|
||||
import sys
|
||||
import threading
|
||||
import time
|
||||
from typing import Any, Optional
|
||||
|
||||
import nonebot
|
||||
from watchdog.events import FileSystemEventHandler
|
||||
from watchdog.observers import Observer
|
||||
|
||||
from liteyuki.plugin.load import load_plugin, load_plugins
|
||||
from src.utils import (
|
||||
adapter_manager,
|
||||
driver_manager,
|
||||
)
|
||||
from src.utils.base.log import logger
|
||||
from liteyuki.bot.lifespan import (
|
||||
Lifespan,
|
||||
LIFESPAN_FUNC,
|
||||
)
|
||||
from liteyuki.core.spawn_process import nb_run, ProcessingManager
|
||||
from liteyuki.bot.lifespan import LIFESPAN_FUNC, Lifespan
|
||||
from liteyuki.core import IS_MAIN_PROCESS
|
||||
from liteyuki.core.manager import ProcessManager
|
||||
from liteyuki.core.spawn_process import mb_run, nb_run
|
||||
from liteyuki.log import init_log, logger
|
||||
from liteyuki.plugin import load_plugins
|
||||
|
||||
__all__ = [
|
||||
"LiteyukiBot",
|
||||
"get_bot"
|
||||
]
|
||||
|
||||
_MAIN_PROCESS = multiprocessing.current_process().name == "MainProcess"
|
||||
__all__ = ["LiteyukiBot", "get_bot"]
|
||||
|
||||
|
||||
class LiteyukiBot:
|
||||
def __init__(self, *args, **kwargs):
|
||||
|
||||
global _BOT_INSTANCE
|
||||
_BOT_INSTANCE = self # 引用
|
||||
self.running = False
|
||||
self.config: dict[str, Any] = kwargs
|
||||
self.lifespan: Lifespan = Lifespan()
|
||||
self.init(**self.config) # 初始化
|
||||
|
||||
def run(self, *args, **kwargs):
|
||||
self.lifespan: Lifespan = Lifespan()
|
||||
|
||||
if _MAIN_PROCESS:
|
||||
load_plugins("liteyuki/plugins")
|
||||
asyncio.run(self.lifespan.before_start())
|
||||
self._run_nb_in_spawn_process(*args, **kwargs)
|
||||
else:
|
||||
# 子进程启动
|
||||
self.process_manager: ProcessManager = ProcessManager(bot=self)
|
||||
self.loop = asyncio.new_event_loop()
|
||||
asyncio.set_event_loop(self.loop)
|
||||
self.loop_thread = threading.Thread(target=self.loop.run_forever, daemon=True)
|
||||
self.call_restart_count = 0
|
||||
|
||||
driver_manager.init(config=self.config)
|
||||
adapter_manager.init(self.config)
|
||||
adapter_manager.register()
|
||||
nonebot.load_plugin("src.liteyuki_main")
|
||||
def run(self):
|
||||
load_plugins("liteyuki/plugins") # 加载轻雪插件
|
||||
|
||||
def _run_nb_in_spawn_process(self, *args, **kwargs):
|
||||
"""
|
||||
在新的进程中运行nonebot.run方法
|
||||
Args:
|
||||
*args:
|
||||
**kwargs:
|
||||
self.loop_thread.start() # 启动事件循环
|
||||
asyncio.run(self.lifespan.before_start()) # 启动前钩子
|
||||
|
||||
Returns:
|
||||
"""
|
||||
self.process_manager.add_target("nonebot", nb_run, **self.config)
|
||||
self.process_manager.start("nonebot")
|
||||
|
||||
timeout_limit: int = 20
|
||||
should_exit = False
|
||||
self.process_manager.add_target("melobot", mb_run, **self.config)
|
||||
self.process_manager.start("melobot")
|
||||
|
||||
while not should_exit:
|
||||
ctx = multiprocessing.get_context("spawn")
|
||||
event = ctx.Event()
|
||||
ProcessingManager.event = event
|
||||
process = ctx.Process(
|
||||
target=nb_run,
|
||||
args=(event,) + args,
|
||||
kwargs=kwargs,
|
||||
asyncio.run(self.lifespan.after_start()) # 启动后钩子
|
||||
|
||||
self.start_watcher() # 启动文件监视器
|
||||
|
||||
def start_watcher(self):
|
||||
if self.config.get("debug", False):
|
||||
|
||||
src_directories = (
|
||||
"liteyuki",
|
||||
"src/liteyuki_main",
|
||||
"src/liteyuki_plugins",
|
||||
"src/nonebot_plugins",
|
||||
"src/utils",
|
||||
)
|
||||
process.start() # 启动进程
|
||||
src_excludes_extensions = ("pyc",)
|
||||
|
||||
asyncio.run(self.lifespan.after_start())
|
||||
logger.debug("轻雪重载 已启用,正在加载文件修改监测……")
|
||||
restart = self.restart_process
|
||||
|
||||
while not should_exit:
|
||||
if ProcessingManager.event.wait(1):
|
||||
logger.info("接收到重启活动信息")
|
||||
process.terminate()
|
||||
process.join(timeout_limit)
|
||||
if process.is_alive():
|
||||
logger.warning(
|
||||
f"进程 {process.pid} 在 {timeout_limit} 秒后依旧存在,强制清灭。"
|
||||
)
|
||||
process.kill()
|
||||
break
|
||||
elif process.is_alive():
|
||||
continue
|
||||
else:
|
||||
should_exit = True
|
||||
class CodeModifiedHandler(FileSystemEventHandler):
|
||||
"""
|
||||
Handler for code file changes
|
||||
"""
|
||||
|
||||
@staticmethod
|
||||
def _run_coroutine(*coro: Coroutine):
|
||||
def on_modified(self, event):
|
||||
if (
|
||||
event.src_path.endswith(src_excludes_extensions)
|
||||
or event.is_directory
|
||||
or "__pycache__" in event.src_path
|
||||
):
|
||||
return
|
||||
logger.info(f"文件 {event.src_path} 已修改,机器人自动重启……")
|
||||
restart()
|
||||
|
||||
code_modified_handler = CodeModifiedHandler()
|
||||
|
||||
observer = Observer()
|
||||
for directory in src_directories:
|
||||
observer.schedule(code_modified_handler, directory, recursive=True)
|
||||
observer.start()
|
||||
|
||||
def restart(self, delay: int = 0):
|
||||
"""
|
||||
运行协程
|
||||
Args:
|
||||
coro:
|
||||
|
||||
重启轻雪本体
|
||||
Returns:
|
||||
|
||||
"""
|
||||
# 检测是否有现有的事件循环
|
||||
new_loop = False
|
||||
try:
|
||||
loop = asyncio.get_event_loop()
|
||||
except RuntimeError:
|
||||
new_loop = True
|
||||
loop = asyncio.new_event_loop()
|
||||
asyncio.set_event_loop(loop)
|
||||
|
||||
if new_loop:
|
||||
for c in coro:
|
||||
loop.run_until_complete(c)
|
||||
loop.close()
|
||||
if self.call_restart_count < 1:
|
||||
executable = sys.executable
|
||||
args = sys.argv
|
||||
logger.info("正在重启 尹灵温...")
|
||||
time.sleep(delay)
|
||||
if platform.system() == "Windows":
|
||||
cmd = "start"
|
||||
elif platform.system() == "Linux":
|
||||
cmd = "nohup"
|
||||
elif platform.system() == "Darwin":
|
||||
cmd = "open"
|
||||
else:
|
||||
cmd = "nohup"
|
||||
self.process_manager.terminate_all()
|
||||
# 进程退出后重启
|
||||
threading.Thread(
|
||||
target=os.system, args=(f"{cmd} {executable} {' '.join(args)}",)
|
||||
).start()
|
||||
sys.exit(0)
|
||||
self.call_restart_count += 1
|
||||
|
||||
else:
|
||||
for c in coro:
|
||||
loop.create_task(c)
|
||||
|
||||
@property
|
||||
def status(self) -> int:
|
||||
"""
|
||||
获取轻雪状态
|
||||
Returns:
|
||||
int: 0:未启动 1:运行中
|
||||
"""
|
||||
return 1 if self.running else 0
|
||||
|
||||
def restart(self):
|
||||
def restart_process(self, name: Optional[str] = None):
|
||||
"""
|
||||
停止轻雪
|
||||
Args:
|
||||
name: 进程名称, 默认为None, 所有进程
|
||||
Returns:
|
||||
|
||||
"""
|
||||
logger.info("正在停止灵温活动…")
|
||||
logger.info("Stopping LiteyukiBot...")
|
||||
|
||||
logger.debug("正在启动 before_restart 的函数…")
|
||||
self._run_coroutine(self.lifespan.before_restart())
|
||||
logger.debug("正在启动 before_shutdown 的函数…")
|
||||
self._run_coroutine(self.lifespan.before_shutdown())
|
||||
self.loop.create_task(self.lifespan.before_shutdown()) # 重启前钩子
|
||||
self.loop.create_task(self.lifespan.before_shutdown()) # 停止前钩子
|
||||
|
||||
ProcessingManager.restart()
|
||||
self.running = False
|
||||
if name:
|
||||
self.process_manager.terminate(name)
|
||||
else:
|
||||
self.process_manager.terminate_all()
|
||||
|
||||
def init(self, *args, **kwargs):
|
||||
"""
|
||||
@ -151,20 +141,14 @@ class LiteyukiBot:
|
||||
"""
|
||||
self.init_config()
|
||||
self.init_logger()
|
||||
if not _MAIN_PROCESS:
|
||||
nonebot.init(**kwargs)
|
||||
asyncio.run(self.lifespan.after_nonebot_init())
|
||||
|
||||
def init_logger(self):
|
||||
from src.utils.base.log import init_log
|
||||
init_log()
|
||||
# 修改nonebot的日志配置
|
||||
init_log(config=self.config)
|
||||
|
||||
def init_config(self):
|
||||
pass
|
||||
|
||||
def register_adapters(self, *args):
|
||||
pass
|
||||
|
||||
def on_before_start(self, func: LIFESPAN_FUNC):
|
||||
"""
|
||||
注册启动前的函数
|
||||
@ -189,7 +173,7 @@ class LiteyukiBot:
|
||||
|
||||
def on_before_shutdown(self, func: LIFESPAN_FUNC):
|
||||
"""
|
||||
注册停止前的函数
|
||||
注册停止前的函数,为子进程停止时调用
|
||||
Args:
|
||||
func:
|
||||
|
||||
@ -211,7 +195,7 @@ class LiteyukiBot:
|
||||
|
||||
def on_before_restart(self, func: LIFESPAN_FUNC):
|
||||
"""
|
||||
注册重启前的函数
|
||||
注册重启前的函数,为子进程重启时调用
|
||||
Args:
|
||||
func:
|
||||
|
||||
@ -253,4 +237,8 @@ def get_bot() -> Optional[LiteyukiBot]:
|
||||
Returns:
|
||||
LiteyukiBot: 当前的轻雪实例
|
||||
"""
|
||||
return _BOT_INSTANCE
|
||||
if IS_MAIN_PROCESS:
|
||||
return _BOT_INSTANCE
|
||||
else:
|
||||
# 从多进程上下文中获取
|
||||
pass
|
||||
|
@ -1,9 +1,6 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
Copyright (C) 2020-2024 LiteyukiStudio. All rights reserved
|
||||
|
||||
版权所有 © 2020-2024 神羽SnowyKami & 金羿Eilles with LiteyukiStudio & TriM Org.
|
||||
保留所有权利
|
||||
Copyright (C) 2020-2024 LiteyukiStudio. All Rights Reserved
|
||||
|
||||
@Time : 2024/7/23 下午8:24
|
||||
@Author : snowykami
|
||||
@ -13,6 +10,7 @@ Copyright (C) 2020-2024 LiteyukiStudio. All rights reserved
|
||||
"""
|
||||
from typing import Any, Awaitable, Callable, TypeAlias
|
||||
|
||||
from liteyuki.log import logger
|
||||
from liteyuki.utils import is_coroutine_callable
|
||||
|
||||
SYNC_LIFESPAN_FUNC: TypeAlias = Callable[[], Any]
|
||||
@ -138,6 +136,7 @@ class Lifespan:
|
||||
启动前
|
||||
Returns:
|
||||
"""
|
||||
logger.debug("正在运行 before_start 之函数")
|
||||
await self._run_funcs(self._before_start_funcs)
|
||||
|
||||
async def after_start(self) -> None:
|
||||
@ -145,6 +144,7 @@ class Lifespan:
|
||||
启动后
|
||||
Returns:
|
||||
"""
|
||||
logger.debug("正在运行 after_start 之函数")
|
||||
await self._run_funcs(self._after_start_funcs)
|
||||
|
||||
async def before_shutdown(self) -> None:
|
||||
@ -152,6 +152,7 @@ class Lifespan:
|
||||
停止前
|
||||
Returns:
|
||||
"""
|
||||
logger.debug("正在运行 before_shutdown 之函数")
|
||||
await self._run_funcs(self._before_shutdown_funcs)
|
||||
|
||||
async def after_shutdown(self) -> None:
|
||||
@ -159,6 +160,7 @@ class Lifespan:
|
||||
停止后
|
||||
Returns:
|
||||
"""
|
||||
logger.debug("正在运行 after_shutdown 之函数")
|
||||
await self._run_funcs(self._after_shutdown_funcs)
|
||||
|
||||
async def before_restart(self) -> None:
|
||||
@ -166,6 +168,7 @@ class Lifespan:
|
||||
重启前
|
||||
Returns:
|
||||
"""
|
||||
logger.debug("正在运行 before_restart 之函数")
|
||||
await self._run_funcs(self._before_restart_funcs)
|
||||
|
||||
async def after_restart(self) -> None:
|
||||
@ -174,6 +177,7 @@ class Lifespan:
|
||||
Returns:
|
||||
|
||||
"""
|
||||
logger.debug("正在运行 after_restart 之函数")
|
||||
await self._run_funcs(self._after_restart_funcs)
|
||||
|
||||
async def after_nonebot_init(self) -> None:
|
||||
@ -181,4 +185,5 @@ class Lifespan:
|
||||
NoneBot 初始化后
|
||||
Returns:
|
||||
"""
|
||||
logger.debug("正在运行 after_nonebot_init 之函数")
|
||||
await self._run_funcs(self._after_nonebot_init_funcs)
|
||||
|
30
liteyuki/comm/__init__.py
Normal file
@ -0,0 +1,30 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
Copyright (C) 2020-2024 LiteyukiStudio. All Rights Reserved
|
||||
|
||||
@Time : 2024/7/26 下午10:36
|
||||
@Author : snowykami
|
||||
@Email : snowykami@outlook.com
|
||||
@File : __init__.py
|
||||
@Software: PyCharm
|
||||
该模块用于轻雪主进程和Nonebot子进程之间的通信
|
||||
"""
|
||||
from liteyuki.comm.channel import (
|
||||
Channel,
|
||||
chan,
|
||||
get_channel,
|
||||
set_channel,
|
||||
set_channels,
|
||||
get_channels
|
||||
)
|
||||
from liteyuki.comm.event import Event
|
||||
|
||||
__all__ = [
|
||||
"Channel",
|
||||
"chan",
|
||||
"Event",
|
||||
"get_channel",
|
||||
"set_channel",
|
||||
"set_channels",
|
||||
"get_channels"
|
||||
]
|
219
liteyuki/comm/channel.py
Normal file
@ -0,0 +1,219 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
Copyright (C) 2020-2024 LiteyukiStudio. All Rights Reserved
|
||||
|
||||
@Time : 2024/7/26 下午11:21
|
||||
@Author : snowykami
|
||||
@Email : snowykami@outlook.com
|
||||
@File : channel.py
|
||||
@Software: PyCharm
|
||||
|
||||
本模块定义了一个通用的通道类,用于进程间通信
|
||||
"""
|
||||
import functools
|
||||
import multiprocessing
|
||||
import threading
|
||||
from multiprocessing import Pipe
|
||||
from typing import Any, Optional, Callable, Awaitable, List, TypeAlias
|
||||
from uuid import uuid4
|
||||
|
||||
from liteyuki.utils import is_coroutine_callable, run_coroutine
|
||||
|
||||
SYNC_ON_RECEIVE_FUNC: TypeAlias = Callable[[Any], Any]
|
||||
ASYNC_ON_RECEIVE_FUNC: TypeAlias = Callable[[Any], Awaitable[Any]]
|
||||
ON_RECEIVE_FUNC: TypeAlias = SYNC_ON_RECEIVE_FUNC | ASYNC_ON_RECEIVE_FUNC
|
||||
|
||||
SYNC_FILTER_FUNC: TypeAlias = Callable[[Any], bool]
|
||||
ASYNC_FILTER_FUNC: TypeAlias = Callable[[Any], Awaitable[bool]]
|
||||
FILTER_FUNC: TypeAlias = SYNC_FILTER_FUNC | ASYNC_FILTER_FUNC
|
||||
|
||||
IS_MAIN_PROCESS = multiprocessing.current_process().name == "MainProcess"
|
||||
|
||||
_channel: dict[str, "Channel"] = {}
|
||||
_callback_funcs: dict[str, ON_RECEIVE_FUNC] = {}
|
||||
|
||||
|
||||
class Channel:
|
||||
"""
|
||||
通道类,用于进程间通信,进程内不可用,仅限主进程和子进程之间通信
|
||||
有两种接收工作方式,但是只能选择一种,主动接收和被动接收,主动接收使用 `receive` 方法,被动接收使用 `on_receive` 装饰器
|
||||
"""
|
||||
|
||||
def __init__(self, _id: str):
|
||||
self.main_send_conn, self.sub_receive_conn = Pipe()
|
||||
self.sub_send_conn, self.main_receive_conn = Pipe()
|
||||
self._closed = False
|
||||
self._on_main_receive_funcs: list[str] = []
|
||||
self._on_sub_receive_funcs: list[str] = []
|
||||
self.name: str = _id
|
||||
|
||||
self.is_main_receive_loop_running = False
|
||||
self.is_sub_receive_loop_running = False
|
||||
|
||||
def __str__(self):
|
||||
return f"Channel({self.name})"
|
||||
|
||||
def send(self, data: Any):
|
||||
"""
|
||||
发送数据
|
||||
Args:
|
||||
data: 数据
|
||||
"""
|
||||
if self._closed:
|
||||
raise RuntimeError("无法发送至已关闭的通道中")
|
||||
if IS_MAIN_PROCESS:
|
||||
print("主进程发送数据:", data)
|
||||
self.main_send_conn.send(data)
|
||||
else:
|
||||
print("子进程发送数据:", data)
|
||||
self.sub_send_conn.send(data)
|
||||
|
||||
def receive(self) -> Any:
|
||||
"""
|
||||
接收数据
|
||||
Args:
|
||||
"""
|
||||
if self._closed:
|
||||
raise RuntimeError("无法从已关闭的通道中接收")
|
||||
|
||||
while True:
|
||||
# 判断receiver是否为None或者receiver是否等于接收者,是则接收数据,否则不动数据
|
||||
if IS_MAIN_PROCESS:
|
||||
data = self.main_receive_conn.recv()
|
||||
print("主进程接收数据:", data)
|
||||
else:
|
||||
data = self.sub_receive_conn.recv()
|
||||
print("子进程接收数据:", data)
|
||||
|
||||
return data
|
||||
|
||||
def close(self):
|
||||
"""
|
||||
关闭通道
|
||||
"""
|
||||
self._closed = True
|
||||
self.sub_receive_conn.close()
|
||||
self.main_send_conn.close()
|
||||
self.sub_send_conn.close()
|
||||
self.main_receive_conn.close()
|
||||
|
||||
def on_receive(self, filter_func: Optional[FILTER_FUNC] = None) -> Callable[[ON_RECEIVE_FUNC], ON_RECEIVE_FUNC]:
|
||||
"""
|
||||
接收数据并执行函数
|
||||
Args:
|
||||
filter_func: 过滤函数,为None则不过滤
|
||||
Returns:
|
||||
装饰器,装饰一个函数在接收到数据后执行
|
||||
"""
|
||||
if (not self.is_sub_receive_loop_running) and not IS_MAIN_PROCESS:
|
||||
threading.Thread(target=self._start_sub_receive_loop).start()
|
||||
|
||||
if (not self.is_main_receive_loop_running) and IS_MAIN_PROCESS:
|
||||
threading.Thread(target=self._start_main_receive_loop).start()
|
||||
|
||||
def decorator(func: ON_RECEIVE_FUNC) -> ON_RECEIVE_FUNC:
|
||||
async def wrapper(data: Any) -> Any:
|
||||
if filter_func is not None:
|
||||
if is_coroutine_callable(filter_func):
|
||||
if not await filter_func(data):
|
||||
return
|
||||
else:
|
||||
if not filter_func(data):
|
||||
return
|
||||
return await func(data)
|
||||
|
||||
function_id = str(uuid4())
|
||||
_callback_funcs[function_id] = wrapper
|
||||
if IS_MAIN_PROCESS:
|
||||
self._on_main_receive_funcs.append(function_id)
|
||||
else:
|
||||
self._on_sub_receive_funcs.append(function_id)
|
||||
return func
|
||||
|
||||
return decorator
|
||||
|
||||
def _run_on_main_receive_funcs(self, data: Any):
|
||||
"""
|
||||
运行接收函数
|
||||
Args:
|
||||
data: 数据
|
||||
"""
|
||||
for func_id in self._on_main_receive_funcs:
|
||||
func = _callback_funcs[func_id]
|
||||
run_coroutine(func(data))
|
||||
|
||||
def _run_on_sub_receive_funcs(self, data: Any):
|
||||
"""
|
||||
运行接收函数
|
||||
Args:
|
||||
data: 数据
|
||||
"""
|
||||
for func_id in self._on_sub_receive_funcs:
|
||||
func = _callback_funcs[func_id]
|
||||
run_coroutine(func(data))
|
||||
|
||||
def _start_main_receive_loop(self):
|
||||
"""
|
||||
开始接收数据
|
||||
"""
|
||||
self.is_main_receive_loop_running = True
|
||||
while not self._closed:
|
||||
data = self.main_receive_conn.recv()
|
||||
self._run_on_main_receive_funcs(data)
|
||||
|
||||
def _start_sub_receive_loop(self):
|
||||
"""
|
||||
开始接收数据
|
||||
"""
|
||||
self.is_sub_receive_loop_running = True
|
||||
while not self._closed:
|
||||
data = self.sub_receive_conn.recv()
|
||||
self._run_on_sub_receive_funcs(data)
|
||||
|
||||
def __iter__(self):
|
||||
return self
|
||||
|
||||
def __next__(self) -> Any:
|
||||
return self.receive()
|
||||
|
||||
|
||||
"""默认通道实例,可直接从模块导入使用"""
|
||||
chan = Channel("default")
|
||||
|
||||
|
||||
def set_channel(name: str, channel: Channel):
|
||||
"""
|
||||
设置通道实例
|
||||
Args:
|
||||
name: 通道名称
|
||||
channel: 通道实例
|
||||
"""
|
||||
_channel[name] = channel
|
||||
|
||||
|
||||
def set_channels(channels: dict[str, Channel]):
|
||||
"""
|
||||
设置通道实例
|
||||
Args:
|
||||
channels: 通道名称
|
||||
"""
|
||||
for name, channel in channels.items():
|
||||
_channel[name] = channel
|
||||
|
||||
|
||||
def get_channel(name: str) -> Optional[Channel]:
|
||||
"""
|
||||
获取通道实例
|
||||
Args:
|
||||
name: 通道名称
|
||||
Returns:
|
||||
"""
|
||||
return _channel.get(name, None)
|
||||
|
||||
|
||||
def get_channels() -> dict[str, Channel]:
|
||||
"""
|
||||
获取通道实例
|
||||
Returns:
|
||||
"""
|
||||
return _channel
|
21
liteyuki/comm/event.py
Normal file
@ -0,0 +1,21 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
Copyright (C) 2020-2024 LiteyukiStudio. All Rights Reserved
|
||||
|
||||
@Time : 2024/7/26 下午10:47
|
||||
@Author : snowykami
|
||||
@Email : snowykami@outlook.com
|
||||
@File : event.py
|
||||
@Software: PyCharm
|
||||
"""
|
||||
from typing import Any
|
||||
|
||||
|
||||
class Event:
|
||||
"""
|
||||
事件类
|
||||
"""
|
||||
|
||||
def __init__(self, name: str, data: dict[str, Any]):
|
||||
self.name = name
|
||||
self.data = data
|
@ -0,0 +1,49 @@
|
||||
import os
|
||||
from typing import List
|
||||
|
||||
import nonebot
|
||||
import yaml
|
||||
from pydantic import BaseModel
|
||||
|
||||
|
||||
config = {} # 主进程全局配置,确保加载后读取
|
||||
|
||||
|
||||
class SatoriNodeConfig(BaseModel):
|
||||
host: str = ""
|
||||
port: str = "5500"
|
||||
path: str = ""
|
||||
token: str = ""
|
||||
|
||||
|
||||
class SatoriConfig(BaseModel):
|
||||
comment: str = "此皆正处于开发之中,切勿在生产环境中启用。"
|
||||
enable: bool = False
|
||||
hosts: List[SatoriNodeConfig] = [SatoriNodeConfig()]
|
||||
|
||||
|
||||
class BasicConfig(BaseModel):
|
||||
host: str = "127.0.0.1"
|
||||
port: int = 20247
|
||||
superusers: list[str] = []
|
||||
command_start: list[str] = ["/", ""]
|
||||
nickname: list[str] = [f"灵温"]
|
||||
satori: SatoriConfig = SatoriConfig()
|
||||
data_path: str = "data/liteyuki"
|
||||
|
||||
|
||||
def load_from_yaml(file: str) -> dict:
|
||||
global config
|
||||
nonebot.logger.debug("Loading config from %s" % file)
|
||||
if not os.path.exists(file):
|
||||
nonebot.logger.warning(f"未找到配置文件 {file} ,已创建默认配置,请修改后重启。")
|
||||
with open(file, "w", encoding="utf-8") as f:
|
||||
yaml.dump(BasicConfig().dict(), f, default_flow_style=False)
|
||||
|
||||
with open(file, "r", encoding="utf-8") as f:
|
||||
conf = yaml.load(f, Loader=yaml.FullLoader)
|
||||
config = conf
|
||||
if conf is None:
|
||||
nonebot.logger.warning(f"配置文件 {file} 为空,已创建默认配置,请修改后重启。")
|
||||
conf = BasicConfig().dict()
|
||||
return conf
|
@ -1,3 +1,11 @@
|
||||
import multiprocessing
|
||||
|
||||
from .spawn_process import *
|
||||
from .manager import *
|
||||
|
||||
__all__ = [
|
||||
"IS_MAIN_PROCESS"
|
||||
]
|
||||
|
||||
IS_MAIN_PROCESS = multiprocessing.current_process().name == "MainProcess"
|
||||
|
||||
|
120
liteyuki/core/manager.py
Normal file
@ -0,0 +1,120 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
Copyright (C) 2020-2024 LiteyukiStudio. All Rights Reserved
|
||||
|
||||
@Time : 2024/7/27 上午11:12
|
||||
@Author : snowykami
|
||||
@Email : snowykami@outlook.com
|
||||
@File : manager.py
|
||||
@Software: PyCharm
|
||||
"""
|
||||
import asyncio
|
||||
import threading
|
||||
from multiprocessing import Process
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from liteyuki.comm import Channel, get_channel, set_channels
|
||||
from liteyuki.log import logger
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from liteyuki.bot import LiteyukiBot
|
||||
|
||||
TIMEOUT = 10
|
||||
|
||||
__all__ = ["ProcessManager"]
|
||||
|
||||
|
||||
class ProcessManager:
|
||||
"""
|
||||
在主进程中被调用
|
||||
"""
|
||||
|
||||
def __init__(self, bot: "LiteyukiBot"):
|
||||
self.bot = bot
|
||||
self.targets: dict[str, tuple[callable, tuple, dict]] = {}
|
||||
self.processes: dict[str, Process] = {}
|
||||
|
||||
set_channels(
|
||||
{
|
||||
"nonebot-active": Channel(_id="nonebot-active"),
|
||||
"melobot-active": Channel(_id="melobot-active"),
|
||||
"nonebot-passive": Channel(_id="nonebot-passive"),
|
||||
"melobot-passive": Channel(_id="melobot-passive"),
|
||||
}
|
||||
)
|
||||
|
||||
def start(self, name: str, delay: int = 0):
|
||||
"""
|
||||
开启后自动监控进程,并添加到进程字典中
|
||||
Args:
|
||||
name:
|
||||
delay:
|
||||
|
||||
Returns:
|
||||
|
||||
"""
|
||||
if name not in self.targets:
|
||||
raise KeyError(f"未有 Process {name} 之存在")
|
||||
|
||||
def _start():
|
||||
should_exit = False
|
||||
while not should_exit:
|
||||
chan_active = get_channel(f"{name}-active")
|
||||
chan_passive = get_channel(f"{name}-passive")
|
||||
process = Process(
|
||||
target=self.targets[name][0],
|
||||
args=(chan_active, chan_passive, *self.targets[name][1]),
|
||||
kwargs=self.targets[name][2],
|
||||
)
|
||||
self.processes[name] = process
|
||||
process.start()
|
||||
while not should_exit:
|
||||
# 0退出 1重启
|
||||
data = chan_active.receive()
|
||||
if data == 1:
|
||||
logger.info(f"重启 {name} 进程")
|
||||
asyncio.run(self.bot.lifespan.before_shutdown())
|
||||
asyncio.run(self.bot.lifespan.before_restart())
|
||||
self.terminate(name)
|
||||
break
|
||||
|
||||
elif data == 0:
|
||||
logger.info(f"关停 {name} 进程")
|
||||
asyncio.run(self.bot.lifespan.before_shutdown())
|
||||
should_exit = True
|
||||
self.terminate(name)
|
||||
else:
|
||||
logger.warning("数据未知,省略:{}".format(data))
|
||||
|
||||
if delay:
|
||||
threading.Timer(delay, _start).start()
|
||||
else:
|
||||
threading.Thread(target=_start).start()
|
||||
|
||||
def add_target(self, name: str, target, *args, **kwargs):
|
||||
self.targets[name] = (target, args, kwargs)
|
||||
|
||||
def join(self):
|
||||
for name, process in self.targets:
|
||||
process.join()
|
||||
|
||||
def terminate(self, name: str):
|
||||
"""
|
||||
终止进程并从进程字典中删除
|
||||
Args:
|
||||
name:
|
||||
|
||||
Returns:
|
||||
|
||||
"""
|
||||
if name not in self.targets:
|
||||
raise logger.warning(f"未有 Process {name} 之存在")
|
||||
process = self.processes[name]
|
||||
process.terminate()
|
||||
process.join(TIMEOUT)
|
||||
if process.is_alive():
|
||||
process.kill()
|
||||
|
||||
def terminate_all(self):
|
||||
for name in self.targets:
|
||||
self.terminate(name)
|
@ -1,14 +1,14 @@
|
||||
from . import (
|
||||
satori,
|
||||
onebot
|
||||
)
|
||||
|
||||
|
||||
def init(config: dict):
|
||||
onebot.init()
|
||||
satori.init(config)
|
||||
|
||||
|
||||
def register():
|
||||
onebot.register()
|
||||
satori.register()
|
||||
from . import (
|
||||
satori,
|
||||
onebot
|
||||
)
|
||||
|
||||
|
||||
def init(config: dict):
|
||||
onebot.init()
|
||||
satori.init(config)
|
||||
|
||||
|
||||
def register():
|
||||
onebot.register()
|
||||
satori.register()
|
@ -1,12 +1,12 @@
|
||||
import nonebot
|
||||
from nonebot.adapters.onebot import v11, v12
|
||||
|
||||
|
||||
def init():
|
||||
pass
|
||||
|
||||
|
||||
def register():
|
||||
driver = nonebot.get_driver()
|
||||
driver.register_adapter(v11.Adapter)
|
||||
driver.register_adapter(v12.Adapter)
|
||||
import nonebot
|
||||
from nonebot.adapters.onebot import v11, v12
|
||||
|
||||
|
||||
def init():
|
||||
pass
|
||||
|
||||
|
||||
def register():
|
||||
driver = nonebot.get_driver()
|
||||
driver.register_adapter(v11.Adapter)
|
||||
driver.register_adapter(v12.Adapter)
|
@ -1,26 +1,26 @@
|
||||
import json
|
||||
import os
|
||||
|
||||
import nonebot
|
||||
from nonebot.adapters import satori
|
||||
|
||||
|
||||
def init(config: dict):
|
||||
if config.get("satori", None) is None:
|
||||
nonebot.logger.info("未查见 Satori 的配置文档,将跳过 Satori 初始化")
|
||||
return None
|
||||
satori_config = config.get("satori")
|
||||
if not satori_config.get("enable", False):
|
||||
nonebot.logger.info("未启用 Satori ,将跳过 Satori 初始化")
|
||||
return None
|
||||
if os.getenv("SATORI_CLIENTS", None) is not None:
|
||||
nonebot.logger.info("Satori 客户端已设入环境变量,跳过此步。")
|
||||
os.environ["SATORI_CLIENTS"] = json.dumps(satori_config.get("hosts", []), ensure_ascii=False)
|
||||
config['satori_clients'] = satori_config.get("hosts", [])
|
||||
return
|
||||
|
||||
|
||||
def register():
|
||||
if os.getenv("SATORI_CLIENTS", None) is not None:
|
||||
driver = nonebot.get_driver()
|
||||
driver.register_adapter(satori.Adapter)
|
||||
import json
|
||||
import os
|
||||
|
||||
import nonebot
|
||||
from nonebot.adapters import satori
|
||||
|
||||
|
||||
def init(config: dict):
|
||||
if config.get("satori", None) is None:
|
||||
nonebot.logger.info("未查见 Satori 的配置文档,将跳过 Satori 初始化")
|
||||
return None
|
||||
satori_config = config.get("satori")
|
||||
if not satori_config.get("enable", False):
|
||||
nonebot.logger.info("未启用 Satori ,将跳过 Satori 初始化")
|
||||
return None
|
||||
if os.getenv("SATORI_CLIENTS", None) is not None:
|
||||
nonebot.logger.info("Satori 客户端已设入环境变量,跳过此步。")
|
||||
os.environ["SATORI_CLIENTS"] = json.dumps(satori_config.get("hosts", []), ensure_ascii=False)
|
||||
config['satori_clients'] = satori_config.get("hosts", [])
|
||||
return
|
||||
|
||||
|
||||
def register():
|
||||
if os.getenv("SATORI_CLIENTS", None) is not None:
|
||||
driver = nonebot.get_driver()
|
||||
driver.register_adapter(satori.Adapter)
|
@ -1,6 +1,6 @@
|
||||
from .auto_set_env import auto_set_env
|
||||
|
||||
|
||||
def init(config: dict):
|
||||
auto_set_env(config)
|
||||
return
|
||||
from .auto_set_env import auto_set_env
|
||||
|
||||
|
||||
def init(config: dict):
|
||||
auto_set_env(config)
|
||||
return
|
@ -1,20 +1,20 @@
|
||||
import os
|
||||
|
||||
import dotenv
|
||||
import nonebot
|
||||
|
||||
from .defines import *
|
||||
|
||||
|
||||
def auto_set_env(config: dict):
|
||||
dotenv.load_dotenv(".env")
|
||||
if os.getenv("DRIVER", None) is not None:
|
||||
nonebot.logger.info("Driver 已设入环境变量中,将跳过自动配置环节。")
|
||||
return
|
||||
if config.get("satori", {'enable': False}).get("enable", False):
|
||||
os.environ["DRIVER"] = get_driver_string(ASGI_DRIVER, HTTPX_DRIVER, WEBSOCKETS_DRIVER)
|
||||
nonebot.logger.info("已启用 Satori,将 driver 设为 ASGI+HTTPX+WEBSOCKETS")
|
||||
else:
|
||||
os.environ["DRIVER"] = get_driver_string(ASGI_DRIVER)
|
||||
nonebot.logger.info("已禁用 Satori,将 driver 设为 ASGI")
|
||||
return
|
||||
import os
|
||||
|
||||
import dotenv
|
||||
import nonebot
|
||||
|
||||
from .defines import *
|
||||
|
||||
|
||||
def auto_set_env(config: dict):
|
||||
dotenv.load_dotenv(".env")
|
||||
if os.getenv("DRIVER", None) is not None:
|
||||
nonebot.logger.info("Driver 已设入环境变量中,将跳过自动配置环节。")
|
||||
return
|
||||
if config.get("satori", {'enable': False}).get("enable", False):
|
||||
os.environ["DRIVER"] = get_driver_string(ASGI_DRIVER, HTTPX_DRIVER, WEBSOCKETS_DRIVER)
|
||||
nonebot.logger.info("已启用 Satori,将 driver 设为 ASGI+HTTPX+WEBSOCKETS")
|
||||
else:
|
||||
os.environ["DRIVER"] = get_driver_string(ASGI_DRIVER)
|
||||
nonebot.logger.info("已禁用 Satori,将 driver 设为 ASGI")
|
||||
return
|
@ -1,17 +1,17 @@
|
||||
ASGI_DRIVER = "~fastapi"
|
||||
HTTPX_DRIVER = "~httpx"
|
||||
WEBSOCKETS_DRIVER = "~websockets"
|
||||
|
||||
|
||||
def get_driver_string(*argv):
|
||||
output_string = ""
|
||||
if ASGI_DRIVER in argv:
|
||||
output_string += ASGI_DRIVER
|
||||
for arg in argv:
|
||||
if arg != ASGI_DRIVER:
|
||||
output_string = f"{output_string}+{arg}"
|
||||
return output_string
|
||||
|
||||
|
||||
def get_driver_full_string(*argv):
|
||||
return f"DRIVER={get_driver_string(argv)}"
|
||||
ASGI_DRIVER = "~fastapi"
|
||||
HTTPX_DRIVER = "~httpx"
|
||||
WEBSOCKETS_DRIVER = "~websockets"
|
||||
|
||||
|
||||
def get_driver_string(*argv):
|
||||
output_string = ""
|
||||
if ASGI_DRIVER in argv:
|
||||
output_string += ASGI_DRIVER
|
||||
for arg in argv:
|
||||
if arg != ASGI_DRIVER:
|
||||
output_string = f"{output_string}+{arg}"
|
||||
return output_string
|
||||
|
||||
|
||||
def get_driver_full_string(*argv):
|
||||
return f"DRIVER={get_driver_string(argv)}"
|
@ -1,37 +1,56 @@
|
||||
import threading
|
||||
from multiprocessing import get_context, Event
|
||||
from typing import Optional, TYPE_CHECKING
|
||||
|
||||
import nonebot
|
||||
from nonebot import logger
|
||||
|
||||
from liteyuki.plugin.load import load_plugins
|
||||
from liteyuki.core.nb import adapter_manager, driver_manager
|
||||
from liteyuki.comm.channel import set_channel
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from liteyuki.comm.channel import Channel
|
||||
|
||||
timeout_limit: int = 20
|
||||
__all__ = [
|
||||
"ProcessingManager",
|
||||
"nb_run",
|
||||
]
|
||||
|
||||
"""导出对象,用于主进程与nonebot通信"""
|
||||
_channels = {}
|
||||
|
||||
|
||||
class ProcessingManager:
|
||||
event: Event = None
|
||||
def nb_run(chan_active: "Channel", chan_passive: "Channel", *args, **kwargs):
|
||||
"""
|
||||
初始化NoneBot并运行在子进程
|
||||
Args:
|
||||
|
||||
@classmethod
|
||||
def restart(cls, delay: int = 0):
|
||||
"""
|
||||
发送终止信号
|
||||
Args:
|
||||
delay: 延迟时间,默认为0,单位秒
|
||||
Returns:
|
||||
"""
|
||||
if cls.event is None:
|
||||
raise RuntimeError("ProcessingManager 未初始化。")
|
||||
if delay > 0:
|
||||
threading.Timer(delay, function=cls.event.set).start()
|
||||
return
|
||||
cls.event.set()
|
||||
chan_active:
|
||||
chan_passive:
|
||||
**kwargs:
|
||||
|
||||
Returns:
|
||||
|
||||
"""
|
||||
set_channel("nonebot-active", chan_active)
|
||||
set_channel("nonebot-passive", chan_passive)
|
||||
nonebot.init(**kwargs)
|
||||
driver_manager.init(config=kwargs)
|
||||
adapter_manager.init(kwargs)
|
||||
adapter_manager.register()
|
||||
nonebot.load_plugin("src.liteyuki_main")
|
||||
nonebot.run()
|
||||
|
||||
|
||||
def nb_run(event, *args, **kwargs):
|
||||
ProcessingManager.event = event
|
||||
nonebot.run(*args, **kwargs)
|
||||
def mb_run(chan_active: "Channel", chan_passive: "Channel", *args, **kwargs):
|
||||
"""
|
||||
初始化MeloBot并运行在子进程
|
||||
Args:
|
||||
chan_active
|
||||
chan_passive
|
||||
*args:
|
||||
**kwargs:
|
||||
|
||||
Returns:
|
||||
|
||||
"""
|
||||
set_channel("melobot-active", chan_active)
|
||||
set_channel("melobot-passive", chan_passive)
|
||||
|
||||
# bot = MeloBot(__name__)
|
||||
# bot.init(AbstractConnector(cd_time=0))
|
||||
# bot.run()
|
||||
|
84
liteyuki/log.py
Normal file
@ -0,0 +1,84 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
Copyright (C) 2020-2024 LiteyukiStudio. All Rights Reserved
|
||||
|
||||
@Time : 2024/7/27 上午9:12
|
||||
@Author : snowykami
|
||||
@Email : snowykami@outlook.com
|
||||
@File : log.py
|
||||
@Software: PyCharm
|
||||
"""
|
||||
import sys
|
||||
import loguru
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
logger = loguru.logger
|
||||
if TYPE_CHECKING:
|
||||
# avoid sphinx autodoc resolve annotation failed
|
||||
# because loguru module do not have `Logger` class actually
|
||||
from loguru import Record
|
||||
|
||||
|
||||
def default_filter(record: "Record"):
|
||||
"""默认的日志过滤器,根据 `config.log_level` 配置改变日志等级。"""
|
||||
log_level = record["extra"].get("nonebot_log_level", "INFO")
|
||||
levelno = logger.level(log_level).no if isinstance(log_level, str) else log_level
|
||||
return record["level"].no >= levelno
|
||||
|
||||
|
||||
# DEBUG日志格式
|
||||
debug_format: str = (
|
||||
"<c>{time:YYYY-MM-DD HH:mm:ss}</c> "
|
||||
"<lvl>[{level.icon}]</lvl> "
|
||||
"<c><{name}.{module}.{function}:{line}></c> "
|
||||
"{message}"
|
||||
)
|
||||
|
||||
# 默认日志格式
|
||||
default_format: str = (
|
||||
"<c>{time:MM-DD HH:mm:ss}</c> "
|
||||
"<lvl>[{level.icon}]</lvl> "
|
||||
"<c><{name}></c> "
|
||||
"{message}"
|
||||
)
|
||||
|
||||
|
||||
def get_format(level: str) -> str:
|
||||
if level == "DEBUG":
|
||||
return debug_format
|
||||
else:
|
||||
return default_format
|
||||
|
||||
|
||||
logger = loguru.logger.bind()
|
||||
|
||||
|
||||
def init_log(config: dict):
|
||||
"""
|
||||
在语言加载完成后执行
|
||||
Returns:
|
||||
|
||||
"""
|
||||
global logger
|
||||
|
||||
logger.remove()
|
||||
logger.add(
|
||||
sys.stdout,
|
||||
level=0,
|
||||
diagnose=False,
|
||||
filter=default_filter,
|
||||
format=get_format(config.get("log_level", "INFO")),
|
||||
)
|
||||
show_icon = config.get("log_icon", True)
|
||||
|
||||
# debug = lang.get("log.debug", default="==DEBUG")
|
||||
# info = lang.get("log.info", default="===INFO")
|
||||
# success = lang.get("log.success", default="SUCCESS")
|
||||
# warning = lang.get("log.warning", default="WARNING")
|
||||
# error = lang.get("log.error", default="==ERROR")
|
||||
#
|
||||
# logger.level("DEBUG", color="<blue>", icon=f"{'🐛' if show_icon else ''}{debug}")
|
||||
# logger.level("INFO", color="<normal>", icon=f"{'ℹ️' if show_icon else ''}{info}")
|
||||
# logger.level("SUCCESS", color="<green>", icon=f"{'✅' if show_icon else ''}{success}")
|
||||
# logger.level("WARNING", color="<yellow>", icon=f"{'⚠️' if show_icon else ''}{warning}")
|
||||
# logger.level("ERROR", color="<red>", icon=f"{'⭕' if show_icon else ''}{error}")
|
@ -1,10 +1,11 @@
|
||||
from liteyuki.plugin.model import Plugin, PluginMetadata
|
||||
from liteyuki.plugin.load import load_plugin, _plugins
|
||||
from liteyuki.plugin.load import load_plugin, load_plugins, _plugins
|
||||
|
||||
__all__ = [
|
||||
"PluginMetadata",
|
||||
"Plugin",
|
||||
"load_plugin",
|
||||
"load_plugins",
|
||||
]
|
||||
|
||||
|
||||
|
@ -1,9 +1,6 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
Copyright (C) 2020-2024 LiteyukiStudio. All rights reserved
|
||||
|
||||
版权所有 © 2020-2024 神羽SnowyKami & 金羿Eilles with LiteyukiStudio & TriM Org.
|
||||
保留所有权利
|
||||
Copyright (C) 2020-2024 LiteyukiStudio. All Rights Reserved
|
||||
|
||||
@Time : 2024/7/23 下午11:59
|
||||
@Author : snowykami
|
||||
@ -25,6 +22,11 @@ from liteyuki.utils import path_to_module_name
|
||||
|
||||
_plugins: dict[str, Plugin] = {}
|
||||
|
||||
__all__ = [
|
||||
"load_plugin",
|
||||
"load_plugins",
|
||||
]
|
||||
|
||||
|
||||
def load_plugin(module_path: str | Path) -> Optional[Plugin]:
|
||||
"""加载单个插件,可以是本地插件或是通过 `pip` 安装的插件。
|
||||
|
@ -1,9 +1,6 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
Copyright (C) 2020-2024 LiteyukiStudio. All rights reserved
|
||||
|
||||
版权所有 © 2020-2024 神羽SnowyKami & 金羿Eilles with LiteyukiStudio & TriM Org.
|
||||
保留所有权利
|
||||
Copyright (C) 2020-2024 LiteyukiStudio. All Rights Reserved
|
||||
|
||||
@Time : 2024/7/24 上午12:02
|
||||
@Author : snowykami
|
||||
@ -19,10 +16,10 @@ from pydantic import BaseModel
|
||||
|
||||
class PluginMetadata(BaseModel):
|
||||
"""
|
||||
轻雪插件元数据,由插件编写者提供
|
||||
轻雪插件元数据,由插件编写者提供,name为必填项
|
||||
"""
|
||||
name: str
|
||||
description: str
|
||||
description: str = ""
|
||||
usage: str = ""
|
||||
type: str = ""
|
||||
homepage: str = ""
|
||||
|
55
liteyuki/plugins/lifespan_monitor.py
Normal file
@ -0,0 +1,55 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright (C) 2020-2024 LiteyukiStudio. All Rights Reserved
|
||||
#
|
||||
# @Time : 2024/7/22 上午11:25
|
||||
# @Author : snowykami
|
||||
# @Email : snowykami@outlook.com
|
||||
# @File : asa.py
|
||||
# @Software: PyCharm
|
||||
import asyncio
|
||||
|
||||
from liteyuki.plugin import PluginMetadata
|
||||
from liteyuki import get_bot, logger
|
||||
from liteyuki.comm.channel import get_channel
|
||||
|
||||
__plugin_meta__ = PluginMetadata(
|
||||
name="lifespan_monitor",
|
||||
)
|
||||
|
||||
bot = get_bot()
|
||||
nbp_chan = get_channel("nonebot-passive")
|
||||
mbp_chan = get_channel("melobot-passive")
|
||||
|
||||
|
||||
@bot.on_before_start
|
||||
def _():
|
||||
logger.info("生命周期监控器:启动前")
|
||||
|
||||
|
||||
@bot.on_before_shutdown
|
||||
def _():
|
||||
print(get_channel("main"))
|
||||
logger.info("生命周期监控器:停止前")
|
||||
|
||||
|
||||
@bot.on_before_restart
|
||||
def _():
|
||||
logger.info("生命周期监控器:重启前")
|
||||
|
||||
|
||||
@bot.on_after_start
|
||||
def _():
|
||||
logger.info("生命周期监控器:启动后")
|
||||
|
||||
|
||||
@bot.on_after_start
|
||||
async def _():
|
||||
logger.info("生命周期监控器:启动后")
|
||||
|
||||
|
||||
|
||||
# @mbp_chan.on_receive()
|
||||
# @nbp_chan.on_receive()
|
||||
# async def _(data):
|
||||
# print("主进程收到数据", data)
|
@ -1,41 +0,0 @@
|
||||
import multiprocessing
|
||||
import time
|
||||
|
||||
import nonebot
|
||||
from nonebot import get_driver
|
||||
|
||||
from liteyuki.plugin import PluginMetadata
|
||||
from liteyuki import get_bot
|
||||
|
||||
__plugin_metadata__ = PluginMetadata(
|
||||
name="plugin_loader",
|
||||
description="轻雪插件加载器",
|
||||
usage="",
|
||||
type="",
|
||||
homepage=""
|
||||
)
|
||||
|
||||
from src.utils import TempConfig, common_db
|
||||
|
||||
liteyuki = get_bot()
|
||||
|
||||
|
||||
@liteyuki.on_after_start
|
||||
def _():
|
||||
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) # 更新数据
|
||||
|
||||
|
||||
@liteyuki.on_before_start
|
||||
def _():
|
||||
print("灵温正在启动")
|
||||
|
||||
|
||||
@liteyuki.on_after_nonebot_init
|
||||
async def _():
|
||||
print("NoneBot初始化完成")
|
||||
nonebot.load_plugin("src.liteyuki_main")
|
@ -1,13 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
Copyright (C) 2020-2024 LiteyukiStudio. All rights reserved
|
||||
|
||||
版权所有 © 2020-2024 神羽SnowyKami & 金羿Eilles with LiteyukiStudio & TriM Org.
|
||||
保留所有权利
|
||||
|
||||
@Time : 2024/7/23 下午11:21
|
||||
@Author : snowykami
|
||||
@Email : snowykami@outlook.com
|
||||
@File : data_source.py
|
||||
@Software: PyCharm
|
||||
"""
|
57
liteyuki/plugins/register_service.py
Normal file
@ -0,0 +1,57 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
Copyright (C) 2020-2024 LiteyukiStudio. All Rights Reserved
|
||||
|
||||
@Time : 2024/8/10 下午11:25
|
||||
@Author : snowykami
|
||||
@Email : snowykami@outlook.com
|
||||
@File : register_service.py
|
||||
@Software: PyCharm
|
||||
"""
|
||||
import json
|
||||
import os.path
|
||||
import platform
|
||||
|
||||
import requests
|
||||
from git import Repo
|
||||
from liteyuki.plugin import PluginMetadata
|
||||
from liteyuki import get_bot, logger
|
||||
|
||||
__plugin_meta__ = PluginMetadata(
|
||||
name="注册服务",
|
||||
)
|
||||
|
||||
liteyuki = get_bot()
|
||||
commit_hash = Repo(".").head.commit.hexsha
|
||||
|
||||
|
||||
def register_bot():
|
||||
url = "https://api.liteyuki.icu/register"
|
||||
data = {
|
||||
"name" : "尹灵温|轻雪-睿乐",
|
||||
"version" : "即时更新",
|
||||
"hash" : commit_hash,
|
||||
"version_i": 99,
|
||||
"python" : f"{platform.python_implementation()} {platform.python_version()}",
|
||||
"os" : f"{platform.system()} {platform.version()} {platform.machine()}"
|
||||
}
|
||||
try:
|
||||
logger.info("正在等待 Liteyuki 注册服务器……")
|
||||
resp = requests.post(url, json=data, timeout=(10, 15))
|
||||
if resp.status_code == 200:
|
||||
data = resp.json()
|
||||
if liteyuki_id := data.get("liteyuki_id"):
|
||||
with open("data/liteyuki/liteyuki.json", "wb") as f:
|
||||
f.write(json.dumps(data).encode("utf-8"))
|
||||
logger.success("成功将 {} 注册到 Liteyuki 服务器".format(liteyuki_id))
|
||||
else:
|
||||
raise ValueError(f"无法向 Liteyuki 服务器注册:{data}")
|
||||
|
||||
except Exception as e:
|
||||
logger.warning(f"虽然向 Liteyuki 服务器注册失败,但无关紧要:{e}")
|
||||
|
||||
|
||||
@liteyuki.on_before_start
|
||||
async def _():
|
||||
if not os.path.exists("data/liteyuki/liteyuki.json"):
|
||||
register_bot()
|
10
liteyuki/plugins/reloader_monitor.py
Normal file
@ -0,0 +1,10 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
Copyright (C) 2020-2024 LiteyukiStudio. All Rights Reserved
|
||||
|
||||
@Time : 2024/8/10 下午5:18
|
||||
@Author : snowykami
|
||||
@Email : snowykami@outlook.com
|
||||
@File : reloader_monitor.py
|
||||
@Software: PyCharm
|
||||
"""
|
@ -2,9 +2,12 @@
|
||||
"""
|
||||
一些常用的工具类,部分来源于 nonebot 并遵循其许可进行修改
|
||||
"""
|
||||
import asyncio
|
||||
import inspect
|
||||
from pathlib import Path
|
||||
from typing import Any, Callable
|
||||
from typing import Any, Callable, Coroutine
|
||||
|
||||
from liteyuki.log import logger
|
||||
|
||||
|
||||
def is_coroutine_callable(call: Callable[..., Any]) -> bool:
|
||||
@ -23,6 +26,39 @@ def is_coroutine_callable(call: Callable[..., Any]) -> bool:
|
||||
return inspect.iscoroutinefunction(func_)
|
||||
|
||||
|
||||
def run_coroutine(*coro: Coroutine):
|
||||
"""
|
||||
运行协程
|
||||
Args:
|
||||
coro:
|
||||
|
||||
Returns:
|
||||
|
||||
"""
|
||||
|
||||
# 检测是否有现有的事件循环
|
||||
|
||||
try:
|
||||
loop = asyncio.get_event_loop()
|
||||
if loop.is_running():
|
||||
# 如果事件循环正在运行,创建任务
|
||||
for c in coro:
|
||||
asyncio.ensure_future(c)
|
||||
else:
|
||||
# 如果事件循环未运行,运行直到完成
|
||||
for c in coro:
|
||||
loop.run_until_complete(c)
|
||||
except RuntimeError:
|
||||
# 如果没有找到事件循环,创建一个新的
|
||||
loop = asyncio.new_event_loop()
|
||||
asyncio.set_event_loop(loop)
|
||||
loop.run_until_complete(asyncio.gather(*coro))
|
||||
loop.close()
|
||||
except Exception as e:
|
||||
# 捕获其他异常,防止协程被重复等待
|
||||
logger.error(f"协程异常:{e}")
|
||||
|
||||
|
||||
def path_to_module_name(path: Path) -> str:
|
||||
"""
|
||||
转换路径为模块名
|
||||
|
4
main.py
@ -1,6 +1,6 @@
|
||||
from liteyuki import LiteyukiBot
|
||||
from src.utils import load_from_yaml
|
||||
from liteyuki.config import load_from_yaml
|
||||
|
||||
if __name__ in ("__main__", "__mp_main__"):
|
||||
if __name__ == "__main__":
|
||||
bot = LiteyukiBot(**load_from_yaml("config.yml"))
|
||||
bot.run()
|
||||
|
@ -4,30 +4,27 @@ from typing import Any, AnyStr
|
||||
|
||||
import nonebot
|
||||
import pip
|
||||
from nonebot import Bot, get_driver, require
|
||||
from nonebot.adapters import satori
|
||||
from nonebot import Bot, get_driver, require # type: ignore
|
||||
from nonebot.adapters import onebot, 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 import event as event_utils, satori_utils
|
||||
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.liteyuki.core import Reloader
|
||||
from src.utils import event as event_utils, satori_utils
|
||||
from liteyuki.core import ProcessingManager
|
||||
from .api import update_liteyuki
|
||||
from liteyuki.bot import get_bot
|
||||
from ..utils.base import reload
|
||||
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,
|
||||
@ -111,16 +108,15 @@ async def _(matcher: Matcher, bot: T_Bot, event: T_MessageEvent):
|
||||
"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
|
||||
else event.chan_active.id
|
||||
),
|
||||
"delta_time": 0,
|
||||
}
|
||||
)
|
||||
|
||||
common_db.save(temp_data)
|
||||
# Reloader.reload(0)
|
||||
bot = get_bot()
|
||||
bot.restart()
|
||||
|
||||
reload()
|
||||
|
||||
|
||||
@on_alconna(
|
||||
@ -380,20 +376,18 @@ async def test_for_md_image(bot: T_Bot, api: str, data: dict):
|
||||
|
||||
@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) # 更新数据
|
||||
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) # 更新数据
|
||||
"""
|
||||
该部分迁移至轻雪生命周期
|
||||
该部分将迁移至轻雪生命周期
|
||||
Returns:
|
||||
|
||||
"""
|
||||
|
||||
pass
|
||||
|
||||
|
||||
@driver.on_shutdown
|
||||
async def on_shutdown():
|
||||
@ -415,10 +409,22 @@ async def _(bot: T_Bot):
|
||||
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) # 更新数据
|
||||
|
||||
return_msg = "轻雪核心 重载耗时 {:.2f} 秒\n灵温 预计体感重载耗时 {:.2f} 秒\n*此数据仅作参考,具体计时请以实际为准".format(
|
||||
delta_time, time.time() - temp_data.data.get("reload_time", 0)
|
||||
)
|
||||
|
||||
if isinstance(bot, satori.Bot):
|
||||
await bot.send_message(
|
||||
channel_id=reload_session_id,
|
||||
message="轻雪核心 重载耗时 {:.2f} 秒\n*此数据仅作参考,具体计时请以实际为准\n灵温 预计体感重载耗时 {:.2f} 秒".format(delta_time,time.time() - temp_data.data.get("reload_time", 0)),
|
||||
message=return_msg,
|
||||
)
|
||||
elif isinstance(bot, onebot.v11.Bot):
|
||||
await bot.send_msg(
|
||||
message_type=reload_session_type,
|
||||
user_id=reload_session_id,
|
||||
group_id=reload_session_id,
|
||||
message=return_msg,
|
||||
)
|
||||
else:
|
||||
await bot.call_api(
|
||||
@ -426,7 +432,7 @@ async def _(bot: T_Bot):
|
||||
message_type=reload_session_type,
|
||||
user_id=reload_session_id,
|
||||
group_id=reload_session_id,
|
||||
message="轻雪核心 重载耗时 {:.2f} 秒\n*此数据仅作参考,具体计时请以实际为准\n灵温 预计体感重载耗时 {:.2f} 秒".format(delta_time,time.time() - temp_data.data.get("reload_time", 0)),
|
||||
message=return_msg,
|
||||
)
|
||||
|
||||
|
||||
@ -439,7 +445,8 @@ async def every_day_update():
|
||||
if result:
|
||||
await broadcast_to_superusers(f"灵温已更新:```\n{logs}\n```")
|
||||
nonebot.logger.info(f"灵温已更新:{logs}")
|
||||
ProcessingManager.restart(3)
|
||||
# ProcessingManager.restart(3)
|
||||
reload()
|
||||
else:
|
||||
nonebot.logger.info(logs)
|
||||
|
||||
|
@ -3,58 +3,31 @@ from watchdog.observers import Observer
|
||||
from watchdog.events import FileSystemEventHandler
|
||||
|
||||
from liteyuki.bot import get_bot
|
||||
from src.utils.base import reload
|
||||
from src.utils.base.config import get_config
|
||||
from liteyuki.core import ProcessingManager
|
||||
from src.utils.base.resource import load_resources
|
||||
|
||||
if get_config("debug", False):
|
||||
|
||||
liteyuki_bot = get_bot()
|
||||
|
||||
src_directories = (
|
||||
"src/liteyuki_main",
|
||||
"src/plugins",
|
||||
"src/utils",
|
||||
)
|
||||
src_excludes_extensions = ("pyc",)
|
||||
|
||||
res_directories = (
|
||||
"src/resources",
|
||||
"resources",
|
||||
)
|
||||
|
||||
nonebot.logger.info("已启用 Liteyuki Reload ,正在监测文件变动。")
|
||||
|
||||
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} 变更,正在重载…")
|
||||
liteyuki_bot.restart()
|
||||
|
||||
class ResourceModifiedHandler(FileSystemEventHandler):
|
||||
"""
|
||||
Handler for resource file changes
|
||||
"""
|
||||
|
||||
def on_modified(self, event):
|
||||
nonebot.logger.info(f"资源 {event.src_path} 变更,重载资源包…")
|
||||
nonebot.logger.info(f"资源 {event.src_path} 变更,重载资源包……")
|
||||
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,3 +1,5 @@
|
||||
import asyncio
|
||||
|
||||
import nonebot.plugin
|
||||
from nonebot import get_driver
|
||||
from src.utils import init_log
|
||||
@ -6,7 +8,9 @@ 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
|
||||
|
||||
from liteyuki import get_bot
|
||||
from liteyuki import get_bot, chan
|
||||
|
||||
from nonebot_plugin_apscheduler import scheduler
|
||||
|
||||
load_resources()
|
||||
init_log()
|
||||
@ -17,7 +21,7 @@ liteyuki_bot = get_bot()
|
||||
|
||||
@driver.on_startup
|
||||
async def load_plugins():
|
||||
nonebot.plugin.load_plugins("src/plugins")
|
||||
nonebot.plugin.load_plugins("src/nonebot_plugins")
|
||||
# 从数据库读取已安装的插件
|
||||
if not get_config("safe_mode", False):
|
||||
# 安全模式下,不加载插件
|
||||
@ -34,34 +38,4 @@ async def load_plugins():
|
||||
nonebot.load_plugin(installed_plugin.module_name)
|
||||
nonebot.plugin.load_plugins("plugins")
|
||||
else:
|
||||
nonebot.logger.info("安全模式已启动,未加载任何插件。")
|
||||
|
||||
|
||||
@liteyuki_bot.on_before_start
|
||||
async def _():
|
||||
print("启动前")
|
||||
|
||||
|
||||
@liteyuki_bot.on_after_start
|
||||
async def _():
|
||||
print("启动后")
|
||||
|
||||
|
||||
@liteyuki_bot.on_before_shutdown
|
||||
async def _():
|
||||
print("停止前")
|
||||
|
||||
|
||||
@liteyuki_bot.on_after_shutdown
|
||||
async def _():
|
||||
print("停止后")
|
||||
|
||||
|
||||
@liteyuki_bot.on_before_restart
|
||||
async def _():
|
||||
print("重启前")
|
||||
|
||||
|
||||
@liteyuki_bot.on_after_restart
|
||||
async def _():
|
||||
print("重启后")
|
||||
nonebot.logger.info("当前处于安全模式,未加载任何插件。")
|
||||
|
3
src/liteyuki_plugins/README.md
Normal file
@ -0,0 +1,3 @@
|
||||
# 说明
|
||||
|
||||
此目录为**轻雪插件**目录,非其他插件目录。
|
@ -1,21 +1,27 @@
|
||||
import multiprocessing
|
||||
|
||||
from nonebot.plugin import PluginMetadata
|
||||
from liteyuki.plugin import get_loaded_plugins
|
||||
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,
|
||||
}
|
||||
)
|
||||
|
||||
print("已加载插件:", len(get_loaded_plugins()))
|
||||
import multiprocessing
|
||||
|
||||
from nonebot.plugin import PluginMetadata
|
||||
from liteyuki.comm import get_channel
|
||||
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,
|
||||
}
|
||||
)
|
||||
|
||||
# chan = get_channel("nonebot-passive")
|
||||
#
|
||||
#
|
||||
# @chan.on_receive()
|
||||
# async def _(d):
|
||||
# print("CRT子进程接收到数据:", d)
|
||||
# chan.send("CRT子进程已接收到数据")
|
@ -1,78 +1,78 @@
|
||||
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))
|
||||
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 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,
|
||||
}
|
||||
)
|
||||
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 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,
|
||||
}
|
||||
)
|
||||
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 .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,168 @@
|
||||
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
|
||||
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
|
||||
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 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("参数错误")
|
||||
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,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,294 @@
|
||||
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)
|
||||
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))),
|
||||
)
|
||||
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)
|