Skip to content
This repository was archived by the owner on May 8, 2025. It is now read-only.

Commit d35a51a

Browse files
committed
Initial import.
1 parent 0339906 commit d35a51a

File tree

10 files changed

+1174
-0
lines changed

10 files changed

+1174
-0
lines changed

LICENSE.txt

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
Copyright (C) 2012 Keith Buck
2+
3+
Permission is hereby granted, free of charge, to any person obtaining a copy of
4+
this software and associated documentation files (the "Software"), to deal in
5+
the Software without restriction, including without limitation the rights to
6+
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
7+
of the Software, and to permit persons to whom the Software is furnished to do
8+
so, subject to the following conditions:
9+
10+
The above copyright notice and this permission notice shall be included in all
11+
copies or substantial portions of the Software.
12+
13+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19+
SOFTWARE.

README.txt

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
Flyrc: Get your bot off the ground.
2+
3+
Flyrc is a modular Internet Relay Chat (IRC) bot library. Flyrc was written to be extremely modular while not sacrificing power. Flyrc has a strong focus on supporting modern IRC features.
4+
5+
Homepage and further documentation: https://github.com/mrflea/flyrc
6+
7+
[For how, a more complete README can be found in the markdown-formatted README.md]

flyrc/__init__.py

Whitespace-only changes.

flyrc/client.py

