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