|
32 | 32 |
|
33 | 33 | poll_interval: 60 |
34 | 34 |
|
35 | | - sourceURL: https://svn.civicrm.org/tools/trunk/bin/scripts/ircbot-civi.py |
| 35 | + project_url: https://svn.civicrm.org/tools/trunk/bin/scripts/ircbot-civi.py |
36 | 36 | ''' |
37 | 37 |
|
| 38 | +from irc import RelayToIRC |
| 39 | +from job import JobQueue |
| 40 | + |
38 | 41 | import sys |
39 | 42 | import os |
40 | | -import re |
41 | | -import datetime |
42 | 43 |
|
43 | 44 | # pip install pyyaml |
44 | 45 | import yaml |
45 | 46 |
|
46 | | -#pip install twisted twisted.words |
47 | | -from twisted.words.protocols import irc |
48 | | -from twisted.internet.protocol import ReconnectingClientFactory |
49 | | -from twisted.internet import reactor |
50 | | -from twisted.internet.task import LoopingCall |
51 | | - |
52 | | -test = False |
53 | | -maxlen = 200 |
54 | | -config = None |
55 | | - |
56 | | -class RelayToIRC(irc.IRCClient): |
57 | | - """ |
58 | | - Bot brain will spawn listening jobs and then relay results to an irc channel. |
59 | | - TODO: |
60 | | - * separate polling manager from irc protocol |
61 | | - * operator ACL and commands which perform an action |
62 | | - """ |
63 | | - sourceURL = "https://github.com/adamwight/slander" |
64 | | - timestamp = None |
65 | | - |
66 | | - def connectionMade(self): |
67 | | - self.config = self.factory.config |
68 | | - self.jobs = create_jobs(self.config["jobs"]) |
69 | | - self.nickname = self.config["irc"]["nick"] |
70 | | - self.realname = self.config["irc"]["realname"] |
71 | | - self.channel = self.config["irc"]["channel"] |
72 | | - global maxlen |
73 | | - if "maxlen" in self.config["irc"]: |
74 | | - maxlen = self.config["irc"]["maxlen"] |
75 | | - if "sourceURL" in self.config: |
76 | | - self.sourceURL = self.config["sourceURL"] |
77 | | - |
78 | | - irc.IRCClient.connectionMade(self) |
79 | | - |
80 | | - def signedOn(self): |
81 | | - self.join(self.channel) |
82 | | - |
83 | | - def joined(self, channel): |
84 | | - print "Joined channel %s as %s" % (channel, self.nickname) |
85 | | - task = LoopingCall(self.check) |
86 | | - task.start(self.config["poll_interval"]) |
87 | | - print "Started polling jobs, every %d seconds." % (self.config["poll_interval"], ) |
88 | | - |
89 | | - def privmsg(self, user, channel, message): |
90 | | - if message.find(self.nickname) >= 0: |
91 | | - if re.search(r'\bhelp\b', message): |
92 | | - self.say(channel, "If I only had a brain: %s -- Commands: help jobs kill last" % (self.sourceURL, )) |
93 | | - elif re.search(r'\bjobs\b', message): |
94 | | - jobs_desc = ", ".join( |
95 | | - [("%s: %s" % (j.config['class'], j.config)) |
96 | | - for j in self.jobs] |
97 | | - ) |
98 | | - jobs_desc = re.sub(r'p(ass)?w(ord)?[ :=]*[^ ]+', r'p***word', jobs_desc) |
99 | | - |
100 | | - self.say(channel, "Running jobs [%s]" % (jobs_desc, )) |
101 | | - #elif re.search(r'\bkill\b', message): |
102 | | - # self.say(self.channel, "Squeal! Killed by %s" % (user, )) |
103 | | - # self.factory.stopTrying() |
104 | | - # self.quit() |
105 | | - elif re.search(r'\blast\b', message): |
106 | | - if self.timestamp: |
107 | | - self.say(channel, "My last post was %s UTC" % (self.timestamp, )) |
108 | | - else: |
109 | | - self.say(channel, "No activity.") |
110 | | - else: |
111 | | - print "Failed to handle incoming command: %s said %s" % (user, message) |
112 | | - |
113 | | - def check(self): |
114 | | - for job in self.jobs: |
115 | | - for line in job.check(): |
116 | | - if line: |
117 | | - if test: |
118 | | - print(line) |
119 | | - self.say(self.channel, str(line)) |
120 | | - self.timestamp = datetime.datetime.utcnow() |
121 | | - |
122 | | - @staticmethod |
123 | | - def run(config): |
124 | | - factory = ReconnectingClientFactory() |
125 | | - factory.protocol = RelayToIRC |
126 | | - factory.config = config |
127 | | - reactor.connectTCP(config["irc"]["host"], config["irc"]["port"], factory) |
128 | | - reactor.run() |
129 | | - |
130 | | - |
131 | | -def strip(text, html=True, space=True): |
132 | | - class MLStripper(HTMLParser): |
133 | | - def __init__(self): |
134 | | - self.reset() |
135 | | - self.fed = [] |
136 | | - def handle_data(self, d): |
137 | | - self.fed.append(d) |
138 | | - def get_data(self): |
139 | | - return ''.join(self.fed) |
140 | | - |
141 | | - if html: |
142 | | - stripper = MLStripper() |
143 | | - stripper.feed(text) |
144 | | - text = stripper.get_data() |
145 | | - if space: |
146 | | - text = re.sub("\s+", " ", text).strip() |
147 | | - return text |
148 | | - |
149 | | -def abbrevs(name): |
150 | | - """ |
151 | | - Turn a space-delimited name into initials, e.g. Frank Ubiquitous Zappa -> FUZ |
152 | | - """ |
153 | | - return "".join([w[:1] for w in name.split()]) |
154 | | - |
155 | | -def truncate(message): |
156 | | - if len(message) > maxlen: |
157 | | - return (message[:(maxlen-3)] + "...") |
158 | | - else: |
159 | | - return message |
160 | | - |
161 | | - |
162 | | -def create_jobs(d): |
163 | | - """ |
164 | | - Read job definitions from a config source, create an instance of the job using its configuration, and store the config for reference. |
165 | | - """ |
166 | | - jobs = [] |
167 | | - for type_name, options in d.items(): |
168 | | - m = __import__(type_name) |
169 | | - classname = type_name.capitalize() + "Poller" |
170 | | - if hasattr(m, classname): |
171 | | - klass = getattr(m, classname) |
172 | | - job = klass(**options) |
173 | | - job.config = options |
174 | | - job.config['class'] = type_name |
175 | | - jobs.append(job) |
176 | | - else: |
177 | | - sys.exit("Failed to create job of type " + classname) |
178 | | - return jobs |
179 | 47 |
|
180 | 48 | def load_config(path): |
181 | 49 | dotfile = os.path.expanduser(path) |
@@ -203,6 +71,11 @@ def parse_args(args): |
203 | 71 | if not config: |
204 | 72 | sys.exit(args[0] + ": No config!") |
205 | 73 |
|
| 74 | + global test |
| 75 | + test = False |
| 76 | + if "test" in config: |
| 77 | + test = config["test"] |
| 78 | + |
206 | 79 | return config |
207 | 80 |
|
208 | 81 | if __name__ == "__main__": |
|
0 commit comments