last commit

This commit is contained in:
cdle
2021-08-31 20:45:27 +08:00
committed by cdle
commit b6cb7726f8
22 changed files with 1926 additions and 0 deletions
+652
View File
@@ -0,0 +1,652 @@
> - 傻妞 是一个 支持Node、Python 机器人框架,开发需要有一定的编程基础,以下主要对Node进行讲解,其他语言类似。
# 开发环境搭建
## 通过 vscode 开发
- 打开 plugins 目录
- 运行傻妞 `./sillyGirl -t`,在这里你可以直接输入信息调试、以及观察 console.log 输出信息、收发消息情况等信息
- 开发插件非常方便~
![1677296097624](image/develop/1677296097624.png) 本人开发插件示例
## 文件目录说明
- 插件开发
- 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");
```
到底啦~ 详细的开发还是要靠插件来说明,多看看官方插件吧~~