SendPong uses SendRaw before the rest will be implemented to use it Kill New function to terminate event loop
315 lines
7.1 KiB
Go
315 lines
7.1 KiB
Go
package irc
|
|
|
|
import (
|
|
"bufio"
|
|
"fmt"
|
|
"log"
|
|
"net"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
"unicode"
|
|
)
|
|
|
|
const (
|
|
//IrcVersion exports the current version of the irc lib
|
|
IrcVersion = "0.1.1776"
|
|
)
|
|
|
|
//Connection holds the callbacks for the client
|
|
type Connection struct {
|
|
Sock net.Conn
|
|
DirectCallback func(string, string)
|
|
PrivmsgCallback func(string, string, string)
|
|
JoinCallback func(string, string)
|
|
QuitCallback func(string, string)
|
|
PartCallback func(string, string, string)
|
|
NickCallback func(string, string)
|
|
NumericCallback func(string, int, string)
|
|
joined bool
|
|
channels []string
|
|
curNick string
|
|
userList map[string][]string
|
|
}
|
|
|
|
func cleanInput(msg string) string {
|
|
ret := msg
|
|
if strings.Contains(msg, "\n") {
|
|
ret = strings.ReplaceAll(msg, "\n", "-")
|
|
}
|
|
if strings.Contains(ret, "\r") {
|
|
ret = strings.ReplaceAll(msg, "\r", "")
|
|
}
|
|
return ret
|
|
}
|
|
|
|
//SendRaw sends a string as a raw irc message
|
|
func (c *Connection) SendRaw(msg string) error {
|
|
_, err := c.Sock.Write([]byte(msg))
|
|
return err
|
|
}
|
|
|
|
//SendPong replies to the received PING
|
|
func (c *Connection) SendPong(ping string) {
|
|
pong := strings.Replace(ping, "PING", "PONG", 1)
|
|
c.SendRaw(pong + "\n")
|
|
}
|
|
|
|
//SendPrivmsg sends a privmsg to channel/target
|
|
func (c *Connection) SendPrivmsg(channel, msg string) {
|
|
msg = cleanInput(msg)
|
|
_, err := c.Sock.Write([]byte(fmt.Sprintf("PRIVMSG %s :%s\n", channel, msg)))
|
|
if err != nil {
|
|
log.Printf("Error sending private message: %s\n", err.Error())
|
|
}
|
|
}
|
|
|
|
//SendNotice sends a notice to channel/target
|
|
func (c *Connection) SendNotice(channel, msg string) {
|
|
msg = cleanInput(msg)
|
|
_, err := c.Sock.Write([]byte(fmt.Sprintf("NOTICE %s :%s\n", channel, msg)))
|
|
if err != nil {
|
|
log.Printf("Error sending NOTICE message: %s\n", err.Error())
|
|
}
|
|
}
|
|
|
|
//SendNick changes the clients nick
|
|
func (c *Connection) SendNick(nick string) {
|
|
c.Sock.Write([]byte(fmt.Sprintf("NICK %s\n", nick)))
|
|
}
|
|
|
|
//SendJoin sends a JOIN
|
|
func (c *Connection) SendJoin(channel string) {
|
|
c.Sock.Write([]byte(fmt.Sprintf("JOIN %s\n", channel)))
|
|
}
|
|
|
|
//SendPart sends a PART
|
|
func (c *Connection) SendPart(channel, msg string) {
|
|
c.Sock.Write([]byte(fmt.Sprintf("PART %s :%s\n", channel, msg)))
|
|
}
|
|
|
|
//SendQuit sends a QUIT
|
|
func (c *Connection) SendQuit(msg string) {
|
|
c.Sock.Write([]byte(fmt.Sprintf("QUIT :%s\n", msg)))
|
|
}
|
|
|
|
//SendNames sends a NAMES request for the channel
|
|
func (c *Connection) SendNames(channel string) {
|
|
c.Sock.Write([]byte(fmt.Sprintf("NAMES %s\n", channel)))
|
|
}
|
|
|
|
//SendPass sends a PASS for network
|
|
func (c *Connection) SendPass(pass string) {
|
|
c.Sock.Write([]byte(fmt.Sprintf("PASS %s\n", pass)))
|
|
}
|
|
|
|
//SendUser sends a USER command for the network
|
|
func (c *Connection) SendUser(user, realname string) {
|
|
c.Sock.Write([]byte(fmt.Sprintf("USER %s * * :%s\n", user, realname)))
|
|
}
|
|
|
|
//DelayedSend sends a privmsg AFTER the specified delay
|
|
func (c *Connection) DelayedSend(sock net.Conn, dur time.Duration, channel, msg string) {
|
|
time.Sleep(dur)
|
|
c.SendPrivmsg(channel, msg)
|
|
}
|
|
|
|
//GetNicks returns a array of all nicks in a channel
|
|
func (c *Connection) GetNicks(channel string) []string {
|
|
return c.userList[channel]
|
|
}
|
|
|
|
//Kill terminates the event loop of connection
|
|
func (c *Connection) Kill() {
|
|
c.Sock.Close()
|
|
}
|
|
|
|
//NewConnection creates and connects an Connection
|
|
func NewConnection(server, nick, user string, pass *string, chans []string) *Connection {
|
|
irc := &Connection{channels: chans, userList: map[string][]string{}}
|
|
|
|
sock, err := net.Dial("tcp", server)
|
|
if err != nil {
|
|
log.Fatalln(err.Error())
|
|
}
|
|
irc.Sock = sock
|
|
|
|
if pass != nil {
|
|
irc.SendPass(*pass)
|
|
}
|
|
|
|
irc.SendNick(nick)
|
|
irc.curNick = nick
|
|
|
|
irc.SendUser(user, user)
|
|
|
|
return irc
|
|
}
|
|
|
|
//GetNick gets the nickname portion of a host string
|
|
func GetNick(name string) string {
|
|
return strings.Split(name, "!")[0]
|
|
}
|
|
|
|
func hasNick(nick string, names []string) bool {
|
|
for _, v := range names {
|
|
if v == nick {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
func hasOpSymbol(nick string) bool {
|
|
s := rune(nick[0])
|
|
if !unicode.IsLetter(s) && !unicode.IsNumber(s) {
|
|
return true
|
|
}
|
|
return false
|
|
}
|
|
|
|
func (c *Connection) updateNicks(channel string, names []string) {
|
|
//log.Printf("updating channel %s with %d nicks\n", channel, len(names))
|
|
for _, i := range names {
|
|
c.addNick(channel, i)
|
|
}
|
|
}
|
|
|
|
func (c *Connection) addNick(channel string, nick string) {
|
|
channel = strings.ToLower(channel)
|
|
|
|
if nick != c.curNick && !hasNick(nick, c.userList[channel]) {
|
|
if hasOpSymbol(nick) {
|
|
nick = nick[1:]
|
|
}
|
|
//log.Printf("added nick: %s to channel %s\n", nick, channel)
|
|
c.userList[channel] = append(c.userList[channel], nick)
|
|
}
|
|
}
|
|
|
|
func (c *Connection) removeNick(channel string, nick string) {
|
|
channel = strings.ToLower(channel)
|
|
nicks := []string{}
|
|
|
|
for _, i := range c.userList[channel] {
|
|
if i != nick {
|
|
nicks = append(nicks, i)
|
|
}
|
|
}
|
|
|
|
//log.Printf("removed nick: %s from channel %s\n", nick, channel)
|
|
c.userList[channel] = nicks
|
|
}
|
|
|
|
func (c *Connection) removeNickAllChans(nick string) {
|
|
for k := range c.userList {
|
|
c.removeNick(k, nick)
|
|
}
|
|
}
|
|
|
|
func (c *Connection) renameNick(old, nick string) {
|
|
for channel, i := range c.userList {
|
|
if hasNick(old, i) {
|
|
c.removeNick(channel, old)
|
|
c.addNick(channel, nick)
|
|
}
|
|
}
|
|
}
|
|
|
|
//TODO: simplify this to pass gocyclo
|
|
func (c *Connection) parseMessage(line string) {
|
|
if line[0] == ':' {
|
|
buf := line[1:]
|
|
params := strings.SplitN(buf, " ", 3)
|
|
from := params[0]
|
|
cmd := params[1]
|
|
args := params[2]
|
|
|
|
if _, e := strconv.Atoi(cmd); e == nil {
|
|
//numeric message
|
|
if !c.joined {
|
|
if cmd == "376" {
|
|
for _, v := range c.channels {
|
|
c.SendJoin(v)
|
|
}
|
|
c.joined = true
|
|
}
|
|
}
|
|
|
|
if cmd == "353" {
|
|
params := strings.SplitN(args, " ", 4)
|
|
target := strings.TrimSpace(params[2])
|
|
c.updateNicks(target, strings.Split(params[3][1:], " "))
|
|
}
|
|
|
|
if c.NumericCallback != nil {
|
|
code, _ := strconv.Atoi(cmd)
|
|
c.NumericCallback(from, code, args)
|
|
}
|
|
} else {
|
|
t := strings.SplitN(args, ":", 2)
|
|
target := strings.TrimSpace(t[0])
|
|
msg := ""
|
|
if len(t) > 1 {
|
|
msg = t[1]
|
|
}
|
|
|
|
switch strings.ToLower(cmd) {
|
|
case "privmsg":
|
|
if target != c.curNick {
|
|
if c.PrivmsgCallback != nil {
|
|
c.PrivmsgCallback(target, from, msg)
|
|
}
|
|
} else {
|
|
if c.DirectCallback != nil {
|
|
c.DirectCallback(from, msg)
|
|
}
|
|
}
|
|
case "join":
|
|
c.addNick(target, GetNick(from))
|
|
if c.JoinCallback != nil {
|
|
c.JoinCallback(from, target)
|
|
}
|
|
case "quit":
|
|
c.removeNickAllChans(GetNick(from))
|
|
if c.QuitCallback != nil {
|
|
c.QuitCallback(from, msg)
|
|
}
|
|
case "part":
|
|
c.removeNick(target, GetNick(from))
|
|
if c.PartCallback != nil {
|
|
c.PartCallback(from, target, msg)
|
|
}
|
|
case "nick":
|
|
if GetNick(from) == c.curNick {
|
|
c.curNick = msg
|
|
log.Printf("BOT NICK CHANGED TO: %s\n", c.curNick)
|
|
}
|
|
if c.NickCallback != nil {
|
|
c.NickCallback(from, msg)
|
|
}
|
|
default:
|
|
log.Printf("unhandled command: %s %s %s", cmd, target, msg)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
//Run runs the irc event loop
|
|
func (c *Connection) Run() {
|
|
rdr := bufio.NewScanner(c.Sock)
|
|
|
|
c.joined = false
|
|
|
|
for rdr.Scan() {
|
|
line := rdr.Text()
|
|
|
|
if strings.HasPrefix(line, "PING") {
|
|
c.SendPong(line)
|
|
continue
|
|
}
|
|
|
|
c.parseMessage(line)
|
|
}
|
|
}
|