Update linter, fix linter errors
This commit is contained in:
@@ -1,3 +1,4 @@
|
||||
// Package jabber contains all buny jabber bot guts.
|
||||
package jabber
|
||||
|
||||
import (
|
||||
@@ -12,11 +13,16 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
goodDictionaryFile string = "./data/good_dictionary.txt"
|
||||
badDictionaryFile string = "./data/bad_dictionary.txt"
|
||||
dataFile string = "./data/data.bin"
|
||||
Bad bayesian.Class = "Bad"
|
||||
Good bayesian.Class = "Good"
|
||||
// goodDictionaryFile путь до файла с заведомо хорошими фразами.
|
||||
goodDictionaryFile string = "./data/good_dictionary.txt" //nolint: unused
|
||||
// badDictionaryFile путь до файла с заведемо плохими фразами.
|
||||
badDictionaryFile string = "./data/bad_dictionary.txt" //nolint: unused
|
||||
// dataFile путь до файла с данными тренировки фильтра.
|
||||
dataFile string = "./data/data.bin" //nolint: unused
|
||||
// Bad bayessian class with bad items.
|
||||
Bad bayesian.Class = "Bad"
|
||||
// Good bayessian class with good items.
|
||||
Good bayesian.Class = "Good"
|
||||
)
|
||||
|
||||
// Выдаёт "очки похожести" для данной фразы.
|
||||
|
||||
@@ -27,7 +27,7 @@ func (j *Jabber) ReadWhitelist() error {
|
||||
return err
|
||||
}
|
||||
|
||||
whitelistJSONPath := fmt.Sprintf("%s/data/whitelist.json", filepath.Dir(executablePath))
|
||||
whitelistJSONPath := filepath.Dir(executablePath) + "/data/whitelist.json"
|
||||
|
||||
locations := []string{
|
||||
"~/.buny-jabber-bot-whitelist.json",
|
||||
@@ -39,12 +39,12 @@ func (j *Jabber) ReadWhitelist() error {
|
||||
for _, location := range locations {
|
||||
fileInfo, err := os.Stat(location)
|
||||
|
||||
// Предполагаем, что файла либо нет, либо мы не можем его прочитать, второе надо бы логгировать, но пока забьём
|
||||
// Предполагаем, что файла либо нет, либо мы не можем его прочитать, второе надо бы логгировать, но пока забьём.
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
// Файл белого списка длинноват для белого списка, попробуем следующего кандидата
|
||||
// Файл белого списка длинноват для белого списка, попробуем следующего кандидата.
|
||||
if fileInfo.Size() > 2097152 {
|
||||
log.Warnf("Whitelist file %s is too long for whitelist, skipping", location)
|
||||
|
||||
@@ -53,7 +53,7 @@ func (j *Jabber) ReadWhitelist() error {
|
||||
|
||||
buf, err := os.ReadFile(location)
|
||||
|
||||
// Не удалось прочитать, попробуем следующего кандидата
|
||||
// Не удалось прочитать, попробуем следующего кандидата.
|
||||
if err != nil {
|
||||
log.Warnf("Skip reading whitelist file %s: %s", location, err)
|
||||
|
||||
@@ -65,12 +65,12 @@ func (j *Jabber) ReadWhitelist() error {
|
||||
// сериализуем в json, а потом json превращаем в структурку. Не очень эффективно, но он и нечасто требуется.
|
||||
var (
|
||||
sampleWhitelist MyWhiteList
|
||||
tmp map[string]interface{}
|
||||
tmp map[string]any
|
||||
)
|
||||
|
||||
err = hjson.Unmarshal(buf, &tmp)
|
||||
|
||||
// Не удалось распарсить - попробуем следующего кандидата
|
||||
// Не удалось распарсить - попробуем следующего кандидата.
|
||||
if err != nil {
|
||||
log.Warnf("Skip parsing whitelist file %s: %s", location, err)
|
||||
|
||||
@@ -79,7 +79,7 @@ func (j *Jabber) ReadWhitelist() error {
|
||||
|
||||
tmpJSON, err := json.Marshal(tmp)
|
||||
|
||||
// Не удалось преобразовать map-ку в json
|
||||
// Не удалось преобразовать map-ку в json.
|
||||
if err != nil {
|
||||
log.Warnf("Skip parsing whitelist file %s: %s", location, err)
|
||||
|
||||
@@ -123,7 +123,7 @@ func (j *Jabber) ReadBlacklist() error {
|
||||
return err
|
||||
}
|
||||
|
||||
whitelistJSONPath := fmt.Sprintf("%s/data/blacklist.json", filepath.Dir(executablePath))
|
||||
whitelistJSONPath := filepath.Dir(executablePath) + "/data/blacklist.json"
|
||||
|
||||
locations := []string{
|
||||
"~/.bunyPresense-jabber-bot-blacklist.json",
|
||||
@@ -135,12 +135,12 @@ func (j *Jabber) ReadBlacklist() error {
|
||||
for _, location := range locations {
|
||||
fileInfo, err := os.Stat(location)
|
||||
|
||||
// Предполагаем, что файла либо нет, либо мы не можем его прочитать, второе надо бы логгировать, но пока забьём
|
||||
// Предполагаем, что файла либо нет, либо мы не можем его прочитать, второе надо бы логгировать, но пока забьём.
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
// Файл чёрного списка длинноват для чёрного списка, попробуем следующего кандидата
|
||||
// Файл чёрного списка длинноват для чёрного списка, попробуем следующего кандидата.
|
||||
if fileInfo.Size() > 16777216 {
|
||||
log.Warnf("Blacklist file %s is too long for blacklist, skipping", location)
|
||||
|
||||
@@ -149,7 +149,7 @@ func (j *Jabber) ReadBlacklist() error {
|
||||
|
||||
buf, err := os.ReadFile(location)
|
||||
|
||||
// Не удалось прочитать, попробуем следующего кандидата
|
||||
// Не удалось прочитать, попробуем следующего кандидата.
|
||||
if err != nil {
|
||||
log.Warnf("Skip reading blacklist file %s: %s", location, err)
|
||||
|
||||
@@ -161,12 +161,12 @@ func (j *Jabber) ReadBlacklist() error {
|
||||
// сериализуем в json, а потом json превращаем в структурку. Не очень эффективно, но он и нечасто требуется.
|
||||
var (
|
||||
sampleBlacklist MyBlackList
|
||||
tmp map[string]interface{}
|
||||
tmp map[string]any
|
||||
)
|
||||
|
||||
err = hjson.Unmarshal(buf, &tmp)
|
||||
|
||||
// Не удалось распарсить - попробуем следующего кандидата
|
||||
// Не удалось распарсить - попробуем следующего кандидата.
|
||||
if err != nil {
|
||||
log.Warnf("Skip parsing blacklist file %s: %s", location, err)
|
||||
|
||||
@@ -175,7 +175,7 @@ func (j *Jabber) ReadBlacklist() error {
|
||||
|
||||
tmpJSON, err := json.Marshal(tmp)
|
||||
|
||||
// Не удалось преобразовать map-ку в json
|
||||
// Не удалось преобразовать map-ку в json.
|
||||
if err != nil {
|
||||
log.Warnf("Skip parsing blacklist file %s: %s", location, err)
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"regexp"
|
||||
"slices"
|
||||
"strings"
|
||||
|
||||
"github.com/davecgh/go-spew/spew"
|
||||
@@ -17,9 +18,9 @@ func (j *Jabber) BunyPresense(v xmpp.Presence) error { //nolint:gocognit,gocyclo
|
||||
var err error
|
||||
|
||||
// Если у presence-а есть JID и presence из одной из комнат, в которой мы есть и если его домен в чёрном
|
||||
// списке, заносим пидора в список outcast-ов
|
||||
// списке, заносим пидора в список outcast-ов.
|
||||
if v.JID != "" {
|
||||
// На всякий случай: себя никогда не баним, явным образом
|
||||
// На всякий случай: себя никогда не баним, явным образом.
|
||||
if v.JID == j.Talk.JID() {
|
||||
return err
|
||||
}
|
||||
@@ -34,23 +35,19 @@ func (j *Jabber) BunyPresense(v xmpp.Presence) error { //nolint:gocognit,gocyclo
|
||||
|
||||
goodJid := strings.SplitN(v.JID, "/", 2)[0]
|
||||
|
||||
// Обрабатываем правила белого списка
|
||||
// Обрабатываем правила белого списка.
|
||||
for _, good := range j.WhiteList.Whitelist {
|
||||
// Глобальный белый список
|
||||
// Глобальный белый список.
|
||||
if good.RoomName == "" {
|
||||
for _, person := range good.Jid {
|
||||
if person == goodJid {
|
||||
return err
|
||||
}
|
||||
if slices.Contains(good.Jid, goodJid) {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// Белый список для конкретных каналов
|
||||
// Белый список для конкретных каналов.
|
||||
if good.RoomName == room {
|
||||
for _, person := range good.Jid {
|
||||
if person == goodJid {
|
||||
return err
|
||||
}
|
||||
if slices.Contains(good.Jid, goodJid) {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -74,16 +71,16 @@ func (j *Jabber) BunyPresense(v xmpp.Presence) error { //nolint:gocognit,gocyclo
|
||||
evilNick := ""
|
||||
evilNicks := strings.SplitN(v.From, "/", 2)
|
||||
|
||||
// Давайте-ка похэндлим нонсенс - когда у нас в строке нету разделителя
|
||||
// Давайте-ка похэндлим нонсенс - когда у нас в строке нету разделителя.
|
||||
if len(evilNicks) > 1 {
|
||||
evilNick = evilNicks[1]
|
||||
}
|
||||
|
||||
// Обрабатываем правила чёрного списка
|
||||
// Обрабатываем правила чёрного списка.
|
||||
for _, cRoom := range j.RoomsConnected {
|
||||
if cRoom == room {
|
||||
for _, bEntry := range j.BlackList.Blacklist {
|
||||
// Обработаем правила глобального чёрного списка
|
||||
// Обработаем правила глобального чёрного списка.
|
||||
if bEntry.RoomName == "" {
|
||||
for _, jidRegexp := range bEntry.JidRe {
|
||||
if jidRegexp == "" {
|
||||
@@ -147,7 +144,7 @@ func (j *Jabber) BunyPresense(v xmpp.Presence) error { //nolint:gocognit,gocyclo
|
||||
nickRegexp,
|
||||
)
|
||||
|
||||
// Баним именно jid
|
||||
// Баним именно jid.
|
||||
id, err := j.Squash(room, evilJid, bEntry.ReasonEnable, v.Type)
|
||||
|
||||
if err != nil {
|
||||
@@ -167,7 +164,7 @@ func (j *Jabber) BunyPresense(v xmpp.Presence) error { //nolint:gocognit,gocyclo
|
||||
continue
|
||||
}
|
||||
|
||||
// Обработаем правила конкретного канала, комнаты конференции
|
||||
// Обработаем правила конкретного канала, комнаты конференции.
|
||||
if bEntry.RoomName == room {
|
||||
for _, jidRegexp := range bEntry.JidRe {
|
||||
if jidRegexp == "" {
|
||||
@@ -232,7 +229,7 @@ func (j *Jabber) BunyPresense(v xmpp.Presence) error { //nolint:gocognit,gocyclo
|
||||
nickRegexp,
|
||||
)
|
||||
|
||||
// Баним именно jid
|
||||
// Баним именно jid.
|
||||
id, err := j.Squash(room, evilJid, bEntry.ReasonEnable, v.Type)
|
||||
|
||||
if err != nil {
|
||||
@@ -265,8 +262,7 @@ func (j *Jabber) BunyPresense(v xmpp.Presence) error { //nolint:gocognit,gocyclo
|
||||
func (j *Jabber) BunyChat(v xmpp.Chat) error {
|
||||
var (
|
||||
room = (strings.SplitN(v.Remote, "/", 2))[0]
|
||||
// nick = (strings.SplitN(v.Remote, "/", 2))[1]
|
||||
err error
|
||||
err error
|
||||
)
|
||||
|
||||
// Действовать мы можем только в рамках тех комнат, где явно присуствуем.
|
||||
@@ -274,7 +270,7 @@ func (j *Jabber) BunyChat(v xmpp.Chat) error {
|
||||
if cRoom == room {
|
||||
// Перебирём правила чёрных списков.
|
||||
for _, bEntry := range j.BlackList.Blacklist {
|
||||
// Обработаем правила глобального чёрного списка
|
||||
// Обработаем правила глобального чёрного списка.
|
||||
if bEntry.RoomName == "" {
|
||||
for _, phraseRegexp := range bEntry.PhraseRe {
|
||||
if phraseRegexp == "" {
|
||||
@@ -317,7 +313,7 @@ func (j *Jabber) BunyChat(v xmpp.Chat) error {
|
||||
}
|
||||
}
|
||||
|
||||
// Обработаем правила чёрного списка для конкретной комнаты
|
||||
// Обработаем правила чёрного списка для конкретной комнаты.
|
||||
if bEntry.RoomName == room {
|
||||
for _, phraseRegexp := range bEntry.PhraseRe {
|
||||
if phraseRegexp == "" {
|
||||
@@ -364,10 +360,10 @@ func (j *Jabber) BunyChat(v xmpp.Chat) error {
|
||||
// Если включено, проверяем фразу на КАПС.
|
||||
for _, channel := range j.C.Jabber.Channels {
|
||||
if channel.Name == room && channel.AllCaps.Enabled {
|
||||
// Нормализуем строку и вырежем из неё пробелы
|
||||
// Нормализуем строку и вырежем из неё пробелы.
|
||||
normPhrase := strings.ReplaceAll(nString(v.Text), " ", "")
|
||||
|
||||
// Проверяем согласно тому, что длина фразы более чем сколько-то символов
|
||||
// Проверяем согласно тому, что длина фразы более чем сколько-то символов.
|
||||
if len(normPhrase) >= channel.AllCaps.MinLength {
|
||||
normPhraseUpper := strings.ReplaceAll(nStringUpper(v.Text), " ", "")
|
||||
|
||||
@@ -401,7 +397,7 @@ func (j *Jabber) BunySoftwareVersion(v xmpp.IQ, ver IqResultSoftwareVersion) err
|
||||
var err error
|
||||
|
||||
if v.From != "" {
|
||||
// На всякий случай: себя никогда не баним, явным образом
|
||||
// На всякий случай: себя никогда не баним, явным образом.
|
||||
if v.From == j.Talk.JID() {
|
||||
return err
|
||||
}
|
||||
@@ -420,6 +416,7 @@ func (j *Jabber) BunySoftwareVersion(v xmpp.IQ, ver IqResultSoftwareVersion) err
|
||||
|
||||
for _, presenceJSONString := range presenceJSONStrings {
|
||||
var p xmpp.Presence
|
||||
|
||||
_ = json.Unmarshal([]byte(presenceJSONString), &p)
|
||||
|
||||
if p.Affiliation == "admin" || p.Affiliation == "owner" {
|
||||
@@ -442,7 +439,7 @@ func (j *Jabber) BunySoftwareVersion(v xmpp.IQ, ver IqResultSoftwareVersion) err
|
||||
for _, cRoom := range j.RoomsConnected {
|
||||
if cRoom == room {
|
||||
for _, bEntry := range j.BlackList.Blacklist {
|
||||
// Обработаем правила глобального чёрного списка
|
||||
// Обработаем правила глобального чёрного списка.
|
||||
if bEntry.RoomName == "" {
|
||||
for _, useragent := range bEntry.UserAgent {
|
||||
switch {
|
||||
|
||||
@@ -4,16 +4,18 @@ import (
|
||||
"sync"
|
||||
)
|
||||
|
||||
// Collection это sync.Map-ка, хранящая в себе произвольные итемы.
|
||||
type Collection struct {
|
||||
items sync.Map
|
||||
close chan struct{}
|
||||
}
|
||||
type (
|
||||
// Collection это sync.Map-ка, хранящая в себе произвольные итемы.
|
||||
Collection struct {
|
||||
close chan struct{}
|
||||
items sync.Map
|
||||
}
|
||||
|
||||
// Item представляет собой интерфейс для хранения произвольных данных.
|
||||
type item struct {
|
||||
data interface{}
|
||||
}
|
||||
// Item представляет собой интерфейс для хранения произвольных данных.
|
||||
item struct {
|
||||
data any
|
||||
}
|
||||
)
|
||||
|
||||
// NewCollection создаёт инстанс коллекции.
|
||||
func NewCollection() *Collection {
|
||||
@@ -25,7 +27,7 @@ func NewCollection() *Collection {
|
||||
}
|
||||
|
||||
// Get достаёт данные по заданному ключу из коллекции.
|
||||
func (collection *Collection) Get(key interface{}) (interface{}, bool) {
|
||||
func (collection *Collection) Get(key any) (any, bool) {
|
||||
obj, exists := collection.items.Load(key)
|
||||
|
||||
if !exists {
|
||||
@@ -38,15 +40,15 @@ func (collection *Collection) Get(key interface{}) (interface{}, bool) {
|
||||
}
|
||||
|
||||
// Set сохраняет данные с заданным ключом в коллекцию.
|
||||
func (collection *Collection) Set(key interface{}, value interface{}) {
|
||||
func (collection *Collection) Set(key any, value any) {
|
||||
collection.items.Store(key, item{
|
||||
data: value,
|
||||
})
|
||||
}
|
||||
|
||||
// Range применяет функцию f ко всем ключам в коллекции.
|
||||
func (collection *Collection) Range(f func(key, value interface{}) bool) {
|
||||
fn := func(key, value interface{}) bool {
|
||||
func (collection *Collection) Range(f func(key, value any) bool) {
|
||||
fn := func(key, value any) bool {
|
||||
item := value.(item)
|
||||
|
||||
return f(key, item.data)
|
||||
@@ -56,13 +58,14 @@ func (collection *Collection) Range(f func(key, value interface{}) bool) {
|
||||
}
|
||||
|
||||
// Delete удаляет ключ и значение из коллекции данных.
|
||||
func (collection *Collection) Delete(key interface{}) {
|
||||
func (collection *Collection) Delete(key any) {
|
||||
collection.items.Delete(key)
|
||||
}
|
||||
|
||||
// Close очищает и высвобождает ресурсы, занятые коллекцией.
|
||||
func (collection *Collection) Close() {
|
||||
collection.close <- struct{}{}
|
||||
|
||||
collection.items = sync.Map{}
|
||||
}
|
||||
|
||||
|
||||
@@ -14,8 +14,8 @@ import (
|
||||
func (j *Jabber) Cmd(v xmpp.Chat) error {
|
||||
var err error
|
||||
|
||||
switch {
|
||||
case v.Text == fmt.Sprintf("%shelp", j.C.CSign) || v.Text == fmt.Sprintf("%sпомощь", j.C.CSign):
|
||||
switch v.Text {
|
||||
case j.C.CSign + "help", j.C.CSign + "помощь":
|
||||
var (
|
||||
answer string
|
||||
chosenOneTalks = false
|
||||
@@ -30,10 +30,11 @@ func (j *Jabber) Cmd(v xmpp.Chat) error {
|
||||
}
|
||||
|
||||
if chosenOneTalks {
|
||||
answer = fmt.Sprintf("%sпомощь - этот список команд\n", j.C.CSign)
|
||||
answer += fmt.Sprintf("%shelp - this commands list\n", j.C.CSign)
|
||||
answer += fmt.Sprintf("%srehash - reload white and black lists (available to bot admins only)\n", j.C.CSign)
|
||||
answer += fmt.Sprintf("%sver|%sversion - prints version of software", j.C.CSign, j.C.CSign)
|
||||
answer = j.C.CSign + "помощь - этот список команд\n"
|
||||
|
||||
answer += j.C.CSign + "help - this commands list\n"
|
||||
answer += j.C.CSign + "rehash - reload white and black lists (available to bot admins only)\n"
|
||||
answer += j.C.CSign + "ver | " + j.C.CSign + "version - prints version of software"
|
||||
} else {
|
||||
answer = "Ничем помочь не могу. Луна не светит на тебя."
|
||||
}
|
||||
@@ -56,15 +57,14 @@ func (j *Jabber) Cmd(v xmpp.Chat) error {
|
||||
return err
|
||||
}
|
||||
|
||||
case v.Text == fmt.Sprintf("%srehash", j.C.CSign): //nolint:wsl
|
||||
case j.C.CSign + "rehash":
|
||||
/* Эта команда может прилетать из приватной беседы с realjid-ом, из приватной беседы с chat-nick-ом, а также из
|
||||
* чятика.
|
||||
* Groupchat мы можем определить по v.Type (groupchat) и тогда понятно что делать.
|
||||
* А вот, например, приватные притязания определить несколько сложно-вато. Но можно попробовать, например,
|
||||
* пытаться по списку presence-ов находить персонажа и его real jid и уже из этого определять master он или нет.
|
||||
*/
|
||||
|
||||
// Попробуем определить каков алгоритм наших действий, в зависимости от типа сообщения
|
||||
// Попробуем определить каков алгоритм наших действий, в зависимости от типа сообщения.
|
||||
switch v.Type {
|
||||
case "groupchat":
|
||||
// Проверим, является ли "командир" избранным или это самозванец?
|
||||
@@ -90,6 +90,7 @@ func (j *Jabber) Cmd(v xmpp.Chat) error {
|
||||
)
|
||||
|
||||
var msg xmpp.Chat
|
||||
|
||||
msg.Remote = v.Remote
|
||||
msg.Type = v.Type
|
||||
msg.Text = "Ничем помочь не могу. Луна не светит на тебя."
|
||||
@@ -103,8 +104,9 @@ func (j *Jabber) Cmd(v xmpp.Chat) error {
|
||||
listsLoaded = false
|
||||
|
||||
var msg xmpp.Chat
|
||||
|
||||
msg.Remote = v.Remote
|
||||
msg.Type = "chat" // private message
|
||||
msg.Type = "chat" // private message.
|
||||
msg.Text = fmt.Sprint(err)
|
||||
|
||||
if _, err := j.Talk.Send(msg); err != nil {
|
||||
@@ -116,8 +118,9 @@ func (j *Jabber) Cmd(v xmpp.Chat) error {
|
||||
listsLoaded = false
|
||||
|
||||
var msg xmpp.Chat
|
||||
|
||||
msg.Remote = v.Remote
|
||||
msg.Type = "chat" // private message
|
||||
msg.Type = "chat" // private message.
|
||||
msg.Text = fmt.Sprint(err)
|
||||
|
||||
if _, err := j.Talk.Send(msg); err != nil {
|
||||
@@ -129,6 +132,7 @@ func (j *Jabber) Cmd(v xmpp.Chat) error {
|
||||
room := (strings.SplitN(v.Remote, "/", 2))[0]
|
||||
|
||||
var msg xmpp.Chat
|
||||
|
||||
msg.Remote = room
|
||||
msg.Type = v.Type
|
||||
msg.Text = "Сделано"
|
||||
@@ -137,17 +141,18 @@ func (j *Jabber) Cmd(v xmpp.Chat) error {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
case "chat":
|
||||
room := (strings.SplitN(v.Remote, "/", 2))[0]
|
||||
|
||||
// Реагировать на приватную команду "из комнаты", только если бот в комнате
|
||||
// Реагировать на приватную команду "из комнаты", только если бот в комнате.
|
||||
if slices.Contains(j.RoomsConnected, room) {
|
||||
var (
|
||||
chosenOneTalks = false
|
||||
listsLoaded = true
|
||||
)
|
||||
|
||||
// Реагировать на rehash только если собеседник является bot master-ом
|
||||
// Реагировать на rehash только если собеседник является bot master-ом.
|
||||
realJID := j.GetRealJIDfromNick(v.Remote)
|
||||
|
||||
for _, master := range j.C.Jabber.BotMasters {
|
||||
@@ -165,6 +170,7 @@ func (j *Jabber) Cmd(v xmpp.Chat) error {
|
||||
)
|
||||
|
||||
var msg xmpp.Chat
|
||||
|
||||
msg.Remote = v.Remote
|
||||
msg.Type = v.Type
|
||||
msg.Text = "Ничем помочь не могу. Луна не светит на тебя."
|
||||
@@ -178,6 +184,7 @@ func (j *Jabber) Cmd(v xmpp.Chat) error {
|
||||
listsLoaded = false
|
||||
|
||||
var msg xmpp.Chat
|
||||
|
||||
msg.Remote = v.Remote
|
||||
msg.Type = v.Type
|
||||
msg.Text = fmt.Sprint(err)
|
||||
@@ -191,6 +198,7 @@ func (j *Jabber) Cmd(v xmpp.Chat) error {
|
||||
listsLoaded = false
|
||||
|
||||
var msg xmpp.Chat
|
||||
|
||||
msg.Remote = v.Remote
|
||||
msg.Type = v.Type
|
||||
msg.Text = fmt.Sprint(err)
|
||||
@@ -202,6 +210,7 @@ func (j *Jabber) Cmd(v xmpp.Chat) error {
|
||||
|
||||
if listsLoaded {
|
||||
var msg xmpp.Chat
|
||||
|
||||
msg.Remote = v.Remote
|
||||
msg.Type = v.Type
|
||||
msg.Text = "Сделано"
|
||||
@@ -214,12 +223,14 @@ func (j *Jabber) Cmd(v xmpp.Chat) error {
|
||||
|
||||
default:
|
||||
log.Infof("Rehash command from outer space: %s", spew.Sdump(v))
|
||||
|
||||
}
|
||||
|
||||
return err
|
||||
case v.Text == fmt.Sprintf("%sver", j.C.CSign) || v.Text == fmt.Sprintf("%sversion", j.C.CSign):
|
||||
|
||||
case j.C.CSign + "ver", j.C.CSign + "version":
|
||||
var (
|
||||
answer = fmt.Sprintf("Version %s", j.C.Version)
|
||||
answer = "Version " + j.C.Version
|
||||
)
|
||||
|
||||
dest := v.Remote
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"encoding/xml"
|
||||
"fmt"
|
||||
"runtime"
|
||||
"slices"
|
||||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
@@ -12,26 +13,24 @@ import (
|
||||
"github.com/davecgh/go-spew/spew"
|
||||
"github.com/eleksir/go-xmpp"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"golang.org/x/exp/slices"
|
||||
)
|
||||
|
||||
// ParseEvent парсит ивенты, прилетающие из модуля xmpp (изменения presence, фразы участников, ошибки).
|
||||
func (j *Jabber) ParseEvent(e interface{}) { //nolint:maintidx,gocognit,gocyclo
|
||||
func (j *Jabber) ParseEvent(e any) { //nolint:maintidx,gocognit,gocyclo
|
||||
j.LastServerActivity = time.Now().Unix()
|
||||
|
||||
switch v := e.(type) {
|
||||
// Сообщение в чяти
|
||||
// Сообщение в чяти.
|
||||
case xmpp.Chat:
|
||||
log.Debugf("Looks like message, ChatType: %s, From: %s, Subject: %s Text: %s",
|
||||
v.Type, v.Remote, v.Subject, v.Text)
|
||||
|
||||
// Топик чятика присылается в виде сообщения с subject, но без text
|
||||
// В то же время сообщения от людей приходят с пустым subject, но с заполненным text
|
||||
// Топик чятика присылается в виде сообщения с subject, но без text.
|
||||
// В то же время сообщения от людей приходят с пустым subject, но с заполненным text.
|
||||
if v.Text != "" {
|
||||
// Чятики бывают групповые и не групповые, от этого зависит Remote, куда направлять сообщение
|
||||
// Чятики бывают групповые и не групповые, от этого зависит Remote, куда направлять сообщение.
|
||||
switch v.Type { //nolint:wsl,whitespace
|
||||
|
||||
// Групповой чятик
|
||||
// Групповой чятик.
|
||||
case "groupchat":
|
||||
var (
|
||||
room = strings.SplitN(v.Remote, "/", 2)[0]
|
||||
@@ -64,7 +63,7 @@ func (j *Jabber) ParseEvent(e interface{}) { //nolint:maintidx,gocognit,gocyclo
|
||||
j.LastMucActivity.Set(muc, j.LastServerActivity)
|
||||
}
|
||||
|
||||
// Приватный чятик
|
||||
// Приватный чятик.
|
||||
case "chat":
|
||||
// Здесь у нас может быть 2 вида From:
|
||||
// slackware-current@conference.jabber.ru/eleksir - это если сообщение из публичного чятика
|
||||
@@ -80,7 +79,7 @@ func (j *Jabber) ParseEvent(e interface{}) { //nolint:maintidx,gocognit,gocyclo
|
||||
j.LastActivity = j.LastServerActivity
|
||||
|
||||
// Внезапно, ошибки. По идее они должны ассоциироваться с отправляемыми сообщениями, но по ходу это
|
||||
// не реализовано, поэтому мы получаем ошибки в форме отдельного чятика
|
||||
// не реализовано, поэтому мы получаем ошибки в форме отдельного чятика.
|
||||
case "error":
|
||||
// Это ошибка со стороны сервера, видимо, в ответ на наши действия и поскольку такие вещи происходят
|
||||
// асинхронно, связать их с конкретными нашими действиями довольно сложно.
|
||||
@@ -90,7 +89,7 @@ func (j *Jabber) ParseEvent(e interface{}) { //nolint:maintidx,gocognit,gocyclo
|
||||
|
||||
// IQ-сообщение - пинг, понг, что-то ещё...
|
||||
case xmpp.IQ:
|
||||
// По правилам IQ обязательно должна содержать ID, причём не пустой
|
||||
// По правилам IQ обязательно должна содержать ID, причём не пустой.
|
||||
if v.ID == "" {
|
||||
log.Info("Got an IQ stanza with empty id, discarding")
|
||||
|
||||
@@ -114,8 +113,7 @@ func (j *Jabber) ParseEvent(e interface{}) { //nolint:maintidx,gocognit,gocyclo
|
||||
|
||||
if err := xml.Unmarshal(v.Query, &iqStruct); err == nil {
|
||||
switch iqStruct.Xmlns { //nolint:wsl,whitespace
|
||||
|
||||
// Запрос номера версии приложения
|
||||
// Запрос номера версии приложения.
|
||||
case "jabber:iq:version":
|
||||
log.Infof("Got IQ get request for version from %s", v.From)
|
||||
|
||||
@@ -138,7 +136,7 @@ func (j *Jabber) ParseEvent(e interface{}) { //nolint:maintidx,gocognit,gocyclo
|
||||
|
||||
parseError = false
|
||||
|
||||
// Нам прислали попингуй
|
||||
// Нам прислали попингуй.
|
||||
case "urn:xmpp:ping":
|
||||
log.Infof("Got IQ get request for pong from %s", v.From)
|
||||
|
||||
@@ -175,11 +173,12 @@ func (j *Jabber) ParseEvent(e interface{}) { //nolint:maintidx,gocognit,gocyclo
|
||||
|
||||
parseError = false
|
||||
|
||||
// Запрос на список поддерживаемых фич - версия, локальное время, итд
|
||||
// Запрос на список поддерживаемых фич - версия, локальное время, итд.
|
||||
case xmpp.XMPPNS_DISCO_INFO:
|
||||
log.Infof("Got IQ get disco#info request from %s", v.From)
|
||||
|
||||
answer := "<query xmlns=\"http://jabber.org/protocol/disco#info\">"
|
||||
|
||||
answer += "<feature var=\"jabber:iq:version\" />"
|
||||
answer += "<feature var=\"urn:xmpp:time\" />"
|
||||
answer += "<feature var=\"urn:xmpp:ping\" />"
|
||||
@@ -209,7 +208,7 @@ func (j *Jabber) ParseEvent(e interface{}) { //nolint:maintidx,gocognit,gocyclo
|
||||
|
||||
parseError = false
|
||||
|
||||
// Спрашивают список команд, чтобы поуправлять этим клиентом (xep-0030)
|
||||
// Спрашивают список команд, чтобы поуправлять этим клиентом (xep-0030).
|
||||
case xmpp.XMPPNS_DISCO_ITEMS:
|
||||
log.Infof("Got IQ get disco#items request from %s, answer service unavailable", v.From)
|
||||
|
||||
@@ -231,7 +230,7 @@ func (j *Jabber) ParseEvent(e interface{}) { //nolint:maintidx,gocognit,gocyclo
|
||||
|
||||
parseError = false
|
||||
|
||||
// Запрашивают что-то, о чём мы не имеем представления
|
||||
// Запрашивают что-то, о чём мы не имеем представления.
|
||||
default:
|
||||
log.Info("Got an unknown IQ get request, discarding")
|
||||
log.Info(spew.Sdump(e))
|
||||
@@ -242,7 +241,7 @@ func (j *Jabber) ParseEvent(e interface{}) { //nolint:maintidx,gocognit,gocyclo
|
||||
parseError = true
|
||||
}
|
||||
|
||||
// Попробуем распарсить входящий запрос как urn:xmpp:time
|
||||
// Попробуем распарсить входящий запрос как urn:xmpp:time.
|
||||
if parseError {
|
||||
var iqStruct TimeIqGetQuery
|
||||
|
||||
@@ -276,7 +275,7 @@ func (j *Jabber) ParseEvent(e interface{}) { //nolint:maintidx,gocognit,gocyclo
|
||||
}
|
||||
}
|
||||
|
||||
// Попробуем распарсить входящий запрос как http://jabber.org/protocol/pubsub
|
||||
// Попробуем распарсить входящий запрос как http://jabber.org/protocol/pubsub.
|
||||
if parseError {
|
||||
var iqStruct PubsubIQGetQuery
|
||||
|
||||
@@ -305,6 +304,7 @@ func (j *Jabber) ParseEvent(e interface{}) { //nolint:maintidx,gocognit,gocyclo
|
||||
default:
|
||||
log.Info("Got unknown IQ get pubsub something request, discarding")
|
||||
log.Info(spew.Sdump(e))
|
||||
|
||||
parseError = true //nolint:wsl
|
||||
}
|
||||
} else {
|
||||
@@ -312,7 +312,7 @@ func (j *Jabber) ParseEvent(e interface{}) { //nolint:maintidx,gocognit,gocyclo
|
||||
}
|
||||
}
|
||||
|
||||
// Попробуем распарсить входящий запрос как urn:xmpp:ping он приходит при MUC Self-Ping (Schrödinger's Chat)
|
||||
// Попробуем распарсить входящий запрос как urn:xmpp:ping он приходит при MUC Self-Ping (Schrödinger's Chat).
|
||||
// Предполагается, что такие респонсы должны приходить только для пинга без серверной оптимизации.
|
||||
// Хотя идейно мы не поддерживаем работу без серверной оптимизации, но на пинг ответим, нам несложно.
|
||||
if parseError {
|
||||
@@ -337,7 +337,7 @@ func (j *Jabber) ParseEvent(e interface{}) { //nolint:maintidx,gocognit,gocyclo
|
||||
parseError = false
|
||||
}
|
||||
|
||||
// Не знаю, как парсить... залоггируем это дело и пойдём дальше
|
||||
// Не знаю, как парсить... залоггируем это дело и пойдём дальше.
|
||||
if parseError {
|
||||
log.Infof("Does not look like parsable via iqStruct")
|
||||
log.Info(spew.Sdump(e))
|
||||
@@ -351,9 +351,10 @@ func (j *Jabber) ParseEvent(e interface{}) { //nolint:maintidx,gocognit,gocyclo
|
||||
}
|
||||
|
||||
switch {
|
||||
// Похоже на pong от сервера (по стандарту в ответе нету query, но go-xmpp нам подсовывает это)
|
||||
// Похоже на pong от сервера (по стандарту в ответе нету query, но go-xmpp нам подсовывает это).
|
||||
case v.From == j.C.Jabber.Server && v.To == j.Talk.JID() && string(v.Query) == "<XMLElement></XMLElement>":
|
||||
log.Debugf("Got S2C pong answer from %s to %s", v.From, v.To)
|
||||
|
||||
j.ServerPingTimestampRx = time.Now().Unix() //nolint:wsl
|
||||
|
||||
// Похоже на понг второй стадии xep-0410 MUC-Ping-а, который у нас не реализован.
|
||||
@@ -378,13 +379,13 @@ func (j *Jabber) ParseEvent(e interface{}) { //nolint:maintidx,gocognit,gocyclo
|
||||
if mucNickMatch {
|
||||
log.Debugf("Got 2-nd stage MUC pong answer (xep-0410) from %s to %s", v.From, v.To)
|
||||
} else {
|
||||
// Поскольку никакой логики у нас на этот счёт не предусмотрено, то просто пропускаем ответ
|
||||
// Поскольку никакой логики у нас на этот счёт не предусмотрено, то просто пропускаем ответ.
|
||||
log.Infof("Got unknown pong from %s to %s", v.From, v.To)
|
||||
log.Debug(spew.Sdump(e))
|
||||
}
|
||||
}
|
||||
|
||||
// Подтверждение бана
|
||||
// Подтверждение бана.
|
||||
case v.To == j.Talk.JID() && v.ID == "ban1":
|
||||
mucNickMatch := false
|
||||
|
||||
@@ -402,15 +403,15 @@ func (j *Jabber) ParseEvent(e interface{}) { //nolint:maintidx,gocognit,gocyclo
|
||||
}
|
||||
}
|
||||
|
||||
// Формально, ответ должен парситься как пустой result и совпадать с типом jabberSimpleIqGetQuery
|
||||
// Формально, ответ должен парситься как пустой result и совпадать с типом jabberSimpleIqGetQuery.
|
||||
// Ответ приходит с 2 xmlns, похоже, он парсится неправильно в go-xmpp.
|
||||
// TODO: после исправления go-xmpp, надо исправить это тут
|
||||
// TODO: после исправления go-xmpp, надо исправить это тут.
|
||||
var iqStruct SimpleIqGetQuery
|
||||
|
||||
if err := xml.Unmarshal(v.Query, &iqStruct); err == nil {
|
||||
if iqStruct.Xmlns == "http://jabber.org/protocol/muc#admin" && iqStruct.Text == "" {
|
||||
if mucNickMatch {
|
||||
// Поскольку никакой логики у нас на этот счёт не предусмотрено, то просто пропускаем ответ
|
||||
// Поскольку никакой логики у нас на этот счёт не предусмотрено, то просто пропускаем ответ.
|
||||
log.Infof("Got ban successful from %s to %s", v.From, v.To)
|
||||
} else {
|
||||
log.Infof("Got ban successful from %s to %s, from room we are not in", v.From, v.To)
|
||||
@@ -465,7 +466,7 @@ func (j *Jabber) ParseEvent(e interface{}) { //nolint:maintidx,gocognit,gocyclo
|
||||
log.Debug(spew.Sdump(e))
|
||||
}
|
||||
|
||||
// Этот бот не управляется со стороны сервера, поэтому все попытки порулить игнорируем
|
||||
// Этот бот не управляется со стороны сервера, поэтому все попытки порулить игнорируем.
|
||||
case xmpp.IQTypeSet:
|
||||
if muc, _ := strings.CutSuffix(v.To, "/"); muc != "" {
|
||||
if slices.Contains(j.RoomsConnected, muc) {
|
||||
@@ -493,7 +494,7 @@ func (j *Jabber) ParseEvent(e interface{}) { //nolint:maintidx,gocognit,gocyclo
|
||||
|
||||
log.Debug(spew.Sdump(e))
|
||||
|
||||
// Нам прилетело сообщение об ошибке
|
||||
// Нам прилетело сообщение об ошибке.
|
||||
case xmpp.IQTypeError:
|
||||
// Если сервер не хочет пинговаться и отвечает ошибкой на пинг, то наверно он не умеет в пинги,
|
||||
// хотя если мы его пингуем, значит он анонсировал такой capability. Вот, засранец!
|
||||
@@ -506,6 +507,7 @@ func (j *Jabber) ParseEvent(e interface{}) { //nolint:maintidx,gocognit,gocyclo
|
||||
if iqPingStruct.Xmlns == "urn:xmpp:ping" {
|
||||
if v.From == j.C.Jabber.Server {
|
||||
msg := "Server announced that it can answer c2s ping, but gives us an error to such query, "
|
||||
|
||||
msg += "fallback to keepalive whitespace pings"
|
||||
log.Error(msg)
|
||||
|
||||
@@ -521,7 +523,7 @@ func (j *Jabber) ParseEvent(e interface{}) { //nolint:maintidx,gocognit,gocyclo
|
||||
parseError = false
|
||||
}
|
||||
|
||||
// Это у нас пинг xep-0410 и мы не в комнате, предполагается, что надо бы заджойниться
|
||||
// Это у нас пинг xep-0410 и мы не в комнате, предполагается, что надо бы заджойниться.
|
||||
if parseError {
|
||||
var iqErrorCancelNotAcceptable IqErrorCancelNotAcceptable
|
||||
|
||||
@@ -572,13 +574,13 @@ func (j *Jabber) ParseEvent(e interface{}) { //nolint:maintidx,gocognit,gocyclo
|
||||
log.Error("Unhandled IQ Error message")
|
||||
log.Error(spew.Sdump(e))
|
||||
}
|
||||
// Нам прилетело что-то неизвестное из семейства IQ stanza
|
||||
// Нам прилетело что-то неизвестное из семейства IQ stanza.
|
||||
default:
|
||||
log.Info("Got an unknown IQ request. Dunno how deal with it, discarding")
|
||||
log.Info(spew.Sdump(e))
|
||||
}
|
||||
|
||||
// Смена статуса участника
|
||||
// Смена статуса участника.
|
||||
case xmpp.Presence:
|
||||
if muc, _ := strings.CutSuffix(v.To, "/"); muc != "" {
|
||||
if slices.Contains(j.RoomsConnected, muc) {
|
||||
@@ -593,13 +595,21 @@ func (j *Jabber) ParseEvent(e interface{}) { //nolint:maintidx,gocognit,gocyclo
|
||||
// клиента забанят из-за спама "вошёл-вышел".
|
||||
// Однако, это не значит, что такую ситуацию мы не должны корректным образом обрабатывать.
|
||||
if v.Type == "unavailable" {
|
||||
// Считаем, что мы больше не в комнате, поэтому не знаем, кто там есть
|
||||
// Считаем, что мы больше не в комнате, поэтому не знаем, кто там есть.
|
||||
j.RoomPresences.Delete(v.From)
|
||||
|
||||
log.Error("Presence notification - looks like another instance of client leaves room")
|
||||
|
||||
if slices.Contains(j.RoomsConnected, v.From) {
|
||||
go j.JoinMuc(v.From)
|
||||
j.GTomb.Go(
|
||||
func() error {
|
||||
if err := j.JoinMuc(v.From); err != nil {
|
||||
return fmt.Errorf("unable to join MUC %s: %w", v.From, err)
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
)
|
||||
}
|
||||
} else {
|
||||
log.Errorf("Presence notification, Type: %s, From: %s, Show: %s, Status: %s",
|
||||
@@ -621,18 +631,19 @@ func (j *Jabber) ParseEvent(e interface{}) { //nolint:maintidx,gocognit,gocyclo
|
||||
return
|
||||
}
|
||||
|
||||
// Это наш собственный Presence
|
||||
// Это наш собственный Presence.
|
||||
if v.Show == "" && v.Status == "" {
|
||||
if nick == j.GetBotNickFromRoomConfig(room) {
|
||||
j.RoomsConnected = append(j.RoomsConnected, room)
|
||||
// На всякий случай дедуплицируем список комнат, к которым мы заджойнились.
|
||||
sort.Strings(j.RoomsConnected)
|
||||
slices.Compact(j.RoomsConnected)
|
||||
|
||||
j.RoomsConnected = slices.Compact(j.RoomsConnected)
|
||||
}
|
||||
}
|
||||
|
||||
switch v.Role {
|
||||
// Участник ушёл
|
||||
// Участник ушёл.
|
||||
case "none":
|
||||
if presenceJSONInterface, present := j.RoomPresences.Get(room); present {
|
||||
presenceJSONStrings := InterfaceToStringSlice(presenceJSONInterface)
|
||||
@@ -641,6 +652,7 @@ func (j *Jabber) ParseEvent(e interface{}) { //nolint:maintidx,gocognit,gocyclo
|
||||
|
||||
for _, presenceJSONstring := range presenceJSONStrings {
|
||||
var p xmpp.Presence
|
||||
|
||||
_ = json.Unmarshal([]byte(presenceJSONstring), &p)
|
||||
|
||||
if p.From == v.From {
|
||||
@@ -652,7 +664,7 @@ func (j *Jabber) ParseEvent(e interface{}) { //nolint:maintidx,gocognit,gocyclo
|
||||
|
||||
j.RoomPresences.Set(room, newPresenceJSONStrings)
|
||||
}
|
||||
// Участник пришёл
|
||||
// Участник пришёл.
|
||||
default:
|
||||
var (
|
||||
presenceJSONStrings []string
|
||||
@@ -666,6 +678,7 @@ func (j *Jabber) ParseEvent(e interface{}) { //nolint:maintidx,gocognit,gocyclo
|
||||
|
||||
for _, presenceJSONString := range presenceJSONStrings {
|
||||
var p xmpp.Presence
|
||||
|
||||
_ = json.Unmarshal([]byte(presenceJSONString), &p)
|
||||
|
||||
// Если находим, что у нас уже есть клиент с таким же From, то есть полным nick-ом (для groupchat)
|
||||
@@ -696,7 +709,7 @@ func (j *Jabber) ParseEvent(e interface{}) { //nolint:maintidx,gocognit,gocyclo
|
||||
}
|
||||
}
|
||||
|
||||
// Ответ на запрос поддерживаемых фич, который "http://jabber.org/protocol/disco#info"
|
||||
// Ответ на запрос поддерживаемых фич, который "http://jabber.org/protocol/disco#info".
|
||||
case xmpp.DiscoResult:
|
||||
if muc, _ := strings.CutSuffix(v.To, "/"); muc != "" {
|
||||
if slices.Contains(j.RoomsConnected, muc) {
|
||||
@@ -704,12 +717,12 @@ func (j *Jabber) ParseEvent(e interface{}) { //nolint:maintidx,gocognit,gocyclo
|
||||
}
|
||||
}
|
||||
|
||||
// Я видел 2 типа disco result и они отличались только []identities. Попробуем вытащить известный identity
|
||||
// TODO: проверять адресата v.To
|
||||
// Я видел 2 типа disco result и они отличались только []identities. Попробуем вытащить известный identity.
|
||||
// TODO: проверять адресата v.To.
|
||||
for _, ident := range v.Identities {
|
||||
switch ident.Category {
|
||||
case "server":
|
||||
// Конкретно сейчас нас интересует только поддержка c2s ping
|
||||
// Конкретно сейчас нас интересует только поддержка c2s ping.
|
||||
for _, feature := range v.Features {
|
||||
log.Debugf("Server %s announced that it supports feature: %s", v.From, feature)
|
||||
j.ServerCapsList.Set(feature, true)
|
||||
@@ -722,6 +735,7 @@ func (j *Jabber) ParseEvent(e interface{}) { //nolint:maintidx,gocognit,gocyclo
|
||||
|
||||
for _, feature := range v.Features {
|
||||
log.Debugf("MUC %s announced that it supports feature: %s", v.From, feature)
|
||||
|
||||
mucCaps[feature] = true //nolint:wsl
|
||||
}
|
||||
|
||||
@@ -736,7 +750,7 @@ func (j *Jabber) ParseEvent(e interface{}) { //nolint:maintidx,gocognit,gocyclo
|
||||
}
|
||||
}
|
||||
|
||||
// Это что-то неизвестное, подампим событие в лог
|
||||
// Это что-то неизвестное, подампим событие в лог.
|
||||
default:
|
||||
log.Info(spew.Sdump(e))
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@ import (
|
||||
// MyLoop - основной цикл программы.
|
||||
func (j *Jabber) MyLoop() error {
|
||||
for {
|
||||
// Зададим начальное значение глобальным переменным
|
||||
// Зададим начальное значение глобальным переменным.
|
||||
j.ServerPingTimestampRx = 0
|
||||
j.ServerPingTimestampTx = 0
|
||||
j.RoomsConnected = make([]string, 1)
|
||||
@@ -23,7 +23,7 @@ func (j *Jabber) MyLoop() error {
|
||||
j.ServerPingTimestampRx = 0
|
||||
j.RoomPresences = NewCollection()
|
||||
|
||||
// Установим коннект
|
||||
// Установим коннект.
|
||||
if err := j.EstablishConnection(); err != nil {
|
||||
log.Error(err)
|
||||
|
||||
@@ -48,14 +48,8 @@ func (j *Jabber) MyLoop() error {
|
||||
// j.GTomb.Go() тут зовётся внутри на каждую комнату, если у нас ошибка, эта горутинка рано или поздно выйдет.
|
||||
go j.ProbeMUCLiveness()
|
||||
|
||||
// Гребём ивенты...
|
||||
for {
|
||||
// Стриггерилось завершение работы приложения, или соединение не установлено (порвалось, например)
|
||||
// грести не надо
|
||||
if j.Shutdown {
|
||||
break
|
||||
}
|
||||
|
||||
// Гребём ивенты, пока стриггерилось завершение работы приложения, или соединение не порвалось.
|
||||
for !j.Shutdown {
|
||||
if !j.IsConnected {
|
||||
// Tight loop - это наверно не очень хорошо, думаю, ничего страшного не будет, если мы поспим 100мс.
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
@@ -63,7 +57,7 @@ func (j *Jabber) MyLoop() error {
|
||||
continue
|
||||
}
|
||||
|
||||
// Вынимаем ивент из "провода"
|
||||
// Вынимаем ивент из "провода".
|
||||
chat, err := j.Talk.Recv()
|
||||
|
||||
if err != nil {
|
||||
|
||||
@@ -28,7 +28,7 @@ func (j *Jabber) ReadConfig() error { //nolint:gocognit,gocyclo
|
||||
return err
|
||||
}
|
||||
|
||||
configJSONPath := fmt.Sprintf("%s/data/config.json", filepath.Dir(executablePath))
|
||||
configJSONPath := filepath.Dir(executablePath) + "/data/config.json"
|
||||
|
||||
locations := []string{
|
||||
"~/.buny-jabber-bot.json",
|
||||
@@ -66,7 +66,7 @@ func (j *Jabber) ReadConfig() error { //nolint:gocognit,gocyclo
|
||||
// сериализуем в json, а потом json превращаем в структурку. Не очень эффективно, но он и нечасто требуется.
|
||||
var (
|
||||
sampleConfig MyConfig
|
||||
tmp map[string]interface{}
|
||||
tmp map[string]any
|
||||
)
|
||||
|
||||
err = hjson.Unmarshal(buf, &tmp)
|
||||
@@ -80,7 +80,7 @@ func (j *Jabber) ReadConfig() error { //nolint:gocognit,gocyclo
|
||||
|
||||
tmpJSON, err := json.Marshal(tmp)
|
||||
|
||||
// Не удалось преобразовать map-ку в json
|
||||
// Не удалось преобразовать map-ку в json.
|
||||
if err != nil {
|
||||
log.Warnf("Skip parsing config file %s: %s", location, err)
|
||||
|
||||
@@ -93,9 +93,10 @@ func (j *Jabber) ReadConfig() error { //nolint:gocognit,gocyclo
|
||||
continue
|
||||
}
|
||||
|
||||
// Значения для Jabber-клиента
|
||||
// Значения для Jabber-клиента.
|
||||
if sampleConfig.Jabber.Server == "" {
|
||||
log.Error("Jabber server is not defined in config, using localhost")
|
||||
|
||||
sampleConfig.Jabber.Server = "localhost" //nolint:wsl
|
||||
}
|
||||
|
||||
@@ -121,7 +122,7 @@ func (j *Jabber) ReadConfig() error { //nolint:gocognit,gocyclo
|
||||
sampleConfig.Jabber.SslVerify = false
|
||||
}
|
||||
|
||||
// sampleConfig.Jabber.InsecureAllowUnencryptedAuth = false, если не задан
|
||||
// sampleConfig.Jabber.InsecureAllowUnencryptedAuth = false, если не задан.
|
||||
|
||||
if sampleConfig.Jabber.ConnectionTimeout == 0 {
|
||||
sampleConfig.Jabber.ConnectionTimeout = 10
|
||||
@@ -175,14 +176,14 @@ func (j *Jabber) ReadConfig() error { //nolint:gocognit,gocyclo
|
||||
log.Infof("Jabber user not defined in config, guessing, it can be %s", sampleConfig.Jabber.User)
|
||||
}
|
||||
|
||||
// Если sampleConfig.Jabber.Password не задан, то авторизации не будет
|
||||
// Если sampleConfig.Jabber.Password не задан, то авторизации не будет.
|
||||
|
||||
// Если не задано ни одного мастера, то бот сам себе мастер
|
||||
// Если не задано ни одного мастера, то бот сам себе мастер.
|
||||
if len(sampleConfig.Jabber.BotMasters) == 0 {
|
||||
sampleConfig.Jabber.BotMasters[0] = sampleConfig.Jabber.User
|
||||
}
|
||||
|
||||
// Нам бот нужен в каких-то чат-румах, а не "просто так"
|
||||
// Нам бот нужен в каких-то чат-румах, а не "просто так".
|
||||
if len(sampleConfig.Jabber.Channels) < 1 {
|
||||
return errors.New("no jabber channels/rooms defined in config, quitting") //nolint:goerr113
|
||||
}
|
||||
@@ -196,8 +197,8 @@ func (j *Jabber) ReadConfig() error { //nolint:gocognit,gocyclo
|
||||
sampleConfig.Jabber.Channels[n].Nick = sampleConfig.Jabber.Nick
|
||||
}
|
||||
|
||||
// channel.Password может быть пустым, тогда пароля нет
|
||||
// channel.Bayes.Enabled будет false, если не проставлен
|
||||
// channel.Password может быть пустым, тогда пароля нет.
|
||||
// channel.Bayes.Enabled будет false, если не проставлен.
|
||||
|
||||
if channel.Bayes.MinLength < 40 {
|
||||
channel.Bayes.MinLength = 40
|
||||
@@ -215,7 +216,7 @@ func (j *Jabber) ReadConfig() error { //nolint:gocognit,gocyclo
|
||||
channel.Bayes.DefaultAction = "log"
|
||||
}
|
||||
|
||||
// channel.AllCaps.Enabled будет false, если не указан
|
||||
// channel.AllCaps.Enabled будет false, если не указан.
|
||||
if channel.AllCaps.MinLength < 10 {
|
||||
channel.AllCaps.MinLength = 10
|
||||
}
|
||||
@@ -229,23 +230,23 @@ func (j *Jabber) ReadConfig() error { //nolint:gocognit,gocyclo
|
||||
}
|
||||
}
|
||||
|
||||
// Если список фраз с которыми стартует бот пустой, вносим в него 1 запись с пустой строкой
|
||||
// Если список фраз с которыми стартует бот пустой, вносим в него 1 запись с пустой строкой.
|
||||
if len(sampleConfig.Jabber.StartupStatus) == 0 {
|
||||
sampleConfig.Jabber.StartupStatus[0] = ""
|
||||
}
|
||||
|
||||
// Если список статусов, с которыми работает бот пустой, вносим в него 1 запись с пустой строкой
|
||||
// Если список статусов, с которыми работает бот пустой, вносим в него 1 запись с пустой строкой.
|
||||
if len(sampleConfig.Jabber.RuntimeStatus.Text) == 0 {
|
||||
sampleConfig.Jabber.RuntimeStatus.Text[0] = ""
|
||||
}
|
||||
|
||||
// Если sampleConfig.Jabber.RuntimeStatus.RotationTime не задан, то он равен 0
|
||||
// Если sampleConfig.Jabber.RuntimeStatus.RotationSplayTime не задан, то он равен 0
|
||||
// Если sampleConfig.Jabber.RuntimeStatus.RotationTime не задан, то он равен 0.
|
||||
// Если sampleConfig.Jabber.RuntimeStatus.RotationSplayTime не задан, то он равен 0.
|
||||
|
||||
// Если sampleConfig.Jabber.BanDelay не задан, то он равен 0
|
||||
// Если sampleConfig.Jabber.BanPhrasesEnable не задан, то он false
|
||||
// Если sampleConfig.Jabber.BanDelay не задан, то он равен 0.
|
||||
// Если sampleConfig.Jabber.BanPhrasesEnable не задан, то он false.
|
||||
|
||||
// Если список фраз, с которыми банят пустой, то вносим в него одну позицию с пустой строкой
|
||||
// Если список фраз, с которыми банят пустой, то вносим в него одну позицию с пустой строкой.
|
||||
if len(sampleConfig.Jabber.BanPhrases) == 0 {
|
||||
sampleConfig.Jabber.BanPhrases[0] = ""
|
||||
}
|
||||
@@ -260,7 +261,7 @@ func (j *Jabber) ReadConfig() error { //nolint:gocognit,gocyclo
|
||||
log.Info("loglevel not defined in config, using info")
|
||||
}
|
||||
|
||||
// sampleConfig.Log = "" if not set
|
||||
// sampleConfig.Log = "" if not set.
|
||||
|
||||
j.C = sampleConfig
|
||||
configLoaded = true
|
||||
|
||||
@@ -8,6 +8,8 @@ import (
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
// SigHandler срабатывает если происходит завершение программы, инициированное либо init-ом, либо пользователем.
|
||||
// Обрабатвает сигналы INT, TERM, QUIT.
|
||||
func (j *Jabber) SigHandler() error {
|
||||
log.Debug("Installing signal handler")
|
||||
|
||||
|
||||
@@ -38,6 +38,7 @@ func (j *Jabber) Squash(room, jid string, reasonEnable bool, vType string) (stri
|
||||
|
||||
if reasonEnable {
|
||||
var t = time.Now()
|
||||
|
||||
ban += fmt.Sprintf(
|
||||
"<reason>autoban at %04d.%02d.%02d %02d:%02d:%02d</reason>",
|
||||
t.Year(),
|
||||
|
||||
@@ -8,208 +8,209 @@ import (
|
||||
"gopkg.in/tomb.v2"
|
||||
)
|
||||
|
||||
// MyConfig прототип структурки с конфигом.
|
||||
type MyConfig struct {
|
||||
type (
|
||||
// MyConfig прототип структурки с конфигом.
|
||||
MyConfig struct {
|
||||
CSign string `json:"csign,omitempty"`
|
||||
Loglevel string `json:"loglevel,omitempty"`
|
||||
Log string `json:"log,omitempty"`
|
||||
Version string `json:"version,omitempty"`
|
||||
ExeName string `json:"exe_name,omitempty"`
|
||||
Jabber struct {
|
||||
Server string `json:"server,omitempty"`
|
||||
Nick string `json:"nick,omitempty"`
|
||||
Resource string `json:"resource,omitempty"`
|
||||
User string `json:"user,omitempty"`
|
||||
Password string `json:"password,omitempty"`
|
||||
StartupStatus []string `json:"startup_status,omitempty"`
|
||||
BotMasters []string `json:"bot_masters,omitempty"`
|
||||
BanPhrases []string `json:"ban_phrases,omitempty"`
|
||||
RuntimeStatus struct {
|
||||
Text []string `json:"text,omitempty"`
|
||||
RotationTime int64 `json:"rotation_time,omitempty"`
|
||||
RotationSplayTime int64 `json:"rotation_splay_time,omitempty"`
|
||||
} `json:"runtime_status"`
|
||||
Channels []struct {
|
||||
Name string `json:"name,omitempty"`
|
||||
Nick string `json:"nick,omitempty"`
|
||||
Password string `json:"password,omitempty"`
|
||||
AllCaps struct {
|
||||
DefaultAction string `json:"default_action,omitempty"`
|
||||
MinLength int `json:"min_length,omitempty"`
|
||||
Enabled bool `json:"enabled,omitempty"`
|
||||
} `json:"all_caps"`
|
||||
Bayes struct {
|
||||
DefaultAction string `json:"default_action,omitempty"`
|
||||
MinWords int64 `json:"min_words,omitempty"`
|
||||
MinLength int `json:"min_length,omitempty"`
|
||||
Enabled bool `json:"enabled,omitempty"`
|
||||
} `json:"bayes"`
|
||||
} `json:"channels"`
|
||||
ConnectionTimeout int64 `json:"connection_timeout,omitempty"`
|
||||
ReconnectDelay int64 `json:"reconnect_delay,omitempty"`
|
||||
ServerPingDelay int64 `json:"server_ping_delay,omitempty"`
|
||||
MucPingDelay int64 `json:"muc_ping_delay,omitempty"`
|
||||
MucRejoinDelay int64 `json:"muc_rejoin_delay,omitempty"`
|
||||
PingSplayDelay int64 `json:"ping_splay_delay,omitempty"`
|
||||
BanDelay int64 `json:"ban_delay,omitempty"`
|
||||
Port int `json:"port,omitempty"`
|
||||
Ssl bool `json:"ssl,omitempty"`
|
||||
StartTLS bool `json:"starttls,omitempty"`
|
||||
SslVerify bool `json:"ssl_verify,omitempty"`
|
||||
InsecureAllowUnencryptedAuth bool `json:"insecureallowunencryptedauth,omitempty"`
|
||||
BanPhrasesEnable bool `json:"ban_phrases_enable,omitempty"`
|
||||
} `json:"jabber"`
|
||||
}
|
||||
|
||||
// MyWhiteList прототип структурки с белым списком jid-ов.
|
||||
MyWhiteList struct {
|
||||
Whitelist []struct {
|
||||
RoomName string `json:"room_name,omitempty"`
|
||||
Jid []string `json:"jid,omitempty"`
|
||||
WipeBans bool `json:"wipe_bans,omitempty"`
|
||||
} `json:"whitelist,omitempty"`
|
||||
}
|
||||
|
||||
// MyBlackList прототип структурки с чёрным списком jid-ов.
|
||||
MyBlackList struct {
|
||||
Blacklist []struct {
|
||||
RoomName string `json:"room_name,omitempty"`
|
||||
JidRe []string `json:"jid_re,omitempty"`
|
||||
NickRe []string `json:"nick_re,omitempty"`
|
||||
PhraseRe []string `json:"phrase_re,omitempty"`
|
||||
UserAgent []struct {
|
||||
Name string `json:"name,omitempty"`
|
||||
Version string `json:"version,omitempty"`
|
||||
Os string `json:"os,omitempty"`
|
||||
} `json:"user_agent"`
|
||||
ReasonEnable bool `json:"reason_enable,omitempty"`
|
||||
} `json:"blacklist"`
|
||||
}
|
||||
|
||||
// Jabber основная структура-объект, содержащая стейты и проч.
|
||||
Jabber struct {
|
||||
Server string `json:"server,omitempty"`
|
||||
Port int `json:"port,omitempty"`
|
||||
Ssl bool `json:"ssl,omitempty"`
|
||||
StartTLS bool `json:"starttls,omitempty"`
|
||||
SslVerify bool `json:"ssl_verify,omitempty"`
|
||||
InsecureAllowUnencryptedAuth bool `json:"insecureallowunencryptedauth,omitempty"`
|
||||
ConnectionTimeout int64 `json:"connection_timeout,omitempty"`
|
||||
ReconnectDelay int64 `json:"reconnect_delay,omitempty"`
|
||||
ServerPingDelay int64 `json:"server_ping_delay,omitempty"`
|
||||
MucPingDelay int64 `json:"muc_ping_delay,omitempty"`
|
||||
MucRejoinDelay int64 `json:"muc_rejoin_delay,omitempty"`
|
||||
PingSplayDelay int64 `json:"ping_splay_delay,omitempty"`
|
||||
Nick string `json:"nick,omitempty"`
|
||||
Resource string `json:"resource,omitempty"`
|
||||
User string `json:"user,omitempty"`
|
||||
Password string `json:"password,omitempty"`
|
||||
BotMasters []string `json:"bot_masters,omitempty"`
|
||||
Channels []struct {
|
||||
Name string `json:"name,omitempty"`
|
||||
Nick string `json:"nick,omitempty"`
|
||||
Password string `json:"password,omitempty"`
|
||||
Bayes struct {
|
||||
Enabled bool `json:"enabled,omitempty"`
|
||||
MinWords int64 `json:"min_words,omitempty"`
|
||||
MinLength int `json:"min_length,omitempty"`
|
||||
DefaultAction string `json:"default_action,omitempty"`
|
||||
} `json:"bayes,omitempty"`
|
||||
AllCaps struct {
|
||||
Enabled bool `json:"enabled,omitempty"`
|
||||
MinLength int `json:"min_length,omitempty"`
|
||||
DefaultAction string `json:"default_action,omitempty"`
|
||||
} `json:"all_caps,omitempty"`
|
||||
} `json:"channels"`
|
||||
StartupStatus []string `json:"startup_status,omitempty"`
|
||||
RuntimeStatus struct {
|
||||
Text []string `json:"text,omitempty"`
|
||||
RotationTime int64 `json:"rotation_time,omitempty"`
|
||||
RotationSplayTime int64 `json:"rotation_splay_time,omitempty"`
|
||||
} `json:"runtime_status,omitempty"`
|
||||
BanDelay int64 `json:"ban_delay,omitempty"`
|
||||
BanPhrasesEnable bool `json:"ban_phrases_enable,omitempty"`
|
||||
BanPhrases []string `json:"ban_phrases,omitempty"`
|
||||
} `json:"jabber,omitempty"`
|
||||
// C - конфиг, как он распарсился из конфиг-файла.
|
||||
C MyConfig
|
||||
|
||||
CSign string `json:"csign,omitempty"`
|
||||
Loglevel string `json:"loglevel,omitempty"`
|
||||
Log string `json:"log,omitempty"`
|
||||
Version string `json:"version,omitempty"`
|
||||
ExeName string `json:"exe_name,omitempty"`
|
||||
}
|
||||
// WhiteList - структурка с безусловно разрешёнными jid-ами.
|
||||
WhiteList MyWhiteList
|
||||
|
||||
// MyWhiteList прототип структурки с белым списком jid-ов.
|
||||
type MyWhiteList struct {
|
||||
Whitelist []struct {
|
||||
RoomName string `json:"room_name,omitempty"`
|
||||
Jid []string `json:"jid,omitempty"`
|
||||
WipeBans bool `json:"wipe_bans,omitempty"`
|
||||
} `json:"whitelist,omitempty"`
|
||||
}
|
||||
// BlackList - структурка с запрещёнными по регуляркам фразами, никами, jid-ами.
|
||||
BlackList MyBlackList
|
||||
|
||||
// MyBlackList прототип структурки с чёрным списком jid-ов.
|
||||
type MyBlackList struct {
|
||||
Blacklist []struct {
|
||||
RoomName string `json:"room_name,omitempty"`
|
||||
ReasonEnable bool `json:"reason_enable,omitempty"`
|
||||
JidRe []string `json:"jid_re,omitempty"`
|
||||
NickRe []string `json:"nick_re,omitempty"`
|
||||
PhraseRe []string `json:"phrase_re,omitempty"`
|
||||
UserAgent []struct {
|
||||
Name string `json:"name,omitempty"`
|
||||
Version string `json:"version,omitempty"`
|
||||
Os string `json:"os,omitempty"`
|
||||
} `json:"user_agent,omitempty"`
|
||||
} `json:"blacklist,omitempty"`
|
||||
}
|
||||
// Опции подключения к xmpp-серверу.
|
||||
Options *xmpp.Options
|
||||
|
||||
// Jabber основная структура-объект, содержащая стейты и проч.
|
||||
type Jabber struct {
|
||||
// C - конфиг, как он распарсился из конфиг-файла.
|
||||
C MyConfig
|
||||
// gTomb пул активных горутин.
|
||||
GTomb tomb.Tomb
|
||||
|
||||
// WhiteList - структурка с безусловно разрешёнными jid-ами
|
||||
WhiteList MyWhiteList
|
||||
// Talk основная структурка xmpp-клиента.
|
||||
Talk *xmpp.Client
|
||||
|
||||
// BlackList - структурка с запрещёнными по регуляркам фразами, никами, jid-ами.
|
||||
BlackList MyBlackList
|
||||
// sync.Map-ка с капабилити сервера.
|
||||
ServerCapsList *Collection
|
||||
|
||||
// Опции подключения к xmpp-серверу.
|
||||
Options *xmpp.Options
|
||||
// ServerCapsQueried показывает, были ли запрошены capabilities сервера.
|
||||
ServerCapsQueried bool
|
||||
|
||||
// gTomb пул активных горутин.
|
||||
GTomb tomb.Tomb
|
||||
// Время последней активности, нужно для c2s пингов - посылаем пинги, только если давненько ничего не приходило с
|
||||
// сервера.
|
||||
LastServerActivity int64
|
||||
|
||||
// Talk основная структурка xmpp-клиента.
|
||||
Talk *xmpp.Client
|
||||
// Время, когда был отправлен c2s ping.
|
||||
ServerPingTimestampTx int64
|
||||
|
||||
// sync.Map-ка с капабилити сервера.
|
||||
ServerCapsList *Collection
|
||||
// Время, когда был принят s2c pong.
|
||||
ServerPingTimestampRx int64
|
||||
|
||||
// ServerCapsQueried показывает, были ли запрошены capabilities сервера.
|
||||
ServerCapsQueried bool
|
||||
// Время последней активности, нужно для jabber:iq:last.
|
||||
LastActivity int64
|
||||
|
||||
// Время последней активности, нужно для c2s пингов - посылаем пинги, только если давненько ничего не приходило с
|
||||
// сервера.
|
||||
LastServerActivity int64
|
||||
// sync.Map-ка с комнатами и их capability.
|
||||
MucCapsList *Collection
|
||||
|
||||
// Время, когда был отправлен c2s ping.
|
||||
ServerPingTimestampTx int64
|
||||
// Время последней активности MUC-ов, нужно для пингов - посылаем пинги, только если давненько ничего не приходило из
|
||||
// muc-ов.
|
||||
LastMucActivity *Collection
|
||||
|
||||
// Время, когда был принят s2c pong.
|
||||
ServerPingTimestampRx int64
|
||||
// Список комнат, в которых находится бот.
|
||||
RoomsConnected []string
|
||||
|
||||
// Время последней активности, нужно для jabber:iq:last.
|
||||
LastActivity int64
|
||||
// sync.Map-ка со списком участников конференций (в json-формате, согласно структуре xmpp.Presence, "room".[]json).
|
||||
RoomPresences *Collection
|
||||
|
||||
// sync.Map-ка с комнатами и их capability.
|
||||
MucCapsList *Collection
|
||||
// Канал, по котором приходят сообщения о том, что ОС отправила некие сигналы процессу.
|
||||
SigChan chan os.Signal
|
||||
|
||||
// Время последней активности MUC-ов, нужно для пингов - посылаем пинги, только если давненько ничего не приходило из
|
||||
// muc-ов.
|
||||
LastMucActivity *Collection
|
||||
// Индиктор того, что процесс завершается.
|
||||
Shutdown bool
|
||||
|
||||
// Список комнат, в которых находится бот.
|
||||
RoomsConnected []string
|
||||
// Индиктор того, что соединение установлено.
|
||||
IsConnected bool
|
||||
|
||||
// sync.Map-ка со списком участников конференций (в json-формате, согласно структуре xmpp.Presence, "room".[]json).
|
||||
RoomPresences *Collection
|
||||
// Индикатор того, что соединение в процессе достукивания до сервера.
|
||||
Connecting bool
|
||||
}
|
||||
|
||||
// Канал, по котором приходят сообщения о том, что ОС отправила некие сигналы процессу.
|
||||
SigChan chan os.Signal
|
||||
// SimpleIqGetQuery прототип структурки для разбора запросов xmpp discovery query, например,
|
||||
// https://xmpp.org/extensions/xep-0030.html#example-18 .
|
||||
SimpleIqGetQuery struct {
|
||||
XMLName xml.Name `xml:"query"`
|
||||
Text string `xml:",chardata"`
|
||||
Xmlns string `xml:"xmlns,attr"`
|
||||
Node string `xml:"node,attr,omitempty"` // для xmlns="http://jabber.org/protocol/disco#items"
|
||||
}
|
||||
|
||||
// Индиктор того, что процесс завершается.
|
||||
Shutdown bool
|
||||
// PubsubIQGetQuery прототип структурки для разбора запросов xmpp pubsub.
|
||||
PubsubIQGetQuery struct {
|
||||
XMLName xml.Name `xml:"pubsub"`
|
||||
Text string `xml:",chardata"`
|
||||
Xmlns string `xml:"xmlns,attr"`
|
||||
Items struct {
|
||||
Text string `xml:",chardata"`
|
||||
Node string `xml:"node,attr"`
|
||||
MaxItems string `xml:"max_items,attr"`
|
||||
} `xml:"items"`
|
||||
}
|
||||
|
||||
// Индиктор того, что соединение установлено.
|
||||
IsConnected bool
|
||||
// TimeIqGetQuery прототип структурки для разбора IQ запросов на локальное время клиента,
|
||||
// https://xmpp.org/extensions/xep-0202.html
|
||||
TimeIqGetQuery struct {
|
||||
// <time xmlns="urn:xmpp:time"/>.
|
||||
XMLName xml.Name `xml:"time"`
|
||||
Text string `xml:",chardata"`
|
||||
Xmlns string `xml:"xmlns,attr"`
|
||||
}
|
||||
|
||||
// Индикатор того, что соединение в процессе достукивания до сервера.
|
||||
Connecting bool
|
||||
}
|
||||
// IqPing прототип структурки для разбора IQ запросов на пинг клиента, https://xmpp.org/extensions/xep-0199.html
|
||||
IqPing struct {
|
||||
XMLName xml.Name `xml:"ping"`
|
||||
Text string `xml:",chardata"`
|
||||
Xmlns string `xml:"xmlns,attr"`
|
||||
}
|
||||
|
||||
// SimpleIqGetQuery прототип структурки для разбора запросов xmpp discovery query, например,
|
||||
// https://xmpp.org/extensions/xep-0030.html#example-18 .
|
||||
type SimpleIqGetQuery struct {
|
||||
XMLName xml.Name `xml:"query"`
|
||||
Text string `xml:",chardata"`
|
||||
Xmlns string `xml:"xmlns,attr"`
|
||||
Node string `xml:"node,attr,omitempty"` // для xmlns="http://jabber.org/protocol/disco#items"
|
||||
}
|
||||
// IqErrorCancelNotAcceptable прототип структурки для разбора IQ ответов, когда сервис (сервер, клиент, etc) не
|
||||
// может или не хочет принимать наш iq-запрос https://xmpp.org/extensions/xep-0099.html
|
||||
IqErrorCancelNotAcceptable struct {
|
||||
XMLName xml.Name `xml:"error"`
|
||||
Text string `xml:",chardata"`
|
||||
Type string `xml:"type,attr"`
|
||||
By string `xml:"by,attr"`
|
||||
NotAcceptable struct {
|
||||
Text string `xml:",chardata"`
|
||||
Xmlns string `xml:"xmlns,attr"`
|
||||
} `xml:"not-acceptable"`
|
||||
}
|
||||
|
||||
// PubsubIQGetQuery прототип структурки для разбора запросов xmpp pubsub.
|
||||
type PubsubIQGetQuery struct {
|
||||
XMLName xml.Name `xml:"pubsub"`
|
||||
Text string `xml:",chardata"`
|
||||
Xmlns string `xml:"xmlns,attr"`
|
||||
Items struct {
|
||||
Text string `xml:",chardata"`
|
||||
Node string `xml:"node,attr"`
|
||||
MaxItems string `xml:"max_items,attr"`
|
||||
} `xml:"items"`
|
||||
}
|
||||
|
||||
// TimeIqGetQuery прототип структурки для разбора IQ запросов на локальное время клиента,
|
||||
// https://xmpp.org/extensions/xep-0202.html
|
||||
type TimeIqGetQuery struct {
|
||||
// <time xmlns="urn:xmpp:time"/>
|
||||
XMLName xml.Name `xml:"time"`
|
||||
Text string `xml:",chardata"`
|
||||
Xmlns string `xml:"xmlns,attr"`
|
||||
}
|
||||
|
||||
// IqPing прототип структурки для разбора IQ запросов на пинг клиента, https://xmpp.org/extensions/xep-0199.html
|
||||
type IqPing struct {
|
||||
XMLName xml.Name `xml:"ping"`
|
||||
Text string `xml:",chardata"`
|
||||
Xmlns string `xml:"xmlns,attr"`
|
||||
}
|
||||
|
||||
// IqErrorCancelNotAcceptable прототип структурки для разбора IQ ответов, когда сервис (сервер, клиент, etc) не
|
||||
// может или не хочет принимать наш iq-запрос https://xmpp.org/extensions/xep-0099.html
|
||||
type IqErrorCancelNotAcceptable struct {
|
||||
XMLName xml.Name `xml:"error"`
|
||||
Text string `xml:",chardata"`
|
||||
Type string `xml:"type,attr"`
|
||||
By string `xml:"by,attr"`
|
||||
NotAcceptable struct {
|
||||
Text string `xml:",chardata"`
|
||||
Xmlns string `xml:"xmlns,attr"`
|
||||
} `xml:"not-acceptable"`
|
||||
}
|
||||
|
||||
// IqResultSoftwareVersion прототип структурки для разбора IQ ответов на запрос о названии и версии клиентского ПО.
|
||||
type IqResultSoftwareVersion struct {
|
||||
XMLName xml.Name `xml:"query"`
|
||||
Text string `xml:",chardata"`
|
||||
Xmlns string `xml:"xmlns,attr"`
|
||||
Name string `xml:"name"`
|
||||
Version string `xml:"version"`
|
||||
Os string `xml:"os,omitempty"`
|
||||
}
|
||||
// IqResultSoftwareVersion прототип структурки для разбора IQ ответов на запрос о названии и версии клиентского ПО.
|
||||
IqResultSoftwareVersion struct {
|
||||
XMLName xml.Name `xml:"query"`
|
||||
Text string `xml:",chardata"`
|
||||
Xmlns string `xml:"xmlns,attr"`
|
||||
Name string `xml:"name"`
|
||||
Version string `xml:"version"`
|
||||
Os string `xml:"os,omitempty"`
|
||||
}
|
||||
)
|
||||
|
||||
/* vim: set ft=go noet ai ts=4 sw=4 sts=4: */
|
||||
|
||||
@@ -26,16 +26,16 @@ func RandomPhrase(list []string) string {
|
||||
|
||||
// InterfaceToStringSlice превращает данный интерфейс в слайс строк.
|
||||
// Если может, конечно :) .
|
||||
func InterfaceToStringSlice(iface interface{}) []string {
|
||||
func InterfaceToStringSlice(iface any) []string {
|
||||
var mySlice []string
|
||||
|
||||
// А теперь мы начинаем дурдом, нам надо превратить ёбанный interface{} в []string
|
||||
// Поскольку interface{} может быть чем угодно, перестрахуемся
|
||||
// А теперь мы начинаем дурдом, нам надо превратить ёбанный any в []string.
|
||||
// Поскольку any может быть чем угодно, перестрахуемся.
|
||||
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)))
|
||||
for v := range shit.Seq() {
|
||||
mySlice = append(mySlice, v.String())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -49,10 +49,10 @@ func (j *Jabber) GetRealJIDfromNick(fullNick string) string {
|
||||
|
||||
room := (strings.SplitN(fullNick, "/", 2))[0]
|
||||
|
||||
// Достанем presence участника
|
||||
// Достанем presence участника.
|
||||
presenceJSONInterface, present := j.RoomPresences.Get(room)
|
||||
|
||||
// Никого нет дома
|
||||
// Никого нет дома.
|
||||
if !present {
|
||||
return ""
|
||||
}
|
||||
@@ -85,8 +85,9 @@ func (j *Jabber) GetBotNickFromRoomConfig(room string) string {
|
||||
// RotateStatus периодически изменяет статус бота в MUC-е согласно настройкам из кофига.
|
||||
func (j *Jabber) RotateStatus(room string) error {
|
||||
for {
|
||||
// TODO: Переделать на ticker-ы
|
||||
// TODO: Переделать на ticker-ы.
|
||||
totalSleepTime := time.Duration(j.C.Jabber.RuntimeStatus.RotationTime) * time.Second
|
||||
|
||||
totalSleepTime += time.Duration(j.C.Jabber.RuntimeStatus.RotationSplayTime) * time.Second
|
||||
|
||||
for {
|
||||
@@ -129,9 +130,9 @@ func (j *Jabber) JoinMuc(room string) error {
|
||||
}
|
||||
|
||||
// Ждём, пока muc нам вернёт список фичей.
|
||||
for i := 0; i < (20 * int(j.C.Jabber.ConnectionTimeout)); i++ {
|
||||
for range 20 * int(j.C.Jabber.ConnectionTimeout) {
|
||||
var (
|
||||
myRoom interface{}
|
||||
myRoom any
|
||||
supported bool
|
||||
exist bool
|
||||
)
|
||||
@@ -154,7 +155,7 @@ func (j *Jabber) JoinMuc(room string) error {
|
||||
}
|
||||
}
|
||||
|
||||
// Заходим в конфу, наконец-то
|
||||
// Заходим в конфу, наконец-то.
|
||||
if _, err := j.Talk.JoinMUCNoHistory(room, j.GetBotNickFromRoomConfig(room)); err != nil {
|
||||
return fmt.Errorf("unable to join to MUC: %s, %w", room, err)
|
||||
}
|
||||
@@ -164,7 +165,7 @@ func (j *Jabber) JoinMuc(room string) error {
|
||||
// Ждём, когда прилетит presence из комнаты, тогда мы точно знаем, что мы вошли.
|
||||
entered := false
|
||||
|
||||
for i := 0; i < (20 * int(j.C.Jabber.ConnectionTimeout)); i++ {
|
||||
for range 20 * int(j.C.Jabber.ConnectionTimeout) {
|
||||
time.Sleep(50 * time.Millisecond)
|
||||
|
||||
if slices.Contains(j.RoomsConnected, room) {
|
||||
@@ -189,7 +190,7 @@ func (j *Jabber) JoinMuc(room string) error {
|
||||
|
||||
j.GTomb.Go(func() error { return j.RotateStatus(room) })
|
||||
|
||||
// Время проверить участников на предмет злобности
|
||||
// Время проверить участников на предмет злобности.
|
||||
namesInterface, present := j.RoomPresences.Get(room)
|
||||
|
||||
// Если room есть в списке presence-ов, то фигачим. room там должен быть, просто обязан.
|
||||
@@ -221,6 +222,7 @@ func (j *Jabber) EstablishConnection() error {
|
||||
j.RoomsConnected = make([]string, 0)
|
||||
|
||||
log.Debugf("Establishing connection to %s", j.Options.Host)
|
||||
|
||||
j.Talk, err = j.Options.NewClient()
|
||||
|
||||
if err != nil {
|
||||
@@ -234,7 +236,7 @@ func (j *Jabber) EstablishConnection() error {
|
||||
|
||||
log.Info("Connected")
|
||||
|
||||
// Джойнимся к чятикам, но делаем это в фоне, чтобы не блочиться на ошибках, например, если бота забанили
|
||||
// Джойнимся к чятикам, но делаем это в фоне, чтобы не блочиться на ошибках, например, если бота забанили.
|
||||
for _, roomStruct := range j.C.Jabber.Channels {
|
||||
room := roomStruct.Name
|
||||
|
||||
@@ -268,6 +270,7 @@ func (j *Jabber) ProbeServerLiveness() error { //nolint:gocognit
|
||||
}
|
||||
|
||||
sleepTime := time.Duration(j.C.Jabber.ServerPingDelay) * 1000 * time.Millisecond
|
||||
|
||||
sleepTime += time.Duration(rand.Int63n(1000*j.C.Jabber.PingSplayDelay)) * time.Millisecond
|
||||
time.Sleep(sleepTime)
|
||||
|
||||
@@ -276,26 +279,25 @@ func (j *Jabber) ProbeServerLiveness() error { //nolint:gocognit
|
||||
}
|
||||
|
||||
// Пингуем, только если не было никакой активности в течение > config.Jabber.ServerPingDelay,
|
||||
// в худшем случе это будет ~ (config.Jabber.PingSplayDelay * 2) + config.Jabber.PingSplayDelay
|
||||
// if (time.Now().Unix() - lastServerActivity) < (config.Jabber.ServerPingDelay + config.Jabber.PingSplayDelay) {
|
||||
// continue
|
||||
// }
|
||||
// в худшем случе это будет ~ (config.Jabber.PingSplayDelay * 2) + config.Jabber.PingSplayDelay.
|
||||
|
||||
if j.ServerCapsQueried { // Сервер ответил на disco#info
|
||||
if j.ServerCapsQueried { // Сервер ответил на disco#info.
|
||||
var (
|
||||
value interface{}
|
||||
value any
|
||||
exist bool
|
||||
)
|
||||
|
||||
value, exist = j.ServerCapsList.Get("urn:xmpp:ping")
|
||||
|
||||
switch {
|
||||
// Сервер анонсировал, что умеет в c2s пинги
|
||||
// Сервер анонсировал, что умеет в c2s пинги.
|
||||
case exist && value.(bool):
|
||||
// Таймаут c2s пинга. Возьмём сумму задержки между пингами, добавим таймаут коннекта и добавим
|
||||
// максимальную корректировку разброса.
|
||||
txTimeout := j.C.Jabber.ServerPingDelay + j.C.Jabber.ConnectionTimeout
|
||||
|
||||
txTimeout += j.C.Jabber.PingSplayDelay
|
||||
|
||||
rxTimeout := txTimeout
|
||||
|
||||
rxTimeAgo := time.Now().Unix() - j.ServerPingTimestampRx
|
||||
@@ -311,7 +313,7 @@ func (j *Jabber) ProbeServerLiveness() error { //nolint:gocognit
|
||||
|
||||
return err
|
||||
|
||||
// По-умолчанию, мы отправляем c2s пинг
|
||||
// По-умолчанию, мы отправляем c2s пинг.
|
||||
default:
|
||||
log.Debugf("Sending c2s ping from %s to %s", j.Talk.JID(), j.C.Jabber.Server)
|
||||
|
||||
@@ -321,7 +323,7 @@ func (j *Jabber) ProbeServerLiveness() error { //nolint:gocognit
|
||||
|
||||
j.ServerPingTimestampTx = time.Now().Unix()
|
||||
}
|
||||
} else { // Первая пуля пока не вылетела, отправляем
|
||||
} else { // Первая пуля пока не вылетела, отправляем.
|
||||
log.Debugf("Sending first c2s ping from %s to %s", j.Talk.JID(), j.C.Jabber.Server)
|
||||
|
||||
if err := j.Talk.PingC2S(j.Talk.JID(), j.C.Jabber.Server); err != nil {
|
||||
@@ -331,7 +333,7 @@ func (j *Jabber) ProbeServerLiveness() error { //nolint:gocognit
|
||||
j.ServerPingTimestampTx = time.Now().Unix()
|
||||
}
|
||||
|
||||
// Сервер не анонсировал, что умеет в c2s пинги
|
||||
// Сервер не анонсировал, что умеет в c2s пинги.
|
||||
default:
|
||||
log.Debug("Sending keepalive whitespace ping")
|
||||
|
||||
@@ -339,7 +341,7 @@ func (j *Jabber) ProbeServerLiveness() error { //nolint:gocognit
|
||||
return err
|
||||
}
|
||||
}
|
||||
} else { // Сервер не ответил на disco#info
|
||||
} else { // Сервер не ответил на disco#info.
|
||||
log.Debug("Sending keepalive whitespace ping")
|
||||
|
||||
if _, err := j.Talk.SendKeepAlive(); err != nil {
|
||||
@@ -358,7 +360,7 @@ func (j *Jabber) ProbeMUCLiveness() { //nolint:gocognit
|
||||
for _, room := range j.RoomsConnected {
|
||||
var (
|
||||
exist bool
|
||||
lastActivityTS interface{}
|
||||
lastActivityTS any
|
||||
)
|
||||
|
||||
// Если записи про комнату нету, то пинговать её бессмысленно.
|
||||
@@ -393,7 +395,7 @@ func (j *Jabber) ProbeMUCLiveness() { //nolint:gocognit
|
||||
}
|
||||
)
|
||||
*/
|
||||
var roomMap interface{}
|
||||
var roomMap any
|
||||
|
||||
roomMap, exist = j.MucCapsList.Get(room)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user