Browse Source

Command running, signal handling

tags/v0.1.0
Russ Garrett 7 years ago
parent
commit
9ee770cc82
No account linked to committer's email address
3 changed files with 127 additions and 3 deletions
  1. 37
    0
      examples/command_runner.py
  2. 5
    0
      irccat.json
  3. 85
    3
      main.go

+ 37
- 0
examples/command_runner.py View File

@@ -0,0 +1,37 @@
1
+#!/usr/bin/python
2
+import os, sys, re, subprocess
3
+
4
+# If this script is set as your command handler, when any ?command is run in IRC it will
5
+# look in the path defined below for a script matching that command name and run it.
6
+#
7
+# e.g. ?uptime would look in "/usr/share/irccat/" (the default) for any script called
8
+# "uptime", with any extension. It would happily run both uptime.sh and uptime.py, or
9
+# a script in whatever language you like. Command names are limited to [0-9a-z].
10
+
11
+path = '/usr/share/irccat/'
12
+
13
+bits = sys.argv[1:]
14
+command = bits[3].lower()
15
+
16
+found = False
17
+if re.match('^[a-z0-9]+$', command):
18
+    for file in os.listdir(path):
19
+
20
+        if re.match('^%s\.[a-z]+$' % command, file):
21
+            found = True
22
+            
23
+            procArgs = [path + file]
24
+            procArgs.extend(bits)
25
+            proc = subprocess.Popen(procArgs, stdout=subprocess.PIPE)
26
+            stdout = proc.stdout
27
+
28
+            while True:
29
+                # We do this to avoid buffering from the subprocess stdout
30
+                print os.read(stdout.fileno(), 65536),
31
+                sys.stdout.flush()
32
+
33
+                if proc.poll() != None:
34
+                    break
35
+
36
+if found == False:
37
+    print "Unknown command '%s'" % command

+ 5
- 0
irccat.json View File

@@ -13,5 +13,10 @@
13 13
     "nick": "irccat2",
14 14
     "realname": "IRCCat",
15 15
     "channels": ["#russtest"]
16
+  },
17
+  "commands": {
18
+    "auth_channel": "#russtest",
19
+    "handler": "./examples/command_runner.py",
20
+    "max_response_lines": 15
16 21
   }
17 22
 }

+ 85
- 3
main.go View File

@@ -1,23 +1,33 @@
1 1
 package main
2 2
 
3 3
 import (
4
+	"bytes"
4 5
 	"crypto/tls"
6
+	"fmt"
5 7
 	"github.com/irccloud/irccat/httplistener"
6 8
 	"github.com/irccloud/irccat/tcplistener"
7 9
 	"github.com/juju/loggo"
8 10
 	"github.com/spf13/viper"
9 11
 	"github.com/thoj/go-ircevent"
12
+	"os"
13
+	"os/exec"
14
+	"os/signal"
15
+	"strings"
16
+	"syscall"
10 17
 )
11 18
 
12 19
 var log = loggo.GetLogger("main")
13 20
 
14 21
 type IRCCat struct {
15
-	irc *irc.Connection
16
-	tcp *tcplistener.TCPListener
22
+	control_chan string
23
+	irc          *irc.Connection
24
+	tcp          *tcplistener.TCPListener
25
+	signals      chan os.Signal
17 26
 }
18 27
 
