Initial implementation of on-enter ban
This commit is contained in:
@@ -61,3 +61,8 @@ Cуществует несколько видов пинга:
|
|||||||
------------------------------------------------------------------------------------------------------------------------
|
------------------------------------------------------------------------------------------------------------------------
|
||||||
Причину бана можно только включить или выключить в конфиге на per-chat основе. Фразу настроить невозможно. (не хотелось
|
Причину бана можно только включить или выключить в конфиге на per-chat основе. Фразу настроить невозможно. (не хотелось
|
||||||
реализовывать парсинг темплейтов, как минимум даты-времени)
|
реализовывать парсинг темплейтов, как минимум даты-времени)
|
||||||
|
|
||||||
|
|
||||||
|
Парсинг списка и бан здобеев на моменте входа в комнату
|
||||||
|
------------------------------------------------------------------------------------------------------------------------
|
||||||
|
Бан реализован только для real jid-ов, но не для nick-ов. Пока непонятно, как изящно хранить и то и другое.
|
||||||
|
|||||||
19
README.md
19
README.md
@@ -55,25 +55,6 @@ make
|
|||||||
помещает этот jid в список изгнанных из комнаты (бан-лист конкретной комнаты, в которой присутствует бот) и выгоняет
|
помещает этот jid в список изгнанных из комнаты (бан-лист конкретной комнаты, в которой присутствует бот) и выгоняет
|
||||||
этого jid-а.
|
этого jid-а.
|
||||||
|
|
||||||
### В чём тонкость?
|
|
||||||
|
|
||||||
Ровно в том, что для исполнения действия, боту надо заметить presence участника, а presence посылается
|
|
||||||
при изменении состояния клиента. То есть, если jid уже был в комнате на момент захода бота в неё, то его не забанят
|
|
||||||
(во всяком случае моментально). Самый простой способ поместить таких персонажей в бан - просто кикнуть их. Ихний
|
|
||||||
affiliation сменится на none, бот заметит presence и, если jid подпадает под правила чёрного списка, то отправит этот
|
|
||||||
jid в бан.
|
|
||||||
|
|
||||||
Но, простите, ведь "по правилам", при заходе клиента в комнату боту присылается presence всех участников, чтобы он мог
|
|
||||||
построить локальный ростер комнаты и начать отслеживать изменения состояния участников конференции. Почему бы в этот
|
|
||||||
момент не банить злодеев?
|
|
||||||
|
|
||||||
Да, так делать можно, однако пока этого нет. Дело в том, что presence самого себя участнику приходит последним (после
|
|
||||||
presence-ов тех, кто находится в комнате на момент появления в ней бота), и, согласно этому событию, можно считать, что
|
|
||||||
клиент зашёл в комнату успешно и формально *только после этого*, клиент может что-то пытаться делать в этой комнате.
|
|
||||||
Соответственно, чтобы кого-то банить исходя из списка presense-ов, который прилетает на входе, необходимо реализовать
|
|
||||||
"одноразовую" очередь тех, кого имеет смысл забанить и разгрести её "когда будет можно". Эта задача факультативная,
|
|
||||||
возможно, она будет решена потом.
|
|
||||||
|
|
||||||
## Как работает белый список?
|
## Как работает белый список?
|
||||||
|
|
||||||
Белый список - это список jid-ов, которых банить нельзя ни при каких обстоятельствах. В нём всегда неявно присутствует
|
Белый список - это список jid-ов, которых банить нельзя ни при каких обстоятельствах. В нём всегда неявно присутствует
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package main
|
|||||||
import (
|
import (
|
||||||
"encoding/xml"
|
"encoding/xml"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@@ -488,6 +489,9 @@ func parseEvent(e interface{}) { //nolint:maintidx,gocognit,gocyclo
|
|||||||
// клиента забанят из-за спама "вошёл-вышел".
|
// клиента забанят из-за спама "вошёл-вышел".
|
||||||
// Однако, это не значит, что такую ситуацию мы не должны корректным образом обрабатывать.
|
// Однако, это не значит, что такую ситуацию мы не должны корректным образом обрабатывать.
|
||||||
if v.Type == "unavailable" {
|
if v.Type == "unavailable" {
|
||||||
|
// Считаем, что мы больше не в комнате, поэтому не знаем, кто там есть
|
||||||
|
roomPresencesJid.Delete(v.From)
|
||||||
|
|
||||||
log.Error("Presence notification - looks like another instance of client leaves room")
|
log.Error("Presence notification - looks like another instance of client leaves room")
|
||||||
|
|
||||||
if slices.Contains(roomsConnected, v.From) {
|
if slices.Contains(roomsConnected, v.From) {
|
||||||
@@ -499,22 +503,74 @@ func parseEvent(e interface{}) { //nolint:maintidx,gocognit,gocyclo
|
|||||||
log.Errorf(spew.Sdump(v))
|
log.Errorf(spew.Sdump(v))
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
log.Infof("Presence notification, Type: %s, From: %s, To: %s Show: %s, Status: %s, Affiliation: %s, Role: %s, JID: %s",
|
log.Infof(
|
||||||
v.Type, v.From, v.To, v.Show, v.Status, v.Affiliation, v.Role, v.JID)
|
"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
|
// Это наш собственный Presence
|
||||||
if v.Show == "" && v.Status == "" {
|
if v.Show == "" && v.Status == "" {
|
||||||
if from := strings.Split(v.From, "/"); from[1] == config.Jabber.Nick {
|
if nick == config.Jabber.Nick {
|
||||||
roomsConnected = append(roomsConnected, from[0])
|
roomsConnected = append(roomsConnected, room)
|
||||||
|
// На всякий случай дедуплицируем список комнат, к которым мы заджойнились.
|
||||||
|
sort.Strings(roomsConnected)
|
||||||
|
slices.Compact(roomsConnected)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
switch v.Role {
|
||||||
|
// Участник ушёл
|
||||||
|
case "none":
|
||||||
|
if namesInterface, present := roomPresencesJid.Get(room); present {
|
||||||
|
var newNames []string
|
||||||
|
names := interfaceToStringSlice(namesInterface)
|
||||||
|
|
||||||
|
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 {
|
if err := buny(v); err != nil {
|
||||||
gTomb.Kill(err)
|
gTomb.Kill(err)
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Ответ на запрос поддерживаемых фич, который "http://jabber.org/protocol/disco#info"
|
// Ответ на запрос поддерживаемых фич, который "http://jabber.org/protocol/disco#info"
|
||||||
case xmpp.DiscoResult:
|
case xmpp.DiscoResult:
|
||||||
|
|||||||
@@ -66,4 +66,7 @@ var serverPingTimestampRx int64
|
|||||||
// Объектик для хранения стейта утилизатора горутинок.
|
// Объектик для хранения стейта утилизатора горутинок.
|
||||||
var gTomb tomb.Tomb
|
var gTomb tomb.Tomb
|
||||||
|
|
||||||
|
// sync.Map-ка со списком участников конференций.
|
||||||
|
var roomPresencesJid *Collection
|
||||||
|
|
||||||
/* vim: set ft=go noet ai ts=4 sw=4 sts=4: */
|
/* vim: set ft=go noet ai ts=4 sw=4 sts=4: */
|
||||||
|
|||||||
43
lib.go
43
lib.go
@@ -6,6 +6,7 @@ import (
|
|||||||
"math/rand"
|
"math/rand"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"reflect"
|
||||||
"syscall"
|
"syscall"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@@ -580,6 +581,30 @@ func joinMuc(room string) {
|
|||||||
log.Infof("Joined to MUC: %s", room)
|
log.Infof("Joined to MUC: %s", room)
|
||||||
|
|
||||||
go RotateStatus(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 проверяет живость соединения с сервером. Для многих серверов обязательная штука, без которой
|
// probeServerLiveness проверяет живость соединения с сервером. Для многих серверов обязательная штука, без которой
|
||||||
@@ -822,4 +847,22 @@ func randomPhrase(list []string) string {
|
|||||||
return phrase
|
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: */
|
/* vim: set ft=go noet ai ts=4 sw=4 sts=4: */
|
||||||
|
|||||||
1
main.go
1
main.go
@@ -128,6 +128,7 @@ func myLoop() {
|
|||||||
mucCapsList = NewCollection()
|
mucCapsList = NewCollection()
|
||||||
serverPingTimestampTx = 0
|
serverPingTimestampTx = 0
|
||||||
serverPingTimestampRx = 0
|
serverPingTimestampRx = 0
|
||||||
|
roomPresencesJid = NewCollection()
|
||||||
|
|
||||||
// Установим коннект
|
// Установим коннект
|
||||||
establishConnection()
|
establishConnection()
|
||||||
|
|||||||
Reference in New Issue
Block a user