diff --git a/gsend.go b/gsend.go deleted file mode 100644 index b8bc092..0000000 --- a/gsend.go +++ /dev/null @@ -1,20 +0,0 @@ -package main - -import ( - "github.com/cxindex/xmpp" - "io/ioutil" - "os" -) - -func main() { - Conn, err := xmpp.Dial("xmpp.ru:5222", "hypnotoad", "xmpp.ru", "password", "gsend", nil) - if err != nil { - println(err) - return - } - - if s, err := ioutil.ReadAll(os.Stdin); err == nil { - Conn.Send("hypnotoad@xmpp.ru", "chat", string(s[:len(s)-1])) - return - } -} diff --git a/gsend.py b/gsend.py new file mode 100755 index 0000000..4428570 --- /dev/null +++ b/gsend.py @@ -0,0 +1,55 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +import sys +import sleekxmpp + +opts = { + "jid": "botname@example.com", + "resource": "resource", + "password": "password", + "connect": "xmpp.example.org:5222", +} + + +def on_session_start(event): + client.get_roster() + # client.send_presence() + + body = "\n".join(sys.argv[1:]).strip() + try: + if body: + client.send_message(mto=opts["jid"], mbody=body, mtype="chat") + except Exception as e: + print("%s: %s" % (type(e).__name__, str(e))) + finally: + client.disconnect(wait=True) + sys.exit(0) + + +if __name__ == "__main__": + if sys.version_info.major < 3: + sleekxmpp.util.misc_ops.setdefaultencoding("utf-8") + + if len(sys.argv) <= 1: + print("At least one argument is required.") + sys.exit(1) + + client = sleekxmpp.ClientXMPP("%s/%s" % (opts["jid"], opts["resource"]), + opts["password"]) + + if opts["connect"]: + connect = opts["connect"].split(":", 1) + if len(connect) != 2 or not connect[1].isdigit(): + print("Connection server format is invalid, should be " + + "example.org:5222") + sys.exit(1) + else: + connect = () + + if client.connect(connect): + client.add_event_handler("session_start", on_session_start) + client.process(block=True) + else: + print("Could not connect to server, or password mismatch!") + sys.exit(1) diff --git a/hptoad.py b/hptoad.py new file mode 100755 index 0000000..457841d --- /dev/null +++ b/hptoad.py @@ -0,0 +1,286 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +import logging +import os +import re +import signal +import subprocess +import sys +import time +import sleekxmpp + +opts = { + "muc": "room@conference.example.com", + "nick": "botname", + "jid": "botname@example.com", + "resource": "resource", + "password": "password", + "connect": "xmpp.example.org:5222", +} + + +class Zhobe: + def __init__(self, opts): + if sys.version_info.major < 3: + sleekxmpp.util.misc_ops.setdefaultencoding("utf-8") + + self.client = sleekxmpp.ClientXMPP("%s/%s" % (opts["jid"], + opts["resource"]), + opts["password"]) + self.client.register_plugin("xep_0199") # XMPP Ping. + self.client.register_plugin("xep_0045") # XMPP MUC. + + self.jid = opts["jid"] + self.connect = opts["connect"] + self.muc = opts["muc"] + self.pure_nick = opts["nick"] + self.nick = self.pure_nick + + def register_handlers(self): + self.client.add_event_handler("session_start", self.on_session_start) + self.client.add_event_handler("message", self.on_message, + threaded=True) + self.client.add_event_handler("muc::%s::presence" % self.muc, + self.on_presence) + + def join_muc(self): + muc_plugin = self.client.plugin["xep_0045"] + + if self.muc in muc_plugin.getJoinedRooms(): + muc_plugin.leaveMUC(self.muc, self.nick, + msg="Replaced by new connection") + muc_plugin.joinMUC(self.muc, self.nick, wait=True) + + @classmethod + def log_exception(cls, ex): + logging.error("%s: %s" % (type(ex).__name__, str(ex))) + + @classmethod + def log_message_event(cls, event): + logging.debug("&{{jabber:client message} %s %s %s %s %s { }}" % + (event["from"], event["id"], event["to"], + event["type"], event["body"])) + + def is_muc_admin(self, muc, nick): + muc_plugin = self.client.plugin["xep_0045"] + if nick not in muc_plugin.rooms[self.muc]: + return False + + affiliation = muc_plugin.getJidProperty(muc, nick, "affiliation") + return True if affiliation in ("admin", "owner") else False + + _trim_regexp = re.compile("(`|\\$|\\.\\.)") + _quote_regexp = re.compile("(\"|')") + + @classmethod + def trim(cls, s): + result = cls._trim_regexp.sub("", s) + result = cls._quote_regexp.sub("“", result).strip() + return result + + # letter(ASCII or cyrillic), number, underscore only. + _cmd_validator_regexp = re.compile("^!(\\w|\\p{Cyrillic})*$") + + def parse_command(self, body, dir_path, nick, is_admin=False): + cmd = body.split(" ", 1) + cmd[0] = cmd[0].strip() + + is_admin = "true" if is_admin else "false" + + if not self._cmd_validator_regexp.match(cmd[0]): + return None, "Bad command \"%s\"" % cmd[0] + + path = "%s/%s" % (dir_path, self.trim(cmd[0][1:])) + + if not os.access(path, os.F_OK): + return None, "\"%s\" does not exist" % path + if not os.path.isfile(path): + return None, "\"%s\" is not a file" % path + if not os.access(path, os.R_OK | os.X_OK): + return None, "\"%s\" is not readable or executable" % path + + proc_args = [path, self.trim(nick), is_admin] + if len(cmd) > 1: + proc_args.append(self.trim(cmd[1])) + + return proc_args, None + + def exec_command(self, body, dir_path, from_id, nick, is_admin=False): + cmd, err = self.parse_command(body, dir_path, nick, + is_admin=is_admin) + if err is not None: + logging.error("Command: %s" % err) + self.client.send_message(mto=self.muc, + mbody="%s: WAT" % nick, + mtype="groupchat") + if is_admin: + self.client.send_message(mto=from_id, + mbody=err, + mtype="chat") + return + + try: + proc = subprocess.Popen(cmd, + stdin=subprocess.PIPE, + stdout=subprocess.PIPE, + universal_newlines=True) + cmd_out, cmd_outerr = proc.communicate() + except subprocess.CalledProcessError as err: + logging.error("Execute: %s" % str(err)) + self.client.send_message(mto=self.muc, + mbody="%s: WAT" % nick, + mtype="groupchat") + if is_admin: + self.client.send_message(mto=from_id, + mbody=str(err), + mtype="chat") + return + + if cmd_outerr and len(cmd_outerr) > 0: + logging.error("Process: %s" % cmd_outerr.strip()) + if is_admin: + self.client.send_message(mto=from_id, + mbody=cmd_outerr.strip(), + mtype="chat") + if cmd_out and len(cmd_out) > 0: + self.client.send_message(mto=self.muc, + mbody=cmd_out.strip(), + mtype="groupchat") + + def on_session_start(self, event): + self.client.get_roster() + self.client.send_presence(pstatus="is there some food in this world?", + ppriority=12) + self.join_muc() + + def handle_self_message(self, body, nick, from_id): + if not body.startswith("!"): + self.client.send_message(mto=self.muc, mbody=body, + mtype="groupchat") + return + + self.exec_command(body, "./plugins", from_id, nick, is_admin=True) + + def handle_muc_message(self, body, nick, from_id): + muc_plugin = self.client.plugin["xep_0045"] + + is_admin = self.is_muc_admin(self.muc, nick) + + # Has to be redone with the current bot nick. + call_regexp = re.compile("^%s[:,]" % self.nick) + + if body == "!megakick": + self.client.send_message(mto=self.muc, + mbody="%s: WAT" % nick, + mtype="groupchat") + + elif body.startswith("!megakick "): + victim = body.split("!megakick ", 1)[1] + + is_bot_admin = self.is_muc_admin(self.muc, self.nick) + is_victim_admin = self.is_muc_admin(self.muc, victim) + + if is_admin and victim != self.nick: + if is_bot_admin and not is_victim_admin and \ + victim in muc_plugin.rooms[self.muc]: + muc_plugin.setRole(self.muc, victim, "none") + else: + self.client.send_message(mto=self.muc, + mbody="%s: Can't megakick %s." % + (nick, victim), + mtype="groupchat") + else: + self.client.send_message(mto=self.muc, + mbody="%s: GTFO" % nick, + mtype="groupchat") + + elif body.startswith("!"): # Any external command. + self.exec_command(body, "./plugins", from_id, nick, + is_admin=is_admin) + + elif call_regexp.match(body): # Chat. + cmd_body = call_regexp.sub("!answer", body) + self.exec_command(cmd_body, "./chat", from_id, nick, + is_admin=is_admin) + + def on_message(self, event): + try: + if not event["type"] in ("chat", "normal", "groupchat"): + return + + self.log_message_event(event) + + body = event["body"] + from_id = event["from"] + + if event["type"] == "groupchat": + nick = event["mucnick"] + self.handle_muc_message(body, nick, from_id) + + elif event["from"].bare == self.jid: + # Use resource as a nickname with self messages. + nick = from_id.resource + self.handle_self_message(body, nick, from_id) + except Exception as e: + self.log_exception(e) + + def on_presence(self, event): + muc_plugin = self.client.plugin["xep_0045"] + try: + typ = event["muc"]["type"] + from_id = event["from"] + nick = event["muc"]["nick"] + + if not typ: + typ = event["type"] + if not nick: + nick = muc_plugin.getNick(self.muc, from_id) + + if typ == "error": + if event["error"]["code"] == "409": + self.nick = self.nick + "_" + self.join_muc() + + elif typ == "unavailable": + if nick == self.nick: + self.nick = self.pure_nick + time.sleep(0.5) + self.join_muc() + except Exception as e: + self.log_exception(e) + + def run(self): + # Reset the nick. + self.nick = self.pure_nick + + if self.connect: + connect = self.connect.split(":", 1) + if len(connect) != 2 or not connect[1].isdigit(): + logging.critical("Conn: Connection server format is " + + "invalid, should be example.org:5222") + sys.exit(1) + else: + connect = () + + if self.client.connect(connect): + self.register_handlers() + self.client.process(block=True) + else: + logging.critical("Auth: Could not connect to server, or " + + "password mismatch!") + sys.exit(1) + + +if __name__ == "__main__": + signal.signal(signal.SIGINT, signal.SIG_DFL) + logging.basicConfig(level=logging.DEBUG, + format="%(asctime)s %(message)s", + datefmt="%Y/%m/%d %H:%M:%S") + # Silence sleekxmpp debug information. + logging.getLogger("sleekxmpp").setLevel(logging.CRITICAL) + zhobe = Zhobe(opts) + while True: + zhobe.run() + logging.error("Unknown: WTF am I doing here?") + time.sleep(0.5) diff --git a/main.go b/main.go deleted file mode 100644 index b85e2b7..0000000 --- a/main.go +++ /dev/null @@ -1,297 +0,0 @@ -package main - -import ( - "fmt" - "github.com/derlaft/xmpp" - "io/ioutil" - "log" - "os" - "os/exec" - "regexp" - "runtime" - "strconv" - "strings" - "time" -) - -const ( - room = "room@conference.example.com" - name = "botname" - server = "example.com" - me = name + "@" + server - id = name - password = "password" - resource = "resource" - connect = "xmpp.example.com:5222" -) - -var ( - admin []string - cs = make(chan xmpp.Stanza) - stop chan struct{} - next xmpp.Stanza -) - -func main() { - runtime.GOMAXPROCS(runtime.NumCPU()) - var ( - Conn *xmpp.Conn - err error - ) - -START: - for { - stop = make(chan struct{}) - if(err != nil) { - admin = admin[:0] - if Conn != nil { - log.Println("Conn check:", Conn.Close()) - } - time.Sleep(5 * time.Second) - } - - Conn, err = xmpp.Dial(connect, id, server, password, resource, nil) - if err != nil { - log.Println("Conn", err) - continue - } - if err := Conn.SignalPresence("dnd", "is there some food in this world?", 12); err != nil { - log.Println("Signal", err) - continue - } - if err := Conn.SendPresence(room + "/" + name, ""); err != nil { - log.Println("Presence", err) - continue - } - - go func(Conn *xmpp.Conn, stop chan struct{}) { - for { - select { - case <-time.After(60 * time.Second): - Conn.SendIQ(server, "set", " 60 ") - if _, _, err = Conn.SendIQ(server, "get", ""); err != nil { - select { - case <-stop: - default: - log.Println("KeepAlive err:", err) - close(stop) - } - return - } - - case <-stop: - return - } - } - }(Conn, stop) - - go func(Conn *xmpp.Conn, stop chan struct{}) { - for { - next, err := Conn.Next() - if err != nil { - select { - case <-stop: - default: - log.Println("KeepAlive err:", err) - close(stop) - } - return - } - cs <- next - } - }(Conn, stop) - - for { - select { - case next = <-cs: - - case <-stop: - Conn.Close() - Conn = nil - continue START - - case <-time.After(65 * time.Second): - log.Println(Conn.Close(), "\n\t", "closed after 65 seconds of inactivity") - close(stop) - Conn = nil - continue START - } - - switch t := next.Value.(type) { - case *xmpp.ClientPresence: - PresenceHandler(Conn, t) - - case *xmpp.ClientMessage: - if len(t.Delay.Stamp) == 0 && len(t.Subject) == 0 { - log.Println(t) - if GetNick(t.From) != name { - if t.Type == "groupchat" { - go MessageHandler(Conn, t) - } else if xmpp.RemoveResourceFromJid(strings.ToLower(t.From)) == me { - go SelfHandler(Conn, t) - } - } - } - } - } - log.Println(Conn.Close(), "\n\t", "wtf am I doing here?") - } -} - -func SelfHandler(Conn *xmpp.Conn, Msg *xmpp.ClientMessage) { - Msg.Body = strings.TrimSpace(Msg.Body) - if(!strings.HasPrefix(Msg.Body, "!")) { - Conn.Send(room, "groupchat", Msg.Body) - return - } - command, err := GetCommand(Msg.Body, Msg.From, "./plugins/") - if(err != nil) { - Conn.Send(Msg.From, "chat", err.Error()) - return - } - out, err := command.CombinedOutput() - if err != nil { - log.Println(err) - Conn.Send(Msg.From, "chat", err.Error()) - return - } - Conn.Send(Msg.From, "chat", strings.TrimRight(string(out), " \t\n")) -} - -var call = regexp.MustCompile("^" + name + "[:,]") -func MessageHandler(Conn *xmpp.Conn, Msg *xmpp.ClientMessage) { - switch { - case strings.HasPrefix(Msg.Body, "!megakick "): - s := strings.Split(Msg.Body, "!megakick ") - if in(admin, Msg.From) { - Conn.ModUse(room, s[1], "none", "") - } else { - Conn.Send(room, "groupchat", fmt.Sprintf("%s: GTFO", GetNick(Msg.From))) - } - - case strings.HasPrefix(Msg.Body, "!"): //any external command - cmd, err := GetCommand(Msg.Body, Msg.From, "./plugins/") - if(err != nil) { - Conn.Send(room, "groupchat", fmt.Sprintf("%s: WAT", GetNick(Msg.From))) - if(in(admin, Msg.From)) { - Conn.Send(Msg.From, "chat", err.Error()) - } - return - } - stdout, _ := cmd.StdoutPipe() - stderr, _ := cmd.StderrPipe() - if err := cmd.Start(); err != nil { - log.Println(err) - Conn.Send(room, "groupchat", fmt.Sprintf("%s: WAT", GetNick(Msg.From))) - if(in(admin, Msg.From)) { - Conn.Send(Msg.From, "chat", err.Error()) - } - return - } - out, _ := ioutil.ReadAll(stdout) - outerr, _ := ioutil.ReadAll(stderr) - cmd.Wait() - if len(outerr) != 0 && in(admin, Msg.From) { - Conn.Send(Msg.From, "chat", strings.TrimRight(string(outerr), " \t\n")) - } - Conn.Send(room, "groupchat", strings.TrimRight(string(out), " \t\n")) - - case call.MatchString(Msg.Body): //chat - command, err := GetCommand(call.ReplaceAllString(Msg.Body, "!answer"), Msg.From, "./chat/") - if err != nil { - log.Println(err) - return - } - out, err := command.CombinedOutput() - if err != nil { - log.Println(err) - if(in(admin, Msg.From)) { - Conn.Send(Msg.From, "chat", err.Error()) - } - return - } - Conn.Send(room, "groupchat", strings.TrimRight(string(out), " \t\n")) - } -} - -func PresenceHandler(Conn *xmpp.Conn, Prs *xmpp.ClientPresence) { - if(Prs.From == room + "/" + name && Prs.Item.Role == "none") { - log.Println("was kicked") - close(stop) - return - } - switch Prs.Item.Affiliation { - case "owner": - fallthrough - case "admin": - if Prs.Item.Role != "none" { - if !in(admin, Prs.From) { - admin = append(admin, Prs.From) - } - } - default: - if in(admin, Prs.From) { - admin = del(admin, Prs.From) - } - } -} - -//letter(ASCII or cyrillic), number, underscore only -var cmd_validator = regexp.MustCompile("^!(\\w|\\p{Cyrillic})*$") -func GetCommand(body, from, dir string) (*exec.Cmd, error) { - split := strings.SplitAfterN(body, " ", 2) - cmd := strings.TrimSpace(split[0]) - - if(!cmd_validator.MatchString(cmd)) { return nil, fmt.Errorf("Bad command \"%s\"", cmd) } - - var ( - info os.FileInfo - err error - ) - path := dir + Strip(cmd[1:]) - if info, err = os.Stat(path); err != nil { return nil, err } - if info.IsDir() || info.Mode() & 0111 == 0 { return nil, fmt.Errorf("\"%s\" isn't executable", path) } - - args := []string{ Strip(GetNick(from)), strconv.FormatBool(in(admin, from)) } - if(len(split) > 1) { args = append(args, Strip(split[1])) } - return exec.Command(path, args...), nil -} - -var strip_regexp = regexp.MustCompile("(`|\\$|\\.\\.)") -var quote_regexp = regexp.MustCompile("(\"|')") -func Strip(s string) string { - return quote_regexp.ReplaceAllString(strip_regexp.ReplaceAllString(s, ""), "“") -} - -func GetNick(s string) string { - slash := strings.Index(s, "/") - if slash != -1 { - return s[slash+1:] - } - return s -} - -func in(slice []string, value string) bool { - for _, v := range slice { - if v == value { - return true - } - } - return false -} - -func pos(slice []string, value string) int { - for p, v := range slice { - if v == value { - return p - } - } - return -1 -} - -func del(slice []string, value string) []string { - if i := pos(slice, value); i >= 0 { - return append(slice[:i], slice[i+1:]...) - } - return slice -}