easybridge/connector/irc/irc.go
Alex Auvolat 38a3f1bdb1 Fix Mattermost event deduplication
Mattermost assigns its own IDs to messages, thus when sending a message
to Mattermost the event_seen key that has to be written must take into
account that ID and not the one that we put in the event (which was the
Matrix event ID)

Note that for XMPP anything can be used as an ID, so using the Matrix
event ID there worked, but it's actually not so good.
2020-02-29 10:01:42 +01:00

371 lines
7.5 KiB
Go

package irc
import (
"fmt"
_ "os"
"strings"
"time"
"github.com/lrstanley/girc"
. "git.deuxfleurs.fr/Deuxfleurs/easybridge/connector"
)
// User id format: nickname@server
// Room id format: #room_name@server
type IRC struct {
handler Handler
connected bool
timeout int
nick string
name string
server string
conn *girc.Client
}
func (irc *IRC) SetHandler(h Handler) {
irc.handler = h
}
func (irc *IRC) Protocol() string {
return IRC_PROTOCOL
}
func (irc *IRC) Configure(c Configuration) error {
if irc.conn != nil {
irc.Close()
}
var err error
irc.nick, err = c.GetString("nick")
if err != nil {
return err
}
irc.server, err = c.GetString("server")
if err != nil {
return err
}
port, err := c.GetInt("port", 6667)
if err != nil {
return err
}
ssl, err := c.GetBool("ssl", false)
if err != nil {
return err
}
client := girc.New(girc.Config{
Server: irc.server,
Port: port,
Nick: irc.nick,
User: irc.nick,
//Out: os.Stderr,
SSL: ssl,
})
client.Handlers.Add(girc.CONNECTED, irc.ircConnected)
//client.Handlers.Add(girc.DISCONNECTED, irc.ircDisconnected)
//client.Handlers.Add(girc.NICK, irc.ircNick)
client.Handlers.Add(girc.PRIVMSG, irc.ircPrivmsg)
client.Handlers.Add(girc.JOIN, irc.ircJoin)
client.Handlers.Add(girc.PART, irc.ircPart)
client.Handlers.Add(girc.RPL_NAMREPLY, irc.ircNamreply)
client.Handlers.Add(girc.TOPIC, irc.ircTopic)
client.Handlers.Add(girc.RPL_TOPIC, irc.ircRplTopic)
irc.conn = client
go irc.connectLoop(client)
for i := 0; i < 42; i++ {
time.Sleep(time.Duration(1) * time.Second)
if irc.conn != client {
break
}
if irc.connected {
return nil
}
}
return fmt.Errorf("Failed to connect after 42s attempting")
}
func (irc *IRC) User() UserID {
return UserID(irc.nick + "@" + irc.server)
}
func (irc *IRC) checkRoomId(id RoomID) (string, error) {
x := strings.Split(string(id), "@")
if len(x) == 1 {
return "", fmt.Errorf("Please write whole room ID with server: %s@%s", id, irc.server)
}
if x[0][0] != '#' || len(x) != 2 || x[1] != irc.server {
return "", fmt.Errorf("Invalid room ID: %s", id)
}
return x[0], nil
}
func (irc *IRC) checkUserId(id UserID) (string, error) {
x := strings.Split(string(id), "@")
if len(x) == 1 {
return "", fmt.Errorf("Please write whole user ID with server: %s@%s", id, irc.server)
}
if x[0][0] == '#' || len(x) != 2 || x[1] != irc.server {
return "", fmt.Errorf("Invalid user ID: %s", id)
}
return x[0], nil
}
func (irc *IRC) SetUserInfo(info *UserInfo) error {
return fmt.Errorf("Not implemented")
}
func (irc *IRC) SetRoomInfo(roomId RoomID, info *RoomInfo) error {
if irc.conn == nil {
return fmt.Errorf("Not connected")
}
ch, err := irc.checkRoomId(roomId)
if err != nil {
return err
}
if info.Topic != "" {
irc.conn.Cmd.Topic(ch, info.Topic)
}
if info.Name != "" && info.Name != ch {
return fmt.Errorf("May not change IRC room name to other than %s", ch)
}
if info.Picture != nil {
return fmt.Errorf("Room picture not supported on IRC")
}
return nil
}
func (irc *IRC) Join(roomId RoomID) error {
if irc.conn == nil {
return fmt.Errorf("Not connected")
}
ch, err := irc.checkRoomId(roomId)
if err != nil {
return err
}
irc.conn.Cmd.Join(ch)
return nil
}
func (irc *IRC) Invite(userId UserID, roomId RoomID) error {
if irc.conn == nil {
return fmt.Errorf("Not connected")
}
who, err := irc.checkUserId(userId)
if err != nil {
return err
}
if roomId == "" {
return nil
}
ch, err := irc.checkRoomId(roomId)
if err != nil {
return err
}
irc.conn.Cmd.Invite(ch, who)
return nil
}
func (irc *IRC) Leave(roomId RoomID) {
if irc.conn == nil {
return
}
ch, err := irc.checkRoomId(roomId)
if err != nil {
return
}
irc.conn.Cmd.Part(ch)
}
func (irc *IRC) Send(event *Event) (string, error) {
if irc.conn == nil {
return "", fmt.Errorf("Not connected")
}
// Workaround girc bug
if event.Text[0] == ':' {
event.Text = " " + event.Text
}
dest := ""
if event.Room != "" {
ch, err := irc.checkRoomId(event.Room)
if err != nil {
return "", err
}
dest = ch
} else if event.Recipient != "" {
ui, err := irc.checkUserId(event.Recipient)
if err != nil {
return "", err
}
dest = ui
} else {
return "", fmt.Errorf("Invalid target")
}
if event.Attachments != nil && len(event.Attachments) > 0 {
for _, at := range event.Attachments {
url := at.URL()
if url == "" {
// TODO find a way to send them using some hosing of some kind
return "", fmt.Errorf("Attachment without URL sent to IRC")
} else {
irc.conn.Cmd.Message(dest, fmt.Sprintf("%s (%s, %dkb)",
url, at.Mimetype(), at.Size()/1024))
}
}
}
if event.Type == EVENT_MESSAGE {
irc.conn.Cmd.Message(dest, event.Text)
} else if event.Type == EVENT_ACTION {
irc.conn.Cmd.Action(dest, event.Text)
} else {
return "", fmt.Errorf("Invalid event type")
}
return "", nil
}
func (irc *IRC) Close() {
conn := irc.conn
irc.conn = nil
irc.connected = false
if conn != nil {
conn.Close()
}
}
func (irc *IRC) connectLoop(c *girc.Client) {
irc.timeout = 10
for {
if irc.conn != c {
return
}
if err := c.Connect(); err != nil {
irc.connected = false
fmt.Printf("IRC failed to connect / disconnected: %s\n", err)
fmt.Printf("Retrying in %ds\n", irc.timeout)
time.Sleep(time.Duration(irc.timeout) * time.Second)
irc.timeout *= 2
if irc.timeout > 600 {
irc.timeout = 600
}
} else {
return
}
}
}
func (irc *IRC) ircConnected(c *girc.Client, e girc.Event) {
fmt.Printf("ircConnected ^^^^\n")
irc.timeout = 10
irc.connected = true
}
func (irc *IRC) ircPrivmsg(c *girc.Client, e girc.Event) {
ev := &Event{
Type: EVENT_MESSAGE,
Author: UserID(e.Source.Name + "@" + irc.server),
Text: e.Last(),
}
if e.IsFromChannel() {
ev.Room = RoomID(e.Params[0] + "@" + irc.server)
}
if e.IsAction() {
ev.Type = EVENT_ACTION
}
irc.handler.Event(ev)
}
func (irc *IRC) ircJoin(c *girc.Client, e girc.Event) {
room := RoomID(e.Params[0] + "@" + irc.server)
if e.Source.Name == irc.nick {
irc.handler.Joined(room)
} else {
user := UserID(e.Source.Name + "@" + irc.server)
ev := &Event{
Type: EVENT_JOIN,
Author: user,
Room: room,
}
irc.handler.Event(ev)
irc.handler.UserInfoUpdated(user, &UserInfo{
DisplayName: e.Source.Name,
})
}
}
func (irc *IRC) ircPart(c *girc.Client, e girc.Event) {
room := RoomID(e.Params[0] + "@" + irc.server)
if e.Source.Name == irc.nick {
irc.handler.Left(room)
} else {
user := UserID(e.Source.Name + "@" + irc.server)
ev := &Event{
Type: EVENT_LEAVE,
Author: user,
Room: room,
}
irc.handler.Event(ev)
irc.handler.UserInfoUpdated(user, &UserInfo{
DisplayName: e.Source.Name,
})
}
}
func (irc *IRC) ircNamreply(c *girc.Client, e girc.Event) {
room := RoomID(e.Params[2] + "@" + irc.server)
names := strings.Split(e.Last(), " ")
for _, name := range names {
if name[0] == '@' {
name = name[1:]
}
src := girc.ParseSource(name)
if src.Name != irc.nick {
irc.handler.Event(&Event{
Type: EVENT_JOIN,
Author: UserID(src.Name + "@" + irc.server),
Room: room,
})
}
}
}
func (irc *IRC) ircTopic(c *girc.Client, e girc.Event) {
source := UserID(e.Source.Name + "@" + irc.server)
room := RoomID(e.Params[0] + "@" + irc.server)
topic := e.Last()
irc.handler.RoomInfoUpdated(room, source, &RoomInfo{
Topic: topic,
})
}
func (irc *IRC) ircRplTopic(c *girc.Client, e girc.Event) {
room := RoomID(e.Params[1] + "@" + irc.server)
topic := e.Last()
irc.handler.RoomInfoUpdated(room, "", &RoomInfo{
Topic: topic,
})
}