Implement ban by phrase regexp, extend rehash command

* ban by blacklisted phrase regex-es
* rehash now works not only in rooms, but in private messages from rooms too
This commit is contained in:
eleksir
2023-10-01 19:42:09 -07:00
parent 48541ce14d
commit 998a827d4b
8 changed files with 202 additions and 21 deletions

View File

@@ -61,3 +61,9 @@ Cуществует несколько видов пинга:
------------------------------------------------------------------------------------------------------------------------ ------------------------------------------------------------------------------------------------------------------------
Причину бана можно только включить или выключить в конфиге на per-chat основе. Фразу настроить невозможно. (не хотелось Причину бана можно только включить или выключить в конфиге на per-chat основе. Фразу настроить невозможно. (не хотелось
реализовывать парсинг темплейтов, как минимум даты-времени) реализовывать парсинг темплейтов, как минимум даты-времени)
Бан за фразы
------------------------------------------------------------------------------------------------------------------------
Бот не читает историю, когда присоединяется к чатам, поэтому если его какое-то время не было в комнате и кто-то произнёс
непотребную фразу, ретроспективного бана не будет.

View File

@@ -12,8 +12,9 @@
без поддержки механизма start tls. Что-то в гошке или на стороне сервера при выборе StartTLS не работает. без поддержки механизма start tls. Что-то в гошке или на стороне сервера при выборе StartTLS не работает.
Имеет возможность заносить пользователей в бан-лист, согласно заданным в чёрном списке правилам: по совпадению с Имеет возможность заносить пользователей в бан-лист, согласно заданным в чёрном списке правилам: по совпадению с
регулярными выражениями в nick-е или jid-е злодея. Правила можно настроимть как глобально, для всех комнат, где регулярными выражениями в nick-е или jid-е злодея, а также по регулярным выражениям характерных фраз. Правила можно
присутсвует бот, так и для каждой комнаты отдельно. При изменении списка правил бота надо перезапускать. настроить как глобально, для всех комнат, где присутсвует бот, так и для каждой комнаты отдельно. При изменении списка
правил бота надо перезапускать.
## Что он не может? ## Что он не может?

97
buny.go
View File

@@ -11,9 +11,9 @@ import (
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
) )
// buny производит проверку по бело-чёрным спискам. Если presence пришёл от злодея (из чёрного списка), то отправляет // bunyPresense производит проверку по бело-чёрным спискам. Если presence пришёл от злодея (из чёрного списка), то
// его в бан. // отправляет его в бан.
func buny(v xmpp.Presence) error { //nolint:gocognit,gocyclo func bunyPresense(v xmpp.Presence) error { //nolint:gocognit,gocyclo
var err error var err error
// Если у presence-а есть JID и presence из одной из комнат, в которой мы есть и если его домен в чёрном // Если у presence-а есть JID и presence из одной из комнат, в которой мы есть и если его домен в чёрном
@@ -217,6 +217,97 @@ func buny(v xmpp.Presence) error { //nolint:gocognit,gocyclo
return err return err
} }
// bunyChat проиводит проверку сообщений участников чата по списку забаненных фраз и в случае нахождения запрещённого
// шаблона банит участника чата.
func bunyChat(v xmpp.Chat) error {
var (
room = (strings.SplitN(v.Remote, "/", 2))[0]
// :wq
// nick = (strings.SplitN(v.Remote, "/", 2))[1]
err error
)
for _, cRoom := range roomsConnected {
if cRoom == room {
for _, bEntry := range blackList.Blacklist {
// Обработаем правила глобального чёрного списка
if bEntry.RoomName == "" {
for _, phraseRegexp := range bEntry.PhraseRe {
if phraseRegexp == "" {
continue
}
re, err := regexp.Compile(phraseRegexp)
if err != nil {
log.Errorf("Incorrect regexp in room %s blacklist: %s, skipping", room, phraseRegexp)
continue
}
log.Debugf("Checking phrase %s vs room %s blacklist regex %s", v.Text, room, phraseRegexp)
if re.MatchString(v.Text) {
if id, err := squash(room, getRealJIDfromNick(v.Remote), bEntry.ReasonEnable, v.Type); err != nil {
err := fmt.Errorf(
"unable to ban user: id=%s, err=%w",
id,
err,
)
gTomb.Kill(err)
continue
}
return err
}
}
}
// Обработаем правила чёрного списка для конкретной комнаты
if bEntry.RoomName == room {
for _, phraseRegexp := range bEntry.PhraseRe {
if phraseRegexp == "" {
continue
}
re, err := regexp.Compile(phraseRegexp)
if err != nil {
log.Errorf("Incorrect regexp in room %s blacklist: %s, skipping", room, phraseRegexp)
continue
}
log.Debugf("Checking phrase %s vs room %s blacklist regex %s", v.Text, room, phraseRegexp)
if re.MatchString(v.Text) {
if id, err := squash(room, getRealJIDfromNick(v.Remote), bEntry.ReasonEnable, v.Type); err != nil {
err := fmt.Errorf(
"unable to ban user: id=%s, err=%w",
id,
err,
)
gTomb.Kill(err)
continue
}
return err
}
}
}
}
break
}
}
return err
}
// squash банит указанный jid в указанной комнате. // squash банит указанный jid в указанной комнате.
// reasonEnable указывает, надо ли писать дату автобана в банлисте в поле reason (это единственная причина, в которую // reasonEnable указывает, надо ли писать дату автобана в банлисте в поле reason (это единственная причина, в которую
// умеет бот). // умеет бот).

