last commit
This commit is contained in:
@@ -0,0 +1,81 @@
|
|||||||
|
name: Go
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [ main ]
|
||||||
|
pull_request:
|
||||||
|
branches: [ main ]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
|
||||||
|
build:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v2
|
||||||
|
|
||||||
|
- name: Set up Go
|
||||||
|
uses: actions/setup-go@v2
|
||||||
|
with:
|
||||||
|
go-version: 1.20.2
|
||||||
|
|
||||||
|
- name: Git clone sillyplus
|
||||||
|
run: |
|
||||||
|
rm -rf *
|
||||||
|
git clone https://${{ secrets.CABLE_TOKEN }}@github.com/cdle/sillyplus.git
|
||||||
|
cp -r sillyplus/* ./
|
||||||
|
- name: Push to binary
|
||||||
|
run: |
|
||||||
|
n=$(date +%s%3N)
|
||||||
|
echo "package core" > core/compile_time.go
|
||||||
|
echo "var compiled_at = \"$n\"" >> core/compile_time.go
|
||||||
|
CGO_ENABLED=0 GOOS=windows GOARCH=amd64 go build -ldflags "-s -w" -o sillyGirl_windows_amd64.exe
|
||||||
|
CGO_ENABLED=0 GOOS=linux GOARCH=arm64 go build -ldflags "-s -w" -o sillyGirl_linux_arm64
|
||||||
|
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -ldflags "-s -w" -o sillyGirl_linux_amd64
|
||||||
|
sudo apt-get install upx-ucl
|
||||||
|
upx sillyGirl_linux_arm64
|
||||||
|
upx sillyGirl_linux_amd64
|
||||||
|
upx sillyGirl_windows_amd64.exe
|
||||||
|
git clone https://${{ secrets.CABLE_TOKEN }}@github.com/cdle/binary.git
|
||||||
|
git config --global user.email "you@example.com"
|
||||||
|
git config --global user.name "Your Name"
|
||||||
|
cd binary
|
||||||
|
git checkout --orphan latest_branch
|
||||||
|
rm -rf *
|
||||||
|
cp ../sillyGirl_windows_amd64.exe sillyGirl_windows_amd64_$n.exe
|
||||||
|
cp ../sillyGirl_linux_amd64 sillyGirl_linux_amd64_$n
|
||||||
|
cp ../sillyGirl_linux_arm64 sillyGirl_linux_arm64_$n
|
||||||
|
cp ../core/compile_time.go compile_time.go
|
||||||
|
git add -A
|
||||||
|
git commit -am "commit message"
|
||||||
|
git branch -D main
|
||||||
|
git branch -m main
|
||||||
|
git push -f origin main
|
||||||
|
wget -q -O - "${{ secrets.VERSION }}$n"
|
||||||
|
|
||||||
|
- name: Upload to release
|
||||||
|
uses: svenstaro/upload-release-action@v2
|
||||||
|
with:
|
||||||
|
repo_token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
file: sillyGirl_linux_amd64
|
||||||
|
asset_name: sillyGirl_linux_amd64
|
||||||
|
tag: ${{ github.ref }}
|
||||||
|
overwrite: true
|
||||||
|
|
||||||
|
- name: Upload to release
|
||||||
|
uses: svenstaro/upload-release-action@v2
|
||||||
|
with:
|
||||||
|
repo_token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
file: sillyGirl_windows_amd64.exe
|
||||||
|
asset_name: sillyGirl_windows_amd64.exe
|
||||||
|
tag: ${{ github.ref }}
|
||||||
|
overwrite: true
|
||||||
|
|
||||||
|
- name: Upload to release
|
||||||
|
uses: svenstaro/upload-release-action@v2
|
||||||
|
with:
|
||||||
|
repo_token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
file: sillyGirl_linux_arm64
|
||||||
|
asset_name: sillyGirl_linux_arm64
|
||||||
|
tag: ${{ github.ref }}
|
||||||
|
overwrite: true
|
||||||
|
|
||||||
@@ -0,0 +1,145 @@
|
|||||||
|
<h1 align="center">傻妞</h1>
|
||||||
|
<div align="center">
|
||||||
|
<font size=3> 支持多种开发语言的机器人框架,一个脚本即可实现跨平台的用户交互.</font>
|
||||||
|
|
||||||
|
<font size=4>一个不太有用的机器人,我愿称之为 **《废物》**.</font>
|
||||||
|
|
||||||
|
[开发文档](https://cdle.github.io/sillyGirl) | [Github](https://github.com/cdle/sillyGirl) | [TG 频道](https://t.me/kczz2021) | [赞助列表](./sponsors.md)
|
||||||
|
|
||||||
|
\_
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
> **傻妞 是一个开箱即用的 社交平台交互 框架。它基于 OOP 函数响应式编程,具有占用小、响应快、开发易等特点,允许开发者创建高度可测试、可扩展、松散耦合且易于维护的应用程序**
|
||||||
|
|
||||||
|
# 特性
|
||||||
|
|
||||||
|
- 多平台多账户接入系统 : 2 个 qq/3 个 wx/4 个 tg? so easy!;
|
||||||
|
- 用户可以前往插件市场,一键安装自己想要接入聊天工具,简单配置即可使用
|
||||||
|

|
||||||
|
- 开发者可以通过 Adapter 自由接入聊天工具,以下是接入`tailchat`的简单插件例子
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
/**
|
||||||
|
* @title Tailchat
|
||||||
|
* @create_at 2023-08-05 21:16:32
|
||||||
|
* @description 🐒配置详见 https://tailchat.msgbyte.com/zh-Hans/docs/advanced-usage/openapp/ws
|
||||||
|
* @author 猫咪
|
||||||
|
* @version v1.0.3
|
||||||
|
* @form {key: "tailchat.APPID", title: "APP ID", required: true }
|
||||||
|
* @form {key: "tailchat.APPSECRET", title: "APP SECRET", required: true }
|
||||||
|
* @form {key: "tailchat.HOST", title: "HOST", required: true, tooltip: "服务器http地址" }
|
||||||
|
* @service true
|
||||||
|
* @icon https://tailchat.msgbyte.com/zh-Hans/img/logo.svg
|
||||||
|
* @public true
|
||||||
|
* @class 机器人
|
||||||
|
*/
|
||||||
|
|
||||||
|
const {
|
||||||
|
Bucket,
|
||||||
|
utils: { parseCQText },
|
||||||
|
Adapter,
|
||||||
|
} = require("sillygirl");
|
||||||
|
const { URL } = require("url");
|
||||||
|
|
||||||
|
(async () => {
|
||||||
|
const platform = "tailchat";
|
||||||
|
const config = new Bucket(platform);
|
||||||
|
const { TailchatWsClient } = require("tailchat-client-sdk");
|
||||||
|
|
||||||
|
let HOST = await config.get("HOST");
|
||||||
|
let APPID = await config.get("APPID");
|
||||||
|
let APPSECRET = await config.get("APPSECRET");
|
||||||
|
|
||||||
|
const parsedUrl = new URL(HOST);
|
||||||
|
HOST = `${parsedUrl.protocol}//${parsedUrl.host}`;
|
||||||
|
|
||||||
|
if (!HOST || !APPID || !APPSECRET) {
|
||||||
|
console.error("require param: HOST, APPID, APPSECRET");
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
const client = new TailchatWsClient(HOST, APPID, APPSECRET);
|
||||||
|
|
||||||
|
client.connect().then(async () => {
|
||||||
|
const bot_info = await client.whoami();
|
||||||
|
const adapter = new Adapter({
|
||||||
|
platform, //平台简称
|
||||||
|
bot_id: bot_info.userId, //机器人ID
|
||||||
|
replyHandler: async ({ content, chat_id: converseId, groupId }) => {
|
||||||
|
client.sendMessage({ converseId, groupId: groupId ?? "", content }); //往社交平台发送消息
|
||||||
|
},
|
||||||
|
}); // 初始化适配器
|
||||||
|
client.onMessage(({ content, author: user_id, groupId, converseId }) => {
|
||||||
|
adapter.receive({
|
||||||
|
user_id,
|
||||||
|
chat_id: converseId,
|
||||||
|
content,
|
||||||
|
groupId,
|
||||||
|
}); //向框架发送消息
|
||||||
|
});
|
||||||
|
});
|
||||||
|
})();
|
||||||
|
```
|
||||||
|
|
||||||
|
- 基于 Golang OOP 函数响应式编程 :占用小,响应快,开发易 ;
|
||||||
|
- 极简的插件设计模式
|
||||||
|
- 系统高度封装,提供简便人性化的系统方法,随心所欲开发插件;
|
||||||
|
- 用户可以轻松安装已发布插件,免去部署环境等繁琐操作
|
||||||
|

|
||||||
|
- 支持多种开发语言 :
|
||||||
|
- 内置 Javascript 引擎,完整支持 ECMAScript 5.1,快速、便捷、门槛低;
|
||||||
|
- 支持 NodeJs,尽情使用其提供的 async/await/Promise 语法糖,可以自由控制异步同步;
|
||||||
|
- 支持 Python3,简洁明了的语法、强大的标准库以及社区优势;
|
||||||
|
- 即将支持 PHP,对对对,这是世界上最好的语言;
|
||||||
|
- 不仅仅是 Chat RoBot : 如果你愿意,可以在本框架上搭建网站、图片服务器、资源共享平台、并发请求等服务,尽情释放你的创造力.
|
||||||
|
|
||||||
|
# 未来
|
||||||
|
|
||||||
|
- [ ] ...
|
||||||
|
|
||||||
|
> 后台目前只有一个蹩脚的前端(本人),如果有前端工程师对此感兴趣,请联系我~
|
||||||
|
|
||||||
|
# 安装
|
||||||
|
|
||||||
|
参见 [安装文档](./md/init.md)
|
||||||
|
|
||||||
|
# 开发
|
||||||
|
|
||||||
|
[开发文档](https://cdle.github.io/sillyGirl)
|
||||||
|
|
||||||
|
> 如果是手机端浏览开发文档,请点击开发文档左下角的按钮手动打开侧边栏目录。
|
||||||
|
|
||||||
|
> 如果你是纯小白可以了解内置插件系统 [开发文档](./md/goja.md),快速进行插件开发
|
||||||
|
|
||||||
|
# 其他
|
||||||
|
|
||||||
|
[Github](https://github.com/cdle/sillyGirl)
|
||||||
|
[TG 频道](https://t.me/kczz2021)
|
||||||
|
[TG 群组](https://t.me/trialerr)
|
||||||
|
[赞助列表](./sponsors.md)
|
||||||
|
|
||||||
|
## 请喝茶 ?
|
||||||
|
|
||||||
|
[联系到我](https://t.me/cdle1994)
|
||||||
|
您的支持是我更新的动力,Thank~
|
||||||
|
|
||||||
|
## 免责声明
|
||||||
|
|
||||||
|
> 任何用户在使用由 Cdle(以下简称「本团队」)研发的机器人框架傻妞前,请您仔细阅读并透彻理解本声明。您可以选择不使用傻妞框架,若您一旦使用傻妞框架,您的使用行为即被视为对本声明全部内容的认可和接受。
|
||||||
|
|
||||||
|
1. 傻妞框架是一款免费的机器人框架,主要用于更高效地开发会话应答式程序。且傻妞框架并不具备「互联网接入、网络数据存储、通讯传输以及窃取用户隐私」中的任何一项与用户数据等信息相关的动态功能。
|
||||||
|
2. 傻妞框架仅是运行 Golang/Node/Python 等程序的服务端框架,其尊重并保护所有用户的个人隐私权,不窃取任何用户计算机中的信息。更不具备用户数据存储等网络传输功能。
|
||||||
|
3. 本框架的初衷是帮助广大同学认识 Golang,并更好利用 Golang 进行学术研究,用户在框架上做的所有改动、开发的任何插件均与本框架无关。
|
||||||
|
4. 本框架内使用的部分包括但不限于字体、npm 库等资源来源于互联网,如果出现侵权可联系本框架移除。
|
||||||
|
5. 由于使用本框架产生的包括由于本协议或由于使用或无法使用本框架而引起的任何性质的任何直接、间接、特殊、偶然或结果性损害(包括但不限于因商誉损失、停工、计算机故障或故障引起的损害赔偿,或任何及所有其他商业损害或损失)由使用者负责。
|
||||||
|
6. 您承诺秉着合法、合理的原则使用傻妞框架,不利用傻妞框架进行任何违法、侵害他人合法利益等恶意的行为,亦不将傻妞框架运用于任何违反我国法律法规的 Web 平台。
|
||||||
|
7. 任何单位或个人因下载使用傻妞框架而产生的任何意外、疏忽、合约毁坏、诽谤、版权或知识产权侵犯及其造成的损失 (包括但不限于直接、间接、附带或衍生的损失等),本团队不承担任何法律责任。
|
||||||
|
8. 用户明确并同意本声明条款列举的全部内容,对使用傻妞框架可能存在的风险和相关后果将完全由用户自行承担,本团队不承担任何法律责任。
|
||||||
|
9. 任何单位或个人在阅读本免责声明后,应在法律所允许的范围内进行合法的发布、传播和使用傻妞框架等行为,若违反本免责声明条款或违反法律法规所造成的法律责任(包括但不限于民事赔偿和刑事责任),由违约者自行承担。
|
||||||
|
10. 传播:任何公司或个人在网络上发布,传播我们框架的行为都是允许的,但因公司或个人传播框架可能造成的任何法律和刑事事件框架作者不负任何责任。
|
||||||
|
11. 本团队对傻妞框架拥有知识产权(包括但不限于商标权、专利权、著作权、商业秘密等),上述产品均受到相关法律法规的保护。
|
||||||
|
12. 任何单位或个人不得在未经本团队书面授权的情况下对傻妞框架本身申请相关的知识产权。
|
||||||
|
13. 如果本声明的任何部分被认为无效或不可执行,则该部分将被解释为反映本团队的初衷,其余部分仍具有完全效力。不可执行的部分声明,并不构成我们放弃执行该声明的权利。
|
||||||
|
14. 本团队有权随时对本声明条款及附件内容进行单方面的变更,并以消息推送、网页公告等方式予以公布,公布后立即自动生效,无需另行单独通知;若您在本声明内容公告变更后继续使用的,表示您已充分阅读、理解并接受修改后的声明内容。
|
||||||
|
15. 本仓库部分开已开源代码遵循 MIT 开源许可协议。
|
||||||
+148
@@ -0,0 +1,148 @@
|
|||||||
|
<h1 align="center">傻妞</h1>
|
||||||
|
<div align="center">
|
||||||
|
<font size=3> 支持多种开发语言的机器人框架,一个脚本即可实现跨平台的用户交互.</font>
|
||||||
|
|
||||||
|
<font size=4>一个不太有用的机器人,我愿称之为 **《废物》**.</font>
|
||||||
|
|
||||||
|
[开发文档](https://cdle.github.io/sillyGirl) | [Github](https://github.com/cdle/sillyGirl) | [TG 频道](https://t.me/kczz2021) | [赞助列表](./sponsors.md)
|
||||||
|
|
||||||
|
\_
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
> **傻妞 是一个开箱即用的 社交平台交互 框架。它基于 OOP 函数响应式编程,具有占用小、响应快、开发易等特点,允许开发者创建高度可测试、可扩展、松散耦合且易于维护的应用程序**
|
||||||
|
|
||||||
|
# 特性
|
||||||
|
|
||||||
|
- 多平台多账户接入系统 : 2 个 qq/3 个 wx/4 个 tg? so easy!;
|
||||||
|
- 用户可以前往插件市场,一键安装自己想要接入聊天工具,只需简单配置即可使用
|
||||||
|

|
||||||
|
- 开发者可以通过 Adapter 自由接入聊天工具,以下是接入`tailchat`的简单插件例子
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
/**
|
||||||
|
* @title Tailchat
|
||||||
|
* @create_at 2023-08-05 21:16:32
|
||||||
|
* @description 🐒配置详见 https://tailchat.msgbyte.com/zh-Hans/docs/advanced-usage/openapp/ws
|
||||||
|
* @author 猫咪
|
||||||
|
* @version v1.0.3
|
||||||
|
* @form {key: "tailchat.APPID", title: "APP ID", required: true }
|
||||||
|
* @form {key: "tailchat.APPSECRET", title: "APP SECRET", required: true }
|
||||||
|
* @form {key: "tailchat.HOST", title: "HOST", required: true, tooltip: "服务器http地址" }
|
||||||
|
* @service true
|
||||||
|
* @icon https://tailchat.msgbyte.com/zh-Hans/img/logo.svg
|
||||||
|
* @public true
|
||||||
|
* @class 机器人
|
||||||
|
*/
|
||||||
|
|
||||||
|
const {
|
||||||
|
Bucket,
|
||||||
|
utils: { parseCQText },
|
||||||
|
Adapter,
|
||||||
|
} = require("sillygirl");
|
||||||
|
const { URL } = require("url");
|
||||||
|
|
||||||
|
(async () => {
|
||||||
|
const platform = "tailchat";
|
||||||
|
const config = new Bucket(platform);
|
||||||
|
const { TailchatWsClient } = require("tailchat-client-sdk");
|
||||||
|
|
||||||
|
let HOST = await config.get("HOST");
|
||||||
|
let APPID = await config.get("APPID");
|
||||||
|
let APPSECRET = await config.get("APPSECRET");
|
||||||
|
|
||||||
|
const parsedUrl = new URL(HOST);
|
||||||
|
HOST = `${parsedUrl.protocol}//${parsedUrl.host}`;
|
||||||
|
|
||||||
|
if (!HOST || !APPID || !APPSECRET) {
|
||||||
|
console.error("require param: HOST, APPID, APPSECRET");
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
const client = new TailchatWsClient(HOST, APPID, APPSECRET);
|
||||||
|
|
||||||
|
client.connect().then(async () => {
|
||||||
|
const bot_info = await client.whoami();
|
||||||
|
const adapter = new Adapter({
|
||||||
|
platform, //平台简称
|
||||||
|
bot_id: bot_info.userId, //机器人ID
|
||||||
|
replyHandler: async ({ content, chat_id: converseId, groupId }) => {
|
||||||
|
client.sendMessage({ converseId, groupId: groupId ?? "", content }); //往社交平台发送消息
|
||||||
|
},
|
||||||
|
}); // 初始化适配器
|
||||||
|
client.onMessage(({ content, author: user_id, groupId, converseId }) => {
|
||||||
|
adapter.receive({
|
||||||
|
user_id,
|
||||||
|
chat_id: converseId,
|
||||||
|
content,
|
||||||
|
groupId,
|
||||||
|
}); //向框架发送消息
|
||||||
|
});
|
||||||
|
});
|
||||||
|
})();
|
||||||
|
```
|
||||||
|
|
||||||
|
- 基于 Golang OOP 函数响应式编程 :占用小,响应快,开发易 ;
|
||||||
|
- 极简的插件设计模式:
|
||||||
|
- 系统高度封装,提供简便人性化的系统方法,随心所欲开发插件;
|
||||||
|
- 用户可以无感安装插件,免去部署环境等繁琐操作;
|
||||||
|

|
||||||
|
- 插件支持热重载,无需重启程序。作为开发者,可以即时的进行调试。作为用户,配置完插件参数后也将触发热重载。
|
||||||
|
- 通过计划任务轻松实现早报推送、定时提醒喝茶和群发问候等功能;
|
||||||
|

|
||||||
|
- 支持多种插件开发语言 :
|
||||||
|
- 内置 Javascript 引擎,完整支持 ECMAScript 5.1,快速、便捷、门槛低;
|
||||||
|
- 支持 NodeJs,尽情使用其提供的 async/await/Promise 语法糖,可以自由控制异步同步;
|
||||||
|
- 支持 Python3,简洁明了的语法、强大的标准库以及社区优势;
|
||||||
|
- 即将支持 PHP,对对对,这是世界上最好的语言;
|
||||||
|
- 不仅仅是 Chat RoBot : 如果你愿意,可以在本框架上搭建网站、图片服务器、资源共享平台、并发请求等服务,尽情释放你的创造力.
|
||||||
|
|
||||||
|
# 未来
|
||||||
|
|
||||||
|
- [ ] ...
|
||||||
|
|
||||||
|
> 后台目前只有一个蹩脚的前端(本人),如果有前端工程师对此感兴趣,请联系我~
|
||||||
|
|
||||||
|
# 安装
|
||||||
|
|
||||||
|
参见 [安装文档](./md/init.md)
|
||||||
|
|
||||||
|
# 开发
|
||||||
|
|
||||||
|
[开发文档](https://cdle.github.io/sillyGirl)
|
||||||
|
|
||||||
|
> 如果是手机端浏览开发文档,请点击开发文档左下角的按钮手动打开侧边栏目录。
|
||||||
|
|
||||||
|
> 如果你是纯小白可以了解 [内置JS引擎](./md/goja.md),快速进行插件开发
|
||||||
|
|
||||||
|
# 其他
|
||||||
|
|
||||||
|
[Github](https://github.com/cdle/sillyGirl)
|
||||||
|
[TG 频道](https://t.me/kczz2021)
|
||||||
|
[TG 群组](https://t.me/trialerr)
|
||||||
|
[赞助列表](./sponsors.md)
|
||||||
|
|
||||||
|
## 请喝茶 ?
|
||||||
|
|
||||||
|
[联系到我](https://t.me/cdle1994)
|
||||||
|
您的支持是我更新的动力,Thank~
|
||||||
|
|
||||||
|
## 免责声明
|
||||||
|
|
||||||
|
> 任何用户在使用由 Cdle(以下简称「本团队」)研发的机器人框架傻妞前,请您仔细阅读并透彻理解本声明。您可以选择不使用傻妞框架,若您一旦使用傻妞框架,您的使用行为即被视为对本声明全部内容的认可和接受。
|
||||||
|
|
||||||
|
1. 傻妞框架是一款免费的机器人框架,主要用于更高效地开发会话应答式程序。且傻妞框架并不具备「互联网接入、网络数据存储、通讯传输以及窃取用户隐私」中的任何一项与用户数据等信息相关的动态功能。
|
||||||
|
2. 傻妞框架仅是运行 Golang/Node/Python 等程序的服务端框架,其尊重并保护所有用户的个人隐私权,不窃取任何用户计算机中的信息。更不具备用户数据存储等网络传输功能。
|
||||||
|
3. 本框架的初衷是帮助广大同学认识 Golang,并更好利用 Golang 进行学术研究,用户在框架上做的所有改动、开发的任何插件均与本框架无关。
|
||||||
|
4. 本框架内使用的部分包括但不限于字体、npm 库等资源来源于互联网,如果出现侵权可联系本框架移除。
|
||||||
|
5. 由于使用本框架产生的包括由于本协议或由于使用或无法使用本框架而引起的任何性质的任何直接、间接、特殊、偶然或结果性损害(包括但不限于因商誉损失、停工、计算机故障或故障引起的损害赔偿,或任何及所有其他商业损害或损失)由使用者负责。
|
||||||
|
6. 您承诺秉着合法、合理的原则使用傻妞框架,不利用傻妞框架进行任何违法、侵害他人合法利益等恶意的行为,亦不将傻妞框架运用于任何违反我国法律法规的 Web 平台。
|
||||||
|
7. 任何单位或个人因下载使用傻妞框架而产生的任何意外、疏忽、合约毁坏、诽谤、版权或知识产权侵犯及其造成的损失 (包括但不限于直接、间接、附带或衍生的损失等),本团队不承担任何法律责任。
|
||||||
|
8. 用户明确并同意本声明条款列举的全部内容,对使用傻妞框架可能存在的风险和相关后果将完全由用户自行承担,本团队不承担任何法律责任。
|
||||||
|
9. 任何单位或个人在阅读本免责声明后,应在法律所允许的范围内进行合法的发布、传播和使用傻妞框架等行为,若违反本免责声明条款或违反法律法规所造成的法律责任(包括但不限于民事赔偿和刑事责任),由违约者自行承担。
|
||||||
|
10. 传播:任何公司或个人在网络上发布,传播我们框架的行为都是允许的,但因公司或个人传播框架可能造成的任何法律和刑事事件框架作者不负任何责任。
|
||||||
|
11. 本团队对傻妞框架拥有知识产权(包括但不限于商标权、专利权、著作权、商业秘密等),上述产品均受到相关法律法规的保护。
|
||||||
|
12. 任何单位或个人不得在未经本团队书面授权的情况下对傻妞框架本身申请相关的知识产权。
|
||||||
|
13. 如果本声明的任何部分被认为无效或不可执行,则该部分将被解释为反映本团队的初衷,其余部分仍具有完全效力。不可执行的部分声明,并不构成我们放弃执行该声明的权利。
|
||||||
|
14. 本团队有权随时对本声明条款及附件内容进行单方面的变更,并以消息推送、网页公告等方式予以公布,公布后立即自动生效,无需另行单独通知;若您在本声明内容公告变更后继续使用的,表示您已充分阅读、理解并接受修改后的声明内容。
|
||||||
|
15. 本仓库部分开已开源代码遵循 MIT 开源许可协议。
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
<!--  -->
|
||||||
|
|
||||||
|
# 傻妞机器人 <small> 1.0.1 </small>
|
||||||
|
|
||||||
|
> 一个不太有用的机器人
|
||||||
|
> 我愿称之为废物
|
||||||
|
<br>
|
||||||
|
<br>
|
||||||
|
<br>
|
||||||
|
- 下拉开启
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
<!-- _navbar.md -->
|
||||||
|
|
||||||
|
* 联系到我
|
||||||
|
* [知识星球](https://wx.zsxq.com/dweb2/index/group/28885424215821)
|
||||||
|
* [TG频道](https://t.me/kczz2021)
|
||||||
|
* [Github地址](https://github.com/cdle/sillyGirl)
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
|
||||||
|
- **文档**
|
||||||
|
- [**安装**](/md/init)
|
||||||
|
- [**使用**](/md/start)
|
||||||
|
- [**开发文档**](/md/develop)
|
||||||
|
- [**更新日志**](/md/updateLogs)
|
||||||
|
|
||||||
|
- **帮助**
|
||||||
|
- [**常见问题Q&A**](/help/Q&A.md)
|
||||||
|
|
||||||
|
|
||||||
|
- **指南**
|
||||||
|
|
||||||
|
|
||||||
Vendored
+1
File diff suppressed because one or more lines are too long
@@ -0,0 +1,54 @@
|
|||||||
|
常见问题Q&A:
|
||||||
|
## 一、拉不了镜像?
|
||||||
|
* 镜像已经推了大概很久左右,如果拉不到就是你网络问题,或者是镜像服务器问题,请换回官方docker Hub源
|
||||||
|
|
||||||
|
## 二、为什么不回复消息 没反应?
|
||||||
|
|
||||||
|
* 当发送管理员命令没有反应时,请检查管理员是否正确
|
||||||
|
* 步骤:
|
||||||
|
* 对着机器人发 'myuid' 机器人会回复你的id然后设置一下管理员(见下文)
|
||||||
|
* 注意! 管理员命令需要在有管理员权限的平台操作,不然无效
|
||||||
|
* 不知道哪个平台有管理员权限的
|
||||||
|
* 进入管理员面板点击右下角打开对话框发
|
||||||
|
* ./sillyGirl -t 在终端发
|
||||||
|
* docker attach sillyGirl 后在控制台发
|
||||||
|
|
||||||
|
```js
|
||||||
|
//设置qq管理员 其他平台类似
|
||||||
|
set qq masters 12345698
|
||||||
|
set wx masters 12345698&2345699
|
||||||
|
set tg masters 12345697&2345698&234599
|
||||||
|
```
|
||||||
|
|
||||||
|
* 当群友在群里发消息机器人没有任何回应时,说明你没对群监听
|
||||||
|
* 快捷操作
|
||||||
|
* 管理员在群聊中发送 'listen' 即可响应群友消息
|
||||||
|
* 发送 'unlisten' 取消监听
|
||||||
|
* 发送 'unreply' 监听消息但是不会回复任何消息
|
||||||
|
* 'reply' 恢复默认
|
||||||
|
|
||||||
|
* 手动设置上述效果,请前控制面板
|
||||||
|
|
||||||
|
## 三、反馈问题?
|
||||||
|
* 请带上报错截图,说明发了什么命令触发、安装方式和系统环境
|
||||||
|
|
||||||
|
## 四、关于部分报错!
|
||||||
|
|
||||||
|
## Error: Cannot find module 'xxxxx'
|
||||||
|
统一为缺少npm模块,通过管理员对机器人发送 npm i xxxx 命令安装模块后重启即可解决
|
||||||
|
|
||||||
|
## Error: Cannot find module './xxxxx'
|
||||||
|
统一为缺少自定义模块,谁写的插件找谁要这些模块,一般对应的插件仓库都有的,是你没装好!
|
||||||
|
|
||||||
|
## 四 、关于安装npm包!
|
||||||
|
* 安装npm失败?
|
||||||
|
* 基本都是你网络环境问题,换网重试!
|
||||||
|
* 百度报错!再不济然后在群里提问。不要一上来就是咔咔一顿截图
|
||||||
|
* 开发者在容器内部安装npm包时,尽量在插件目录下执行npm i 命令 ,否则在更新镜像时、重建docker时 会丢失安装过的npm包
|
||||||
|
* 正确示例:
|
||||||
|
```
|
||||||
|
docker exec -it sillyGirl /bin/sh
|
||||||
|
cd plugins/插件文件
|
||||||
|
npm i xxxx
|
||||||
|
```
|
||||||
|
* 或者在宿主机cd到容器映射目录 (需要宿主机安装过node 且npm版本 不低于 9.5.0,使用npm -v 查看版本)直接安装npm包
|
||||||
@@ -0,0 +1,85 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<title>Document</title>
|
||||||
|
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" />
|
||||||
|
<meta name="description" content="Description" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0, minimum-scale=1.0" />
|
||||||
|
<!-- 设置浏览器图标 -->
|
||||||
|
<!-- <link rel="icon" href="/favicon.ico" type="image/x-icon" />
|
||||||
|
<link rel="shortcut icon" href="/favicon.ico" type="image/x-icon" /> -->
|
||||||
|
<!-- 默认主题 -->
|
||||||
|
<link rel="stylesheet" href="//cdn.jsdelivr.net/npm/docsify/themes/vue.css" />
|
||||||
|
<!-- 箭头 -->
|
||||||
|
<link rel="stylesheet" href="//cdn.jsdelivr.net/npm/docsify-sidebar-collapse/dist/sidebar.min.css" />
|
||||||
|
<!-- <link rel="stylesheet" href="//cdn.jsdelivr.net/npm/docsify-sidebar-collapse/dist/sidebar-folder.min.css" /> -->
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="app">加载中....</div>
|
||||||
|
<script>
|
||||||
|
window.$docsify = {
|
||||||
|
// logo: '/_media/icon.svg',
|
||||||
|
name: '傻妞机器人',
|
||||||
|
nameLink: '#',
|
||||||
|
repo: 'https://github.com/cdle/sillyGirl',
|
||||||
|
// 侧边栏支持,默认加载的是项目根目录下的_sidebar.md文件
|
||||||
|
loadSidebar: true,
|
||||||
|
// 导航栏支持,默认加载的是项目根目录下的_navbar.md文件
|
||||||
|
loadNavbar: true,
|
||||||
|
// 封面支持,默认加载的是项目根目录下的_coverpage.md文件
|
||||||
|
coverpage: true,
|
||||||
|
// 最大支持渲染的标题层级
|
||||||
|
maxLevel: 5,
|
||||||
|
// 自定义侧边栏后默认不会再生成目录,设置生成目录的最大层级(建议配置为2-4)
|
||||||
|
subMaxLevel: 5,
|
||||||
|
sidebarDisplayLevel: 1,
|
||||||
|
// 小屏设备下合并导航栏到侧边栏
|
||||||
|
mergeNavbar: true,
|
||||||
|
// 自动增加标题
|
||||||
|
autoHeader: true,
|
||||||
|
auto2top: true,
|
||||||
|
//搜索
|
||||||
|
search: {
|
||||||
|
maxAge: 86400000, // 过期时间,单位毫秒,默认一天
|
||||||
|
// paths: auto, // 注意:仅适用于 paths: 'auto' 模式
|
||||||
|
placeholder: '搜索',
|
||||||
|
// 支持本地化
|
||||||
|
placeholder: {
|
||||||
|
'/zh-cn/': '搜索',
|
||||||
|
'/': '搜索',
|
||||||
|
},
|
||||||
|
noData: '没有该结果',
|
||||||
|
depth: 4,
|
||||||
|
hideOtherSidebarContent: false,
|
||||||
|
namespace: 'Docsify-Guide',
|
||||||
|
},
|
||||||
|
//阅读字数
|
||||||
|
count: {
|
||||||
|
countable: true,
|
||||||
|
fontsize: '0.9em',
|
||||||
|
color: 'rgb(90,90,90)',
|
||||||
|
language: 'chinese',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<!-- docsify的js依赖 -->
|
||||||
|
<script src="./docsify.min.js"></script>
|
||||||
|
<!-- <script src="//cdn.jsdelivr.net/npm/docsify/lib/docsify.min.js"></script> -->
|
||||||
|
<!-- emoji表情支持 -->
|
||||||
|
<script src="//cdn.jsdelivr.net/npm/docsify/lib/plugins/emoji.min.js"></script>
|
||||||
|
<!-- 图片放大缩小支持 -->
|
||||||
|
<script src="//cdn.jsdelivr.net/npm/docsify/lib/plugins/zoom-image.min.js"></script>
|
||||||
|
<!-- 搜索功能支持 -->
|
||||||
|
<script src="//cdn.jsdelivr.net/npm/docsify/lib/plugins/search.min.js"></script>
|
||||||
|
<!--在所有的代码块上添加一个简单的Click to copy按钮来允许用户从你的文档中轻易地复制代码-->
|
||||||
|
<script src="//cdn.jsdelivr.net/npm/docsify-copy-code/dist/docsify-copy-code.min.js"></script>
|
||||||
|
<!-- bash代码高亮 -->
|
||||||
|
<script src="//cdn.jsdelivr.net/npm/prismjs@1/components/prism-bash.min.js"></script>
|
||||||
|
<!-- 展开折叠支持 -->
|
||||||
|
<script src="//cdn.jsdelivr.net/npm/docsify-sidebar-collapse/dist/docsify-sidebar-collapse.min.js"></script>
|
||||||
|
|
||||||
|
<script src="//unpkg.com/docsify-count/dist/countable.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -0,0 +1,652 @@
|
|||||||
|
> - 傻妞 是一个 支持Node、Python 机器人框架,开发需要有一定的编程基础,以下主要对Node进行讲解,其他语言类似。
|
||||||
|
|
||||||
|
# 开发环境搭建
|
||||||
|
|
||||||
|
## 通过 vscode 开发
|
||||||
|
|
||||||
|
- 打开 plugins 目录
|
||||||
|
- 运行傻妞 `./sillyGirl -t`,在这里你可以直接输入信息调试、以及观察 console.log 输出信息、收发消息情况等信息
|
||||||
|
- 开发插件非常方便~
|
||||||
|
 本人开发插件示例
|
||||||
|
|
||||||
|
## 文件目录说明
|
||||||
|
|
||||||
|
- 插件开发
|
||||||
|
|
||||||
|
- plugins 下只有二级目录下的 index.js 作为入口文件才会被尝试载入,根目录以及 3 级以下不会加载,示例 :
|
||||||
|
|
||||||
|
```
|
||||||
|
plugins/hello/index.js //会被当做插件载入
|
||||||
|
plugins/index.js //忽略
|
||||||
|
```
|
||||||
|
|
||||||
|
- 重载:plugins 下的所有文件都是保存既重载
|
||||||
|
- 默认识别为 Node 开发语言,否则请在插件目录加上 `py_` 、`php_` 前缀
|
||||||
|
- 内置的 JS 引擎仅支持在后台管理开发面板中进行开发
|
||||||
|
|
||||||
|
- language
|
||||||
|
|
||||||
|
- 存放语言支持的一些依赖,请不要动它。当然目前只有`node`、`python`
|
||||||
|
|
||||||
|
# 模块方法介绍
|
||||||
|
|
||||||
|
## async sleep
|
||||||
|
|
||||||
|
Node 休眠(阻塞运行),传 number,注意单位是毫秒
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
//内置js
|
||||||
|
time.sleep(5000);
|
||||||
|
```
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
//node
|
||||||
|
await sleep(5000);
|
||||||
|
```
|
||||||
|
|
||||||
|
```python
|
||||||
|
#python
|
||||||
|
await asyncio.sleep(5)
|
||||||
|
```
|
||||||
|
|
||||||
|
## utils 工具包
|
||||||
|
|
||||||
|
### buildCQTag 拼接 CQ 码
|
||||||
|
|
||||||
|
本项目消息机遇字符串,图片和视频等通过 CQ 码实现,因此提供了拼接方法
|
||||||
|
|
||||||
|
```
|
||||||
|
/**
|
||||||
|
* @author 猫咪
|
||||||
|
* @origin 傻妞官方
|
||||||
|
* @version v1.0.0
|
||||||
|
* @create_at 2022-09-08 07:31:42
|
||||||
|
* @title 二维码
|
||||||
|
* @rule 二维码 ?
|
||||||
|
* @description 🐒生成指定内容的一个二维码。
|
||||||
|
* @public true
|
||||||
|
* @icon https://bpic.51yuansu.com/pic3/cover/02/70/77/5a1878243d237_610.jpg
|
||||||
|
* @class 图片
|
||||||
|
*/
|
||||||
|
```
|
||||||
|
|
||||||
|
```js
|
||||||
|
//内置js
|
||||||
|
let url = `https://api.pwmqr.com/qrcode/create/?url=${encodeURIComponent(s.param(1))}`;
|
||||||
|
s.reply(strings.buildCQTag("image", { url }));
|
||||||
|
//等价写法
|
||||||
|
s.reply(image(url));
|
||||||
|
```
|
||||||
|
|
||||||
|
```js
|
||||||
|
//node
|
||||||
|
const {
|
||||||
|
sender: s,
|
||||||
|
utils: { buildCQTag, image },
|
||||||
|
} = require("sillygirl");
|
||||||
|
|
||||||
|
(async () => {
|
||||||
|
let url = `https://api.pwmqr.com/qrcode/create/?url=${encodeURIComponent(
|
||||||
|
await s.param(1)
|
||||||
|
)}`;
|
||||||
|
await s.reply(buildCQTag("image", { url }));
|
||||||
|
//等价写法
|
||||||
|
await s.reply(image(url));
|
||||||
|
})();
|
||||||
|
```
|
||||||
|
|
||||||
|
```python
|
||||||
|
#python
|
||||||
|
from sillygirl import sender as s, utils
|
||||||
|
from urllib.parse import quote
|
||||||
|
import asyncio
|
||||||
|
|
||||||
|
async def main():
|
||||||
|
url = f"https://api.pwmqr.com/qrcode/create/?url={quote(await s.param(1))}"
|
||||||
|
await s.reply(utils.buildCQTag("image", { url }))
|
||||||
|
#等价写法
|
||||||
|
await s.reply(utils.image(url))
|
||||||
|
|
||||||
|
loop = asyncio.get_event_loop()
|
||||||
|
loop.run_until_complete(main())
|
||||||
|
```
|
||||||
|
|
||||||
|
### parseCQText 解析 CQ 码
|
||||||
|
|
||||||
|
```js
|
||||||
|
/**
|
||||||
|
* @title 钉钉机器人
|
||||||
|
* @create_at 2023-07-03 08:07:43
|
||||||
|
* @description 🐔钉钉机器人适配器。
|
||||||
|
* @author 佚名
|
||||||
|
* @version v1.0.3
|
||||||
|
* @public true
|
||||||
|
* @class 机器人
|
||||||
|
* @service true
|
||||||
|
*/
|
||||||
|
|
||||||
|
const {
|
||||||
|
utils: { parseCQText },
|
||||||
|
} = require("sillygirl");
|
||||||
|
|
||||||
|
(async () => {
|
||||||
|
let adapter = new Adapter({
|
||||||
|
platform: "dingtalk",
|
||||||
|
bot_id: wsdata.msg.user.id,
|
||||||
|
replyHandler: async ({ user_id, chat_id, content }) => {
|
||||||
|
for (let item of parseCQText(content)) {
|
||||||
|
if (typeof item == "string") {
|
||||||
|
// item 此时是文本,执行发送文本消息逻辑
|
||||||
|
}
|
||||||
|
if (item.type == "image") {
|
||||||
|
let url = item.params.url;
|
||||||
|
// url 是图片链接,执行发送图片消息逻辑
|
||||||
|
}
|
||||||
|
if (item.type == "video") {
|
||||||
|
let url = item.params.url;
|
||||||
|
// url 是视频链接,执行发送视频消息逻辑
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return "${message_id}";
|
||||||
|
},
|
||||||
|
});
|
||||||
|
})();
|
||||||
|
```
|
||||||
|
|
||||||
|
### utils.npmInstall 安装 npm 包 (待实现)
|
||||||
|
|
||||||
|
```js
|
||||||
|
await utils.npmInstall("request"); //会返回执行信息String
|
||||||
|
await utils.npmInstall("request", { outConsole: true }); // 将会在控制台实时打印安装情况,返回结果为null
|
||||||
|
```
|
||||||
|
|
||||||
|
### utils.testModule 测试 npm 包是否存在 (待实现)
|
||||||
|
|
||||||
|
```js
|
||||||
|
await utils.testModule(["telegram", "input"]); //将只测试,返回结果
|
||||||
|
await utils.testModule(["telegram", "input"], { install: true }); //发现少模块自动安装
|
||||||
|
```
|
||||||
|
|
||||||
|
## Adapter 适配器构造类
|
||||||
|
|
||||||
|
- 具体使用方法参见适配器开发
|
||||||
|
|
||||||
|
## Bucket 数据库构造类
|
||||||
|
|
||||||
|
系统数据库为轻量型内嵌式 KV 数据库。
|
||||||
|
|
||||||
|
开发插件时,你可以使用任意你喜欢的数据库来存放数据 (不建议,会破坏用户使用体验)
|
||||||
|
|
||||||
|
有关数据库操作,你可以在官方插件中找到全部用法示例
|
||||||
|
|
||||||
|
> Linux 存放于 `/etc/sillyplus`
|
||||||
|
|
||||||
|
> Windows 存放于 `C:\ProgramData\sillyplus`
|
||||||
|
|
||||||
|
> Darwin 存放于 `.sillyplus`
|
||||||
|
|
||||||
|
> 建议定期备份
|
||||||
|
|
||||||
|
### get()获取数据
|
||||||
|
|
||||||
|
该方法接受二个参数
|
||||||
|
|
||||||
|
- 需要读取的 key
|
||||||
|
- 未读取到值默认返回值(可选)
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
//内置js
|
||||||
|
const test = new Bucket("test");
|
||||||
|
//读取一个key的 value 值 如果没有该数据返回undefined
|
||||||
|
consoloe.log(test.name1)// undefined
|
||||||
|
consoloe.log(test["name1"])// undefined
|
||||||
|
// 第二个参数作为未读取到数据的返回值
|
||||||
|
test.get("name1", "阿明")// 阿明
|
||||||
|
```
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
//node
|
||||||
|
const test = new Bucket("test");
|
||||||
|
await test.get("name1");
|
||||||
|
await test.get("name1", "阿明");
|
||||||
|
```
|
||||||
|
|
||||||
|
```python
|
||||||
|
#python
|
||||||
|
test = Bucket("test")
|
||||||
|
await test.get("name1") #None
|
||||||
|
await test.get("name1", "阿明")
|
||||||
|
|
||||||
|
#支持同步,小心阻塞watch方法
|
||||||
|
print(test.name1) #None
|
||||||
|
test.name1 = "阿明"
|
||||||
|
print(test.name1)
|
||||||
|
```
|
||||||
|
|
||||||
|
### set()存储数据
|
||||||
|
|
||||||
|
该方法接受三个参数
|
||||||
|
|
||||||
|
- 需要设置的 key
|
||||||
|
- 需要设置的 value
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
//创建一个测试数据库实例
|
||||||
|
const test = new Bucket('test');
|
||||||
|
|
||||||
|
const {
|
||||||
|
changed // boolean 实际值是否变更
|
||||||
|
message // string 修改回调消息,修改可能被变更或拦截
|
||||||
|
error // string 修改失败时的错误消息
|
||||||
|
} = await test.set('name', '阿明');
|
||||||
|
```
|
||||||
|
|
||||||
|
### delete()删除数据
|
||||||
|
|
||||||
|
该方法接受一个参数
|
||||||
|
|
||||||
|
- 需要删除的 key
|
||||||
|
|
||||||
|
返回结果同 set
|
||||||
|
|
||||||
|
### getAll() 读取所有键值对
|
||||||
|
|
||||||
|
### empty() 清空数据库
|
||||||
|
|
||||||
|
### keys()读取所有 key 返回一个 Array
|
||||||
|
|
||||||
|
返回一个字符串数组
|
||||||
|
|
||||||
|
### watch() 监听数据变更,可进行拦截、修改
|
||||||
|
|
||||||
|
```python
|
||||||
|
# python
|
||||||
|
from sillygirl import Bucket
|
||||||
|
import asyncio
|
||||||
|
|
||||||
|
test = Bucket("test")
|
||||||
|
|
||||||
|
async def main():
|
||||||
|
async def handle(old_value, new_value, key):
|
||||||
|
print("Bucket value changed!")
|
||||||
|
print("Key:", key)
|
||||||
|
print("Old value:", old_value)
|
||||||
|
print("New value:", new_value)
|
||||||
|
new_value = f"new {new_value}"
|
||||||
|
print("New New value:", new_value)
|
||||||
|
return {
|
||||||
|
"now": new_value,
|
||||||
|
#"error": "具体错误,将会撤销修改"
|
||||||
|
#"message": "携带的额外信息"
|
||||||
|
}
|
||||||
|
|
||||||
|
test.watch("*", handle)
|
||||||
|
await asyncio.Queue().get()
|
||||||
|
loop = asyncio.get_event_loop()
|
||||||
|
loop.run_until_complete(main())
|
||||||
|
```
|
||||||
|
|
||||||
|
# 适配器开发
|
||||||
|
|
||||||
|
## 元信息配置
|
||||||
|
|
||||||
|
通过 jsdoc 注释的方式来定义元信息,service true 时,将随程序一起执行
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
/**
|
||||||
|
* @service true
|
||||||
|
*/
|
||||||
|
```
|
||||||
|
|
||||||
|
## 初始化一个适配器
|
||||||
|
|
||||||
|
基本使用方法:
|
||||||
|
|
||||||
|
```
|
||||||
|
/**
|
||||||
|
* @title 钉钉机器人
|
||||||
|
* @create_at 2023-07-30 11:09:12
|
||||||
|
* @description 🐒钉钉机器人适配器。
|
||||||
|
* @author cdle
|
||||||
|
* @version v1.0.0
|
||||||
|
* @service true
|
||||||
|
*/
|
||||||
|
```
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
//node
|
||||||
|
const { Adapter, sleep } = require("sillygirl");
|
||||||
|
|
||||||
|
const bot_id = "";
|
||||||
|
const platform = "dingtalk";
|
||||||
|
|
||||||
|
const dingtalk = new Adapter({
|
||||||
|
platform,
|
||||||
|
bot_id,
|
||||||
|
// 处理 Sender.reply 回复的消息
|
||||||
|
replyHandler: ({
|
||||||
|
user_id, //用户ID
|
||||||
|
chat_id, //群聊ID,为什么不用group_id,因为本人觉得别扭、不对齐
|
||||||
|
message_id, //消息ID
|
||||||
|
content, //消息内容,其中图片、文本使用CQ码实现
|
||||||
|
//... 自定义参数,来自 Adapter.receive
|
||||||
|
}) => {
|
||||||
|
//... 实际消息发送逻辑
|
||||||
|
return "${message_id}"; //返回消息ID,用于消息撤回等处理
|
||||||
|
},
|
||||||
|
// 处理 Sender.doAction 发出的指令
|
||||||
|
actionHandler: (
|
||||||
|
{
|
||||||
|
//... 系列参数,如删除消息:{ type: "delete_message", message_id }
|
||||||
|
}
|
||||||
|
) => {
|
||||||
|
return "${action_result}"; //返回结果
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
//向框架内部发送信息
|
||||||
|
dingtalk.receive({
|
||||||
|
user_id: "用户ID",
|
||||||
|
chat_id: "群聊ID",
|
||||||
|
message_id: "消息ID",
|
||||||
|
content: "消息内容",
|
||||||
|
//以下为可选
|
||||||
|
user_name: "用户名", //用户名称
|
||||||
|
chat_name: "群聊名名", //群聊名称
|
||||||
|
//... 自定义参数
|
||||||
|
});
|
||||||
|
|
||||||
|
//向框架外部推送消息,无视 unreply
|
||||||
|
dingtalk.push({
|
||||||
|
ser_id: "用户ID",
|
||||||
|
chat_id: "群聊ID",
|
||||||
|
message_id: "消息ID",
|
||||||
|
content: "消息内容",
|
||||||
|
});
|
||||||
|
|
||||||
|
//获取 sender 实例
|
||||||
|
const ns = dingtalk.sender({
|
||||||
|
user_id: "用户ID",
|
||||||
|
chat_id: "群聊ID",
|
||||||
|
message_id: "消息ID",
|
||||||
|
content: "消息内容",
|
||||||
|
});
|
||||||
|
|
||||||
|
// 1秒后主动销毁适配器,会抛出异常注意捕获
|
||||||
|
// sleep(1000).then(() => {
|
||||||
|
// dingtalk.destroy();
|
||||||
|
// });
|
||||||
|
```
|
||||||
|
|
||||||
|
```python
|
||||||
|
#python
|
||||||
|
from sillygirl import Adapter, console
|
||||||
|
async def main():
|
||||||
|
def replyHandler(message):
|
||||||
|
console.log("message", message)
|
||||||
|
return "ok"
|
||||||
|
bot = Adapter(platform="terminal2", bot_id="default", replyHandler=replyHandler)
|
||||||
|
ns = await bot.sender({"user_id": "user_id"})
|
||||||
|
console.log(await ns.reply("hello"))
|
||||||
|
# await bot.destroy()
|
||||||
|
await asyncio.sleep(10)
|
||||||
|
```
|
||||||
|
|
||||||
|
# 插件开发
|
||||||
|
|
||||||
|
## sender 方法合集
|
||||||
|
|
||||||
|
开发插件前,我们先了解一下 sender,该方法只有在插件中可用
|
||||||
|
|
||||||
|
### 导入 sender 为 s
|
||||||
|
|
||||||
|
```js
|
||||||
|
/**
|
||||||
|
* @rule Hello ?
|
||||||
|
*/
|
||||||
|
|
||||||
|
const { sender: s } = require("sillygirl");
|
||||||
|
```
|
||||||
|
|
||||||
|
### sender.getUserId() 获取用户 id
|
||||||
|
|
||||||
|
```js
|
||||||
|
//内置js
|
||||||
|
console.log(s.getUserId())
|
||||||
|
```
|
||||||
|
|
||||||
|
```js
|
||||||
|
//node
|
||||||
|
s.getUserId().then((user_id) => console.log(`用户ID:${user_id}`));
|
||||||
|
```
|
||||||
|
|
||||||
|
```python
|
||||||
|
#python
|
||||||
|
print(await s.getUserId())
|
||||||
|
```
|
||||||
|
|
||||||
|
### sender.getChatId() 获取群聊 id
|
||||||
|
|
||||||
|
```js
|
||||||
|
s.getChatId().then((chat_id) => console.log(`群聊ID:${chat_id}`));
|
||||||
|
```
|
||||||
|
|
||||||
|
### sender.getMessageId() 获取消息 id
|
||||||
|
|
||||||
|
```js
|
||||||
|
s.getMessageId().then((message_id) => console.log(`消息ID:${message_id}`));
|
||||||
|
```
|
||||||
|
|
||||||
|
### sender.getContent() 获取消息内容
|
||||||
|
|
||||||
|
```js
|
||||||
|
s.getContent().then((content) => console.log(`消息内容:${content}`));
|
||||||
|
```
|
||||||
|
|
||||||
|
### sender.param() 获取参数,对应元消息中的 rule 中子匹配
|
||||||
|
|
||||||
|
```js
|
||||||
|
s.param(1).then((param1) => console.log(`参数1:${param1}`));
|
||||||
|
```
|
||||||
|
|
||||||
|
### sender.reply() 回复消息
|
||||||
|
|
||||||
|
```js
|
||||||
|
s.reply("你好").then((message_id) => console.log(`消息ID:${message_id}`));
|
||||||
|
```
|
||||||
|
|
||||||
|
### sender.listen() 监听消息
|
||||||
|
|
||||||
|
#### 例 1
|
||||||
|
|
||||||
|
```js
|
||||||
|
(async () => {
|
||||||
|
await s.reply("请输入11位手机号:");
|
||||||
|
let s2 = await s.listen({
|
||||||
|
rules: ["^\\d{11}$"],
|
||||||
|
});
|
||||||
|
let phone = await s2.getContent();
|
||||||
|
await s.reply(`你的手机号码是:${phone}`);
|
||||||
|
//... 业务逻辑
|
||||||
|
})();
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 例 2
|
||||||
|
|
||||||
|
```js
|
||||||
|
(async () => {
|
||||||
|
await s.reply("请在10秒内输入11位手机号:");
|
||||||
|
let s2 = await s.listen({
|
||||||
|
rules: ["^\\d{11}$"],
|
||||||
|
timeout: 10000,
|
||||||
|
});
|
||||||
|
if (!s2) {
|
||||||
|
await s.reply("输入超时!");
|
||||||
|
} else {
|
||||||
|
let phone = await s2.getContent();
|
||||||
|
await s.reply(`你的手机号码是:${phone}`);
|
||||||
|
//... 业务逻辑
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
```
|
||||||
|
|
||||||
|
### sender.holdOn() 继续监听消息
|
||||||
|
|
||||||
|
#### 例 3
|
||||||
|
|
||||||
|
```js
|
||||||
|
(async () => {
|
||||||
|
await s.reply("请在10秒内输入11位手机号:");
|
||||||
|
let phone = "";
|
||||||
|
await s.listen({
|
||||||
|
handle: async (s) => {
|
||||||
|
phone = await s.getContent();
|
||||||
|
if (phone.length != 11) {
|
||||||
|
return s.holdOn("错误,请重新输入(直到正确):");
|
||||||
|
} else {
|
||||||
|
return "输入正确。";
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
await s.reply(`你的手机号码是:${phone}`);
|
||||||
|
//... 业务逻辑
|
||||||
|
})();
|
||||||
|
```
|
||||||
|
|
||||||
|
### sender.doAction() 发起指令
|
||||||
|
|
||||||
|
#### 撤回消息
|
||||||
|
|
||||||
|
```js
|
||||||
|
(async () => {
|
||||||
|
const { sleep } = require("sillygirl");
|
||||||
|
let message_id = await s.reply("本消息3秒后撤回!");
|
||||||
|
await sleep(3000);
|
||||||
|
await s.doAction({
|
||||||
|
type: "delete_message",
|
||||||
|
message_id,
|
||||||
|
});
|
||||||
|
})();
|
||||||
|
```
|
||||||
|
|
||||||
|
### sender.getEvent() 获取事件
|
||||||
|
|
||||||
|
### sender.isAdmin() 是否管理员消息
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
sender.isAdmin().then((bool) => console.log(`${bool ? "是" : "不是"}管理员。`));
|
||||||
|
```
|
||||||
|
|
||||||
|
### sender.getUserName() 获取用户昵称
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
s.getUserName().then((user_name) => console.log(user_name));
|
||||||
|
```
|
||||||
|
|
||||||
|
### sender.getChatName() 获取群聊昵称
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
s.getChatName().then((v) => console.log(v));
|
||||||
|
```
|
||||||
|
|
||||||
|
### sender.getPlatform() 获取消息平台
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
s.getPlatform().then((v) => console.log(v));
|
||||||
|
```
|
||||||
|
|
||||||
|
### sender.getBotId() 获取机器人 id
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
s.getBotId().then((v) => console.log(v));
|
||||||
|
```
|
||||||
|
|
||||||
|
### sender.getAdapter() 获取适配器
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
s.getAdapter().then((adapter) => console.log(adapter););
|
||||||
|
```
|
||||||
|
|
||||||
|
### sender.continue() 消息继续向下传递
|
||||||
|
|
||||||
|
### sender.setContent() 篡改消息内容
|
||||||
|
|
||||||
|
### sender.destroy() 销毁会话,
|
||||||
|
|
||||||
|
脚本结束后自动调用,如果脚本长期不结束需要用手动调用避免内存泄漏
|
||||||
|
|
||||||
|
## 元信息配置
|
||||||
|
|
||||||
|
```js
|
||||||
|
/**作者
|
||||||
|
* @author Cdle
|
||||||
|
* 插件名
|
||||||
|
* @name 日常命令
|
||||||
|
* 组织名
|
||||||
|
* @origin 傻妞官方
|
||||||
|
* 版本号
|
||||||
|
* @version 1.0.5
|
||||||
|
* 说明
|
||||||
|
* @description 🐷日常命令,个人认为会比较便捷
|
||||||
|
* 限制平台 不在该范围内的平台消息该插件不会被触发
|
||||||
|
* @platform tg qq
|
||||||
|
* 触发正则,^开头或raw 开头
|
||||||
|
* @rule ^你好 ([^\n]+)$
|
||||||
|
* @rule raw 你好
|
||||||
|
* 直接用问号代替要匹配的内容
|
||||||
|
* @rule 你好 ?
|
||||||
|
* 使用方括号代替要匹配的内容,s.param("姓名") 取参
|
||||||
|
* @rule 你好 [姓名]
|
||||||
|
* 参数名加?表示为选值
|
||||||
|
* @rule 你好 [姓名?]
|
||||||
|
* // 是否管理员才能触发命令
|
||||||
|
* @admin true
|
||||||
|
* // 是否发布插件
|
||||||
|
* @public false
|
||||||
|
* // 插件优先级,越大优先级越高 如果两个插件正则一样,则优先级高的先被匹配
|
||||||
|
* @priority 9999
|
||||||
|
* // 是否禁用插件
|
||||||
|
* @disable false
|
||||||
|
* // 每5小时运行一次插件,触发时 platform 为 cron
|
||||||
|
* @cron 0 0 *\/5 * * *
|
||||||
|
* // 是否服务模块,会在系统启动时执行该插件内容,触发时 platform 为 *
|
||||||
|
* @service false
|
||||||
|
* // 给插件设置一个好看的头像
|
||||||
|
* @icon https://wx.zsxq.com/dweb2/assets/images/favicon_32.ico
|
||||||
|
```
|
||||||
|
|
||||||
|
## 写一个简单的 hello world
|
||||||
|
|
||||||
|
```js
|
||||||
|
/**作者
|
||||||
|
* @author Cdle
|
||||||
|
* @name Hello
|
||||||
|
* @version 1.0.5
|
||||||
|
* @description 你好,世界!
|
||||||
|
* @rule [参数1:hello,你好] [参数2]
|
||||||
|
* @admin false
|
||||||
|
* @public false
|
||||||
|
* @priority 1
|
||||||
|
* @disable false
|
||||||
|
*/
|
||||||
|
|
||||||
|
(async (s) => {
|
||||||
|
let name = await s.param("参数2");
|
||||||
|
await s.reply(`你好,我是${name}`);
|
||||||
|
})();
|
||||||
|
```
|
||||||
|
|
||||||
|
## 插件加密(暂未实现)
|
||||||
|
|
||||||
|
为保护开发者知识产权,如果不想公开插件源码,可以用块级注释来包裹需要加密的内容
|
||||||
|
`/*hidden*/` 开始,`/*neddih*/`结束,必须严格按照要求,多/少空格都不会被识别到
|
||||||
|
|
||||||
|
```js
|
||||||
|
/*hidden*/
|
||||||
|
let key = 1234;
|
||||||
|
/*neddih*/
|
||||||
|
|
||||||
|
console.log("key");
|
||||||
|
```
|
||||||
|
|
||||||
|
到底啦~ 详细的开发还是要靠插件来说明,多看看官方插件吧~~
|
||||||
+483
@@ -0,0 +1,483 @@
|
|||||||
|
# 傻妞
|
||||||
|
|
||||||
|
一个不太有用的机器人,不生产消息,只搬运消息。
|
||||||
|
|
||||||
|
## 特性
|
||||||
|
|
||||||
|
- 简单易用的消息搬运功能。
|
||||||
|
- 简单强大的自定义回复功能。
|
||||||
|
- 完整支持 ECMAScript 5.1 的插件系统,基于 [otto](https://github.com/robertkrimen/otto)。
|
||||||
|
- 支持通过内置的阉割版 `Express` / `request` ,接入互联网。
|
||||||
|
- 内置 `Cron` ,轻松实现定时任务。
|
||||||
|
- 持久化的 `Bucket` 存储模块。
|
||||||
|
- 支持同时接入多个平台多个机器人,自己开发。
|
||||||
|
|
||||||
|
## 快速上手
|
||||||
|
|
||||||
|
### 安装
|
||||||
|
|
||||||
|
在 [releases](https://github.com/cdle/sillyGirl/releases) 中找到合适自己系统版本的程序运行带 `-t` 可以开启终端机器人,直接与程序进行交互。
|
||||||
|
|
||||||
|
```shell
|
||||||
|
./sillyplus -t
|
||||||
|
2023/05/24 14:12:01.859 [I] 默认使用boltdb进行数据存储。
|
||||||
|
2023/05/24 14:12:01.950 [I] Http服务已运行(8080)。
|
||||||
|
```
|
||||||
|
|
||||||
|
### 开发第一个插件
|
||||||
|
|
||||||
|
```js
|
||||||
|
/**
|
||||||
|
* @title HelleWorld
|
||||||
|
* @rule raw ^你好$
|
||||||
|
*/
|
||||||
|
|
||||||
|
s.reply("Helle World!");
|
||||||
|
```
|
||||||
|
|
||||||
|
怼着程序输入 `你好` ,就可以看到机器人回复的 `Helle World!` 了
|
||||||
|
|
||||||
|
```sh
|
||||||
|
你好
|
||||||
|
2023/05/24 14:15:48.350 [I] 匹配到规则:^你好$
|
||||||
|
Helle World!
|
||||||
|
```
|
||||||
|
|
||||||
|
插件注释 `@rule raw ^你好$` 中的正则表达式被消息匹配时插件脚本就会被触发。
|
||||||
|
|
||||||
|
### 添加和销毁定时任务
|
||||||
|
|
||||||
|
插件注释 `@service true` 时是作为傻妞系统服务,伴随机器人一起启动。
|
||||||
|
|
||||||
|
```js
|
||||||
|
/**
|
||||||
|
* @title 定时任务
|
||||||
|
* @service true
|
||||||
|
*/
|
||||||
|
|
||||||
|
const task = Cron();
|
||||||
|
let taskId = 0;
|
||||||
|
let times = 5;
|
||||||
|
const { id } = task.add("*/5 * * * * *", () => {
|
||||||
|
// 同样支持分钟级任务,如:*/5 * * * *
|
||||||
|
times--;
|
||||||
|
console.log(
|
||||||
|
`每5秒执行一次任务,${
|
||||||
|
times ? `${times}次后结束任务` : "这是最后一次任务"
|
||||||
|
}。`
|
||||||
|
);
|
||||||
|
if (times == 0) {
|
||||||
|
task.remove(taskId); //移除任务
|
||||||
|
}
|
||||||
|
});
|
||||||
|
taskId = id;
|
||||||
|
```
|
||||||
|
|
||||||
|
程序输出:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
2023/05/27 19:57:00.000 [I] 每5秒执行一次任务,4次后结束任务。
|
||||||
|
2023/05/27 19:57:05.001 [I] 每5秒执行一次任务,3次后结束任务。
|
||||||
|
2023/05/27 19:57:10.001 [I] 每5秒执行一次任务,2次后结束任务。
|
||||||
|
2023/05/27 19:57:15.001 [I] 每5秒执行一次任务,1次后结束任务。
|
||||||
|
2023/05/27 19:57:20.000 [I] 每5秒执行一次任务,这是最后一次任务。
|
||||||
|
```
|
||||||
|
|
||||||
|
### 与用户交互
|
||||||
|
|
||||||
|
```js
|
||||||
|
/**
|
||||||
|
* @title 用户交互插件
|
||||||
|
* @rule 猜拳
|
||||||
|
*/
|
||||||
|
|
||||||
|
s.reply("你先出,请在10秒内出拳!");
|
||||||
|
ns = s.listen({
|
||||||
|
rules: ["[出拳:剪刀,石头,布]"], // []中出拳是参数名,剪刀,石头,布是参数可能值
|
||||||
|
timeout: 10000, // 超时设置
|
||||||
|
handle: (s) => {
|
||||||
|
let choose = s.param("出拳");
|
||||||
|
s.reply(
|
||||||
|
`我出${
|
||||||
|
choose == "石头" ? "剪刀" : choose == "布" ? "剪刀" : "石头"
|
||||||
|
},我赢了。`
|
||||||
|
);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
if (!ns) {
|
||||||
|
s.reply("你没出拳,算我赢了!");
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 开发 HTTP 接口
|
||||||
|
|
||||||
|
```js
|
||||||
|
/**
|
||||||
|
* @title 第一个web服务
|
||||||
|
* @service true
|
||||||
|
*/
|
||||||
|
|
||||||
|
const app = Express(); //导入HTTP服务,傻妞默认开启,端口8080
|
||||||
|
app.get("/helloWorld", function (req, res) {
|
||||||
|
res.send("Hello world!");
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
打开浏览器访问 `http://127.0.0.1:8080/helloWorld` ,当然地址根据实际情况,理论上可以看到接口返回的 `Hello world!` 。
|
||||||
|
|
||||||
|
### 实现一个 HTTP 请求
|
||||||
|
|
||||||
|
```js
|
||||||
|
/**
|
||||||
|
* @title 实现一个HTTP 请求
|
||||||
|
* @service true
|
||||||
|
*/
|
||||||
|
|
||||||
|
let api = "/testRequest"; //接口地址
|
||||||
|
|
||||||
|
//第一步,实现一个原样返回请求数据的接口
|
||||||
|
const app = Express();
|
||||||
|
app.post(api, (req, res) => res.json(req.json()));
|
||||||
|
|
||||||
|
//第二步,请求第一步实现的接口
|
||||||
|
const port = Bucket("app").port ?? "8080"; // 获取http服务端口
|
||||||
|
const url = `http://127.0.0.1:${port}${api}`;
|
||||||
|
const { body } = request({
|
||||||
|
url,
|
||||||
|
method: "POST",
|
||||||
|
body: { value: "test" },
|
||||||
|
json: true,
|
||||||
|
});
|
||||||
|
console.log(`value is ${body.value}`);
|
||||||
|
```
|
||||||
|
|
||||||
|
### 持久化存储
|
||||||
|
|
||||||
|
```js
|
||||||
|
/**
|
||||||
|
* @title 持久化存储
|
||||||
|
* @rule raw ^我是谁$
|
||||||
|
* @rule 我是[姓名]
|
||||||
|
*/
|
||||||
|
|
||||||
|
const user = Bucket("user"); //初始化存储桶user
|
||||||
|
let name = s.param("姓名");
|
||||||
|
|
||||||
|
if (user.name == "") {
|
||||||
|
s.reply(`我不知道你是谁!`);
|
||||||
|
} else if (name == "谁") {
|
||||||
|
s.reply(`你是${user.name}`);
|
||||||
|
} else {
|
||||||
|
user.name = name;
|
||||||
|
s.reply(`好的,你的姓名更新为${user.name}`);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
插件实现了记名字的功能,其中`姓名`是方括号里匹配到的值,本质还是正则匹配到的。
|
||||||
|
|
||||||
|
```
|
||||||
|
我是谁
|
||||||
|
2023/05/24 15:43:40.121 [I] 匹配到规则:^我是谁$
|
||||||
|
我不知道你是谁!
|
||||||
|
我是小千
|
||||||
|
2023/05/24 15:43:49.735 [I] 匹配到规则:^我是([\s\S]+)$
|
||||||
|
好的,你的姓名更新为小千
|
||||||
|
我是谁
|
||||||
|
2023/05/24 15:43:53.727 [I] 匹配到规则:^我是谁$
|
||||||
|
你是小千
|
||||||
|
```
|
||||||
|
|
||||||
|
有了 `Bucket` 才有了傻妞从不认识小千到认识小千的过程。
|
||||||
|
|
||||||
|
### 管理员
|
||||||
|
|
||||||
|
```js
|
||||||
|
const masters = Bucket("qq")["masters"];
|
||||||
|
```
|
||||||
|
|
||||||
|
`masters` 是管理员账号通过"&"拼接起来的,系统默认依此判断用户是否是管理员。
|
||||||
|
|
||||||
|
### 群组消息
|
||||||
|
|
||||||
|
默认不监听不回复任何群组,监听口令 `listen` 和 `unlisten`,回复口令 `reply` 和 `noreply`,需要管理员在对应群组发送口令。
|
||||||
|
|
||||||
|
## 深入了解
|
||||||
|
|
||||||
|
### 插件注释
|
||||||
|
|
||||||
|
| 字段 | 举例 | 用法 |
|
||||||
|
| ------------- | ---------------------------------- | -------------------------------------------------- |
|
||||||
|
| `title` | HelloWorld | 插件标题 |
|
||||||
|
| `rule` | raw `^我是([\s\S]+)$` | 可写多行,取括号内参数 `s.param(1)` ,多个参数类推 |
|
||||||
|
| `priority` | `1` | 插件优先级,越高则优先处理 |
|
||||||
|
| `service` | `true` | 插件后台任务执行脚本,避免重复运行 |
|
||||||
|
| `disable` | `true` | 禁用脚本 |
|
||||||
|
| `form` | `{title: "姓名", key:"user.name"}` | 插件表,key 值对应 `存储桶.键名` |
|
||||||
|
| `public` | `true` | 公开插件 |
|
||||||
|
| `create_at` | 2023-05-24 15:14:53 | 插件创建时间 |
|
||||||
|
| `description` | 本插件用于每天向女友问好 | 插件描述 |
|
||||||
|
| `author` | `cdle` | 插件作者 |
|
||||||
|
| `version` | `v1.0.0` | 插件版本 |
|
||||||
|
| `icon` | url 省略... | 给插件增加图标 |
|
||||||
|
|
||||||
|
### Sender
|
||||||
|
|
||||||
|
傻妞搬运的核心对象,在插件中为全局变量 s or sender。
|
||||||
|
|
||||||
|
```ts
|
||||||
|
interface Sender {
|
||||||
|
getUserId(): string; //获取用户ID
|
||||||
|
getUserName(): string; //获取用户昵称
|
||||||
|
getChatId(): string; //获取群聊ID
|
||||||
|
getChatName(): string; //获取群聊名称
|
||||||
|
getMessageId(): string; //获取消息ID
|
||||||
|
getContent(): string; //获取消息内容
|
||||||
|
continue(): void; //使消息继续往下匹配正则,消息正常第一次被匹配就会停止继续匹配
|
||||||
|
setContent(content: string): void; //修改接收到的消息内容,可配合`continue`被其他规则匹配
|
||||||
|
param(index: string | number): string; //获取`rule`匹配参数,可取[]内参数,?型参数从1开始取,例 `@rule 回复 ?` 对应 `s.param(1)`
|
||||||
|
holdOn(content: string): string; //持续监听
|
||||||
|
listen({
|
||||||
|
rules: string[]; //匹配规则
|
||||||
|
timeout: number; //超时,单位毫秒
|
||||||
|
handle: (s: Sender): string;//如果匹配成功,则进入消息处理逻辑。如果将 holdOn(content) 的结果作为返回值,会继续监听
|
||||||
|
listen_private: boolean; //监听用户群内消息时,同时监听用户消息
|
||||||
|
listen_group: boolean; //监听用户群内消息时,同时监听群员消息
|
||||||
|
allow_platforms: string[]; //平台白名单
|
||||||
|
prohibit_platforms: string[]; //平台黑名单
|
||||||
|
allow_groups: string[]; //群聊白名单
|
||||||
|
prohibit_groups: string[]; //群聊黑名单
|
||||||
|
allow_users: string[]; //用户白名单
|
||||||
|
prohibit_users: string[]; //群聊白名单
|
||||||
|
}): Sender; //超时,返回undefined
|
||||||
|
isAdmin(): boolean; //判断消息是否来自管理员
|
||||||
|
getPlatform(): string; //获取消息平台
|
||||||
|
getBotId(): string; //获取机器人ID
|
||||||
|
reply(content: string): {message_id: string, error: string}; //回复消息,媒体消息推荐使用CQ码实现,返回消息ID
|
||||||
|
recallMessage(meesageId: string | string[] | number): {error: string}; //撤回消息,number类型时为延时毫秒
|
||||||
|
kick(user_id: string): {error: string}; //移出群聊
|
||||||
|
unkick(user_id: string): {error: string}; //取消移出群聊
|
||||||
|
ban(user_id: string, duration: number): {error: string}; //禁言,并指定时长
|
||||||
|
unban(user_id: string): {error: string}; //取消禁言
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Express `Request` / `Response`
|
||||||
|
|
||||||
|
只能说是够用,有需求可联系作者。插件中通过 `Express()` 返回一个对象。
|
||||||
|
|
||||||
|
```ts
|
||||||
|
interface Request {
|
||||||
|
body(): string; //获取请求体
|
||||||
|
json(): any; //将请求体解析为JSON
|
||||||
|
ip(): string; //获取客户端IP地址
|
||||||
|
originalUrl(): string; //获取原始请求URL
|
||||||
|
query(param: string): string; //获取查询参数
|
||||||
|
param(i: number): string; //根据索引获取路径参数
|
||||||
|
querys(): Record<string, string[]>; //获取所有查询参数
|
||||||
|
postForm(s: string): string; //获取表单数据
|
||||||
|
postForms(): Record<string, string[]>; //获取所有表单数据
|
||||||
|
path(): string; //获取请求路径
|
||||||
|
header(s: string): string; //获取请求头
|
||||||
|
get(s: string): string; //获取请求头
|
||||||
|
headers(): Record<string, string[]>; //获取所有请求头
|
||||||
|
method(): string; //获取请求方法
|
||||||
|
cookie(s: string): string; //获取 cookie
|
||||||
|
cookies(): Record<string, string>; //获取 cookies
|
||||||
|
continue(): void; //继续匹配其他路由
|
||||||
|
setSession(k: string, v: string): string; //设置会话值
|
||||||
|
getSession(k: string): string; //获取会话值
|
||||||
|
getSessionId(): string; //获取会话ID
|
||||||
|
destroySession(): string; //销毁会话
|
||||||
|
logined(): boolean; //是否面板登录状态
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Response {
|
||||||
|
send(body: any): Response; //发送响应体
|
||||||
|
sendStatus(status: number): Response; //发送状态码
|
||||||
|
json(...ps: any[]): Response; //发送JSON响应
|
||||||
|
header(str: string, value: string): Response; //设置响应头
|
||||||
|
set(str: string, value: string): void; //设置响应头
|
||||||
|
render(view: string, params: Record<string, any>): Response; //渲染视图
|
||||||
|
redirect(...is: any[]): void; //重定向到URL
|
||||||
|
status(i: number, ...s: string[]): Response; //设置状态码和文本
|
||||||
|
setCookie(name: string, value: string, ...i: any[]): Response; //设置 Cookie
|
||||||
|
stop(): void; //代码片段停止
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### request
|
||||||
|
|
||||||
|
由 `net/http` 封装而成,如有更多需求可以联系作者。
|
||||||
|
|
||||||
|
```ts
|
||||||
|
function request(options: {
|
||||||
|
url: string; //请求地址
|
||||||
|
method: string; //请求方法
|
||||||
|
headers: { [key: string]: string }; //请求头
|
||||||
|
json: boolean; // 返回json对象,等价于 responseType: "json"
|
||||||
|
timeout: number; //超时参数,单位毫秒
|
||||||
|
form: { [key: string]: any }; //formData表单数据,优先于下面的body
|
||||||
|
body: any; // 请求体,支持字符串、二进制,对象自动转json字符串和添加相应请求头
|
||||||
|
allow_redirects: boolean; // 是否允许重定向,默认允许
|
||||||
|
proxy: {};
|
||||||
|
}): {
|
||||||
|
status: number; // 状态码,同statusCode
|
||||||
|
headers: { [key: string]: string };
|
||||||
|
body: any;
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
### Adapter
|
||||||
|
|
||||||
|
```ts
|
||||||
|
interface Message{
|
||||||
|
message_id: string; // 消息ID
|
||||||
|
user_id: string; // 用户ID
|
||||||
|
chat_id: string; // 聊天ID
|
||||||
|
content: string; // 聊天内容
|
||||||
|
user_name: string; // 用户名
|
||||||
|
chat_name: string; // 群组名
|
||||||
|
}
|
||||||
|
|
||||||
|
class Adapter(botplt: string, botid: string) {
|
||||||
|
isAdapter(botid: string): boolean; //判断id是否为机器人
|
||||||
|
push(message: Message): string; //推送消息,无视禁言设置
|
||||||
|
getReplyMessage(): Promise<message: Message>; //获取一条回复消息,实际发送成功后,如果有id,请设置 message.message_id
|
||||||
|
setReplyHandler(func: (message: Message): string): void; //设置回复事件处理方法,方法中返回消息ID,不推荐使用。
|
||||||
|
receive(message: Message): Sender; //接收一个消息,并返回一个Sender对象
|
||||||
|
setRecallMessage(func: (i: string | string[]) => boolean): void;//设置撤回消息函数。
|
||||||
|
setGroupKick(func: (user_id: string, chat_id: string, reject_add_request: boolean) => void): boolean; //设置群聊成员移除函数,reject_add_request指5是否继续接受请求
|
||||||
|
setGroupBan(func: (user_id: string, chat_id: string, duration: number) => void): boolean;//设置群聊成员禁言函数
|
||||||
|
setGroupUnban(func: (user_id: string, chat_id: string) => void): boolean;//设置群聊成员解除禁言函数
|
||||||
|
setIsAdmin(func: (user_id: string) => boolean): void; //设置用户是否是成员函数,默认自动实现
|
||||||
|
destroy(): void;//销毁机器人
|
||||||
|
}
|
||||||
|
function getAdapter(platform: string, bot_id string): {Adapter: string, error: string}; //获取一个机器人
|
||||||
|
|
||||||
|
function getAdapterBotsID(bot_id string): Adapter[]; //获取一个平台的所有机器人
|
||||||
|
|
||||||
|
function getAdapterBotPlts(platform: string): string[]; //所有机器人平台
|
||||||
|
```
|
||||||
|
|
||||||
|
### Bucket
|
||||||
|
|
||||||
|
例:通过 `Bucket("app")` 初始化一个 app 存储痛
|
||||||
|
|
||||||
|
```ts
|
||||||
|
interface Bucket(name: string) {
|
||||||
|
get(key: string, defaultValue: any): any; // 取值
|
||||||
|
set(key: string, value: any): Error | null; // 设值
|
||||||
|
watch(key: string, event: (old: any, new_: any, key: string) => void); // 设置监听器,key 值为 * 时将监听整个桶的存储事件
|
||||||
|
getAll(): []; // 获取全部值
|
||||||
|
delete(key: string): Error | null; // 删值
|
||||||
|
empty(): Error | undefined; // 清空桶
|
||||||
|
keys(): string[]; // 获取所有键名
|
||||||
|
len(): number | undefined; // 获取数据数目
|
||||||
|
buckets(): string[]; // 获取所有存在的桶名
|
||||||
|
_name(): string; // 获取当前桶名
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Cron
|
||||||
|
|
||||||
|
可以通过`let task = Cron()`返回的对象来添加定时任务 `const {id, error} = task.add("* * * * *", ()=>{})`
|
||||||
|
|
||||||
|
```ts
|
||||||
|
interface Cron {
|
||||||
|
add(crontab: string, ()=>void): {id: number, error: string}//添加定时任务 crontab同时支持秒级和分钟级
|
||||||
|
remove(id: number): void//移除定时任务
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 插件表单
|
||||||
|
|
||||||
|
可以使用注释 `@form {title: "标题", key: "test.title"}` 添加表单元素。当如也可以直接在插件代码中添加,如下。
|
||||||
|
|
||||||
|
```js
|
||||||
|
// 单个表单元素
|
||||||
|
Form({
|
||||||
|
title: "姓名",
|
||||||
|
key: "test.name",
|
||||||
|
});
|
||||||
|
// 多个表单元素
|
||||||
|
Form([
|
||||||
|
{
|
||||||
|
title: "姓名",
|
||||||
|
key: "test.name",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "性别",
|
||||||
|
key: "test.sex",
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
// 使用schema-form
|
||||||
|
Form([
|
||||||
|
{
|
||||||
|
title: "创建时间",
|
||||||
|
key: "test.createName",
|
||||||
|
dataIndex: "test.createName",
|
||||||
|
valueType: "date",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "创建时间",
|
||||||
|
key: "test.createName",
|
||||||
|
dataIndex: "test.createName",
|
||||||
|
valueType: "date",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "分组",
|
||||||
|
valueType: "group",
|
||||||
|
columns: [
|
||||||
|
{
|
||||||
|
title: "状态",
|
||||||
|
dataIndex: "test.groupState",
|
||||||
|
valueType: "select",
|
||||||
|
width: "xs",
|
||||||
|
valueEnum: {
|
||||||
|
all: { text: "全部", status: "Default" },
|
||||||
|
open: {
|
||||||
|
text: "未解决",
|
||||||
|
status: "Error",
|
||||||
|
},
|
||||||
|
closed: {
|
||||||
|
text: "已解决",
|
||||||
|
status: "Success",
|
||||||
|
disabled: true,
|
||||||
|
},
|
||||||
|
processing: {
|
||||||
|
text: "解决中",
|
||||||
|
status: "Processing",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "标题",
|
||||||
|
width: "md",
|
||||||
|
dataIndex: "test.groupTitle",
|
||||||
|
formItemProps: {
|
||||||
|
rules: [
|
||||||
|
{
|
||||||
|
required: true,
|
||||||
|
message: "此项为必填项",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
```
|
||||||
|
|
||||||
|
### 其他
|
||||||
|
|
||||||
|
```ts
|
||||||
|
sleep(millsec: number): void; //等待
|
||||||
|
md5(string): string; //加密
|
||||||
|
running(): boolean; //服务是否运行
|
||||||
|
genUuid(): string; //生成uuid
|
||||||
|
```
|
||||||
|
|
||||||
|
### 项目赞助
|
||||||
|
|
||||||
|
打开微信扫一扫,深入了解作者~
|
||||||
|

|
||||||
Binary file not shown.
|
After Width: | Height: | Size: 1.3 MiB |
Binary file not shown.
|
After Width: | Height: | Size: 51 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 495 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 436 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 291 KiB |
@@ -0,0 +1,64 @@
|
|||||||
|
|
||||||
|
目前支持 `windows amd64`/`liunx amd64`/`darwin arm64`二进制双击运行安装
|
||||||
|
|
||||||
|
## Docker
|
||||||
|
```bash
|
||||||
|
# 在你要存放数据的目录下手动新建sillyGirl文件夹
|
||||||
|
# (以root目录为例)
|
||||||
|
# 警告!群晖用户请勿在root下存放任何文件!修改成你的硬盘目录!
|
||||||
|
mkdir /root/sillyGirl #在root目录新建sillyGirl文件夹
|
||||||
|
|
||||||
|
# 拉取并运行容器 并进入交互控制台
|
||||||
|
docker run -itd \
|
||||||
|
-v /root/sillyGirl/data:/etc/sillyplus \
|
||||||
|
-v /root/sillyGirl/plugins:/usr/local/sillyplus/plugins \
|
||||||
|
-p 8080:8080 \
|
||||||
|
--name sillyGirl \
|
||||||
|
--restart always \
|
||||||
|
jackytj/sillyplus && docker attach sillyGirl
|
||||||
|
|
||||||
|
```
|
||||||
|
进入容器交互控制台
|
||||||
|
```bash
|
||||||
|
#进入
|
||||||
|
docker attach sillyGirl
|
||||||
|
# 退出交互控制台
|
||||||
|
Ctrl+p Ctrl+q
|
||||||
|
```
|
||||||
|
|
||||||
|
查看日志
|
||||||
|
```bash
|
||||||
|
docker logs sillyGirl
|
||||||
|
```
|
||||||
|
|
||||||
|
进入容器命令行(一般用不到)
|
||||||
|
```bash
|
||||||
|
docker exec -it sillyGirl /bin/sh
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
## Windows
|
||||||
|
下载双击运行
|
||||||
|
|
||||||
|
- [sillyGirl_windows_amd64.exe](https://github.com/cdle/sillyGirl/releases/download/main/sillyGirl_windows_amd64.exe)
|
||||||
|
|
||||||
|
|
||||||
|
## MacOS
|
||||||
|
|
||||||
|
敬请期待
|
||||||
|
|
||||||
|
## Liunx
|
||||||
|
|
||||||
|
下载双击运行
|
||||||
|
|
||||||
|
- [sillyGirl_linux_amd64](https://github.com/cdle/sillyGirl/releases/download/main/sillyGirl_linux_amd64)
|
||||||
|
|
||||||
|
- [sillyGirl_linux_arm64](https://github.com/cdle/sillyGirl/releases/download/main/sillyGirl_linux_arm64)
|
||||||
|
|
||||||
|
## Other
|
||||||
|
|
||||||
|
- 如果你的运行环境不受支持
|
||||||
|
|
||||||
|
- 如果你在使用上有任何困难
|
||||||
|
|
||||||
|
- 可以加入本项目的[知识星球](https://wx.zsxq.com/dweb2/index/group/28885424215821),以获取手拉手教程、特殊资源共享、定制需求和同作者实时交流
|
||||||
@@ -0,0 +1,68 @@
|
|||||||
|
# 配置
|
||||||
|
初次启动创建3个文件夹,分别为 `language` `plugins`
|
||||||
|
`/etc/sillyplus` 会自动生成一些启动所需的配置文件,已进行详细注释,根据自己情况来填写;
|
||||||
|
|
||||||
|
`language` 计算机语言支持依赖,请不要动它;
|
||||||
|
|
||||||
|
`plugins` 插件目录,从插件市场安装的插件都在这里;
|
||||||
|
|
||||||
|
`/etc/sillyplus` 为系统数据库存放目录;
|
||||||
|
|
||||||
|
# 面板
|
||||||
|
|
||||||
|
运行时会提示面板,本人蹩脚前端,将就用吧;
|
||||||
|
|
||||||
|
# 适配器
|
||||||
|
|
||||||
|
运行傻妞`-t`启用自带`terminal`适配器,其他适配器请前往插件市场,目前已支持`QQ`、`QQ频道`、`微信`、`公众号`、`飞书`、`钉钉`、`Telegram`、`Pagermaid`等,从`机器人`分类安装;
|
||||||
|
|
||||||
|
|
||||||
|
# 管理员命令没反应?群聊不回复群友?
|
||||||
|
参见 [**常见问题Q&A**](/help/Q&A.md)
|
||||||
|
|
||||||
|
# 常用指令
|
||||||
|
|
||||||
|
首次安装时将自动从插件市场安装,如果不需要可以禁用
|
||||||
|
|
||||||
|
```js
|
||||||
|
//获取数据库数据
|
||||||
|
get 表 key
|
||||||
|
//例如获取管理员
|
||||||
|
get qq masters
|
||||||
|
// 设置数据库
|
||||||
|
set 表 key value
|
||||||
|
//例如设置管理员
|
||||||
|
set qq masters 123456789
|
||||||
|
//获取时间
|
||||||
|
time
|
||||||
|
//启动时间
|
||||||
|
started_at
|
||||||
|
//获取机器码
|
||||||
|
machine_id
|
||||||
|
//获取版本
|
||||||
|
compiled_at
|
||||||
|
// 获取群id
|
||||||
|
chat_id
|
||||||
|
//获取个人id
|
||||||
|
myuid
|
||||||
|
//监听群消息 (默认屏蔽所有群)
|
||||||
|
listen
|
||||||
|
//屏蔽群消息
|
||||||
|
unlisten
|
||||||
|
//不回复该群
|
||||||
|
unreply
|
||||||
|
//回复该群
|
||||||
|
reply
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
# 其他命令
|
||||||
|
其他命令要视插件情况而定,具体问队友插件作者
|
||||||
|
|
||||||
|
# 关于报错!
|
||||||
|
|
||||||
|
## Error: Cannot find module 'xxxxx'
|
||||||
|
统一为缺少npm模块,通过管理员对机器人发送 npm i xxxx 命令安装模块后重启即可解决
|
||||||
|
|
||||||
|
## Error: Cannot find module './xxxxx'
|
||||||
|
统一为缺少自定义模块,谁写的插件找谁要这些模块,一般对应的插件仓库都有的,是你没装好!
|
||||||
Vendored
+108
@@ -0,0 +1,108 @@
|
|||||||
|
declare class Sender {
|
||||||
|
private uuid;
|
||||||
|
private destoried;
|
||||||
|
constructor(uuid: string);
|
||||||
|
destroy(): void;
|
||||||
|
getUserId(): Promise<string>;
|
||||||
|
getUserName(): Promise<string>;
|
||||||
|
getChatId(): Promise<string>;
|
||||||
|
getChatName(): Promise<string>;
|
||||||
|
getMessageId(): Promise<string>;
|
||||||
|
getPlatform(): Promise<string>;
|
||||||
|
getBotId(): Promise<string>;
|
||||||
|
getContent(): Promise<string>;
|
||||||
|
isAdmin(): Promise<boolean>;
|
||||||
|
param(key: number | string): Promise<string>;
|
||||||
|
setContent(content: string): Promise<undefined>;
|
||||||
|
continue(): Promise<undefined>;
|
||||||
|
getAdapter(): Promise<Adapter>;
|
||||||
|
listen(options?: {
|
||||||
|
rules?: string[];
|
||||||
|
timeout?: number;
|
||||||
|
handle?: (s: Sender) => Promise<string | void> | string | void;
|
||||||
|
listen_private?: boolean;
|
||||||
|
listen_group?: boolean;
|
||||||
|
allow_platforms?: string[];
|
||||||
|
prohibit_platforms?: string[];
|
||||||
|
allow_groups?: string[];
|
||||||
|
prohibit_groups?: string[];
|
||||||
|
allow_users?: string[];
|
||||||
|
prohibit_users?: string[];
|
||||||
|
}): Promise<Sender | undefined>;
|
||||||
|
holdOn(str: string): string;
|
||||||
|
reply(content: string): Promise<string>;
|
||||||
|
doAction(options: Record<string, any>): Promise<any>;
|
||||||
|
getEvent(): Promise<Record<string, any>>;
|
||||||
|
}
|
||||||
|
declare class Bucket {
|
||||||
|
private name;
|
||||||
|
constructor(name: string);
|
||||||
|
transform(v: string | undefined): string | number | boolean | undefined;
|
||||||
|
reverseTransform(value: any): string;
|
||||||
|
get(key: string, defaultValue?: any): Promise<any>;
|
||||||
|
set(key: string, value: any): Promise<{
|
||||||
|
message?: string;
|
||||||
|
changed?: boolean;
|
||||||
|
}>;
|
||||||
|
getAll(): Promise<Record<string, any>>;
|
||||||
|
delete(key: string): Promise<{
|
||||||
|
message?: string;
|
||||||
|
changed?: boolean;
|
||||||
|
}>;
|
||||||
|
deleteAll(): Promise<undefined>;
|
||||||
|
keys(): Promise<string[]>;
|
||||||
|
len(): Promise<number>;
|
||||||
|
buckets(): Promise<string[]>;
|
||||||
|
watch(key: string, handle: (old: any, now: any, key: string) => StorageModifier | void): void;
|
||||||
|
getName(): Promise<string>;
|
||||||
|
}
|
||||||
|
interface StorageModifier {
|
||||||
|
echo?: string;
|
||||||
|
now?: any;
|
||||||
|
message?: string;
|
||||||
|
error?: string;
|
||||||
|
}
|
||||||
|
interface Message {
|
||||||
|
message_id?: string;
|
||||||
|
user_id: string;
|
||||||
|
chat_id?: string;
|
||||||
|
content: string;
|
||||||
|
user_name?: string;
|
||||||
|
chat_name?: string;
|
||||||
|
}
|
||||||
|
declare class Adapter {
|
||||||
|
platform: string;
|
||||||
|
bot_id: string;
|
||||||
|
call: any;
|
||||||
|
constructor(options: {
|
||||||
|
platform: string;
|
||||||
|
bot_id: string;
|
||||||
|
replyHandler?: (message: Message) => Promise<string | undefined>;
|
||||||
|
actionHandler?: (message: Message) => Promise<string | undefined>;
|
||||||
|
});
|
||||||
|
receive(message: Message): Promise<undefined>;
|
||||||
|
push(message: Message): Promise<string>;
|
||||||
|
destroy(): Promise<void>;
|
||||||
|
sender(options: any): Promise<Sender>;
|
||||||
|
}
|
||||||
|
declare let sender: Sender;
|
||||||
|
declare function sleep(ms?: number): Promise<unknown>;
|
||||||
|
interface CQItem {
|
||||||
|
type: string;
|
||||||
|
params: Record<string, string>;
|
||||||
|
}
|
||||||
|
interface CQParams {
|
||||||
|
[key: string]: string | number | boolean;
|
||||||
|
}
|
||||||
|
declare let utils: {
|
||||||
|
buildCQTag: (type: string, params: CQParams, prefix?: string) => string;
|
||||||
|
parseCQText: (text: string, prefix?: string) => (string | CQItem)[];
|
||||||
|
};
|
||||||
|
declare let console: {
|
||||||
|
log(...args: any[]): void;
|
||||||
|
info(...args: any[]): void;
|
||||||
|
error(...args: any[]): void;
|
||||||
|
debug(...args: any[]): void;
|
||||||
|
};
|
||||||
|
|
||||||
|
export { Adapter, Bucket, sender, sleep, utils, console };
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
# 1.0.0
|
||||||
|
正式版本
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
# Sponsors 赞助列表
|
||||||
|
|
||||||
|
|
||||||
|
|Date|Sponsor|Amount|
|
||||||
|
|-|-|-|
|
||||||
Reference in New Issue
Block a user