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:
@@ -61,3 +61,9 @@ Cуществует несколько видов пинга:
|
||||
------------------------------------------------------------------------------------------------------------------------
|
||||
Причину бана можно только включить или выключить в конфиге на per-chat основе. Фразу настроить невозможно. (не хотелось
|
||||
реализовывать парсинг темплейтов, как минимум даты-времени)
|
||||
|
||||
|
||||
Бан за фразы
|
||||
------------------------------------------------------------------------------------------------------------------------
|
||||
Бот не читает историю, когда присоединяется к чатам, поэтому если его какое-то время не было в комнате и кто-то произнёс
|
||||
непотребную фразу, ретроспективного бана не будет.
|
||||
|
||||
@@ -12,8 +12,9 @@
|
||||
без поддержки механизма start tls. Что-то в гошке или на стороне сервера при выборе StartTLS не работает.
|
||||
|
||||
Имеет возможность заносить пользователей в бан-лист, согласно заданным в чёрном списке правилам: по совпадению с
|
||||
регулярными выражениями в nick-е или jid-е злодея. Правила можно настроимть как глобально, для всех комнат, где
|
||||
присутсвует бот, так и для каждой комнаты отдельно. При изменении списка правил бота надо перезапускать.
|
||||
регулярными выражениями в nick-е или jid-е злодея, а также по регулярным выражениям характерных фраз. Правила можно
|
||||
настроить как глобально, для всех комнат, где присутсвует бот, так и для каждой комнаты отдельно. При изменении списка
|
||||
правил бота надо перезапускать.
|
||||
|
||||
## Что он не может?
|
||||
|
||||
|
||||
97
buny.go
97
buny.go
@@ -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 (это единственная причина, в которую
|
||||
// умеет бот).
|
||||
|
||||
68
commands.go
68
commands.go
@@ -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))
|
||||
}
|
||||
|
||||
@@ -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.$"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -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
24
lib.go
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user