//ircManager takes the connection to the IRC server and then coordinates the //communication between the irc server, and the connected webclients //It it will block until the connection to the irc server is closed, or Stop() is called func ircManager(c ircchat) { //ircConn irc.Conn, newClients chan irc.Conn fmt.Println("*** Entering ircManager ***") defer fmt.Println("*** Leaving ircManager ***") fromServer := make(chan irc.Message) errChan := make(chan error) //errors from irc server //Listen for incoming messages form server go serverListener(c.client, fromServer, errChan, c.quit) for { select { case msg := <-fromServer: //Recieved message from irc server //Send it to all clients c.webClientsLock.RLock() for _, client := range c.webclients { err := client.Write(msg) if err != nil { fmt.Printf("***Error sending to websocket for %s.", c.account.Username()) } } c.webClientsLock.RUnlock() //Received a message from a webclient case msg := <-c.toServer: err := c.client.Write(msg.Message) if err != nil { fmt.Printf("Error writing to server: %s", err.Error()) } c.webClientsLock.RLock() for k, client := range c.webclients { if k != msg.SessionID { //Notify webclients other than the one that sent it cerr := client.Write(msg.Message) if cerr != nil { fmt.Printf("***Error sending to websocket for %s.", c.account.Username()) } if err != nil && cerr == nil { //If no error from client, but an error from server, forward server error client.Write(irc.NewMessage("Error sending message: " + err.Error())) } } } c.webClientsLock.RUnlock() case err := <-errChan: log.Printf("Recieved error: %s", err.Error()) c.Stop() case <-c.quit: c.client.Write(irc.NewMessage("QUIT")) return } } }
/* The socketHandler for the websocket connection. Accepts the websocket, hands it off through the socketChan, and waits until the socket is closed before exiting the function. //TODO: Actually manage disconnections properly. */ func webSocketHandler(ws *websocket.Conn) { //Notify the irc manager of a new websocket log.Println("socketHandler starting") defer log.Println("socketHandler exiting") var client ircClient = newWSClient(ws) //Authenticate websocket: var user *iwcUser = nil for user == nil { client.SendMessage(irc.NewMessage("Enter a username.")) msg, err := client.ReceiveMessage() if err != nil { return } username := strings.TrimSpace(msg.String()) client.SendMessage(irc.NewMessage("Enter a password.")) msg, err = client.ReceiveMessage() if err != nil { return } password := strings.TrimSpace(msg.String()) user = authenticate(username, password) if user != nil { client.SendMessage("Successfully logged in.") } else { client.SendMessage(irc.NewMessage("Invalid username/password.")) } } newclients := getSessionNotifier(user.username) if newclients == nil { client.SendMessage("Unable to find session. Closing...") log.Printf("Unable to find session for ", user.username) return } //Notify the client what the user's current nick is client.SendMessage(irc.NewMessage("NICK " + user.profile.nick.name)) newclients <- &client for { if ws.IsServerConn() { time.Sleep(100 * time.Millisecond) } else { log.Println("!!!!socketHandler returning after IsServerConn returned false") return } } }
//Join adds a webclient to the list of active clients. It blocks until webclient socket closes or chat ends func (c ircchat) Join(sessionID string, webclient irc.Conn) error { if !c.Active() { return errors.New("The chat session is not active or enabled. Check settings") } webclient.Write(irc.NickMessage(c.settings.Login.Nick)) //Send open channels to client for _, ch := range c.client.ChannelNames() { webclient.Write(irc.JoinMessage(ch)) //Send users in the rooms as a names reply commands (353 to indicate start, 366 to indicate end) //Webclient doesn't care about length, but a traditional IRC client will //TODO: Indicate if channel is public, private or secret ( "=" / "*" / "@" ), current sends as pub //:tepper.freenode.net 353 goirctest @ #gotest :goirctest @Oooska //:tepper.freenode.net 366 goirctest #gotest :End of /NAMES list. users, _ := c.client.Users(ch) namesRepl := fmt.Sprintf("353 %s = %s :%s", c.settings.Login.Nick, ch, strings.Join(users, " ")) namesEndRepl := fmt.Sprintf("366 %s %s", c.settings.Login.Nick, ch) webclient.Write(irc.NewMessage(namesRepl)) webclient.Write(irc.NewMessage(namesEndRepl)) messages, err := persistenceInstance.messages(c.account, ch, time.Now(), 200) if err != nil { log.Printf("Error retrieving message logs: %s", err.Error()) } for _, msg := range messages { webclient.Write(msg) } } //Register as a listener c.registerClient(sessionID, webclient) for { select { case <-c.quit: fmt.Println("Exiting ircClientListener") return errors.New("IRC Session has ended") default: msg, err := webclient.Read() if err != nil { c.unregisterClient(sessionID) return err } c.toServer <- clientMessage{SessionID: sessionID, Message: msg} } } }
//ircManager takes the connection to the IRC server and then coordinates the //communication between the irc server, and the active IRCClients func ircManager(ircConn irc.IRCConn, newClients chan *ircClient) { fmt.Println("*** Entering ircManager ***") defer fmt.Println("*** Leaving ircManager ***") var clients []*ircClient fromServer := make(chan irc.Message) fromClient := make(chan irc.Message) errChan := make(chan error) quitChan := make(chan bool) //Listen for incoming messages form server go ircServerListener(ircConn, fromServer, errChan, quitChan) for { select { case msg := <-fromServer: //Log it, log.Println(msg) //Repsond if it's a ping if len(msg) >= 4 && msg[0:4] == "PING" { var end string = msg[4:].String() ircConn.Write(irc.NewMessage("PONG" + end)) //break } //...and send it to all clients for k := 0; k < len(clients); k++ { client := *clients[k] err := client.SendMessage(msg) if err != nil { stopClientListener(&client) client.Close() clients = deleteNthItem(clients, k) k-- //Account for socket deletion in slice fmt.Println("*** Disconnected irc Client. ", len(clients), "remaining.") } } //Received a message from the server case msg := <-fromClient: err := ircConn.Write(msg) if err != nil { fmt.Println("Error writing to server: ", err) } //A new client has connected case client := <-newClients: clients = append(clients, client) startClientListener(client, fromClient) fmt.Println("*** Accepted the ", len(clients), " client connection ***") } } quitChan <- true }
//parseLine returns an irc.Message object. If the line starts with a forward //slash, everything after the '/' is converted directly to a server command //If there is no slash, the first word is taken to be the channel or user to //send a PRIVMSG to func parseLine(line string) (msg irc.Message, err error) { if line[0] == '/' { msg = irc.NewMessage(line[1:]) } else { splitlines := strings.SplitN(line, " ", 2) if len(splitlines) > 1 { msg = irc.PrivMessage(splitlines[0], splitlines[1]) } else { err = errors.New("Unable to parse input") } } return }
//A basic irc client that communicates over a websocket. func main() { origin := "http://127.0.0.1/" url := "ws://127.0.0.1:8080/chat/socket" ws, err := websocket.Dial(url, "", origin) if err != nil { panic(err) } conn := irc.IRCConnectionWrapper(ws) go func() { //Read from stdin reader := bufio.NewReader(os.Stdin) for { line, err := reader.ReadString('\n') if err != nil { log.Fatalf("Cannot read from stdin: %s", err) } line = strings.TrimSpace(line) if len(line) == 0 { continue } msg, err := parseLine(line) if err != nil { log.Println("Err: ", err) } else { log.Println("YOU: ", msg) conn.Write(msg) } } }() var msg irc.Message // handle incomming messages for { msg, err = conn.Read() if err != nil { fmt.Printf("ERROR: %s\n", err) return } if len(msg) >= 4 && msg[0:4] == "PING" { var end string = msg[4:].String() conn.Write(irc.NewMessage("PONG" + end)) } fmt.Println(msg) } }
func startSession(user iwcUser) error { //Start the IRC connection... //TODO: Move this elsewhere. conn, err := irc.NewIRCConnection(user.profile.address, false) if err != nil { return err } //Channel to accept new http clients logging in var newClients chan *ircClient = make(chan *ircClient) conn.Write(irc.UserMessage(user.username, "servername", "hostname", user.profile.realname)) conn.Write(irc.NickMessage(user.profile.nick.name)) conn.Write(irc.NewMessage("join #gotest")) go ircManager(conn, newClients) log.Println("Starting session for ", user.username) sessionNewClientsMap[user.username] = newClients return nil }
//Receives a message from the client/user //TODO: Remove terrible parsing rules in parseLine() func (wsclient wsClient) ReceiveMessage() (irc.Message, error) { msg, err := wsclient.reader.ReadString('\n') return irc.NewMessage(msg), err }