Compare commits

...

14 Commits

Author SHA1 Message Date
fumiama
173925b57a 增加动态加载,添加默认禁用 2021-10-11 21:44:46 +08:00
fumiama
43735722bf ✏️ 收集 max / min 到 data 包 2021-10-11 14:24:22 +08:00
fumiama
d89e7aebec 增加-u参数说明 2021-10-11 13:35:03 +08:00
fumiama
4677d789f2 增加-u参数 2021-10-11 13:32:23 +08:00
fumiama
2fb445746e Merge branch 'master' of https://github.com/FloatTech/ZeroBot-Plugin 2021-10-11 13:18:15 +08:00
fumiama
505dfef48c 增加-h参数说明 2021-10-11 13:18:06 +08:00
fumiama
19a9a8ef83 增加-h参数说明 2021-10-11 13:16:30 +08:00
fumiama
119730bada 增加-h参数,优化参数处理 2021-10-11 13:15:38 +08:00
fumiama
c46748524a 增加-w参数 2021-10-10 12:00:23 +08:00
fumiama
29f833db41 Merge branch 'master' of https://github.com/FloatTech/ZeroBot-Plugin 2021-10-10 11:52:26 +08:00
fumiama
1232856b21 beautify 2021-10-10 11:52:18 +08:00
github-actions[bot]
770ae6ebd0 🎨 改进代码样式 2021-10-10 03:51:49 +00:00
fumiama
8c9ced0bda ✏️ 解耦 web gui 2021-10-10 11:51:09 +08:00
fumiama
3b3dd3df99 fortune 个人用户也可设置底图 2021-10-10 11:37:41 +08:00
13 changed files with 218 additions and 116 deletions

1
.gitignore vendored
View File

