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.
517 lines
13 KiB
517 lines
13 KiB
package squirssi
|
|
|
|
import (
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
|
|
"code.dopame.me/veonik/squircy3/event"
|
|
"code.dopame.me/veonik/squircy3/irc"
|
|
"github.com/sirupsen/logrus"
|
|
)
|
|
|
|
type Command func(*Server, []string)
|
|
|
|
func modeHandler(mode string) Command {
|
|
return func(srv *Server, args []string) {
|
|
args = append(append(append([]string{}, args[:1]...), mode), args[1:]...)
|
|
modeChange(srv, args)
|
|
}
|
|
}
|
|
|
|
// builtInsOrdered is ordered in that it is in the desired order with
|
|
// related commands grouped together.
|
|
var builtInsOrdered = []string{
|
|
"exit",
|
|
"connect",
|
|
"disconnect",
|
|
"w",
|
|
"wc",
|
|
"join",
|
|
"part",
|
|
"invite",
|
|
"topic",
|
|
"whois",
|
|
"names",
|
|
"nick",
|
|
"me",
|
|
"msg",
|
|
"ctcp",
|
|
"notice",
|
|
"kick",
|
|
"mode",
|
|
"ban",
|
|
"unban",
|
|
"op",
|
|
"deop",
|
|
"voice",
|
|
"devoice",
|
|
"mute",
|
|
"unmute",
|
|
"echo",
|
|
"raw",
|
|
"eval",
|
|
"help",
|
|
}
|
|
|
|
var builtIns = map[string]Command{
|
|
"help": helpCmd,
|
|
"?": helpCmd,
|
|
"exit": exitProgram,
|
|
"w": selectWindow,
|
|
"wc": closeWindow,
|
|
"join": joinChannel,
|
|
"part": partChannel,
|
|
"invite": inviteTarget,
|
|
"topic": topicChange,
|
|
"whois": whoisNick,
|
|
"names": namesChannel,
|
|
"nick": changeNick,
|
|
"me": actionTarget,
|
|
"msg": msgTarget,
|
|
"ctcp": ctcpTarget,
|
|
"notice": noticeTarget,
|
|
|
|
"kick": kickTarget,
|
|
"mode": modeChange,
|
|
"ban": modeHandler("+b"),
|
|
"unban": modeHandler("-b"),
|
|
"op": modeHandler("+o"),
|
|
"deop": modeHandler("-o"),
|
|
"voice": modeHandler("+v"),
|
|
"devoice": modeHandler("-v"),
|
|
"mute": modeHandler("+q"),
|
|
"unmute": modeHandler("-q"),
|
|
|
|
"connect": connectServer,
|
|
"disconnect": disconnectServer,
|
|
"quit": disconnectServer,
|
|
|
|
"echo": func(srv *Server, args []string) {
|
|
win := srv.windows.Active()
|
|
_, _ = win.WriteString(strings.Join(args[1:], " "))
|
|
},
|
|
"raw": func(srv *Server, args []string) {
|
|
srv.IRCDoAsync(func(conn *irc.Connection) error {
|
|
conn.SendRaw(strings.Join(args[1:], " "))
|
|
win := srv.windows.Active()
|
|
if win != nil {
|
|
WriteRaw(win, "-> "+strings.Join(args[1:], " "))
|
|
}
|
|
return nil
|
|
})
|
|
},
|
|
"eval": func(srv *Server, args []string) {
|
|
win := srv.windows.Active()
|
|
script := strings.Join(args[1:], " ")
|
|
go func() {
|
|
WriteEval(win, script)
|
|
res, err := srv.vm.RunString(script).Await()
|
|
if err != nil {
|
|
WriteEvalError(win, err.Error())
|
|
return
|
|
}
|
|
WriteEvalResult(win, res.ToString().String())
|
|
}()
|
|
},
|
|
}
|
|
|
|
var builtInDescriptions = map[string]string{
|
|
"help": "Prints this help text.",
|
|
"exit": "Exits squirssi.",
|
|
"w": "Switches to the given window by number.",
|
|
"wc": "Closes the given window by number, or the currently active window.",
|
|
"join": "Attempts to join the given channel.",
|
|
"part": "Parts the given channel.",
|
|
"invite": "Invites a user to the given channel.",
|
|
"topic": "Sets the topic for the given channel, or the currently active window.",
|
|
"whois": "Runs a WHOIS query on the given nickname.",
|
|
"names": "Runs a NAMES query on the given channel.",
|
|
"nick": "Changes the current nickname.",
|
|
"me": "Performs an action message in the current window.",
|
|
"msg": "Sends a message to the given target.",
|
|
"ctcp": "Sends a CTCP query to the given target.",
|
|
"notice": "Sends a NOTICE to the given target.",
|
|
"kick": "Kicks a user from the given channel.",
|
|
"mode": "Sets mode on a channel or the current user.",
|
|
"ban": "Bans (+b) a user from the given channel.",
|
|
"unban": "Unbans (-b) a user from the given channel.",
|
|
"op": "Ops (+o) a user on the given channel.",
|
|
"deop": "Deops (-o) a user on the given channel.",
|
|
"voice": "Voices (+v) a user on the given channel.",
|
|
"devoice": "Devoices (-v) a user on the given channel.",
|
|
"mute": "Mutes (+q) a user on the given channel.",
|
|
"unmute": "Unmutes (-q) a user on the given channel.",
|
|
"connect": "Connects to the configured IRC server.",
|
|
"disconnect": "Disconnects from the connected IRC server.",
|
|
"echo": "Writes any arguments given to the currently active window.",
|
|
"raw": "Sends a raw IRC command.",
|
|
"eval": "Evaluate some javascript in the embedded runtime.",
|
|
}
|
|
|
|
func helpCmd(srv *Server, args []string) {
|
|
win := srv.windows.Active()
|
|
if win == nil {
|
|
return
|
|
}
|
|
if len(args) > 1 {
|
|
if desc, ok := builtInDescriptions[args[1]]; ok {
|
|
WriteHelpGeneric(win, "Help information for "+args[1])
|
|
WriteHelp(win, args[1], desc)
|
|
} else {
|
|
WriteHelpGeneric(win, "Unknown command: "+args[1])
|
|
}
|
|
return
|
|
}
|
|
// print all help.
|
|
WriteHelpGeneric(win, "[Available commands:](mod:bold)")
|
|
for _, cmd := range builtInsOrdered {
|
|
WriteHelp(win, cmd, builtInDescriptions[cmd])
|
|
}
|
|
}
|
|
|
|
func connectServer(srv *Server, _ []string) {
|
|
go func() {
|
|
if err := srv.irc.Connect(); err != nil {
|
|
logrus.Errorln("Unable to connect:", err)
|
|
}
|
|
}()
|
|
}
|
|
|
|
func disconnectServer(srv *Server, _ []string) {
|
|
go func() {
|
|
if err := srv.irc.Disconnect(); err != nil {
|
|
logrus.Errorln("Unable to disconnect:", err)
|
|
}
|
|
}()
|
|
}
|
|
|
|
func exitProgram(srv *Server, _ []string) {
|
|
srv.mu.Lock()
|
|
defer srv.mu.Unlock()
|
|
if srv.interrupt != nil {
|
|
srv.interrupt()
|
|
}
|
|
}
|
|
|
|
func topicChange(srv *Server, args []string) {
|
|
args = guessTargetInArgs(srv, args, 1)
|
|
target := args[1]
|
|
if len(args) == 2 {
|
|
srv.IRCDoAsync(func(conn *irc.Connection) error {
|
|
conn.SendRawf("TOPIC %s", target)
|
|
return nil
|
|
})
|
|
return
|
|
}
|
|
topic := strings.Join(args[2:], " ")
|
|
srv.IRCDoAsync(func(conn *irc.Connection) error {
|
|
conn.SendRawf("TOPIC %s :%s", target, topic)
|
|
return nil
|
|
})
|
|
}
|
|
|
|
func kickTarget(srv *Server, args []string) {
|
|
args = guessTargetInArgs(srv, args, 1)
|
|
target := args[1]
|
|
if len(target) > 0 && target[0] != '#' {
|
|
logrus.Warnln("kick: unable to determine current channel")
|
|
return
|
|
}
|
|
nick := args[2]
|
|
msg := strings.Join(args[3:], " ")
|
|
srv.IRCDoAsync(func(conn *irc.Connection) error {
|
|
conn.Kick(nick, target, msg)
|
|
return nil
|
|
})
|
|
}
|
|
|
|
func modeChange(srv *Server, args []string) {
|
|
if len(args) < 2 || strings.HasPrefix(args[1], "+") || strings.HasPrefix(args[1], "-") {
|
|
win := srv.windows.Active()
|
|
t := ""
|
|
if win == nil || win.Title() == "status" {
|
|
t = srv.CurrentNick()
|
|
} else {
|
|
t = win.Title()
|
|
}
|
|
args = append(append([]string{}, args[0], t), args[1:]...)
|
|
}
|
|
target := args[1]
|
|
modes := args[2:]
|
|
var irc324Handler event.Handler
|
|
var irc329Handler event.Handler
|
|
if len(modes) == 0 || len(modes[0]) == 0 {
|
|
irc324Handler = event.HandlerFunc(func(ev *event.Event) {
|
|
args := ev.Data["Args"].([]string)
|
|
modes := strings.Join(args[2:], " ")
|
|
win := srv.windows.Named(args[1])
|
|
WriteModes(win, modes)
|
|
srv.events.Unbind("irc.324", irc324Handler)
|
|
})
|
|
irc329Handler = event.HandlerFunc(func(ev *event.Event) {
|
|
args := ev.Data["Args"].([]string)
|
|
target := args[1]
|
|
win := srv.windows.Named(target)
|
|
if _, ok := win.(*Channel); ok {
|
|
created, _ := strconv.Atoi(args[2])
|
|
t := time.Unix(int64(created), 0)
|
|
Write329(win, t)
|
|
}
|
|
srv.events.Unbind("irc.329", irc329Handler)
|
|
})
|
|
}
|
|
srv.IRCDoAsync(func(conn *irc.Connection) error {
|
|
if irc324Handler != nil {
|
|
srv.events.Bind("irc.324", irc324Handler)
|
|
}
|
|
if irc329Handler != nil {
|
|
srv.events.Bind("irc.329", irc329Handler)
|
|
}
|
|
conn.Mode(target, modes...)
|
|
return nil
|
|
})
|
|
}
|
|
|
|
func selectWindow(srv *Server, args []string) {
|
|
if len(args) < 2 {
|
|
logrus.Warnln("window: expected one argument")
|
|
return
|
|
}
|
|
var err error
|
|
ch, err := strconv.Atoi(args[1])
|
|
if err != nil {
|
|
logrus.Warnln("window: expected first argument to be an integer")
|
|
return
|
|
}
|
|
srv.windows.SelectIndex(ch)
|
|
}
|
|
|
|
func closeWindow(srv *Server, args []string) {
|
|
var ch int
|
|
if len(args) < 2 {
|
|
ch = srv.windows.ActiveIndex()
|
|
} else {
|
|
var err error
|
|
ch, err = strconv.Atoi(args[1])
|
|
if err != nil {
|
|
logrus.Warnln("window_close: expected first argument to be an integer")
|
|
return
|
|
}
|
|
}
|
|
win := srv.windows.Index(ch)
|
|
if ch, ok := win.(*Channel); ok {
|
|
myNick := srv.CurrentNick()
|
|
if ch.HasUser(myNick) {
|
|
srv.IRCDoAsync(func(conn *irc.Connection) error {
|
|
conn.Part(win.Title())
|
|
return nil
|
|
})
|
|
}
|
|
}
|
|
srv.windows.CloseIndex(ch)
|
|
}
|
|
|
|
func guessTargetInArgs(srv *Server, args []string, targetIndex int) []string {
|
|
if targetIndex < 0 {
|
|
return args
|
|
}
|
|
if len(args) < targetIndex+1 || !strings.HasPrefix(args[targetIndex], "#") {
|
|
win := srv.windows.Active()
|
|
t := ""
|
|
if win != nil && win.Title() != "status" {
|
|
t = win.Title()
|
|
}
|
|
args = append(append([]string{}, args[targetIndex-1], t), args[targetIndex:]...)
|
|
}
|
|
return args
|
|
}
|
|
|
|
func joinChannel(srv *Server, args []string) {
|
|
args = guessTargetInArgs(srv, args, 1)
|
|
target := args[1]
|
|
if len(target) > 0 && target[0] != '#' {
|
|
logrus.Warnln("join: unable to determine current channel")
|
|
return
|
|
}
|
|
srv.IRCDoAsync(func(conn *irc.Connection) error {
|
|
conn.Join(target)
|
|
return nil
|
|
})
|
|
}
|
|
|
|
func partChannel(srv *Server, args []string) {
|
|
args = guessTargetInArgs(srv, args, 1)
|
|
target := args[1]
|
|
if len(target) > 0 && target[0] != '#' {
|
|
logrus.Warnln("part: unable to determine current channel")
|
|
return
|
|
}
|
|
srv.IRCDoAsync(func(conn *irc.Connection) error {
|
|
conn.Part(target)
|
|
return nil
|
|
})
|
|
}
|
|
|
|
func inviteTarget(srv *Server, args []string) {
|
|
args = guessTargetInArgs(srv, args, 1)
|
|
target := args[1]
|
|
if len(target) > 0 && target[0] != '#' {
|
|
logrus.Warnln("invite: unable to determine current channel")
|
|
return
|
|
}
|
|
nick := args[2]
|
|
srv.IRCDoAsync(func(conn *irc.Connection) error {
|
|
conn.SendRawf("INVITE %s :%s", nick, target)
|
|
return nil
|
|
})
|
|
}
|
|
|
|
func whoisNick(srv *Server, args []string) {
|
|
if len(args) < 2 {
|
|
logrus.Warnln("whois: expected one argument")
|
|
return
|
|
}
|
|
srv.IRCDoAsync(func(conn *irc.Connection) error {
|
|
conn.SendRawf("WHOIS %s", args[1])
|
|
return nil
|
|
})
|
|
}
|
|
|
|
func namesChannel(srv *Server, args []string) {
|
|
args = guessTargetInArgs(srv, args, 1)
|
|
target := args[1]
|
|
if len(target) > 0 && target[0] != '#' {
|
|
logrus.Warnln("names: unable to determine current channel")
|
|
return
|
|
}
|
|
win := srv.windows.Named(target)
|
|
if win == nil {
|
|
logrus.Warnln("names: no window named", target)
|
|
return
|
|
}
|
|
irc353Handler := event.HandlerFunc(func(ev *event.Event) {
|
|
args := ev.Data["Args"].([]string)
|
|
chanName := args[2]
|
|
nicks := args[3]
|
|
logrus.Infof("NAMES %s: %s", chanName, nicks)
|
|
})
|
|
var irc366Handler event.Handler
|
|
irc366Handler = event.HandlerFunc(func(ev *event.Event) {
|
|
args := ev.Data["Args"].([]string)
|
|
chanName := args[1]
|
|
logrus.Infof("END NAMES %s", chanName)
|
|
srv.events.Unbind("irc.353", irc353Handler)
|
|
srv.events.Unbind("irc.366", irc366Handler)
|
|
})
|
|
srv.events.Bind("irc.353", irc353Handler)
|
|
srv.events.Bind("irc.366", irc366Handler)
|
|
srv.IRCDoAsync(func(conn *irc.Connection) error {
|
|
conn.SendRawf("NAMES :%s", target)
|
|
return nil
|
|
})
|
|
}
|
|
|
|
func changeNick(srv *Server, args []string) {
|
|
if len(args) < 2 {
|
|
logrus.Warnln("nick: expected one argument")
|
|
return
|
|
}
|
|
srv.IRCDoAsync(func(conn *irc.Connection) error {
|
|
conn.Nick(args[1])
|
|
return nil
|
|
})
|
|
}
|
|
|
|
func actionTarget(srv *Server, args []string) {
|
|
message := strings.Join(args[1:], " ")
|
|
window := srv.windows.Active()
|
|
if window == nil || window.Title() == "status" {
|
|
return
|
|
}
|
|
srv.IRCDoAsync(func(conn *irc.Connection) error {
|
|
conn.Action(window.Title(), message)
|
|
return nil
|
|
})
|
|
myNick := MyNick(srv.CurrentNick())
|
|
WriteAction(window, myNick, MyMessage(message))
|
|
}
|
|
|
|
func msgTarget(srv *Server, args []string) {
|
|
if len(args) < 3 {
|
|
logrus.Warnln("msg: expected at least 2 arguments")
|
|
return
|
|
}
|
|
target := args[1]
|
|
if target == "status" {
|
|
return
|
|
}
|
|
message := strings.Join(args[2:], " ")
|
|
srv.IRCDoAsync(func(conn *irc.Connection) error {
|
|
conn.Privmsg(target, message)
|
|
return nil
|
|
})
|
|
window := srv.windows.Named(target)
|
|
if !strings.HasPrefix(target, "#") {
|
|
// direct message!
|
|
if window == nil {
|
|
dm := &DirectMessage{
|
|
newBufferedWindow(target, srv.events),
|
|
}
|
|
srv.windows.Append(dm)
|
|
window = dm
|
|
}
|
|
}
|
|
myNick := MyNick(srv.CurrentNick())
|
|
if window == nil {
|
|
// no window for this but we might still have sent the message, so write it to the status window
|
|
window = srv.windows.Index(0)
|
|
message = target + " -> " + message
|
|
}
|
|
WritePrivmsg(window, myNick, MyMessage(message))
|
|
}
|
|
|
|
func noticeTarget(srv *Server, args []string) {
|
|
if len(args) < 3 {
|
|
logrus.Warnln("notice: expected at least 2 arguments")
|
|
return
|
|
}
|
|
target := args[1]
|
|
if target == "status" {
|
|
return
|
|
}
|
|
message := strings.Join(args[2:], " ")
|
|
srv.IRCDoAsync(func(conn *irc.Connection) error {
|
|
conn.Notice(target, message)
|
|
return nil
|
|
})
|
|
window := srv.windows.Named(target)
|
|
if window == nil {
|
|
// no window for this but we might still have sent the message, so write it to the status window
|
|
window = srv.windows.Index(0)
|
|
}
|
|
WriteNotice(window, SomeTarget(target, srv.CurrentNick()), true, message)
|
|
}
|
|
|
|
func ctcpTarget(srv *Server, args []string) {
|
|
if len(args) < 3 {
|
|
logrus.Warnln("ctcp: expected at least 2 arguments")
|
|
return
|
|
}
|
|
target := args[1]
|
|
if target == "status" {
|
|
return
|
|
}
|
|
message := strings.Join(args[2:], " ")
|
|
srv.IRCDoAsync(func(conn *irc.Connection) error {
|
|
conn.SendRawf("PRIVMSG %s :\x01%s\x01", target, message)
|
|
return nil
|
|
})
|
|
window := srv.windows.Named(target)
|
|
if window == nil {
|
|
// no window for this but we might still have sent the message, so write it to the status window
|
|
window = srv.windows.Index(0)
|
|
}
|
|
WriteCTCP(window, SomeTarget(target, srv.CurrentNick()), true, message)
|
|
}
|