aboutsummaryrefslogblamecommitdiff
path: root/connector/irc/irc.go
blob: dafe9db916844b0932ce41e0250bfcc52e1e54ed (plain) (tree)
1
2
3
4
5
6
7
8
9


           
             
              
                 
              
 
                                   

                                                             


                                  
 



                                    

                      
                     
 

                     
                     
                           





                                       
                                   
                           


                                                  



                            
                     
 



                                           
 
                                               


                          
 
                                           



                          
                                           

                          



                                       


                                 
                                 
                         








                                                                     

                                                            




                                  
                                                          






                                       
                                                                   







                                                        



                                                                                                      






                                                                



                                                                                                      









                                                                  



                                                  




                                          


                                                  





                                                                                      



                                           



                                                  








                                          
                                                            



                                                  




                                           



                          




                                          



                                    
                                      



                            








                                          



                                                  




                                             
















                                                           










                                                                                              


                                        
                                                      
                                              
                                                     






                                                       
                        
                      
                             


                            









                                                   

                                                                                     

                                                                            


                                                 













                                                            
                                      
                                                                 
                                 














                                                                
                                                                
                             
                                           
                                     
                                     

                                     


                                                            







                                                       
                                                                
                             
                                            
                                     
                                     

                                     


                                                            



                                                           








                                                      
                                                   
                                                                            
                                             


                          


                                                        








                                                            

                                                      

                                                        
          
 
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) 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,
	})
}