View File

@@ -2,6 +2,7 @@ package main
import ( import (
"fmt" "fmt"
"slices"
"strings" "strings"
"github.com/davecgh/go-spew/spew" "github.com/davecgh/go-spew/spew"
@@ -128,6 +129,73 @@ func cmd(v xmpp.Chat) error {
} }
} }
case "chat": case "chat":
room := (strings.SplitN(v.Remote, "/", 2))[0]
// Реагировать на приватную команду "из комнаты", только если бот в комнате
if slices.Contains(roomsConnected, room) {
var (
chosenOneTalks = false
listsLoaded = true
)
// Реагировать на rehash только если собеседник является bot master-ом
realJID := getRealJIDfromNick(v.Remote)
for _, master := range config.Jabber.BotMasters {
if (strings.SplitN(realJID, "/", 2))[0] == master {
chosenOneTalks = true
}
}
if !chosenOneTalks {
log.Infof(
"Command %srehash given by non-bot_master user %s(%s), ignoring",
config.CSign,
realJID,
v.Remote,
)
return err
}
if err := readWhitelist(); err != nil {
listsLoaded = false
var msg xmpp.Chat
msg.Remote = v.Remote
msg.Type = v.Type
msg.Text = fmt.Sprint(err)
if _, err := talk.Send(msg); err != nil {
return err
}
}
if err := readBlacklist(); err != nil {
listsLoaded = false
var msg xmpp.Chat
msg.Remote = v.Remote
msg.Type = v.Type
msg.Text = fmt.Sprint(err)
if _, err := talk.Send(msg); err != nil {
return err
}
}
if listsLoaded {
var msg xmpp.Chat
msg.Remote = v.Remote
msg.Type = v.Type
msg.Text = "Сделано"
if _, err := talk.Send(msg); err != nil {
return err
}
}
}
default: default:
log.Infof("Rehash command from outer space: %s", spew.Sdump(v)) log.Infof("Rehash command from outer space: %s", spew.Sdump(v))
} }

View File

@@ -3,9 +3,11 @@
{ {
# Пустой список означает глобальные баны, которые применяются ко всем комнатам, где присутствует бот # Пустой список означает глобальные баны, которые применяются ко всем комнатам, где присутствует бот
"room_name": "", "room_name": "",
# Записывать ли в reason в банлисте комнаты сообщение вида autoban at yyyy.MM.dd hh:mm:ss # Записывать ли в reason в банлисте комнаты сообщение вида autoban at yyyy.MM.dd hh:mm:ss
# Если не задано, то false # Если не задано, то false
"reason_enable": false, "reason_enable": false,
# Список регулярок JID-ов, которых надо банить # Список регулярок JID-ов, которых надо банить
"jid_re": [ "jid_re": [
"^[Mm]ary@server.tld/resource1$", "^[Mm]ary@server.tld/resource1$",
@@ -13,10 +15,16 @@
"^(his|her)_cat@sweet.home/.*$", "^(his|her)_cat@sweet.home/.*$",
"^(her|his)_dog@.*\\.street/.*" "^(her|his)_dog@.*\\.street/.*"
], ],
# Список регулярок nick-ов злодеев # Список регулярок nick-ов злодеев
"nick_re": [ "nick_re": [
"^(Annoying_)?[F|f]ly$", "^(Annoying_)?[F|f]ly$",
"^Buzzing_Wasp$" "^Buzzing_Wasp$"
],
# Список регулярок, запрещённых к упоминанию в чяте
"phrase_re": [
"^Exterminate.$"
] ]
} }
] ]

View File

