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