Lines changed: 310 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,310 @@
1+
#!/usr/bin/python
2+
3+
# flyrc:
4+
# Loosely based upon geventirc (https://github.com/gwik/geventirc)
5+
6+
import gevent
7+
import gevent.pool
8+
from gevent import queue, socket
9+
from flyrc import handler, message, util
10+
from time import time
11+
12+
# Client events: connected, disconnected, error, global_send, global_recv, load, unload
13+
# (client event names are prefixed with client_)
14+
# 'connected' fires when the client connects to the IRC server.
15+
# 'disconnected' fires when the client's socket closes.
16+
# 'error' fires when the socket generates an error.
17+
# 'global_send' fires any time a message is sent.
18+
# 'global_recv' fires any time a message is received.
19+
# 'load' fires when the handler is loaded (note: only the loading handler's 'load' will be triggered)
20+
# 'unload' fires when the handler is unloaded (note: only the unloading handler's 'unload' will be triggered)
21+
22+
class ClientError(Exception):
23+
"""A generic flyrc client error."""
24+
25+
class DuplicateHandlerObject(ClientError):
26+
"""Exception raised when an already-added handler is added a
27+
second time.
28+
29+
Attributes:
30+
obj - the object itself.
31+
"""
32+
def __init__(self, obj):
33+
self.obj = obj
34+
35+
class MissingHandlerObject(ClientError):
36+
"""Exception raised when attempting to remove a handler that isn't
37+
loaded.
38+
39+
Attributes:
40+
obj - the object itself.
41+
"""
42+
def __init__(self, obj):
43+
self.obj = obj
44+
45+
class DependencyViolation(ClientError):
46+
"""Exception raised when adding or removing a handler causes a
47+
dependency violation of some kind.
48+
49+
Attributes:
50+
args - the object subject to the conflict.
51+
"""
52+
53+
class UnsatisfiedDependency(DependencyViolation):
54+
"""Exception raised when a dependency isn't satisfied.
55+
56+
Attributes:
57+
args - the missing dependency.
58+
"""
59+
60+
class LingeringDependency(DependencyViolation):
61+
"""Exception raised when a handler being unloaded still has
62+
dependencies.
63+
64+
Attributes:
65+
args - the handler being unloaded.
66+
"""
67+
68+
class InvalidDependencyTree(DependencyViolation):
69+
"""Exception raised when the dependency tree has become invalid
70+
(probably due to prior Dependency exceptions.)
71+
72+
Attributes:
73+
args - the handler with invalid dependency information.
74+
"""
75+
76+
class Client(object):
77+
def __init__(self, host, port, ssl=False, timeout=300):
78+
self._rqueue = queue.Queue()
79+
self._squeue = queue.Queue()
80+
self.host = host
81+
self.port = port
82+
self.ssl = ssl
83+
self._timeout = timeout
84+
self._socket = None
85+
self._group = gevent.pool.Group()
86+
self._coregroup = gevent.pool.Group()
87+
88+
self._handlers = {}
89+
self._handlerobjects = {}
90+
91+
self.throttle_delay = 2
92+
self.throttle_burst = 5
93+
94+
self.enforce_order = False
95+
96+
def _create_socket(self):
97+
sock = gevent.socket.socket()
98+
if self.ssl:
99+
sock = gevent.ssl.wrap_socket(sock)
100+
101+
sock.setblocking(1)
102+
sock.settimeout(self._timeout)
103+
104+
return sock
105+
106+
def _ioerror(self, e, step):
107+
self.stop()
108+
self._rqueue.put(message.Error(e, step))
109+
110+
@property
111+
def timeout(self):
112+
"""The TCP socket's timeout."""
113+
return self._timeout
114+
115+
@timeout.setter
116+
def timeout(self, t):
117+
self._timeout = t
118+
self._socket.settimeout(self._timeout)
119+
120+
def start(self):
121+
try:
122+
self._socket = self._create_socket()
123+
self._socket.connect((self.host, self.port))
124+
except socket.error, e:
125+
self._ioerror(e, message.Step.CONNECT)
126+
else:
127+
self._coregroup.spawn(self._send_loop)
128+
self._coregroup.spawn(self._recv_loop)
129+
self._handle('client_connected')
130+
self._coregroup.spawn(self._process_loop)
131+
132+
def stop(self):
133+
if self._socket:
134+
self._socket.close()
135+
self._socket = None
136+
137+
self._handle('client_disconnected')
138+
139+
def shutdown(self):
140+
self.stop()
141+
self._coregroup.kill()
142+
self._rqueue = queue.Queue()
143+
self._squeue = queue.Queue()
144+
145+
def join(self):
146+
self._coregroup.join()
147+
self._group.join()
148+
149+
def _send_loop(self):
150+
buf = ''
151+
burst_remaining = self.throttle_burst * 1.0
152+
last_message = 0
153+
while True:
154+
msg = self._squeue.get()
155+
buf += msg.render().encode('utf-8', 'replace') + '\r\n'
156+
try:
157+
self._socket.sendall(buf)
158+
buf = ''
159+
except socket.error, e:
160+
print "I/O error in SEND: " + str(e)
161+
self._ioerror(e, message.Step.SEND)
162+
except AttributeError:
163+
# Socket closed, exit.
164+
return
165+
166+
# Do throttling, but only if throttle_delay != 0.
167+
if self.throttle_delay:
168+
# Add any owed slots.
169+
if last_message:
170+
burst_remaining += (time() - last_message) / self.throttle_delay
171+
if (burst_remaining > self.throttle_burst):
172+
burst_remaining = self.throttle_burst * 1.0
173+
174+
# Penalize for the message that was just sent.
175+
last_message = time()
176+
burst_remaining -= 1
177+
178+
# Sleep if we're out of burst.
179+
if burst_remaining < 1:
180+
gevent.sleep(self.throttle_delay)
181+
else:
182+
gevent.sleep(0)
183+
184+
def _process_loop(self):
185+
while True:
186+
if self.enforce_order:
187+
self._group.join()
188+
msg = self._rqueue.get()
189+
if hasattr(msg, 'e'):
190+
self._handle('client_error', msg)
191+
else:
192+
self._handle_recv(msg)
193+
194+
def _recv_loop(self):
195+
buf = ''
196+
while True:
197+
try:
198+
buf += self._socket.recv(4096)
199+
except socket.error, e:
200+
print "I/O error in RECV: " + str(e)
201+
self._ioerror(e, message.Step.RECV)
202+
except AttributeError:
203+
# Socket has been closed, exit.
204+
return
205+
else:
206+
lines = buf.split('\r\n')
207+
buf = lines.pop()
208+
for line in lines:
209+
self._rqueue.put(message.parse_message(line))
210+
gevent.sleep(0)
211+
212+
def dependency_satisfier(self, dep):
213+
for item in self._handlerobjects.keys():
214+
if isinstance(item, dep):
215+
return item
216+
return None
217+
218+
def add_handler(self, handler):
219+
if handler in self._handlerobjects.keys():
220+
raise DuplicateHandlerObject(item)
221+
222+
dependencies, h_funcs = util.get_handler_properties(handler)
223+
for dep in dependencies:
224+
sat = self.dependency_satisfier(dep)
225+
if not sat:
226+
raise UnsatisfiedDependency(dep)
227+
else:
228+
# Increment refcount.
229+
self._handlerobjects[sat] += 1
230+
231+
self._handlerobjects[handler] = 0
232+
233+
for h_name in h_funcs.iterkeys():
234+
if self._handlers.has_key(h_name):
235+
self._handlers[h_name].add(h_funcs[h_name])
236+
else:
237+
self._handlers[h_name] = set([h_funcs[h_name]])
238+
239+
# Special - spawn just this instance, not all "client_load" handlers.
240+
if h_funcs.has_key('client_load'):
241+
self._group.spawn(h_funcs['client_load'], self)
242+
243+
def remove_handler(self, handler):
244+
if handler not in self._handlerobjects.keys():
245+
raise MissingHandlerObject(handler)
246+
247+
if self._handlerobjects[handler] > 0:
248+
raise LingeringDependency(handler)
249+
250+
del self._handlerobjects[handler]
251+
252+
dependencies, h_funcs = util.get_handler_properties(handler)
253+
for dep in dependencies:
254+
sat = self.dependency_satisfier(dep)
255+
if not sat:
256+
raise InvalidDependencyTree(dep)
257+
else:
258+
# Decrement refcount.
259+
self._handlerobjects[sat] -= 1
260+
if self._handlerobjects[sat] < 0:
261+
raise InvalidDependencyTree(sat)
262+
263+
for h_name in h_funcs.iterkeys():
264+
self._handlers[h_name] -= set([h_funcs[h_name]])
265+
# If there aren't any handler functions left, remove that event entirely.
266+
if not self._handlers[h_name]:
267+
del self._handlers[h_name]
268+
269+
# Special - spawn just this instance of client_unload.
270+
if h_funcs.has_key('client_unload'):
271+
self._group.spawn(h_funcs['client_unload'], self)
272+
273+
def _handle(self, hname, *args, **kwargs):
274+
if self._handlers.has_key(hname):
275+
for handler in self._handlers[hname]:
276+
self._group.spawn(handler, self, *args, **kwargs)
277+
278+
def _handle_recv(self, message):
279+
self._handle('client_global_recv', message)
280+
self._handle(message.command.upper(), message)
281+
282+
def send(self, message):
283+
self._handle('client_global_send', message)
284+
self._squeue.put(message)
285+
286+
def trigger_handler(self, handler, *args, **kwargs):
287+
self._handle(handler, *args, **kwargs)
288+
289+
def get_handled_events(self):
290+
return self._handlers.keys()
291+
292+
# A simple client that can stay connected to an IRC network and supports NickServ/SASL authentication.
293+
class SimpleClient(Client):
294+
def __init__(self, nick, user, gecos, host, port, ssl=False, timeout=300, autoreconnect=False, version=None):
295+
super(SimpleClient, self).__init__(host, port, ssl, timeout)
296+
297+
self.add_handler(handler.Ping())
298+
self.add_handler(handler.User(nick, user, gecos))
299+
self.add_handler(handler.NickInUse())
300+
self.add_handler(handler.MessageProcessor())
301+
302+
if autoreconnect:
303+
self.add_handler(handler.AutoReconnect())
304+
else:
305+
self.add_handler(handler.GenericDisconnect())
306+
307+
if version:
308+
self.add_handler(handler.BasicCTCP(version))
309+
else:
310+
self.add_handler(handler.BasicCTCP())

0 commit comments

Comments
 (0)