- 傻妞 是一个 支持Node、Python 机器人框架,开发需要有一定的编程基础,以下主要对Node进行讲解,其他语言类似。
插件开发
plugins 下只有二级目录下的 index.js 作为入口文件才会被尝试载入,根目录以及 3 级以下不会加载,示例 :
plugins/hello/index.js //会被当做插件载入
plugins/index.js //忽略
重载:plugins 下的所有文件都是保存既重载
默认识别为 Node 开发语言,否则请在插件目录加上 py_ 、php_ 前缀
内置的 JS 引擎仅支持在后台管理开发面板中进行开发
language
node、pythonNode 休眠(阻塞运行),传 number,注意单位是毫秒
//内置js
time.sleep(5000);
//node
await sleep(5000);
#python
await asyncio.sleep(5)
本项目消息机遇字符串,图片和视频等通过 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
let url = `https://api.pwmqr.com/qrcode/create/?url=${encodeURIComponent(s.param(1))}`;
s.reply(strings.buildCQTag("image", { url }));
//等价写法
s.reply(image(url));
//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
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())
/**
* @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}";
},
});
})();
await utils.npmInstall("request"); //会返回执行信息String
await utils.npmInstall("request", { outConsole: true }); // 将会在控制台实时打印安装情况,返回结果为null
await utils.testModule(["telegram", "input"]); //将只测试,返回结果
await utils.testModule(["telegram", "input"], { install: true }); //发现少模块自动安装
系统数据库为轻量型内嵌式 KV 数据库。
开发插件时,你可以使用任意你喜欢的数据库来存放数据 (不建议,会破坏用户使用体验)
有关数据库操作,你可以在官方插件中找到全部用法示例
Linux 存放于
/etc/sillyplusWindows 存放于
C:\ProgramData\sillyplusDarwin 存放于
.sillyplus建议定期备份
该方法接受二个参数
未读取到值默认返回值(可选)
//内置js
const test = new Bucket("test");
//读取一个key的 value 值 如果没有该数据返回undefined
consoloe.log(test.name1)// undefined
consoloe.log(test["name1"])// undefined
// 第二个参数作为未读取到数据的返回值
test.get("name1", "阿明")// 阿明
//node
const test = new Bucket("test");
await test.get("name1");
await test.get("name1", "阿明");
#python
test = Bucket("test")
await test.get("name1") #None
await test.get("name1", "阿明")
#支持同步,小心阻塞watch方法
print(test.name1) #None
test.name1 = "阿明"
print(test.name1)
该方法接受三个参数
需要设置的 value
//创建一个测试数据库实例
const test = new Bucket('test');
const {
changed // boolean 实际值是否变更
message // string 修改回调消息,修改可能被变更或拦截
error // string 修改失败时的错误消息
} = await test.set('name', '阿明');
该方法接受一个参数
返回结果同 set
返回一个字符串数组
# 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 时,将随程序一起执行
/**
* @service true
*/
基本使用方法:
/**
* @title 钉钉机器人
* @create_at 2023-07-30 11:09:12
* @description 🐒钉钉机器人适配器。
* @author cdle
* @version v1.0.0
* @service true
*/
//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
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,该方法只有在插件中可用
/**
* @rule Hello ?
*/
const { sender: s } = require("sillygirl");
//内置js
console.log(s.getUserId())
//node
s.getUserId().then((user_id) => console.log(`用户ID:${user_id}`));
#python
print(await s.getUserId())
s.getChatId().then((chat_id) => console.log(`群聊ID:${chat_id}`));
s.getMessageId().then((message_id) => console.log(`消息ID:${message_id}`));
s.getContent().then((content) => console.log(`消息内容:${content}`));
s.param(1).then((param1) => console.log(`参数1:${param1}`));
s.reply("你好").then((message_id) => console.log(`消息ID:${message_id}`));
(async () => {
await s.reply("请输入11位手机号:");
let s2 = await s.listen({
rules: ["^\\d{11}$"],
});
let phone = await s2.getContent();
await s.reply(`你的手机号码是:${phone}`);
//... 业务逻辑
})();
(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}`);
//... 业务逻辑
}
})();
(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}`);
//... 业务逻辑
})();
(async () => {
const { sleep } = require("sillygirl");
let message_id = await s.reply("本消息3秒后撤回!");
await sleep(3000);
await s.doAction({
type: "delete_message",
message_id,
});
})();
sender.isAdmin().then((bool) => console.log(`${bool ? "是" : "不是"}管理员。`));
s.getUserName().then((user_name) => console.log(user_name));
s.getChatName().then((v) => console.log(v));
s.getPlatform().then((v) => console.log(v));
s.getBotId().then((v) => console.log(v));
s.getAdapter().then((adapter) => console.log(adapter););
脚本结束后自动调用,如果脚本长期不结束需要用手动调用避免内存泄漏
/**作者
* @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
/**作者
* @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*/结束,必须严格按照要求,多/少空格都不会被识别到
/*hidden*/
let key = 1234;
/*neddih*/
console.log("key");
到底啦~ 详细的开发还是要靠插件来说明,多看看官方插件吧~~