diff --git a/main.go b/main.go index 625bb402..fbb7013b 100644 --- a/main.go +++ b/main.go @@ -24,8 +24,8 @@ import ( // vvvvvvv高优先级区vvvvvvv // // vvvvvvvvvvvvvv // // vvvv // - - _ "github.com/FloatTech/ZeroBot-Plugin/plugin/chat" // 基础词库 + _ "github.com/FloatTech/ZeroBot-Plugin/plugin/antiabuse" // 违禁词 + _ "github.com/FloatTech/ZeroBot-Plugin/plugin/chat" // 基础词库 _ "github.com/FloatTech/ZeroBot-Plugin/plugin/sleep_manage" // 统计睡眠时间 diff --git a/plugin/antiabuse/anti.go b/plugin/antiabuse/anti.go new file mode 100644 index 00000000..af337959 --- /dev/null +++ b/plugin/antiabuse/anti.go @@ -0,0 +1,76 @@ +// Package antiabuse defines anti_abuse plugin ,support abuse words check and add/remove abuse words +package antiabuse + +import ( + "fmt" + "strings" + "time" + + fcext "github.com/FloatTech/floatbox/ctxext" + ctrl "github.com/FloatTech/zbpctrl" + "github.com/FloatTech/zbputils/control" + zero "github.com/wdvxdr1123/ZeroBot" + "github.com/wdvxdr1123/ZeroBot/message" +) + +func init() { + engine := control.Register("anti_abuse", &ctrl.Options[*zero.Ctx]{ + DisableOnDefault: false, + Help: "违禁词检测", + PrivateDataFolder: "anti_abuse", + }) + onceRule := fcext.DoOnceOnSuccess(func(ctx *zero.Ctx) bool { + db.DBPath = engine.DataFolder() + "anti_abuse.db" + err := db.Open(time.Hour * 4) + if err != nil { + ctx.SendChain(message.Text("open db error: ", err)) + return false + } + err = db.Create("banUser", &banUser{}) + if err != nil { + ctx.SendChain(message.Text("create table error: ", err)) + return false + } + err = db.Create("banWord", &banWord{}) + if err != nil { + ctx.SendChain(message.Text("create table error: ", err)) + return false + } + err = recoverUser() + if err != nil { + ctx.SendChain(message.Text("recover data error: ", err)) + return false + } + err = recoverWord() + if err != nil { + ctx.SendChain(message.Text("recover data error: ", err)) + return false + } + return true + }) + engine.OnMessage(zero.OnlyGroup, onceRule, banRule) + engine.OnCommand("添加违禁词", zero.OnlyGroup, zero.AdminPermission, onceRule).Handle( + func(ctx *zero.Ctx) { + if err := insertWord(ctx.Event.GroupID, ctx.State["args"].(string)); err != nil { + ctx.SendChain(message.Text("add ban word error:", err)) + } + }) + engine.OnCommand("删除违禁词", zero.OnlyGroup, zero.AdminPermission, onceRule).Handle( + func(ctx *zero.Ctx) { + if err := deleteWord(ctx.Event.GroupID, ctx.State["args"].(string)); err != nil { + ctx.SendChain(message.Text("add ban word error:", err)) + } + }) + engine.OnCommand("查看违禁词", zero.OnlyGroup, onceRule).Handle( + func(ctx *zero.Ctx) { + gidPrefix := fmt.Sprintf("%d-", ctx.Event.GroupID) + var words []string + _ = wordSet.Iter(func(s string) error { + trueWord := strings.SplitN(s, gidPrefix, 1)[1] + words = append(words, trueWord) + return nil + }) + ctx.SendChain(message.Text("本群违禁词有:\n", strings.Join(words, " |"))) + }) + +} diff --git a/plugin/antiabuse/database.go b/plugin/antiabuse/database.go new file mode 100644 index 00000000..3fcec42d --- /dev/null +++ b/plugin/antiabuse/database.go @@ -0,0 +1,75 @@ +package antiabuse + +import ( + "fmt" + "time" + + sqlite "github.com/FloatTech/sqlite" +) + +var db = &sqlite.Sqlite{} + +type banUser struct { + UUID string `db:"uuid"` + DueTime int64 `db:"due_time"` +} + +func insertUser(gid, uid int64) error { + obj := &banUser{fmt.Sprintf("%d-%d", gid, uid), time.Now().Add(4 * time.Hour).UnixNano()} + return db.Insert("banUser", obj) +} + +func deleteUser(gid, uid int64) error { + sql := fmt.Sprintf("WHERE uuid=%d-%d", gid, uid) + return db.Del("banUser", sql) +} + +func recoverUser() error { + obj := &banUser{} + var uuids []string + err := db.FindFor("banUser", obj, "", func() error { + if time.Now().UnixNano() < obj.DueTime { + uuids = append(uuids, obj.UUID) + } else { + if err := db.Del("banUser", "WHERE uuid="+obj.UUID); err != nil { + return err + } + } + return nil + }, + ) + if err != nil { + return err + } + banSet.AddMany(uuids) + return nil +} + +type banWord struct { + GroupWord string `db:"group_word"` +} + +func insertWord(gid int64, word string) error { + obj := &banWord{fmt.Sprintf("%d-%s", gid, word)} + return db.Insert("banWord", obj) +} + +func deleteWord(gid int64, word string) error { + sql := fmt.Sprintf("WHERE group_word = %d-%s", gid, word) + return db.Del("banWord", sql) +} + +func recoverWord() error { + obj := &banWord{} + var groupWords []string + err := db.FindFor("banWord", obj, "", func() error { + groupWords = append(groupWords, obj.GroupWord) + return nil + }, + ) + if err != nil { + return err + } + wordSet.AddMany(groupWords) + return nil +} diff --git a/plugin/antiabuse/set.go b/plugin/antiabuse/set.go new file mode 100644 index 00000000..ce153842 --- /dev/null +++ b/plugin/antiabuse/set.go @@ -0,0 +1,57 @@ +package antiabuse + +import "sync" + +//Set defines HashSet structure +type Set struct { + sync.RWMutex + m map[string]struct{} +} + +var banSet = &Set{m: make(map[string]struct{})} +var wordSet = &Set{m: make(map[string]struct{})} + +// Add adds element to Set +func (s *Set) Add(key string) { + s.Lock() + defer s.Unlock() + s.m[key] = struct{}{} +} + +// Include asserts element in Set +func (s *Set) Include(key string) bool { + s.RLock() + defer s.RUnlock() + _, ok := s.m[key] + return ok +} + +// Iter calls f when traversing Set +func (s *Set) Iter(f func(string) error) error { + s.Lock() + defer s.Unlock() + var err error + for key := range s.m { + err = f(key) + if err != nil { + return err + } + } + return nil +} + +// Remove removes element from Set +func (s *Set) Remove(key string) { + s.Lock() + defer s.Unlock() + delete(s.m, key) +} + +// AddMany adds multiple elements to Set +func (s *Set) AddMany(keys []string) { + s.Lock() + defer s.Unlock() + for _, k := range keys { + s.m[k] = struct{}{} + } +} diff --git a/plugin/antiabuse/utils.go b/plugin/antiabuse/utils.go new file mode 100644 index 00000000..8a7ec8aa --- /dev/null +++ b/plugin/antiabuse/utils.go @@ -0,0 +1,44 @@ +package antiabuse + +import ( + "fmt" + "strings" + "time" + + zero "github.com/wdvxdr1123/ZeroBot" + "github.com/wdvxdr1123/ZeroBot/message" +) + +func banRule(ctx *zero.Ctx) bool { + gid := ctx.Event.GroupID + uid := ctx.Event.UserID + uuid := fmt.Sprintf("%d-%d", gid, uid) + if banSet.Include(uuid) { + return false + } + gidPrefix := fmt.Sprintf("%d-", ctx.Event.GroupID) + var words []string + _ = wordSet.Iter(func(s string) error { + trueWord := strings.SplitN(s, gidPrefix, 1)[1] + words = append(words, trueWord) + return nil + }) + for _, word := range words { + if strings.Contains(ctx.MessageString(), word) { + if err := insertUser(gid, uid); err != nil { + ctx.SendChain(message.Text("ban error: ", err)) + } + banSet.Add(uuid) + ctx.SetGroupBan(gid, uid, 4*3600) + time.AfterFunc(4*time.Hour, func() { + banSet.Remove(uuid) + if err := deleteUser(gid, uid); err != nil { + ctx.SendChain(message.Text("ban error: ", err)) + } + }) + ctx.SendChain(message.Text("检测到违禁词,已封禁/屏蔽4小时")) + return false + } + } + return true +}