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
-}