Implement grabbing +B flag, connect command sequence fixes
* Implement i-am-bot, +B, flag grabbing. * Move collections to internal packages. * Postpone nickserv login sequence to 376 and 422 events, as it should be after motd message * Postpone join channels to 376 and 422 events, as it should be after motd message
This commit is contained in:
3
Makefile
3
Makefile
@@ -6,8 +6,7 @@ BUILDOPTS=-ldflags="-s -w" -a -gcflags=all=-l -trimpath
|
|||||||
all: clean build
|
all: clean build
|
||||||
|
|
||||||
build:
|
build:
|
||||||
${GOOPTS} go build ${BUILDOPTS} -o aleesa-irc-go types.go globals.go collection.go irc-user-mode.go \
|
${GOOPTS} go build ${BUILDOPTS} -o aleesa-irc-go .
|
||||||
settings-db-util.go util.go msg-parsers.go aleesa-irc-go-lib.go main.go
|
|
||||||
|
|
||||||
clean:
|
clean:
|
||||||
go clean
|
go clean
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import (
|
|||||||
irc "github.com/thoj/go-ircevent"
|
irc "github.com/thoj/go-ircevent"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// TODO: Implement 005 REPL_BOUNCE
|
||||||
// ircClientInit горутинка для работы с протоколом irc
|
// ircClientInit горутинка для работы с протоколом irc
|
||||||
func ircClientRun() {
|
func ircClientRun() {
|
||||||
for {
|
for {
|
||||||
@@ -47,7 +48,33 @@ func ircClientRun() {
|
|||||||
ircClient.UseSASL = true
|
ircClient.UseSASL = true
|
||||||
}
|
}
|
||||||
|
|
||||||
// Навесим коллбэков на некоторые ответы сервера на наши запросы
|
// Навесим коллбэков на некоторые ответы сервера на наши запросы.
|
||||||
|
|
||||||
|
// 001 RPL_WELCOME уже есть в github.com/thoj/go-ircevent/irc_callback.go.
|
||||||
|
// Сделаем уже что-то полезное! Motd нам уже прислали и теперь можно авторизоваться и джойниться
|
||||||
|
ircClient.AddCallback("004", func(e *irc.Event) {
|
||||||
|
// Из это строки мы можем узнать, какие флаги для user MODE и channem MODE можно навешивать.
|
||||||
|
// Как минимум, эта строка нам нужна, чтобы выяснить, можем ли мы взять себе +B, мы же бот :).
|
||||||
|
log.Debugf("004 RPL_MYINFO, %s", e.Raw)
|
||||||
|
|
||||||
|
e.Connection.Lock()
|
||||||
|
|
||||||
|
// Формат строки с MODE-ами https://datatracker.ietf.org/doc/html/rfc2812#section-5.1 .
|
||||||
|
log.Debugf("Add %s to list of available user modes", e.Arguments[3])
|
||||||
|
|
||||||
|
for _, mode := range strings.Split(e.Arguments[3], "") {
|
||||||
|
availableUserModes.Set(mode, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Debugf("Add %s to list available channel modes", e.Arguments[4])
|
||||||
|
|
||||||
|
for _, mode := range strings.Split(e.Arguments[4], "") {
|
||||||
|
availableChanModes.Set(mode, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
e.Connection.Unlock()
|
||||||
|
})
|
||||||
|
|
||||||
ircClient.AddCallback("319", func(e *irc.Event) {
|
ircClient.AddCallback("319", func(e *irc.Event) {
|
||||||
/* Это одна из строк с данными, прилетающая в ответ на запрос whois на определённого юзера
|
/* Это одна из строк с данными, прилетающая в ответ на запрос whois на определённого юзера
|
||||||
* Из этой строки нас интересует, на каких каналах пользователь op (то есть с префиксом @) или имеет voice
|
* Из этой строки нас интересует, на каких каналах пользователь op (то есть с префиксом @) или имеет voice
|
||||||
@@ -83,9 +110,12 @@ func ircClientRun() {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// 433 ERR_NICKNAMEINUSE уже есть в github.com/thoj/go-ircevent/irc_callback.go.
|
||||||
|
|
||||||
ircClient.AddCallback("353", func(e *irc.Event) {
|
ircClient.AddCallback("353", func(e *irc.Event) {
|
||||||
// Это одна из строк данных, прилетающая в ответ на запрос names, на канале
|
// Это одна из строк данных, прилетающая в ответ на запрос names, на канале
|
||||||
log.Debugf("353 RPL_NAMREPLY, %s", e.Raw)
|
log.Debugf("353 RPL_NAMREPLY, %s", e.Raw)
|
||||||
|
e.Connection.Lock()
|
||||||
namesString := e.Arguments[3]
|
namesString := e.Arguments[3]
|
||||||
channel := e.Arguments[2]
|
channel := e.Arguments[2]
|
||||||
|
|
||||||
@@ -114,6 +144,7 @@ func ircClientRun() {
|
|||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
e.Connection.Unlock()
|
||||||
})
|
})
|
||||||
|
|
||||||
ircClient.AddCallback("352", func(e *irc.Event) {
|
ircClient.AddCallback("352", func(e *irc.Event) {
|
||||||
@@ -122,6 +153,39 @@ func ircClientRun() {
|
|||||||
log.Warnf("352 RPL_WHOREPLY, %s", e.Raw)
|
log.Warnf("352 RPL_WHOREPLY, %s", e.Raw)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// Если сервер не может прочитать MOTD, то он может вернуть 422 ERR_NOMOTD, тоде самое навесим и туда тоже.
|
||||||
|
ircClient.AddCallback("376", func(e *irc.Event) {
|
||||||
|
log.Debugf("376 RPL_ENDOFMOTD %s", e.Raw)
|
||||||
|
|
||||||
|
// Если у нас есть доступный +B возьмём его себе, мы же бот.
|
||||||
|
botFlag, ok := availableUserModes.Get("B")
|
||||||
|
|
||||||
|
if ok && botFlag {
|
||||||
|
log.Info("Grabbing +B flag to mark me as bot")
|
||||||
|
ircClient.Mode(ircClient.GetNick(), "+B")
|
||||||
|
} else {
|
||||||
|
if ok {
|
||||||
|
log.Info("Skip +B flag, server does not support it, noone will know that I am bot")
|
||||||
|
} else {
|
||||||
|
log.Info("Skip +B flag, server did not announce modes (yet?), noone will know that I am bot")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !config.Irc.Sasl && config.Irc.Password != "" && !nickIsUsed {
|
||||||
|
log.Info("Identifying via NickServ")
|
||||||
|
message := fmt.Sprintf("identify %s %s", config.Irc.Nick, config.Irc.Password)
|
||||||
|
imChan <- iMsg{ChatId: "NickServ", Text: message}
|
||||||
|
}
|
||||||
|
// TODO: wait until +R flag been set? could be implemented with waiting for channel message.
|
||||||
|
|
||||||
|
log.Debug("Trying to join to preconfigured channels")
|
||||||
|
|
||||||
|
for _, channel := range config.Irc.Channels {
|
||||||
|
log.Infof("Joining to %s channel", channel)
|
||||||
|
ircClient.Join(channel)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
// Навесим коллбэков на все возможные и невозможные error status code, которые мы можем получить и сдампим это
|
// Навесим коллбэков на все возможные и невозможные error status code, которые мы можем получить и сдампим это
|
||||||
// дело в лог. https://datatracker.ietf.org/doc/html/rfc1459 и https://datatracker.ietf.org/doc/html/rfc2812
|
// дело в лог. https://datatracker.ietf.org/doc/html/rfc1459 и https://datatracker.ietf.org/doc/html/rfc2812
|
||||||
ircClient.AddCallback("401", func(e *irc.Event) {
|
ircClient.AddCallback("401", func(e *irc.Event) {
|
||||||
@@ -157,6 +221,39 @@ func ircClientRun() {
|
|||||||
log.Errorf("421 ERR_UNKNOWNCOMMAND, %s", e.Raw)
|
log.Errorf("421 ERR_UNKNOWNCOMMAND, %s", e.Raw)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// Аналогичный коллбэк висит на 376 RPL_ENDOFMOTD.
|
||||||
|
ircClient.AddCallback("422", func(e *irc.Event) {
|
||||||
|
log.Debugf("422 ERR_NOMOTD %s", e.Raw)
|
||||||
|
|
||||||
|
// Если у нас есть доступный +B возьмём его себе, мы же бот.
|
||||||
|
botFlag, ok := availableUserModes.Get("B")
|
||||||
|
|
||||||
|
if ok && botFlag {
|
||||||
|
log.Info("Grabbing +B flag to mark me as bot")
|
||||||
|
ircClient.Mode(ircClient.GetNick(), "+B")
|
||||||
|
} else {
|
||||||
|
if ok {
|
||||||
|
log.Info("Skip +B flag, server does not support it, noone will know that I am bot")
|
||||||
|
} else {
|
||||||
|
log.Info("Skip +B flag, server did not announce modes (yet?), noone will know that I am bot")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !config.Irc.Sasl && config.Irc.Password != "" && !nickIsUsed {
|
||||||
|
log.Info("Identifying via NickServ")
|
||||||
|
message := fmt.Sprintf("identify %s %s", config.Irc.Nick, config.Irc.Password)
|
||||||
|
imChan <- iMsg{ChatId: "NickServ", Text: message}
|
||||||
|
}
|
||||||
|
// TODO: wait until +R flag been set? could be implemented with waiting for channel message.
|
||||||
|
|
||||||
|
log.Debug("Trying to join to preconfigured channels")
|
||||||
|
|
||||||
|
for _, channel := range config.Irc.Channels {
|
||||||
|
log.Infof("Joining to %s channel", channel)
|
||||||
|
ircClient.Join(channel)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
ircClient.AddCallback("431", func(e *irc.Event) {
|
ircClient.AddCallback("431", func(e *irc.Event) {
|
||||||
log.Errorf("431 ERR_NONICKNAMEGIVEN, %s", e.Raw)
|
log.Errorf("431 ERR_NONICKNAMEGIVEN, %s", e.Raw)
|
||||||
})
|
})
|
||||||
@@ -179,6 +276,7 @@ func ircClientRun() {
|
|||||||
log.Errorf("436 ERR_NICKCOLLISION, %s", e.Raw)
|
log.Errorf("436 ERR_NICKCOLLISION, %s", e.Raw)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// 437: ERR_UNAVAILRESOURCE уже есть в github.com/thoj/go-ircevent/irc_callback.go.
|
||||||
ircClient.AddCallback("437", func(e *irc.Event) {
|
ircClient.AddCallback("437", func(e *irc.Event) {
|
||||||
log.Errorf("437 ERR_UNAVAILRESOURCE, %s", e.Raw)
|
log.Errorf("437 ERR_UNAVAILRESOURCE, %s", e.Raw)
|
||||||
})
|
})
|
||||||
@@ -294,22 +392,6 @@ func ircClientRun() {
|
|||||||
log.Errorf("502 ERR_USERSDONTMATCH, %s", e.Raw)
|
log.Errorf("502 ERR_USERSDONTMATCH, %s", e.Raw)
|
||||||
})
|
})
|
||||||
|
|
||||||
// Сделаем уже что-то полезное! Motd нам уже прислали и теперь можно авторизоваться и джойниться
|
|
||||||
ircClient.AddCallback("001", func(e *irc.Event) {
|
|
||||||
if !config.Irc.Sasl && config.Irc.Password != "" && !nickIsUsed {
|
|
||||||
log.Info("Identifying via NickServ")
|
|
||||||
message := fmt.Sprintf("identify %s %s", config.Irc.Nick, config.Irc.Password)
|
|
||||||
imChan <- iMsg{ChatId: "NickServ", Text: message}
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Debug("Trying to join to preconfigured channels")
|
|
||||||
|
|
||||||
for _, channel := range config.Irc.Channels {
|
|
||||||
log.Infof("Joining to %s channel", channel)
|
|
||||||
ircClient.Join(channel)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
// Навесим коллбэков на другие, интересные нам события
|
// Навесим коллбэков на другие, интересные нам события
|
||||||
ircClient.AddCallback("KICK", func(e *irc.Event) {
|
ircClient.AddCallback("KICK", func(e *irc.Event) {
|
||||||
dstNick := e.Arguments[1]
|
dstNick := e.Arguments[1]
|
||||||
|
|||||||
11
globals.go
11
globals.go
@@ -4,6 +4,9 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
|
"aleesa-irc-go/internal/anycollection"
|
||||||
|
"aleesa-irc-go/internal/boolcollection"
|
||||||
|
|
||||||
"github.com/cockroachdb/pebble"
|
"github.com/cockroachdb/pebble"
|
||||||
"github.com/go-redis/redis/v8"
|
"github.com/go-redis/redis/v8"
|
||||||
irc "github.com/thoj/go-ircevent"
|
irc "github.com/thoj/go-ircevent"
|
||||||
@@ -45,6 +48,12 @@ var msgBucket bucket
|
|||||||
var settingsDB = make(map[string]*pebble.DB)
|
var settingsDB = make(map[string]*pebble.DB)
|
||||||
|
|
||||||
// "Базюлька" с MODE-ами пользователей на каналах
|
// "Базюлька" с MODE-ами пользователей на каналах
|
||||||
var userMode = NewCollection()
|
var userMode = anycollection.NewCollection()
|
||||||
|
|
||||||
|
// "Базюлька" с доступными MODE-ами пользователя
|
||||||
|
var availableUserModes = boolcollection.NewCollection()
|
||||||
|
|
||||||
|
// "Базюлька" c с доступными MODE-ами каналов
|
||||||
|
var availableChanModes = boolcollection.NewCollection()
|
||||||
|
|
||||||
/* 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,21 +1,21 @@
|
|||||||
package main
|
package anycollection
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"sync"
|
"sync"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Collection это sync.Map-ка, хранящая в себе произвольные итемы
|
// Collection это sync.Map-ка, хранящая в себе произвольные итемы.
|
||||||
type Collection struct {
|
type Collection struct {
|
||||||
items sync.Map
|
items sync.Map
|
||||||
close chan struct{}
|
close chan struct{}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Item представляет собой интерфейс для хранения произвольных данных
|
// Item представляет собой интерфейс для хранения произвольных данных.
|
||||||
type item struct {
|
type item struct {
|
||||||
data interface{}
|
data any
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewCollection создаёт инстанс коллекции
|
// NewCollection создаёт инстанс коллекции.
|
||||||
func NewCollection() *Collection {
|
func NewCollection() *Collection {
|
||||||
c := &Collection{
|
c := &Collection{
|
||||||
close: make(chan struct{}),
|
close: make(chan struct{}),
|
||||||
@@ -24,8 +24,8 @@ func NewCollection() *Collection {
|
|||||||
return c
|
return c
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get достаёт данные по заданному ключу из коллекции
|
// Get достаёт данные по заданному ключу из коллекции.
|
||||||
func (collection *Collection) Get(key interface{}) (interface{}, bool) {
|
func (collection *Collection) Get(key any) (any, bool) {
|
||||||
obj, exists := collection.items.Load(key)
|
obj, exists := collection.items.Load(key)
|
||||||
|
|
||||||
if !exists {
|
if !exists {
|
||||||
@@ -37,16 +37,16 @@ func (collection *Collection) Get(key interface{}) (interface{}, bool) {
|
|||||||
return item.data, true
|
return item.data, true
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set сохраняет данные с заданным ключом в коллекцию
|
// Set сохраняет данные с заданным ключом в коллекцию.
|
||||||
func (collection *Collection) Set(key interface{}, value interface{}) {
|
func (collection *Collection) Set(key any, value any) {
|
||||||
collection.items.Store(key, item{
|
collection.items.Store(key, item{
|
||||||
data: value,
|
data: value,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// Range применяет функцию f ко всем ключам в коллекции
|
// Range применяет функцию f ко всем ключам в коллекции.
|
||||||
func (collection *Collection) Range(f func(key, value interface{}) bool) {
|
func (collection *Collection) Range(f func(key, value any) bool) {
|
||||||
fn := func(key, value interface{}) bool {
|
fn := func(key, value any) bool {
|
||||||
item := value.(item)
|
item := value.(item)
|
||||||
|
|
||||||
return f(key, item.data)
|
return f(key, item.data)
|
||||||
@@ -55,12 +55,12 @@ func (collection *Collection) Range(f func(key, value interface{}) bool) {
|
|||||||
collection.items.Range(fn)
|
collection.items.Range(fn)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Delete удаляет ключ и значение из коллекции данных
|
// Delete удаляет ключ и значение из коллекции данных.
|
||||||
func (collection *Collection) Delete(key interface{}) {
|
func (collection *Collection) Delete(key any) {
|
||||||
collection.items.Delete(key)
|
collection.items.Delete(key)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Close очищает и высвобождает ресурсы, занятые коллекцией
|
// Close очищает и высвобождает ресурсы, занятые коллекцией.
|
||||||
func (collection *Collection) Close() {
|
func (collection *Collection) Close() {
|
||||||
collection.close <- struct{}{}
|
collection.close <- struct{}{}
|
||||||
collection.items = sync.Map{}
|
collection.items = sync.Map{}
|
||||||
58
internal/boolcollection/boolcollection.go
Normal file
58
internal/boolcollection/boolcollection.go
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
package boolcollection
|
||||||
|
|
||||||
|
import (
|
||||||
|
"sync"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Collection это sync.Map-ка, хранящая в себе итемы типа bool.
|
||||||
|
type Collection struct {
|
||||||
|
items sync.Map
|
||||||
|
close chan struct{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Item представляет собой интерфейс для хранения данных типа bool.
|
||||||
|
type item struct {
|
||||||
|
data bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewCollection создаёт инстанс коллекции.
|
||||||
|
func NewCollection() *Collection {
|
||||||
|
c := &Collection{
|
||||||
|
close: make(chan struct{}),
|
||||||
|
}
|
||||||
|
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get достаёт данные по заданному ключу из коллекции.
|
||||||
|
func (collection *Collection) Get(key string) (bool, bool) {
|
||||||
|
obj, exists := collection.items.Load(key)
|
||||||
|
|
||||||
|
if !exists {
|
||||||
|
return false, false
|
||||||
|
}
|
||||||
|
|
||||||
|
item := obj.(item)
|
||||||
|
|
||||||
|
return item.data, true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set сохраняет данные с заданным ключом в коллекцию.
|
||||||
|
func (collection *Collection) Set(key string, value bool) {
|
||||||
|
collection.items.Store(key, item{
|
||||||
|
data: value,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete удаляет ключ и значение из коллекции данных.
|
||||||
|
func (collection *Collection) Delete(key string) {
|
||||||
|
collection.items.Delete(key)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close очищает и высвобождает ресурсы, занятые коллекцией.
|
||||||
|
func (collection *Collection) Close() {
|
||||||
|
collection.close <- struct{}{}
|
||||||
|
collection.items = sync.Map{}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* vim: set ft=go noet ai ts=4 sw=4 sts=4: */
|
||||||
@@ -361,8 +361,6 @@ func ircMsgParser(channel string, nick string, user string, source string, msg s
|
|||||||
log.Debugf("Sent msg to redis channel %s: %s", config.Redis.Channel, string(data))
|
log.Debugf("Sent msg to redis channel %s: %s", config.Redis.Channel, string(data))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// redisMsgParser парсит json-чики прилетевшие из REDIS-ки, причём, json-чики должны быть относительно валидными
|
// redisMsgParser парсит json-чики прилетевшие из REDIS-ки, причём, json-чики должны быть относительно валидными
|
||||||
@@ -443,8 +441,6 @@ func redisMsgParser(msg string) {
|
|||||||
imChan <- iMsg{ChatId: j.Chatid, Text: message}
|
imChan <- iMsg{ChatId: j.Chatid, Text: message}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* vim: set ft=go noet ai ts=4 sw=4 sts=4: */
|
/* vim: set ft=go noet ai ts=4 sw=4 sts=4: */
|
||||||
|
|||||||
2
util.go
2
util.go
@@ -108,7 +108,7 @@ func readConfig() {
|
|||||||
log.Infof("Irc port is not defined in config, using 6667")
|
log.Infof("Irc port is not defined in config, using 6667")
|
||||||
}
|
}
|
||||||
|
|
||||||
if sampleConfig.Irc.Ssl != true {
|
if !sampleConfig.Irc.Ssl {
|
||||||
sampleConfig.Irc.SslVerify = false
|
sampleConfig.Irc.SslVerify = false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user