@@ -5,6 +5,7 @@ data/manager
data/acgimage
data/fortune
data/hs
plugins/*.so
.idea/
.DS_Store
.vscode

View File

@@ -20,13 +20,30 @@
## 命令行参数
```bash
zerobot [-d] [-g] qq1 qq2 qq3 ...
zerobot -h -t token -u url [-d|w] [-g] qq1 qq2 qq3 ...
```
- **-d**: 开启 debug 级别日志输出
- **-h**: 显示帮助
- **-t token**: 设置`AccessToken`,默认为空
- **-u url**: 设置`Url`,默认为`ws://127.0.0.1:6700`
- **-d|w**: 开启 debug | warning 级别及以上日志输出
- **-g**: 开启 [webgui](https://github.com/FloatTech/bot-manager)
- **qqs**: superusers 的 qq 号
## 功能
> 在编译时,以下功能除插件控制外,均可通过注释`main.go`中的相应`import`而物理禁用,减小插件体积。
> 通过插件控制,还可动态管理某个功能在某个群的打开/关闭。
- **web管理**
- 因为开启后可执行文件大约增加 5M ,默认注释不开启。
- 需要配合 [webgui](https://github.com/FloatTech/bot-manager) 使用
- **动态加载插件**
- [x] /刷新插件
- 仅 Linux, FreeBSD, macOS 可用,默认注释不开启。
- 开启后`zbp`可执行文件约增大 2M ,每个插件的`.so`文件约 4 ~ 20 M ,如非必要建议不开启。
- 动态加载的插件需放置在`plugins/`下,编译命令如下。插件包名必须为`main`
```bash
go build -ldflags "-s -w" -buildmode=plugin
```
- 插件一经加载,无法再卸载,只能通过`control`控制。
- **插件控制**
- [x] /启用 xxx
- [x] /禁用 xxx
@@ -179,7 +196,7 @@ zerobot [-d] [-g] qq1 qq2 qq3 ...
1. 点击右上角 Fork 本项目,并转跳到自己 Fork 的仓库
2. 点击仓库上方的 Actions 按钮,确认使用 Actions
3. 编辑 main.go 文件,内容按需修改
4. 前往 Release 页面发布一个 Release`tag`形如`vx.y.z`,以触发稳定版编译流程
4. 前往 Release 页面发布一个 Release`tag`形如`v1.2.3`,以触发稳定版编译流程
5. 点击 Actions 按钮,等待编译完成,回到 Release 页面下载编译好的文件
6. 运行 OneBot 框架,并同时运行本插件
7. 啾咪~

View File

@@ -49,8 +49,8 @@ func newctrl(service string, o *Options) *Control {
return m
}
// enable enables a group to pass the Manager.
func (m *Control) enable(groupID int64) {
// Enable enables a group to pass the Manager.
func (m *Control) Enable(groupID int64) {
m.Lock()
err := db.Insert(m.service, &grpcfg{groupID, 0})
if err != nil {
@@ -59,8 +59,8 @@ func (m *Control) enable(groupID int64) {
m.Unlock()
}
// disable disables a group to pass the Manager.
func (m *Control) disable(groupID int64) {
// Disable disables a group to pass the Manager.
func (m *Control) Disable(groupID int64) {
m.Lock()
err := db.Insert(m.service, &grpcfg{groupID, 1})
if err != nil {
@@ -69,7 +69,7 @@ func (m *Control) disable(groupID int64) {
m.Unlock()
}
func (m *Control) isEnabledIn(gid int64) bool {
func (m *Control) IsEnabledIn(gid int64) bool {
m.RLock()
var c grpcfg
err := db.Find(m.service, &c, "WHERE gid = "+strconv.FormatInt(gid, 10))
@@ -81,9 +81,9 @@ func (m *Control) isEnabledIn(gid int64) bool {
logrus.Errorf("[control] %v", err)
m.RUnlock()
if m.options.DisableOnDefault {
m.disable(gid)
m.Disable(gid)
} else {
m.enable(gid)
m.Enable(gid)
}
return !m.options.DisableOnDefault
}
@@ -92,21 +92,21 @@ func (m *Control) isEnabledIn(gid int64) bool {
func (m *Control) Handler() zero.Rule {
return func(ctx *zero.Ctx) bool {
ctx.State["manager"] = m
return m.isEnabledIn(ctx.Event.GroupID)
return m.IsEnabledIn(ctx.Event.GroupID)
}
}
// lookup returns a Manager by the service name, if
// Lookup returns a Manager by the service name, if
// not exist, it will returns nil.
func lookup(service string) (*Control, bool) {
func Lookup(service string) (*Control, bool) {
mu.RLock()
defer mu.RUnlock()
m, ok := managers[service]
return m, ok
}
// forEach iterates through managers.
func forEach(iterator func(key string, manager *Control) bool) {
// ForEach iterates through managers.
func ForEach(iterator func(key string, manager *Control) bool) {
mu.RLock()
m := copyMap(managers)
mu.RUnlock()
@@ -138,11 +138,11 @@ func init() {
Handle(func(ctx *zero.Ctx) {
model := extension.CommandModel{}
_ = ctx.Parse(&model)
service, ok := lookup(model.Args)
service, ok := Lookup(model.Args)
if !ok {
ctx.Send("没有找到指定服务!")
}
service.enable(ctx.Event.GroupID)
service.Enable(ctx.Event.GroupID)
ctx.Send(message.Text("已启用服务: " + model.Args))
})
@@ -150,11 +150,11 @@ func init() {
Handle(func(ctx *zero.Ctx) {
model := extension.CommandModel{}
_ = ctx.Parse(&model)
service, ok := lookup(model.Args)
service, ok := Lookup(model.Args)
if !ok {
ctx.Send("没有找到指定服务!")
}
service.disable(ctx.Event.GroupID)
service.Disable(ctx.Event.GroupID)
ctx.Send(message.Text("已关闭服务: " + model.Args))
})
@@ -162,7 +162,7 @@ func init() {
Handle(func(ctx *zero.Ctx) {
model := extension.CommandModel{}
_ = ctx.Parse(&model)
service, ok := lookup(model.Args)
service, ok := Lookup(model.Args)
if !ok {
ctx.Send("没有找到指定服务!")
}
@@ -177,10 +177,10 @@ func init() {
Handle(func(ctx *zero.Ctx) {
msg := `---服务列表---`
i := 0
forEach(func(key string, manager *Control) bool {
ForEach(func(key string, manager *Control) bool {
i++
msg += "\n" + strconv.Itoa(i) + `: `
if manager.isEnabledIn(ctx.Event.GroupID) {
if manager.IsEnabledIn(ctx.Event.GroupID) {
msg += "●" + key
} else {
msg += "○" + key

View File

@@ -1,4 +1,4 @@
package control
package web
import (
"encoding/json"
@@ -17,6 +17,8 @@ import (
log "github.com/sirupsen/logrus"
zero "github.com/wdvxdr1123/ZeroBot"
"github.com/wdvxdr1123/ZeroBot/message"
ctrl "github.com/FloatTech/ZeroBot-Plugin/control"
)
var (
@@ -34,8 +36,8 @@ var (
type logWriter struct {
}
// InitGui 初始化gui
func InitGui() {
// initGui 初始化gui
func initGui() {
// 将日志重定向到前端hook
writer := io.MultiWriter(l, os.Stderr)
log.SetOutput(writer)
@@ -86,8 +88,8 @@ func controller() {
// 获取插件列表
engine.POST("/get_plugins", func(context *gin.Context) {
var datas []map[string]interface{}
forEach(func(key string, manager *Control) bool {
datas = append(datas, map[string]interface{}{"id": 1, "handle_type": "", "name": key, "enable": manager.isEnabledIn(0)})
ctrl.ForEach(func(key string, manager *ctrl.Control) bool {
datas = append(datas, map[string]interface{}{"id": 1, "handle_type": "", "name": key, "enable": manager.IsEnabledIn(0)})
return true
})
context.JSON(200, datas)
@@ -134,14 +136,14 @@ func updateAllPluginStatus(context *gin.Context) {
return true
})
forEach(func(key string, manager *Control) bool {
ctrl.ForEach(func(key string, manager *ctrl.Control) bool {
if enable {
for _, group := range groups {
manager.enable(group)
manager.Enable(group)
}
} else {
for _, group := range groups {
manager.disable(group)
manager.Disable(group)
}
}
return true
@@ -168,7 +170,7 @@ func updatePluginAllGroupStatus(context *gin.Context) {
name = parse["name"].(string)
enable = parse["enable"].(bool)
}
control, b := lookup(name)
control, b := ctrl.Lookup(name)
if !b {
context.JSON(404, nil)
return
@@ -176,9 +178,9 @@ func updatePluginAllGroupStatus(context *gin.Context) {
zero.RangeBot(func(id int64, ctx *zero.Ctx) bool {
for _, group := range ctx.GetGroupList().Array() {
if enable {
control.enable(group.Get("group_id").Int())
control.Enable(group.Get("group_id").Int())
} else {
control.disable(group.Get("group_id").Int())
control.Disable(group.Get("group_id").Int())
}
}
@@ -205,15 +207,15 @@ func updatePluginStatus(context *gin.Context) {
name := parse["name"].(string)
enable := parse["enable"].(bool)
fmt.Println(name)
control, b := lookup(name)
control, b := ctrl.Lookup(name)
if !b {
context.JSON(404, "服务不存在")
return
}
if enable {
control.enable(groupID)
control.Enable(groupID)
} else {
control.disable(groupID)
control.Disable(groupID)
}
context.JSON(200, nil)
}
@@ -237,12 +239,12 @@ func getPluginStatus(context *gin.Context) {
groupID = int64(parse["group_id"].(float64))
name = parse["name"].(string)
}
control, b := lookup(name)
control, b := ctrl.Lookup(name)
if !b {
context.JSON(404, "服务不存在")
return
}
context.JSON(200, gin.H{"enable": control.isEnabledIn(groupID)})
context.JSON(200, gin.H{"enable": control.IsEnabledIn(groupID)})
}
// getPluginsStatus
@@ -263,8 +265,8 @@ func getPluginsStatus(context *gin.Context) {
groupID = int64(parse["group_id"].(float64))
}
var datas []map[string]interface{}
forEach(func(key string, manager *Control) bool {
enable := manager.isEnabledIn(groupID)
ctrl.ForEach(func(key string, manager *ctrl.Control) bool {
enable := manager.IsEnabledIn(groupID)
datas = append(datas, map[string]interface{}{"name": key, "enable": enable})
return true
})

11
control/web/init.go Normal file
View File

@@ -0,0 +1,11 @@
// Package web 网页管理后端
package web
import "flag"
func init() {
// 解析命令行参数,输入 `-g` 即可启用 gui
if *flag.Bool("g", false, "Enable web gui.") {
initGui()
}
}

17
data/math.go Normal file
View File

@@ -0,0 +1,17 @@
package data
// min 返回两数最大值,该函数将被内联
func Max(a, b int) int {
if a > b {
return a
}
return b
}
// min 返回两数最小值,该函数将被内联
func Min(a, b int) int {
if a > b {
return b
}
return a
}

49
dyloader/scan.go Normal file
View File

@@ -0,0 +1,49 @@
//go:build !windows
// +build !windows
package dyloader
import (
"io/fs"
"path/filepath"
"plugin"
"strings"
"github.com/sirupsen/logrus"
zero "github.com/wdvxdr1123/ZeroBot"
"github.com/wdvxdr1123/ZeroBot/message"
)
func init() {
_ = scan()
zero.OnCommand("刷新插件", zero.SuperUserPermission).SetBlock(true).FirstPriority().
Handle(func(ctx *zero.Ctx) {
err := scan()
if err != nil {
ctx.SendChain(message.Text("Error: " + err.Error()))
}
})
}
func scan() error {
return filepath.WalkDir("plugins/", load)
}
func load(path string, d fs.DirEntry, err error) error {
if err != nil {
return err
}
if d.IsDir() {
return nil
}
if strings.HasSuffix(d.Name(), ".so") {
_, err = plugin.Open(path)
if err == nil {
logrus.Infoln("[dyloader]加载插件", path, "成功")
}
if err != nil {
logrus.Errorln("[dyloader]加载插件", path, "错误:", err)
}
}
return nil
}

4
dyloader/winign.go Normal file
View File

@@ -0,0 +1,4 @@
//go:build windows
// +build windows
package dyloader

57
main.go
View File

@@ -3,13 +3,16 @@ package main
import (
"flag"
"fmt"
"os"
"strings"
// 注:以下插件均可通过前面加 // 注释,注释后停用并不加载插件
// 下列插件可与 wdvxdr1123/ZeroBot v1.1.2 以上配合单独使用
// 词库类
"github.com/sirupsen/logrus"
// 插件控制
//_ "github.com/FloatTech/ZeroBot-Plugin/control/web" // web 后端控制
// 词库类
_ "github.com/FloatTech/ZeroBot-Plugin/plugin_atri" // ATRI词库
_ "github.com/FloatTech/ZeroBot-Plugin/plugin_chat" // 基础词库
_ "github.com/FloatTech/ZeroBot-Plugin/plugin_qingyunke" // 青云客
@@ -45,11 +48,10 @@ import (
_ "github.com/FloatTech/ZeroBot-Plugin/plugin_setutime" // 来份涩图
// 以下为内置依赖,勿动
"github.com/sirupsen/logrus"
zero "github.com/wdvxdr1123/ZeroBot"
"github.com/wdvxdr1123/ZeroBot/driver"
"github.com/FloatTech/ZeroBot-Plugin/control"
//_ "github.com/FloatTech/ZeroBot-Plugin/dyloader"
)
var (
@@ -60,30 +62,45 @@ var (
"* Project: https://github.com/FloatTech/ZeroBot-Plugin",
}
banner = strings.Join(contents, "\n")
token *string
url *string
)
func init() {
var en bool
var debg bool
// 解析命令行参数,输入 `-g` 即可启用 gui
flag.BoolVar(&en, "g", false, "Enable web gui.")
// 解析命令行参数,输入 `-d` 即可开启 debug log
flag.BoolVar(&debg, "d", false, "Enable debug log.")
// 解析命令行参数
d := flag.Bool("d", false, "Enable debug level log and higher.")
w := flag.Bool("w", false, "Enable warning level log and higher.")
h := flag.Bool("h", false, "Display this help.")
// 直接写死 AccessToken 时,请更改下面第二个参数
token = flag.String("t", "", "Set AccessToken of WSClient.")
// 直接写死 URL 时,请更改下面第二个参数
url = flag.String("u", "ws://127.0.0.1:6700", "Set Url of WSClient.")
flag.Parse()
if en {
control.InitGui()
}
if debg {
logrus.SetLevel(logrus.DebugLevel)
if *h {
printBanner()
fmt.Println("Usage:")
flag.PrintDefaults()
os.Exit(0)
} else {
if *d && !*w {
logrus.SetLevel(logrus.DebugLevel)
}
if *w {
logrus.SetLevel(logrus.WarnLevel)
}
}
}
func main() {
func printBanner() {
fmt.Print(
"\n======================[ZeroBot-Plugin]======================",
"\n", banner, "\n",
"============================================================\n",
) // 启动打印
)
}
func main() {
printBanner()
zero.Run(zero.Config{
NickName: []string{"椛椛", "ATRI", "atri", "亚托莉", "アトリ"},
CommandPrefix: "/",
@@ -96,8 +113,8 @@ func main() {
Driver: []zero.Driver{
&driver.WSClient{
// OneBot 正向WS 默认使用 6700 端口
Url: "ws://127.0.0.1:6700",
AccessToken: "",
Url: *url,
AccessToken: *token,
},
},
})

View File

@@ -53,14 +53,20 @@ func init() {
"- 运势|抽签\n" +
"- 设置底图[车万 DC4 爱因斯坦 星空列车 樱云之恋 富婆妹 李清歌 公主连结 原神 明日方舟 碧蓝航线 碧蓝幻想 战双 阴阳师]",
})
en.OnRegex(`^设置底图(.*)`, zero.OnlyGroup).SetBlock(true).SecondPriority().
en.OnRegex(`^设置底图(.*)`).SetBlock(true).SecondPriority().
Handle(func(ctx *zero.Ctx) {
gid := ctx.Event.GroupID
if gid <= 0 {
// 个人用户设为负数
gid = -ctx.Event.UserID
}
i, ok := index[ctx.State["regex_matched"].([]string)[1]]
if ok {
conf.Kind[ctx.Event.GroupID] = i
conf.Kind[gid] = i
savecfg("cfg.pb")
ctx.SendChain(message.Text("设置成功~"))
} else {
ctx.Send("没有这个底图哦~")
ctx.SendChain(message.Text("没有这个底图哦~"))
}
})
en.OnFullMatchGroup([]string{"运势", "抽签"}).SetBlock(true).SecondPriority().
@@ -89,7 +95,12 @@ func init() {
}
// 获取该群背景类型,默认车万
kind := "车万"
if v, ok := conf.Kind[ctx.Event.GroupID]; ok {
gid := ctx.Event.GroupID
if gid <= 0 {
// 个人用户设为负数
gid = -ctx.Event.UserID
}
if v, ok := conf.Kind[gid]; ok {
kind = table[v]
}
// 检查背景图片是否存在
@@ -116,13 +127,13 @@ func init() {
t, _ := strconv.ParseInt(time.Now().Format("20060102"), 10, 64)
seed := ctx.Event.UserID + t
// 随机获取背景
background, err := randimage(base+kind+"/", seed)
background, err := randimage(folder+"/", seed)
if err != nil {
ctx.SendChain(message.Text("ERROR: ", err))
return
}
// 随机获取签文
title, text, err := randtext(base+"运势签文.json", seed)
title, text, err := randtext(mikuji, seed)
if err != nil {
ctx.SendChain(message.Text("ERROR: ", err))
return
@@ -235,25 +246,6 @@ func draw(background, title, text string) ([]byte, error) {
if err := canvas.LoadFontFace(base+"sakura.ttf", 23); err != nil {
return nil, err
}
offest := func(total, now int, distance float64) float64 {
if total%2 == 0 {
return (float64(now-total/2) - 1) * distance
}
return (float64(now-total/2) - 1.5) * distance
}
rowsnum := func(total, div int) int {
temp := total / div
if total%div != 0 {
temp++
}
return temp
}
min := func(a, b int) int {
if a < b {
return a
}
return b
}
tw, th := canvas.MeasureString("测")
tw, th = tw+10, th+10
r := []rune(text)
@@ -262,7 +254,7 @@ func draw(background, title, text string) ([]byte, error) {
default:
for i, o := range r {
xnow := rowsnum(i+1, 9)
ysum := min(len(r)-(xnow-1)*9, 9)
ysum := data.Min(len(r)-(xnow-1)*9, 9)
ynow := i%9 + 1
canvas.DrawString(string(o), -offest(xsum, xnow, tw)+115, offest(ysum, ynow, th)+320.0)
}
@@ -270,7 +262,7 @@ func draw(background, title, text string) ([]byte, error) {
div := rowsnum(len(r), 2)
for i, o := range r {
xnow := rowsnum(i+1, div)
ysum := min(len(r)-(xnow-1)*div, div)
ysum := data.Min(len(r)-(xnow-1)*div, div)
ynow := i%div + 1
switch xnow {
case 1:
@@ -292,3 +284,18 @@ func draw(background, title, text string) ([]byte, error) {
encoder.Close()
return buffer.Bytes(), nil
}
func offest(total, now int, distance float64) float64 {
if total%2 == 0 {
return (float64(now-total/2) - 1) * distance
}
return (float64(now-total/2) - 1.5) * distance
}
func rowsnum(total, div int) int {
temp := total / div
if total%div != 0 {
temp++
}
return temp
}

View File

@@ -11,6 +11,7 @@ import (
"github.com/wdvxdr1123/ZeroBot/message"
"github.com/FloatTech/ZeroBot-Plugin/control"
"github.com/FloatTech/ZeroBot-Plugin/data"
)
const (
@@ -30,7 +31,7 @@ func init() {
}).OnFullMatch("来份萝莉").SetBlock(true).
Handle(func(ctx *zero.Ctx) {
go func() {
for i := 0; i < min(cap(queue)-len(queue), 2); i++ {
for i := 0; i < data.Min(cap(queue)-len(queue), 2); i++ {
resp, err := http.Get(api)
if err != nil {
ctx.SendChain(message.Text("ERROR: ", err))
@@ -60,10 +61,3 @@ func init() {
}
})
}
func min(a, b int) int {
if a < b {
return a
}
return b
}

View File

@@ -16,6 +16,8 @@ import (
"github.com/wdvxdr1123/ZeroBot/message"
timer "github.com/FloatTech/ZeroBot-Plugin-Timer"
"github.com/FloatTech/ZeroBot-Plugin/data"
)
const (
@@ -300,13 +302,7 @@ func init() { // 插件主体
sort.SliceStable(temp, func(i, j int) bool {
return temp[i].Get("last_sent_time").Int() < temp[j].Get("last_sent_time").Int()
})
max := func(a, b int) int {
if a > b {
return a
}
return b
}
temp = temp[max(0, len(temp)-10):]
temp = temp[data.Max(0, len(temp)-10):]
rand.Seed(time.Now().UnixNano())
who := temp[rand.Intn(len(temp))]
if who.Get("user_id").Int() == ctx.Event.SelfID {

View File

@@ -106,7 +106,7 @@ func init() { // 插件主体
var imgtype = ctx.State["regex_matched"].([]string)[1]
// 补充池子
go func() {
times := min(pool.Max-pool.size(imgtype), 2)
times := data.Min(pool.Max-pool.size(imgtype), 2)
for i := 0; i < times; i++ {
illust := &pixiv.Illust{}
// 查询出一张图片
@@ -122,7 +122,6 @@ func init() { // 插件主体
ctx.SendGroupMessage(pool.Group, []message.MessageSegment{message.Image(file(illust))})
// 向缓冲池添加一张图片
pool.push(imgtype, illust)
time.Sleep(time.Second * 1)
}
}()
@@ -217,18 +216,6 @@ func firstValueInList(list []string) zero.Rule {
}
}
// min 返回两数最小值
func min(a, b int) int {
switch {
default:
return a
case a > b:
return b
case a < b:
return a
}
}
// size 返回缓冲池指定类型的现有大小
func (p *imgpool) size(imgtype string) int {
return len(p.Pool[imgtype])