插件开发
要求:
- 会 Python 的基础知识
- 会使用 Python 解析 Json
- 如果会 CQ 码会更好
- ...
示范(可从仓库中的PluginTemplate
文件夹内找到模板):Plugin.json
(必要):
Json
{
"PluginName": "插件名",
"DependentLibraries": null,
"IsLibraries": false,
"PluginIcon": null,
"PluginAuthor": "插件作者名",
"MainPyName": "插件主 .py 文件名, 不要带上 .py 后缀名",
"PluginDescription": "插件描述",
"EnablePlugin": true,
"Help": [
{
"Class": "分类",
"Commands": [
{
"Command": "指令",
"Content": "指令介绍"
}
]
}
]
}
Plugin.json
文件键值对照表:
PluginName
: 插件名DependentLibraries
: 依赖的第三方库的名称, 若AutoInstallLibraries
值为true
则会尝试自动安装这些库, 格式为:["库名", "库名", ...]
IsLibraries
: 是否为依赖库插件PluginIcon
: 插件的图标, 格式为:xxx.png
(要带上后缀, 主流文件格式即可, 可以在文件夹下, 但是相对路径是从Plugin.json
文件所在的目录开始的)PluginAuthor
: 插件作者名MainPyName
: 插件主 .py 文件名, 不要带上 .py 后缀名, 另外 py 文件里不要赋值__name__
变量!PluginDescription
: 插件描述EnablePlugin
: 是否启用插件, 这是一个标志, 用于启用和禁用插件的, 默认值为true
即可Help
: 帮助菜单内容列表Class
: 指令分类Commands
: 分类的指令列表Command
: 指令Content
: 指令介绍
前言
WARNING
目前 WebSocket 协调连接可能调用一些 API 尚存一些问题, 一些框架的 API 调用起来可能会比较复杂, 目前做不到把它变得更加完美, 当前的WebSocket链接仍然处于不稳定状态
插件主文件编写示范
Python
# 前言,这里用不到的函数可以不定义,可以直接删去,包括API也可以删去不定义,不会报错的
flora_api = {} # 顾名思义,FloraBot的API,载入(若插件已设为禁用则不载入)后会赋值上
def occupying_function(*values): # 该函数仅用于占位,并没有任何意义
pass
call_api = occupying_function
send_msg = occupying_function
def init(): # 插件初始化函数,在载入(若插件已设为禁用则不载入)或启用插件时会调用一次,API可能没有那么快更新,可等待,无传入参数
global send_msg
global call_api
print(flora_api)
send_msg = flora_api.get("SendMsg")
call_api = flora_api.get("CallApi")
print("FloraBot插件模板 加载成功")
def api_update_event(): # 在API更新时会调用一次(若插件已设为禁用则不调用),可及时获得最新的API内容,无传入参数
print(flora_api)
def event(data: dict): # 事件函数,FloraBot每收到一个事件都会调用这个函数(若插件已设为禁用则不调用),传入原消息JSON参数
print(data)
send_type = data.get("SendType")
send_address = data.get("SendAddress")
ws_client = send_address.get("WebSocketClient")
ws_server = send_address.get("WebSocketServer")
send_host = send_address.get("SendHost")
send_port = send_address.get("SendPort")
uid = data.get("user_id") # 事件对象QQ号
gid = data.get("group_id") # 事件对象群号
mid = data.get("message_id") # 消息ID
msg = data.get("raw_message") # 消息内容
if msg is not None:
msg = msg.replace("[", "[").replace("]", "]").replace("&", "&").replace(",", ",") # 消息需要将URL编码替换到正确内容
print(send_type, uid, gid, mid, msg, ws_client, ws_server, send_host, send_port)
上面的注释已经很详细了
注意事项:
- 这些函数以及
flora_api
变量都不是必要的, 少了也不会报错 init
函数里获取 API 内容的话,PluginsDict
和PluginsInfoDict
还不是正确的, 推荐放到api_update_event
函数中处理
插件API
这些在插件中都可以使用 flora_api.get()
获取到
FloraPath
: FloraBot.py 文件所在的绝对路径, 不是这个文件的路径, 而是所在目录FloraHost
: Bot 监听的 IP 地址FloraPort
: Bot 监听的端口号FrameworkAddress
: QQ 框架的 Http 协议监听地址BotID
: 登录的 Bot 账号的 IDAdministrator
: 管理员/所有者/主人的 ID 列表FloraVersion
: Bot 的版本号FloraServer
: Bot 的 Flask 实例UpdateFloraApi
: 更新flora_api
的函数, 调用了会同时调用插件中的api_update_event
函数, 无参数LoadPlugins
: 加载/重载插件函数, 会调用UpdateFloraApi
函数, 无参数BroadcastEvent
: 广播消息函数, 向所有插件包括内置功能广播基于 OneBot 协议的数据, 参数如下方注释解释
Python
def broadcast_event(data: dict, send_type: str, ws_client=None, ws_server=None, send_host: str = "", send_port: int | str = ""):
# 广播消息函数,data: 基于OneBot协议的数据
# send_type: 发送类型,告诉插件是用HTTP还是WebSocket发送消息
# ws_client: WebSocket连接实例,ws_server: WebSocket服务端实例(若发送类型为WebSocket这两个参数必填)
# send_host: HTTP协议发送地址,send_port: HTTP协议发送端口(若填这两个参数则使用自定义地址发送)
调用 BroadcastEvent
示例:
如果你希望其他插件能够将消息从你的插件中开启的 HTTP 发送消息, 你可以这么写:
Python
broadcast_event(data, "HTTP", None, None, "地址", "端口")
WebSocket:
Python
broadcast_event(data, "WebSocket", "地址", "端口")
SendMsg
: 发送信息函数, 也可以发送事件, 只要你会 CQ 码, 参数如下方注释解释SendMsg
函数:
Python
def send_msg(msg: str, uid: str | int, gid: str | int | None, mid: str | int | None = None, ws_client=None, ws_server=None, send_host: str = "", send_port: int | str = ""):
# 发送消息函数,send_type: 发送类型,决定是用HTTP还是WebSocket发送消息
# msg: 正文,uid: ID,gid: 群号,mid: 消息编号
# ws_client: WebSocket连接实例,ws_server: WebSocket服务端实例(若发送类型为WebSocket这两个参数必填)
# send_host: HTTP协议发送地址,send_port: HTTP协议发送端口(若填这两个参数则使用自定义地址发送)
关于 SendMsg
的补充, 默认传入参数如下即可:
Python
send_msg(send_type, "正文", uid, gid, mid, ws_client, ws_server)
默认为回复信息, 如果不需要回复将参数 mid
改成 None
即可
CallApi
: 向框架调用 API, 会返回 dict 类型的数据, 参数与SendMsg
差不多, 函数如下:
Python
def call_api(send_type: str, api: str, params: dict, ws_client=None, ws_server=None, send_host: str = "", send_port: int | str = ""):
# 发送消息函数,send_type: 发送类型,决定是用HTTP还是WebSocket发送消息
# api: 接口/终结点去掉"/"(str类型), data: 数据(dict类型)
# ws_client: WebSocket连接实例,ws_server: WebSocket服务端实例(若发送类型为WebSocket这两个参数必填)
# send_host: HTTP协议发送地址,send_port: HTTP协议发送端口(若填这两个参数则使用自定义地址发送)
API(终结点) 以及该 API 的参数查阅 OneBot 文档调用, 调用了函数后会返回相应的信息(dict类型), 有需求可获取其中的数据, 以下是调用示范:
Python
print(call_api(send_type, "API", {}, ws_client, ws_server, send_host, send_port))
HelpInfoDict
: 所有插件包括 FloraBot 的帮助字典CallApiReturned
: 调用框架 API 时, 如果为 WebSocket 协议, 这里将会记录返回的数据, 这个逻辑也相对的引出了新的 Bug, 当插件以 WebSocket 协议广播消息时, 若其他插件会发送消息或调用 API 时会等待 WebSocket 返回调用参数返回的数据, 因为是插件广播的, 所以会一直永远的等待下去, 若插件有这个需求, 还请查看源代码进行开发, 作者已经尽力了(一般绝对用不上, 要用请参考源代码)PluginsDict
: 插件对象字典, 使用对应的插件名获取, 并赋值给变量(或不赋值直接调用), 即可将对应的插件当作库来调用PluginsInfoDict
: 插件信息字典, 使用对应的插件名获取, 可获取到对应插件的Plugin.json
已转换为 Python 对象的内容ThePluginPath
: 插件对于FloraBot.py
文件所在的目录的相对路径, 由于是将插件导入再调用的, 所以任何相对路径都是从FloraBot.py
文件所在的目录开始的, 这非常重要, 不推荐使用自己手动定义到插件资源的相对路径, 而是推荐使用ThePluginPath
+ 插件相对于资源的相对路径(因为可能会出现种种原因导致你手动定义到插件资源的相对路径不能正确使用插件文件夹中的文件), 示例: 我有一个叫做 Test.json 的文件, 在插件目录中的文件夹 Test 中(即 Test/Test.json), 那么获取ThePluginPath
的值拼接到路径"/Test/Test.json"的前面即可获得FloraBot.py
与该文件的相对路径, 现在就可以在插件中正确的使用这个文件了(希望不会那么拗口:) )
如果还是不能理解ThePluginPath
的话, 直接上代码:
Python
import json
group_white_list = []
def init():
global group_white_list
with open(f"./{flora_api.get('ThePluginPath')}/Plugin.json", "r", encoding="UTF-8") as plugin_config:
group_white_list = json.loads(plugin_config.read()).get("GroupWhiteList")
上述代码使用 ThePluginPath
拼接了当前插件配置文件的路径, 并且读取并获取了当中的 GroupWhiteList
键的值
另外, 如果你要发送图片等本地文件需要绝对路径可以这么写:
Python
flora_api = {}
def occupying_function(*values): # 该函数仅用于占位,并没有任何意义
pass
send_msg = occupying_function
def init():
global send_msg
send_msg = flora_api.get("SendMsg")
def event(data: dict): # 事件函数,FloraBot每收到一个事件都会调用这个函数(若插件已设为禁用则不调用),传入原消息JSON参数
print(data)
send_type = data.get("SendType")
send_address = data.get("SendAddress")
ws_client = send_address.get("WebSocketClient")
ws_server = send_address.get("WebSocketServer")
send_host = send_address.get("SendHost")
send_port = send_address.get("SendPort")
uid = data.get("user_id") # 事件对象QQ号
gid = data.get("group_id") # 事件对象群号
mid = data.get("message_id") # 消息ID
msg = data.get("raw_message") # 消息内容
if msg is not None:
msg = msg.replace("[", "[").replace("]", "]").replace("&", "&").replace(",", ",") # 消息需要将URL编码替换到正确内容
if msg == "TestSendImage":
send_msg(send_type, f"[CQ:image,file=file:///{flora_api.get('FloraPath')}/{flora_api.get('ThePluginPath')}/Test.png]", uid, gid, mid, ws_client, ws_server, send_host, send_port)
上述代码发送图片时使用了 FloraPath
和 ThePluginPath
拼接了当前插件文件夹下的 Test.png 的绝对路径, 或获取相对路径的文件的绝对路径
注意!!!: 拼接路径请使用 /
而不是 \
, 因为如果路径中出现了 \
则只能在 Windows 中使用, 而 /
则是全平台, Windows 支持使用 /
拼接路径