19 28
 func main() {
20 29
 	loggo.ConfigureLoggers("<root>=DEBUG")
30
+	log.Infof("IRCCat starting...")
21 31
 	viper.SetConfigName("irccat")
22 32
 	viper.AddConfigPath("/etc")
23 33
 	viper.AddConfigPath(".")
@@ -25,10 +35,14 @@ func main() {
25 35
 
26 36
 	err = viper.ReadInConfig()
27 37
 	if err != nil {
38
+		log.Errorf("Error reading config file - exiting. I'm looking for irccat.[json|yaml|toml|hcl] in . or /etc")
28 39
 		return
29 40
 	}
30 41
 
31 42
 	irccat := IRCCat{}
43
+	irccat.signals = make(chan os.Signal, 1)
44
+	signal.Notify(irccat.signals, os.Interrupt, syscall.SIGINT, syscall.SIGTERM)
45
+	go irccat.signalHandler()
32 46
 
33 47
 	irccat.tcp, err = tcplistener.New()
34 48
 	if err != nil {
@@ -48,11 +62,19 @@ func main() {
48 62
 	irccat.irc.Loop()
49 63
 }
50 64
 
65
+func (i *IRCCat) signalHandler() {
66
+	sig := <-i.signals
67
+	log.Infof("Exiting on %s", sig)
68
+	i.irc.QuitMessage = fmt.Sprintf("Exiting on %s", sig)
69
+	i.irc.Quit()
70
+}
71
+
51 72
 func (i *IRCCat) connectIRC() error {
52 73
 	irccon := irc.IRC(viper.GetString("irc.nick"), viper.GetString("irc.realname"))
74
+	irccon.Debug = true
53 75
 	irccon.UseTLS = viper.GetBool("irc.tls")
54 76
 	if viper.GetBool("irc.tls_skip_verify") {
55
-		irccon.TLSConfig = &tls.Config{InsecureSkipVerify: true}
77
+		irccon.TLSConfig = &tls.Config{InsecureSkipVerify: true, MaxVersion: tls.VersionTLS11}
56 78
 	}
57 79
 
58 80
 	err := irccon.Connect(viper.GetString("irc.server"))
@@ -61,13 +83,73 @@ func (i *IRCCat) connectIRC() error {
61 83
 	}
62 84
 
63 85
 	irccon.AddCallback("001", i.handleWelcome)
86
+	irccon.AddCallback("PRIVMSG", func(event *irc.Event) {
87
+		if event.Message()[0] == '?' || event.Message()[0] == '!' {
88
+			go i.handleCommand(event)
89
+		}
90
+	})
64 91
 
65 92
 	i.irc = irccon
66 93
 	return nil
67 94
 }
68 95
 
96
+func (i *IRCCat) authorisedUser(nick string) bool {
97
+	return false
98
+}
99
+
69 100
 func (i *IRCCat) handleWelcome(e *irc.Event) {
70 101
 	for _, channel := range viper.GetStringSlice("irc.channels") {
71 102
 		i.irc.Join(channel)
72 103
 	}
73 104
 }
105
+
106
+func (i *IRCCat) handleCommand(event *irc.Event) {
107
+	msg := event.Message()
108
+	respond_to := event.Arguments[0]
109
+
110
+	if respond_to[0] != '#' && !i.authorisedUser(event.Nick) {
111
+		// Command not in a channel, or not from an authorised user
112
+		log.Infof("Unauthorised command: %s (%s) %s", event.Nick, respond_to, msg)
113
+		return
114
+	}
115
+	log.Infof("Authorised command: %s (%s) %s", event.Nick, respond_to, msg)
116
+
117
+	channel := ""
118
+	if respond_to[0] == '#' {
119
+		channel = respond_to
120
+	}
121
+
122
+	parts := strings.SplitN(msg, " ", 1)
123
+
124
+	var cmd *exec.Cmd
125
+	if len(parts) == 1 {
126
+		cmd = exec.Command(viper.GetString("commands.handler"), event.Nick, channel, respond_to, parts[0][1:])
127
+	} else {
128
+		cmd = exec.Command(viper.GetString("commands.handler"), event.Nick, channel, respond_to, parts[0][1:], parts[1])
129
+	}
130
+	i.runCommand(cmd, respond_to)
131
+}
132
+
133
+// Run a command with the output going to the nick/channel identified by respond_to
134
+func (i *IRCCat) runCommand(cmd *exec.Cmd, respond_to string) {
135
+	var out bytes.Buffer
136
+	cmd.Stdout = &out
137
+	cmd.Stderr = &out
138
+	err := cmd.Run()
139
+	if err != nil {
140
+		log.Errorf("Running command %s failed: %s", cmd.Args, err)
141
+		i.irc.Privmsgf(respond_to, "Command failed: %s", err)
142
+	}
143
+
144
+	lines := strings.Split(out.String(), "\n")
145
+	line_count := len(lines)
146
+	if line_count > viper.GetInt("commands.max_response_lines") {
147
+		line_count = viper.GetInt("commands.max_response_lines")
148
+	}
149
+
150
+	for _, line := range lines[0:line_count] {
151
+		if line != "" {
152
+			i.irc.Privmsg(respond_to, line)
153
+		}
154
+	}
155
+}

Loading…
Cancel
Save