From 59b92f450d3722d7157009b4611d3b118ffcfa76 Mon Sep 17 00:00:00 2001 From: eleksir Date: Sat, 2 Sep 2023 10:22:39 -0700 Subject: [PATCH] Initial implementation of on-enter ban --- Design.txt | 5 ++++ README.md | 19 -------------- event_parser.go | 70 ++++++++++++++++++++++++++++++++++++++++++++----- globals.go | 3 +++ lib.go | 43 ++++++++++++++++++++++++++++++ main.go | 1 + 6 files changed, 115 insertions(+), 26 deletions(-) diff --git a/Design.txt b/Design.txt index 4bbed4f..ebc88c0 100644 --- a/Design.txt +++ b/Design.txt @@ -61,3 +61,8 @@ Cуществует несколько видов пинга: ------------------------------------------------------------------------------------------------------------------------ Причину бана можно только включить или выключить в конфиге на per-chat основе. Фразу настроить невозможно. (не хотелось реализовывать парсинг темплейтов, как минимум даты-времени) + + +Парсинг списка и бан здобеев на моменте входа в комнату +------------------------------------------------------------------------------------------------------------------------ +Бан реализован только для real jid-ов, но не для nick-ов. Пока непонятно, как изящно хранить и то и другое. diff --git a/README.md b/README.md index bc02b04..b97530b 100644 --- a/README.md +++ b/README.md @@ -55,25 +55,6 @@ make помещает этот jid в список изгнанных из комнаты (бан-лист конкретной комнаты, в которой присутствует бот) и выгоняет этого jid-а. -### В чём тонкость? - -Ровно в том, что для исполнения действия, боту надо заметить presence участника, а presence посылается -при изменении состояния клиента. То есть, если jid уже был в комнате на момент захода бота в неё, то его не забанят -(во всяком случае моментально). Самый простой способ поместить таких персонажей в бан - просто кикнуть их. Ихний -affiliation сменится на none, бот заметит presence и, если jid подпадает под правила чёрного списка, то отправит этот -jid в бан. - -Но, простите, ведь "по правилам", при заходе клиента в комнату боту присылается presence всех участников, чтобы он мог -построить локальный ростер комнаты и начать отслеживать изменения состояния участников конференции. Почему бы в этот -момент не банить злодеев? - -Да, так делать можно, однако пока этого нет. Дело в том, что presence самого себя участнику приходит последним (после -presence-ов тех, кто находится в комнате на момент появления в ней бота), и, согласно этому событию, можно считать, что -клиент зашёл в комнату успешно и формально *только после этого*, клиент может что-то пытаться делать в этой комнате. -Соответственно, чтобы кого-то банить исходя из списка presense-ов, который прилетает на входе, необходимо реализовать -"одноразовую" очередь тех, кого имеет смысл забанить и разгрести её "когда будет можно". Эта задача факультативная, -возможно, она будет решена потом. - ## Как работает белый список? Белый список - это список jid-ов, которых банить нельзя ни при каких обстоятельствах. В нём всегда неявно присутствует diff --git a/event_parser.go b/event_parser.go index e53b274..f32b9d9 100644 --- a/event_parser.go +++ b/event_parser.go @@ -3,6 +3,7 @@ package main import ( "encoding/xml" "fmt" + "sort" "strings" "time" @@ -488,6 +489,9 @@ func parseEvent(e interface{}) { //nolint:maintidx,gocognit,gocyclo // клиента забанят из-за спама "вошёл-вышел". // Однако, это не значит, что такую ситуацию мы не должны корректным образом обрабатывать. if v.Type == "unavailable" { + // Считаем, что мы больше не в комнате, поэтому не знаем, кто там есть + roomPresencesJid.Delete(v.From) + log.Error("Presence notification - looks like another instance of client leaves room") if slices.Contains(roomsConnected, v.From) { @@ -499,20 +503,72 @@ func parseEvent(e interface{}) { //nolint:maintidx,gocognit,gocyclo log.Errorf(spew.Sdump(v)) } } else { - log.Infof("Presence notification, Type: %s, From: %s, To: %s Show: %s, Status: %s, Affiliation: %s, Role: %s, JID: %s", - v.Type, v.From, v.To, v.Show, v.Status, v.Affiliation, v.Role, v.JID) + log.Infof( + "Presence notification, Type: %s, From: %s, To: %s Show: %s, Status: %s, Affiliation: %s, Role: %s, JID: %s", + v.Type, v.From, v.To, v.Show, v.Status, v.Affiliation, v.Role, v.JID, + ) + + room := strings.SplitN(v.From, "/", 2)[0] + nick := strings.SplitN(v.From, "/", 2)[1] + + if nick == "" { + log.Infof("Presence stanza contains incorrect from attribute: %s", v.From) + + return + } // Это наш собственный Presence if v.Show == "" && v.Status == "" { - if from := strings.Split(v.From, "/"); from[1] == config.Jabber.Nick { - roomsConnected = append(roomsConnected, from[0]) + if nick == config.Jabber.Nick { + roomsConnected = append(roomsConnected, room) + // На всякий случай дедуплицируем список комнат, к которым мы заджойнились. + sort.Strings(roomsConnected) + slices.Compact(roomsConnected) } } - if err := buny(v); err != nil { - gTomb.Kill(err) + switch v.Role { + // Участник ушёл + case "none": + if namesInterface, present := roomPresencesJid.Get(room); present { + var newNames []string + names := interfaceToStringSlice(namesInterface) - return + for _, name := range names { + if name != v.JID { + newNames = append(newNames, name) + } + } + + if len(newNames) == 0 { + roomPresencesJid.Delete(room) + } else { + roomPresencesJid.Set(room, newNames) + } + } + // Участник пришёл + default: + var newNames []string + + if namesInterface, present := roomPresencesJid.Get(room); present { + names := interfaceToStringSlice(namesInterface) + names = append(names, v.JID) + roomPresencesJid.Set(room, names) + } else { + newNames = append(newNames, v.JID) + roomPresencesJid.Set(room, newNames) + } + } + + // Проверяем, а не злодей ли зашёл? Сделать это мы можем, только если мы находимся в комнате. + // По правилам, мы можем что-то делать, только после того, как нам прилетит наш собственный presence, это + // значит, что мы вошли в комнату. + if slices.Contains(roomsConnected, room) { + if err := buny(v); err != nil { + gTomb.Kill(err) + + return + } } } diff --git a/globals.go b/globals.go index dd5ec3e..d6e4140 100644 --- a/globals.go +++ b/globals.go @@ -66,4 +66,7 @@ var serverPingTimestampRx int64 // Объектик для хранения стейта утилизатора горутинок. var gTomb tomb.Tomb +// sync.Map-ка со списком участников конференций. +var roomPresencesJid *Collection + /* vim: set ft=go noet ai ts=4 sw=4 sts=4: */ diff --git a/lib.go b/lib.go index 0634242..421b13e 100644 --- a/lib.go +++ b/lib.go @@ -6,6 +6,7 @@ import ( "math/rand" "os" "path/filepath" + "reflect" "syscall" "time" @@ -580,6 +581,30 @@ func joinMuc(room string) { log.Infof("Joined to MUC: %s", room) go RotateStatus(room) + + // Время прoверить участников на предмет злобности + namesInterface, present := roomPresencesJid.Get(room) + + // Если room есть в списке presence-ов, то фигачим. room там должен быть, просто обязан. + if present { + for _, name := range interfaceToStringSlice(namesInterface) { + var v xmpp.Presence + v.From = name + v.To = fmt.Sprintf("%s/%s", room, config.Jabber.Nick) + v.ID = "fake_id" + v.JID = v.From + v.Affiliation = "member" + v.Role = "participant" + v.Priority = "" + v.Show = "" + v.Status = "" + + // Оно там внутри всё само обработает, если вдруг возникнет wire error, то зарекконетится. + _ = buny(v) + } + } + + // TODO: тоже самое и для nick-ов } // probeServerLiveness проверяет живость соединения с сервером. Для многих серверов обязательная штука, без которой @@ -822,4 +847,22 @@ func randomPhrase(list []string) string { return phrase } +// interfaceToStringSlice превращает данный интерфейс в слайс строк. +// Если может, конечно :) +func interfaceToStringSlice(iface interface{}) []string { + var mySlice []string + + // А теперь мы начинаем дурдом, нам надо превратить ёбанный interface{} в []string + // Поскольку interface{} может быть чем угодно, перестрахуемся + if reflect.TypeOf(iface).Kind() == reflect.Slice { + shit := reflect.ValueOf(iface) + + for i := 0; i < shit.Len(); i++ { + mySlice = append(mySlice, fmt.Sprint(shit.Index(i))) + } + } + + return mySlice +} + /* vim: set ft=go noet ai ts=4 sw=4 sts=4: */ diff --git a/main.go b/main.go index a5987aa..53a649d 100644 --- a/main.go +++ b/main.go @@ -128,6 +128,7 @@ func myLoop() { mucCapsList = NewCollection() serverPingTimestampTx = 0 serverPingTimestampRx = 0 + roomPresencesJid = NewCollection() // Установим коннект establishConnection()