@@ -33,7 +33,7 @@ func parseEvent(e interface{}) { //nolint:maintidx,gocognit,gocyclo
// Групповой чятик // Групповой чятик
case "groupchat": case "groupchat":
log.Debugf("Message from public chat: %s", v.Text) log.Debugf("Message from public chat: %s", v.Text)
// dest := strings.SplitN(v.Remote, "/", 2)[0] // room := strings.SplitN(v.Remote, "/", 2)[0]
nick := strings.SplitN(v.Remote, "/", 2)[1] nick := strings.SplitN(v.Remote, "/", 2)[1]
if nick == config.Jabber.Nick { if nick == config.Jabber.Nick {
@@ -48,6 +48,12 @@ func parseEvent(e interface{}) { //nolint:maintidx,gocognit,gocyclo
return return
} }
if err := bunyChat(v); err != nil {
gTomb.Kill(err)
return
}
lastActivity = lastServerActivity lastActivity = lastServerActivity
if muc, _ := strings.CutSuffix(v.Remote, "/"); muc != "" { if muc, _ := strings.CutSuffix(v.Remote, "/"); muc != "" {
@@ -111,7 +117,7 @@ func parseEvent(e interface{}) { //nolint:maintidx,gocognit,gocyclo
if id, err := talk.IqVersionResponse( if id, err := talk.IqVersionResponse(
v, v,
"buny-jabber-bot", "bunyPresense-jabber-bot",
"1.с_чем-то", "1.с_чем-то",
"линупс", "линупс",
); err != nil { ); err != nil {
@@ -592,7 +598,7 @@ func parseEvent(e interface{}) { //nolint:maintidx,gocognit,gocyclo
// По правилам, мы можем что-то делать, только после того, как нам прилетит наш собственный presence, это // По правилам, мы можем что-то делать, только после того, как нам прилетит наш собственный presence, это
// значит, что мы вошли в комнату. // значит, что мы вошли в комнату.
if slices.Contains(roomsConnected, room) { if slices.Contains(roomsConnected, room) {
if err := buny(v); err != nil { if err := bunyPresense(v); err != nil {
gTomb.Kill(err) gTomb.Kill(err)
return return

24
lib.go
View File

@@ -88,9 +88,9 @@ func readConfig() error { //nolint:gocognit,gocyclo
configJSONPath := fmt.Sprintf("%s/data/config.json", filepath.Dir(executablePath)) configJSONPath := fmt.Sprintf("%s/data/config.json", filepath.Dir(executablePath))
locations := []string{ locations := []string{
"~/.buny-jabber-bot.json", "~/.bunyPresense-jabber-bot.json",
"~/buny-jabber-bot.json", "~/bunyPresense-jabber-bot.json",
"/etc/buny-jabber-bot.json", "/etc/bunyPresense-jabber-bot.json",
configJSONPath, configJSONPath,
} }
@@ -221,9 +221,9 @@ func readConfig() error { //nolint:gocognit,gocyclo
} }
if sampleConfig.Jabber.Resource == "" { if sampleConfig.Jabber.Resource == "" {
sampleConfig.Jabber.Resource = "buny bot" sampleConfig.Jabber.Resource = "bunyPresense bot"
log.Info("Jabber resource not defined in config, using buny bot") log.Info("Jabber resource not defined in config, using bunyPresense bot")
} }
if sampleConfig.Jabber.User == "" { if sampleConfig.Jabber.User == "" {
@@ -309,9 +309,9 @@ func readWhitelist() error {
whitelistJSONPath := fmt.Sprintf("%s/data/whitelist.json", filepath.Dir(executablePath)) whitelistJSONPath := fmt.Sprintf("%s/data/whitelist.json", filepath.Dir(executablePath))
locations := []string{ locations := []string{
"~/.buny-jabber-bot-whitelist.json", "~/.bunyPresense-jabber-bot-whitelist.json",
"~/buny-jabber-bot-whitelist.json", "~/bunyPresense-jabber-bot-whitelist.json",
"/etc/buny-jabber-bot-whitelist.json", "/etc/bunyPresense-jabber-bot-whitelist.json",
whitelistJSONPath, whitelistJSONPath,
} }
@@ -405,9 +405,9 @@ func readBlacklist() error {
whitelistJSONPath := fmt.Sprintf("%s/data/blacklist.json", filepath.Dir(executablePath)) whitelistJSONPath := fmt.Sprintf("%s/data/blacklist.json", filepath.Dir(executablePath))
locations := []string{ locations := []string{
"~/.buny-jabber-bot-blacklist.json", "~/.bunyPresense-jabber-bot-blacklist.json",
"~/buny-jabber-bot-blacklist.json", "~/bunyPresense-jabber-bot-blacklist.json",
"/etc/buny-jabber-bot-blacklist.json", "/etc/bunyPresense-jabber-bot-blacklist.json",
whitelistJSONPath, whitelistJSONPath,
} }
@@ -618,7 +618,7 @@ func joinMuc(room string) {
_ = json.Unmarshal([]byte(name), &v) _ = json.Unmarshal([]byte(name), &v)
log.Infof("Fake presence forged for %s just for on-enter check", name) log.Infof("Fake presence forged for %s just for on-enter check", name)
// Оно там внутри всё само обработает, если вдруг возникнет wire error, то зарекконетится. // Оно там внутри всё само обработает, если вдруг возникнет wire error, то зарекконетится.
_ = buny(v) _ = bunyPresense(v)
} }
} }
} }

View File

@@ -56,6 +56,7 @@ type myBlackList struct {
ReasonEnable bool `json:"reason_enable,omitempty"` ReasonEnable bool `json:"reason_enable,omitempty"`
JidRe []string `json:"jid_re,omitempty"` JidRe []string `json:"jid_re,omitempty"`
NickRe []string `json:"nick_re,omitempty"` NickRe []string `json:"nick_re,omitempty"`
PhraseRe []string `json:"phrase_re,omitempty"`
} `json:"blacklist,omitempty"` } `json:"blacklist,omitempty"`
} }