easybridge/connector/irc/irc.go

406 lines
8.3 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
joinedRooms map[string]bool
}
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
}
server_pass, _ := c.GetString("server_pass", "")
sasl_user, _ := c.GetString("sasl_user", "")
sasl_pass, _ := c.GetString("sasl_pass", "")
var sasl girc.SASLMech
if sasl_user != "" && sasl_pass != "" {
sasl = &girc.SASLPlain{
User: sasl_user,
Pass: sasl_pass,
}
}
client := girc.New(girc.Config{
Server: irc.server,
ServerPass: server_pass,
Port: port,
Nick: irc.nick,
User: irc.nick,
SASL: sasl,
//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.joinedRooms = make(map[string]bool)
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.MediaObject != 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) SearchForUsers(query string) ([]UserSearchResult, error) {
// TODO
return nil, fmt.Errorf("Not implemented")
}
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) UserCommand(cm string) {
irc.handler.SystemMessage("Command not supported.")
}
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
irc.handler.SystemMessage(fmt.Sprintf("IRC failed to connect / disconnected (%s), reconnecting in %ds", err, 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) {
irc.handler.SystemMessage("Connected to IRC.")
irc.timeout = 10
irc.connected = true
for room, joined := range irc.joinedRooms {
if joined {
irc.conn.Cmd.Join(room)
}
}
}
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)
irc.joinedRooms[e.Params[0]] = true
} 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)
delete(irc.joinedRooms, e.Params[0])
} 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,
})
}