mirror of https://github.com/veonik/squirssi
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
516 lines
12 KiB
516 lines
12 KiB
package squirssi
|
|
|
|
import (
|
|
"strings"
|
|
"sync"
|
|
"time"
|
|
|
|
"code.dopame.me/veonik/squircy3/event"
|
|
"code.dopame.me/veonik/squircy3/irc"
|
|
"github.com/sirupsen/logrus"
|
|
irc2 "github.com/thoj/go-ircevent"
|
|
)
|
|
|
|
func bindIRCHandlers(srv *Server, events *event.Dispatcher) {
|
|
events.Bind("irc.CONNECT", HandleIRCEvent(srv, onIRCConnect))
|
|
events.Bind("irc.DISCONNECT", HandleIRCEvent(srv, onIRCDisconnect))
|
|
events.Bind("irc.PRIVMSG", HandleIRCEvent(srv, onIRCPrivmsg))
|
|
events.Bind("irc.NOTICE", HandleIRCEvent(srv, onIRCNotice))
|
|
events.Bind("irc.CTCP_ACTION", HandleIRCEvent(srv, onIRCAction))
|
|
events.Bind("irc.JOIN", HandleIRCEvent(srv, onIRCJoin))
|
|
events.Bind("irc.PART", HandleIRCEvent(srv, onIRCPart))
|
|
events.Bind("irc.KICK", HandleIRCEvent(srv, onIRCKick))
|
|
events.Bind("irc.JOIN", HandleIRCEvent(srv, onIRCNames))
|
|
events.Bind("irc.PART", HandleIRCEvent(srv, onIRCNames))
|
|
events.Bind("irc.KICK", HandleIRCEvent(srv, onIRCNames))
|
|
events.Bind("irc.NICK", HandleIRCEvent(srv, onIRCNick))
|
|
events.Bind("irc.433", HandleIRCEvent(srv, onIRC433))
|
|
events.Bind("irc.353", HandleIRCEvent(srv, onIRC353))
|
|
events.Bind("irc.366", HandleIRCEvent(srv, onIRC366))
|
|
events.Bind("irc.QUIT", HandleIRCEvent(srv, onIRCQuit))
|
|
events.Bind("irc.MODE", HandleIRCEvent(srv, onIRCMode))
|
|
events.Bind("irc.324", HandleIRCEvent(srv, onIRC324))
|
|
events.Bind("irc.332", HandleIRCEvent(srv, onIRC332))
|
|
events.Bind("irc.331", HandleIRCEvent(srv, onIRC331))
|
|
events.Bind("irc.TOPIC", HandleIRCEvent(srv, onIRCTopic))
|
|
errorCodes := []string{"irc.401", "irc.403", "irc.404", "irc.405", "irc.406", "irc.407", "irc.408", "irc.421"}
|
|
for _, code := range errorCodes {
|
|
events.Bind(code, HandleIRCEvent(srv, onIRCError))
|
|
}
|
|
whoisCodes := []string{"irc.311", "irc.312", "irc.313", "irc.317", "irc.318", "irc.319", "irc.314", "irc.369"}
|
|
for _, code := range whoisCodes {
|
|
events.Bind(code, HandleIRCEvent(srv, onIRCWhois))
|
|
}
|
|
miscCodes := []string{
|
|
"irc.001", "irc.002", "irc.003", "irc.250", "irc.251",
|
|
"irc.252", "irc.253", "irc.254", "irc.255", "irc.265",
|
|
"irc.266", "irc.375", "irc.372", "irc.376",
|
|
}
|
|
for _, code := range miscCodes {
|
|
events.Bind(code, HandleIRCEvent(srv, onIRCMessage))
|
|
}
|
|
events.Bind("debug.IRC", event.HandlerFunc(handleIRCDebugEvent))
|
|
}
|
|
|
|
type IRCEvent struct {
|
|
Code string
|
|
Raw string
|
|
Nick string // <nick>
|
|
Host string // <nick>!<usr>@<host>
|
|
Source string // <host>
|
|
User string // <usr>
|
|
Target string
|
|
Message string
|
|
Args []string
|
|
}
|
|
|
|
func normalizeDebugEvent(ev *event.Event) *IRCEvent {
|
|
if ev.Data == nil {
|
|
return nil
|
|
}
|
|
v, ok := ev.Data["source"].(*irc2.Event)
|
|
if !ok {
|
|
return nil
|
|
}
|
|
return &IRCEvent{
|
|
Code: v.Code,
|
|
Raw: v.Raw,
|
|
Nick: v.Nick,
|
|
Host: v.Host,
|
|
Source: v.Source,
|
|
User: v.User,
|
|
Target: v.Arguments[0],
|
|
Message: v.Message(),
|
|
Args: v.Arguments,
|
|
}
|
|
}
|
|
|
|
var debugIgnore = map[string]struct{}{
|
|
"CTCP_ACTION": {},
|
|
"CTCP_TIME": {},
|
|
"CTCP_VERSION": {},
|
|
"CTCP_USERINFO": {},
|
|
"CTCP_CLIENTINFO": {},
|
|
"CTCP_PING": {},
|
|
|
|
"PRIVMSG": {},
|
|
"NOTICE": {},
|
|
"TOPIC": {},
|
|
"MODE": {},
|
|
"KICK": {},
|
|
"NICK": {},
|
|
"QUIT": {},
|
|
"JOIN": {},
|
|
"PART": {},
|
|
|
|
"366": {},
|
|
"353": {},
|
|
"324": {},
|
|
"329": {},
|
|
"331": {},
|
|
"332": {},
|
|
"001": {},
|
|
"002": {},
|
|
"003": {},
|
|
"251": {},
|
|
"252": {},
|
|
"253": {},
|
|
"254": {},
|
|
"255": {},
|
|
"250": {},
|
|
"265": {},
|
|
"266": {},
|
|
"311": {},
|
|
"312": {},
|
|
"313": {},
|
|
"317": {},
|
|
"318": {},
|
|
"319": {},
|
|
"369": {},
|
|
"375": {},
|
|
"372": {},
|
|
"376": {},
|
|
"433": {},
|
|
}
|
|
|
|
func handleIRCDebugEvent(ev *event.Event) {
|
|
nev := normalizeDebugEvent(ev)
|
|
if _, ok := debugIgnore[nev.Code]; ok {
|
|
return
|
|
}
|
|
logrus.Debugf("irc.%s - T(%s) N(%s) => %s", nev.Code, nev.Target, nev.Nick, strings.Join(nev.Args[1:], " "))
|
|
}
|
|
|
|
func NormalizeIRCEvent(ev *event.Event) *IRCEvent {
|
|
if ev.Data == nil {
|
|
return nil
|
|
}
|
|
return &IRCEvent{
|
|
Code: ev.Data["Code"].(string),
|
|
Raw: ev.Data["Raw"].(string),
|
|
Nick: ev.Data["Nick"].(string),
|
|
Host: ev.Data["Host"].(string),
|
|
Source: ev.Data["Source"].(string),
|
|
User: ev.Data["User"].(string),
|
|
Target: ev.Data["Target"].(string),
|
|
Message: ev.Data["Message"].(string),
|
|
Args: ev.Data["Args"].([]string),
|
|
}
|
|
}
|
|
|
|
type IRCEventHandler func(srv *Server, ev *IRCEvent)
|
|
|
|
func HandleIRCEvent(srv *Server, h IRCEventHandler) event.Handler {
|
|
return event.HandlerFunc(func(ev *event.Event) {
|
|
nev := NormalizeIRCEvent(ev)
|
|
h(srv, nev)
|
|
})
|
|
}
|
|
|
|
func onIRC324(srv *Server, ev *IRCEvent) {
|
|
modes := strings.Join(ev.Args[2:], " ")
|
|
win := srv.windows.Named(ev.Args[1])
|
|
if ch, ok := win.(*Channel); ok {
|
|
ch.mu.Lock()
|
|
ch.modes = modes
|
|
ch.mu.Unlock()
|
|
}
|
|
}
|
|
|
|
func onIRC331(srv *Server, ev *IRCEvent) {
|
|
target := ev.Args[1]
|
|
win := srv.windows.Named(target)
|
|
if ch, ok := win.(*Channel); ok {
|
|
ch.mu.Lock()
|
|
ch.topic = ""
|
|
ch.mu.Unlock()
|
|
Write331(win)
|
|
}
|
|
}
|
|
|
|
func onIRC332(srv *Server, ev *IRCEvent) {
|
|
target := ev.Args[1]
|
|
win := srv.windows.Named(target)
|
|
if ch, ok := win.(*Channel); ok {
|
|
topic := strings.Join(ev.Args[2:], " ")
|
|
ch.mu.Lock()
|
|
ch.topic = topic
|
|
ch.mu.Unlock()
|
|
Write332(win, topic)
|
|
}
|
|
}
|
|
|
|
func onIRCConnect(srv *Server, _ *IRCEvent) {
|
|
srv.IRCDoAsync(func(conn *irc.Connection) error {
|
|
srv.setCurrentNick(conn.GetNick())
|
|
conn.AddCallback("*", func(ev *irc2.Event) {
|
|
srv.events.Emit("debug.IRC", map[string]interface{}{
|
|
"source": ev,
|
|
})
|
|
})
|
|
return nil
|
|
})
|
|
}
|
|
|
|
func onIRCDisconnect(srv *Server, _ *IRCEvent) {
|
|
logrus.Infoln("*** Disconnected")
|
|
srv.setCurrentNick("")
|
|
}
|
|
|
|
func onIRCMode(srv *Server, ev *IRCEvent) {
|
|
target := ev.Target
|
|
nick := SomeNick(ev.Nick)
|
|
mode := strings.Join(ev.Args[1:], " ")
|
|
currNick := srv.CurrentNick()
|
|
if ev.Nick == currNick {
|
|
nick.me = true
|
|
} else if target == currNick {
|
|
nick = MyNick(target)
|
|
}
|
|
win := srv.windows.Named(target)
|
|
if win != nil {
|
|
WriteMode(win, nick, mode)
|
|
onIRCNames(srv, ev)
|
|
srv.IRCDoAsync(func(conn *irc.Connection) error {
|
|
conn.Mode(target)
|
|
return nil
|
|
})
|
|
} else {
|
|
WriteMode(srv.windows.Index(0), nick, mode)
|
|
}
|
|
}
|
|
|
|
func onIRCTopic(srv *Server, ev *IRCEvent) {
|
|
target := ev.Target
|
|
nick := SomeNick(ev.Nick)
|
|
topic := strings.Join(ev.Args[1:], " ")
|
|
if ev.Nick == srv.CurrentNick() {
|
|
nick.me = true
|
|
}
|
|
win := srv.windows.Named(target)
|
|
if win != nil {
|
|
WriteTopic(win, nick, topic)
|
|
} else {
|
|
logrus.Warnln("received topic with no channel window:", target, nick, topic)
|
|
}
|
|
}
|
|
|
|
func onIRC433(srv *Server, _ *IRCEvent) {
|
|
srv.IRCDoAsync(func(conn *irc.Connection) error {
|
|
srv.setCurrentNick(conn.GetNick())
|
|
return nil
|
|
})
|
|
}
|
|
|
|
func onIRCNick(srv *Server, ev *IRCEvent) {
|
|
nick := SomeNick(ev.Nick)
|
|
newNick := SomeNick(ev.Message)
|
|
if ev.Nick == srv.CurrentNick() {
|
|
nick.me = true
|
|
newNick.me = true
|
|
srv.setCurrentNick(newNick.string)
|
|
}
|
|
WriteNick(srv.windows, nick, newNick)
|
|
}
|
|
|
|
func onIRCKick(srv *Server, ev *IRCEvent) {
|
|
channel := ev.Target
|
|
kicker := SomeNick(ev.Nick)
|
|
kicked := SomeNick(ev.Args[1])
|
|
myNick := srv.CurrentNick()
|
|
if kicked.string == myNick {
|
|
kicked.me = true
|
|
}
|
|
if kicker.string == myNick {
|
|
kicker.me = true
|
|
}
|
|
if kicked.me {
|
|
go func() {
|
|
<-time.After(2 * time.Second)
|
|
if err := srv.irc.Do(func(conn *irc.Connection) error {
|
|
conn.Join(channel)
|
|
return nil
|
|
}); err != nil {
|
|
logrus.Warnln("failed to rejoin after kick:", err)
|
|
}
|
|
}()
|
|
}
|
|
win := srv.windows.Named(channel)
|
|
if win == nil {
|
|
logrus.Errorln("received kick with no Window:", channel, ev.Message, ev.Nick)
|
|
return
|
|
}
|
|
if ch, ok := win.(*Channel); ok {
|
|
ch.DeleteUser(kicked.string)
|
|
}
|
|
WriteKick(win, kicker, kicked, ev.Message)
|
|
}
|
|
|
|
var namesCache = &struct {
|
|
sync.Mutex
|
|
values map[string][]string
|
|
}{values: make(map[string][]string)}
|
|
|
|
func onIRC353(srv *Server, ev *IRCEvent) {
|
|
// NAMES
|
|
chanName := ev.Args[2]
|
|
nicks := strings.Split(ev.Args[3], " ")
|
|
win := srv.windows.Named(chanName)
|
|
if win == nil {
|
|
logrus.Warnln("received NAMES for channel with no window:", chanName)
|
|
return
|
|
}
|
|
namesCache.Lock()
|
|
defer namesCache.Unlock()
|
|
namesCache.values[chanName] = append(namesCache.values[chanName], nicks...)
|
|
}
|
|
|
|
func onIRC366(srv *Server, ev *IRCEvent) {
|
|
// END NAMES
|
|
chanName := ev.Args[1]
|
|
win := srv.windows.Named(chanName)
|
|
if win == nil {
|
|
logrus.Warnln("received END NAMES for channel with no window:", chanName)
|
|
return
|
|
}
|
|
ch, ok := win.(*Channel)
|
|
if !ok {
|
|
logrus.Warnln("received END NAMES for a non channel:", chanName)
|
|
return
|
|
}
|
|
namesCache.Lock()
|
|
defer namesCache.Unlock()
|
|
ch.SetUsers(namesCache.values[chanName])
|
|
delete(namesCache.values, chanName)
|
|
srv.windows.events.Emit("ui.DIRTY", nil)
|
|
}
|
|
|
|
func onIRCError(srv *Server, ev *IRCEvent) {
|
|
var kind string
|
|
if len(ev.Args) > 1 {
|
|
kind = ev.Args[1]
|
|
} else {
|
|
kind = ev.Target
|
|
}
|
|
win := srv.windows.Named(kind)
|
|
WriteError(win, kind, ev.Message)
|
|
}
|
|
|
|
func onIRCWhois(srv *Server, ev *IRCEvent) {
|
|
nick := ev.Args[1]
|
|
data := ev.Args[2:]
|
|
win := srv.windows.NamedOrActive(nick)
|
|
WriteWhois(win, nick, data)
|
|
}
|
|
|
|
func onIRCMessage(srv *Server, ev *IRCEvent) {
|
|
win := srv.windows.Index(0)
|
|
WriteMessage(win, strings.Join(ev.Args[1:], " "))
|
|
}
|
|
|
|
func onIRCNames(srv *Server, ev *IRCEvent) {
|
|
if ev.Code == "PART" || ev.Code == "KICK" {
|
|
myNick := srv.CurrentNick()
|
|
if ev.Nick == myNick {
|
|
// dont bother trying to get names when we are the one leaving
|
|
return
|
|
}
|
|
}
|
|
target := ev.Target
|
|
if strings.HasPrefix(target, "#") {
|
|
srv.IRCDoAsync(func(conn *irc.Connection) error {
|
|
conn.SendRawf("NAMES :%s", target)
|
|
return nil
|
|
})
|
|
}
|
|
}
|
|
|
|
func onIRCJoin(srv *Server, ev *IRCEvent) {
|
|
target := ev.Target
|
|
win := srv.windows.Named(target)
|
|
nick := SomeNick(ev.Nick)
|
|
if ev.Nick == srv.CurrentNick() {
|
|
nick.me = true
|
|
}
|
|
if win == nil {
|
|
ch := &Channel{
|
|
bufferedWindow: newBufferedWindow(target, srv.events),
|
|
users: []User{},
|
|
}
|
|
srv.windows.Append(ch)
|
|
win = ch
|
|
if nick.me {
|
|
srv.windows.SelectIndex(srv.windows.Len() - 1)
|
|
srv.IRCDoAsync(func(conn *irc.Connection) error {
|
|
conn.Mode(target)
|
|
return nil
|
|
})
|
|
}
|
|
}
|
|
if ch, ok := win.(*Channel); ok {
|
|
ch.AddUser(SomeUser(nick.string))
|
|
}
|
|
WriteJoin(win, nick)
|
|
}
|
|
|
|
func onIRCPart(srv *Server, ev *IRCEvent) {
|
|
target := ev.Target
|
|
nick := SomeNick(ev.Nick)
|
|
win := srv.windows.Named(target)
|
|
if ev.Nick == srv.CurrentNick() {
|
|
nick.me = true
|
|
}
|
|
if win == nil {
|
|
if !nick.me {
|
|
// dont bother logging if we are the ones leaving
|
|
logrus.Errorln("received message with no Window:", target, ev.Message, nick)
|
|
}
|
|
return
|
|
}
|
|
if ch, ok := win.(*Channel); ok {
|
|
ch.DeleteUser(nick.string)
|
|
}
|
|
WritePart(win, nick, ev.Message)
|
|
}
|
|
|
|
func onIRCAction(srv *Server, ev *IRCEvent) {
|
|
direct := false
|
|
target := ev.Target
|
|
nick := ev.Nick
|
|
myNick := MyNick(srv.CurrentNick())
|
|
if target == myNick.string {
|
|
// its a direct message!
|
|
direct = true
|
|
target = nick
|
|
}
|
|
win := srv.windows.Named(target)
|
|
if win == nil {
|
|
if !direct {
|
|
logrus.Warnln("received action message with no Window:", target, ev.Message, nick)
|
|
return
|
|
} else {
|
|
ch := &DirectMessage{bufferedWindow: newBufferedWindow(target, srv.events)}
|
|
srv.windows.Append(ch)
|
|
win = ch
|
|
}
|
|
}
|
|
msg := SomeMessage(ev.Message, myNick)
|
|
WriteAction(win, SomeNick(nick), msg)
|
|
}
|
|
|
|
func onIRCPrivmsg(srv *Server, ev *IRCEvent) {
|
|
direct := false
|
|
target := ev.Target
|
|
nick := ev.Nick
|
|
myNick := MyNick(srv.CurrentNick())
|
|
if target == myNick.string {
|
|
// its a direct message!
|
|
direct = true
|
|
target = nick
|
|
}
|
|
win := srv.windows.Named(target)
|
|
if win == nil {
|
|
if !direct {
|
|
logrus.Warnln("received message with no Window:", target, ev.Message, nick)
|
|
return
|
|
} else {
|
|
ch := &DirectMessage{bufferedWindow: newBufferedWindow(target, srv.events)}
|
|
srv.windows.Append(ch)
|
|
win = ch
|
|
}
|
|
}
|
|
msg := SomeMessage(ev.Message, myNick)
|
|
WritePrivmsg(win, SomeNick(nick), msg)
|
|
}
|
|
|
|
func onIRCNotice(srv *Server, ev *IRCEvent) {
|
|
me := srv.CurrentNick()
|
|
target := SomeTarget(ev.Target, me)
|
|
// "*" is used by at least Freenode when you don't yet have a nick.
|
|
if target.string == "*" {
|
|
target.me = true
|
|
}
|
|
if target.me {
|
|
target = SomeTarget(ev.Nick, me)
|
|
}
|
|
win := srv.windows.Named(target.string)
|
|
if win == nil {
|
|
win = srv.windows.Index(0)
|
|
}
|
|
|
|
if strings.Contains(ev.Message, "\x01") {
|
|
WriteCTCP(win, target, false, ev.Message)
|
|
return
|
|
}
|
|
WriteNotice(win, target, false, ev.Message)
|
|
}
|
|
|
|
func onIRCQuit(srv *Server, ev *IRCEvent) {
|
|
nick := SomeNick(ev.Nick)
|
|
message := ev.Message
|
|
if ev.Nick == srv.CurrentNick() {
|
|
nick.me = true
|
|
}
|
|
WriteQuit(srv.windows, nick, message)
|
|
}
|