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 основе. Фразу настроить невозможно. (не хотелось
реализовывать парсинг темплейтов, как минимум даты-времени)
Бан за фразы
------------------------------------------------------------------------------------------------------------------------
Бот не читает историю, когда присоединяется к чатам, поэтому если его какое-то время не было в комнате и кто-то произнёс
непотребную фразу, ретроспективного бана не будет.

View File

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

97
buny.go
View File

@@ -11,9 +11,9 @@ import (
log "github.com/sirupsen/logrus"
)
// buny производит проверку по бело-чёрным спискам. Если presence пришёл от злодея (из чёрного списка), то отправляет
// его в бан.
func buny(v xmpp.Presence) error { //nolint:gocognit,gocyclo
// bunyPresense производит проверку по бело-чёрным спискам. Если presence пришёл от злодея (из чёрного списка), то
// отправляет его в бан.
func bunyPresense(v xmpp.Presence) error { //nolint:gocognit,gocyclo
var err error
// Если у presence-а есть JID и presence из одной из комнат, в которой мы есть и если его домен в чёрном
@@ -217,6 +217,97 @@ func buny(v xmpp.Presence) error { //nolint:gocognit,gocyclo
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 в указанной комнате.
// reasonEnable указывает, надо ли писать дату автобана в банлисте в поле reason (это единственная причина, в которую
// умеет бот).

View File

@@ -2,6 +2,7 @@ package main
import (
"fmt"
"slices"
"strings"
"github.com/davecgh/go-spew/spew"
@@ -128,6 +129,73 @@ func cmd(v xmpp.Chat) error {
}
}
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:
log.Infof("Rehash command from outer space: %s", spew.Sdump(v))
}

View File

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

View File

@@ -33,7 +33,7 @@ func parseEvent(e interface{}) { //nolint:maintidx,gocognit,gocyclo
// Групповой чятик
case "groupchat":
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]
if nick == config.Jabber.Nick {
@@ -48,6 +48,12 @@ func parseEvent(e interface{}) { //nolint:maintidx,gocognit,gocyclo
return
}
if err := bunyChat(v); err != nil {
gTomb.Kill(err)
return
}
lastActivity = lastServerActivity
if muc, _ := strings.CutSuffix(v.Remote, "/"); muc != "" {
@@ -111,7 +117,7 @@ func parseEvent(e interface{}) { //nolint:maintidx,gocognit,gocyclo
if id, err := talk.IqVersionResponse(
v,
"buny-jabber-bot",
"bunyPresense-jabber-bot",
"1.с_чем-то",
"линупс",
); err != nil {
@@ -592,7 +598,7 @@ func parseEvent(e interface{}) { //nolint:maintidx,gocognit,gocyclo
// По правилам, мы можем что-то делать, только после того, как нам прилетит наш собственный presence, это
// значит, что мы вошли в комнату.
if slices.Contains(roomsConnected, room) {
if err := buny(v); err != nil {
if err := bunyPresense(v); err != nil {
gTomb.Kill(err)
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))
locations := []string{
"~/.buny-jabber-bot.json",
"~/buny-jabber-bot.json",
"/etc/buny-jabber-bot.json",
"~/.bunyPresense-jabber-bot.json",
"~/bunyPresense-jabber-bot.json",
"/etc/bunyPresense-jabber-bot.json",
configJSONPath,
}
@@ -221,9 +221,9 @@ func readConfig() error { //nolint:gocognit,gocyclo
}
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 == "" {
@@ -309,9 +309,9 @@ func readWhitelist() error {
whitelistJSONPath := fmt.Sprintf("%s/data/whitelist.json", filepath.Dir(executablePath))
locations := []string{
"~/.buny-jabber-bot-whitelist.json",
"~/buny-jabber-bot-whitelist.json",
"/etc/buny-jabber-bot-whitelist.json",
"~/.bunyPresense-jabber-bot-whitelist.json",
"~/bunyPresense-jabber-bot-whitelist.json",
"/etc/bunyPresense-jabber-bot-whitelist.json",
whitelistJSONPath,
}
@@ -405,9 +405,9 @@ func readBlacklist() error {
whitelistJSONPath := fmt.Sprintf("%s/data/blacklist.json", filepath.Dir(executablePath))
locations := []string{
"~/.buny-jabber-bot-blacklist.json",
"~/buny-jabber-bot-blacklist.json",
"/etc/buny-jabber-bot-blacklist.json",
"~/.bunyPresense-jabber-bot-blacklist.json",
"~/bunyPresense-jabber-bot-blacklist.json",
"/etc/bunyPresense-jabber-bot-blacklist.json",
whitelistJSONPath,
}
@@ -618,7 +618,7 @@ func joinMuc(room string) {
_ = json.Unmarshal([]byte(name), &v)
log.Infof("Fake presence forged for %s just for on-enter check", name)
// Оно там внутри всё само обработает, если вдруг возникнет wire error, то зарекконетится.
_ = buny(v)
_ = bunyPresense(v)
}
}
}

View File

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