Last active 1630415123

IRC Single Message Bot. Sends a single IRC message to a channel, then quits. Useful for job notifications maybe

Revision f14bbca1a5f505bfe9ebb6150128c507e1663b94

singlemessagebot.py Raw
1from irc.bot import ServerSpec
2import irc
3import sys
4import json
5
6try:
7 with open('smbotconfig.json') as f:
8 conf = json.load(f)
9except:
10 print "Error in config or no config found, returning to defaults. Please edit smbotconfig.json (http://jsonlint.com/) and restart the bot"
11 with open('smbotconfig.json', 'w') as f:
12 f.write(json.dumps(dict(channel="#blha303",
13 nickname="SingleMessageTest",
14 server="irc.esper.net") ) )
15 sys.exit(1)
16
17class SingleMessageClient(object):
18 reactor_class = irc.client.Reactor
19
20 def __init__(self):
21 self.reactor = self.reactor_class()
22 self.connection = self.reactor.server()
23 self.dcc_connections = []
24 self.reactor.add_global_handler("all_events", self._dispatcher, -10)
25 self.reactor.add_global_handler("dcc_disconnect",
26 self._dcc_disconnect, -10)
27
28 def _dispatcher(self, connection, event):
29 """
30 Dispatch events to on_<event.type> method, if present.
31 """
32 do_nothing = lambda c, e: None
33 method = getattr(self, "on_" + event.type, do_nothing)
34 method(connection, event)
35
36 def _dcc_disconnect(self, c, e):
37 self.dcc_connections.remove(c)
38
39 def connect(self, *args, **kwargs):
40 """Connect using the underlying connection"""
41 self.connection.connect(*args, **kwargs)
42
43 def dcc_connect(self, address, port, dcctype="chat"):
44 """Connect to a DCC peer.
45
46 Arguments:
47
48 address -- IP address of the peer.
49
50 port -- Port to connect to.
51
52 Returns a DCCConnection instance.
53 """
54 dcc = self.reactor.dcc(dcctype)
55 self.dcc_connections.append(dcc)
56 dcc.connect(address, port)
57 return dcc
58
59 def dcc_listen(self, dcctype="chat"):
60 """Listen for connections from a DCC peer.
61
62 Returns a DCCConnection instance.
63 """
64 dcc = self.reactor.dcc(dcctype)
65 self.dcc_connections.append(dcc)
66 dcc.listen()
67 return dcc
68
69 def start(self):
70 """Start the IRC client."""
71 while 1:
72 if not self.connection.is_connected():
73 break
74 self.reactor.process_once(0.2)
75
76class SingleMessageBot(SingleMessageClient):
77 def __init__(self, msg, channel=conf.get('channel'),
78 nickname=conf.get('nickname'),
79 realname=conf.get('realname', conf.get('nickname', None)),
80 server=conf.get('server'),
81 port=conf.get('port', 6667),
82 password=conf.get('password', None),
83 **connect_params):
84 super(SingleMessageBot, self).__init__()
85 self.__connect_params = connect_params
86 self.channel = channel
87 self.server = ServerSpec(server, port, password)
88 self.msg = msg
89 self._nickname = nickname
90 self._realname = realname
91 for i in ["join", "kick", "mode",
92 "namreply", "nick", "part", "quit"]:
93 self.connection.add_global_handler(i, getattr(self, "_on_" + i),
94 -20)
95
96 def _connect(self):
97 """
98 Establish a connection to the server at the front of the server_list.
99 """
100 try:
101 self.connect(self.server.host, self.server.port, self._nickname,
102 self.server.password, ircname=self._realname,
103 **self.__connect_params)
104 except irc.client.ServerConnectionError:
105 pass
106
107 def _on_join(self, c, e):
108 ch = e.target
109 nick = e.source.nick
110 if nick == c.get_nickname():
111 self.channels[ch] = Channel()
112 self.channels[ch].add_user(nick)
113
114 def _on_kick(self, c, e):
115 nick = e.arguments[0]
116 channel = e.target
117
118 if nick == c.get_nickname():
119 del self.channels[channel]
120 else:
121 self.channels[channel].remove_user(nick)
122
123 def _on_mode(self, c, e):
124 modes = irc.modes.parse_channel_modes(" ".join(e.arguments))
125 t = e.target
126 if irc.client.is_channel(t):
127 ch = self.channels[t]
128 for mode in modes:
129 if mode[0] == "+":
130 f = ch.set_mode
131 else:
132 f = ch.clear_mode
133 f(mode[1], mode[2])
134 else:
135 # Mode on self... XXX
136 pass
137
138 def _on_namreply(self, c, e):
139 """
140 e.arguments[0] == "@" for secret channels,
141 "*" for private channels,
142 "=" for others (public channels)
143 e.arguments[1] == channel
144 e.arguments[2] == nick list
145 """
146
147 ch_type, channel, nick_list = e.arguments
148
149 if channel == '*':
150 # User is not in any visible channel
151 # http://tools.ietf.org/html/rfc2812#section-3.2.5
152 return
153
154 for nick in nick_list.split():
155 nick_modes = []
156
157 if nick[0] in self.connection.features.prefix:
158 nick_modes.append(self.connection.features.prefix[nick[0]])
159 nick = nick[1:]
160
161 for mode in nick_modes:
162 self.channels[channel].set_mode(mode, nick)
163
164 self.channels[channel].add_user(nick)
165
166 def _on_nick(self, c, e):
167 before = e.source.nick
168 after = e.target
169 for ch in self.channels.values():
170 if ch.has_user(before):
171 ch.change_nick(before, after)
172
173 def _on_part(self, c, e):
174 nick = e.source.nick
175 channel = e.target
176
177 if nick == c.get_nickname():
178 del self.channels[channel]
179 else:
180 self.channels[channel].remove_user(nick)
181
182 def _on_quit(self, c, e):
183 nick = e.source.nick
184 for ch in self.channels.values():
185 if ch.has_user(nick):
186 ch.remove_user(nick)
187
188 def die(self, msg="Bye, cruel world!"):
189 self.connection.disconnect(msg)
190
191 def get_version(self):
192 """Returns the bot version.
193
194 Used when answering a CTCP VERSION request.
195 """
196 return "Python irc.bot ({version})".format(
197 version=irc.client.VERSION_STRING)
198
199 def jump_server(self, msg="Changing servers"):
200 """Connect to a new server, possibly disconnecting from the current.
201
202 The bot will skip to next server in the server_list each time
203 jump_server is called.
204 """
205 if self.connection.is_connected():
206 self.connection.disconnect(msg)
207
208 self.server_list.append(self.server_list.pop(0))
209 self._connect()
210
211 def on_ctcp(self, c, e):
212 """Default handler for ctcp events.
213
214 Replies to VERSION and PING requests and relays DCC requests
215 to the on_dccchat method.
216 """
217 nick = e.source.nick
218 if e.arguments[0] == "VERSION":
219 c.ctcp_reply(nick, "VERSION " + self.get_version())
220 elif e.arguments[0] == "PING":
221 if len(e.arguments) > 1:
222 c.ctcp_reply(nick, "PING " + e.arguments[1])
223 elif e.arguments[0] == "DCC" and e.arguments[1].split(" ", 1)[0] == "CHAT":
224 self.on_dccchat(c, e)
225
226 def on_dccchat(self, c, e):
227 pass
228
229 def start(self):
230 """Start the bot."""
231 self._connect()
232 super(SingleMessageBot, self).start()
233
234 def on_welcome(self, c, e):
235 c.join(self.channel)
236 c.privmsg(self.channel, self.msg)
237 c.close()
238
239def main(msg):
240 b = SingleMessageBot(msg)
241 b.start()
242 return 0
243
244if __name__ == "__main__":
245 sys.exit(main(" ".join(sys.argv[1:]) if len(sys.argv) > 1 else "This is a test of SingleMessageBot!"))