]>
Commit | Line | Data |
---|---|---|
b069ba10 JR |
1 | import socket, random, select |
2 | from collections import deque | |
3 | ||
4 | from util import * | |
5 | ||
6 | class User: | |
7 | def __init__(self, nick): | |
8 | self.nick = nick | |
9 | self.auth = '' | |
10 | self.access = 0 | |
11 | self.alist = [] | |
12 | self.commonchans = [] | |
13 | self.host = None | |
14 | def __str__(self): return self.nick | |
15 | def __repr__(self): return "<User: %s (%d)>" % (self.nick, self.access) | |
16 | def authed(self, username): | |
17 | self.auth = username | |
18 | if username in cache.admins: | |
19 | self.access = cache.admins[username] | |
20 | def joined(self, chan): | |
21 | self.commonchans.append(chan) | |
22 | def parted(self, chan): | |
23 | if chan in self.commonchans: self.commonchans.remove(chan) | |
24 | if self.commonchans == []: | |
25 | self.auth = '' | |
26 | self.access = 0 | |
27 | ||
28 | class Channel: | |
29 | def __init__(self, name, bot, id): | |
30 | self.id = id | |
31 | self.name = name | |
32 | self.bot = bot | |
33 | ||
34 | self.triggers = {} | |
35 | ||
36 | self.alist = {} | |
37 | curs = cache.dbc.cursor() | |
38 | curs.execute("SELECT authname, level FROM chusers WHERE chid = %s", (self.id,)) | |
39 | row = curs.fetchone() | |
40 | while row is not None: | |
41 | self.alist[row['authname']] = row['level'] | |
42 | row = curs.fetchone() | |
43 | ||
44 | self.ops = [] | |
45 | self.voices = [] | |
46 | self.users = [] | |
47 | def joined(self, user): | |
48 | self.users.append(user) | |
49 | user.joined(self.name) | |
50 | def parted(self, user): | |
51 | if user in self.users: self.users.remove(user) | |
52 | if user in self.ops: self.ops.remove(user) | |
53 | if user in self.voices: self.voices.remove(user) | |
54 | user.parted(self.name) | |
55 | def opped(self, user): | |
56 | self.ops.append(user) | |
57 | def voiced(self, user): | |
58 | self.voices.append(user) | |
59 | def __str__(self): return self.name | |
60 | def __repr__(self): return "<Chan: %s>" % (self.name) | |
61 | ||
62 | class Bot: | |
63 | def __init__(self, id): | |
64 | self.id = id | |
65 | self.online = False | |
66 | self.chans = {} | |
67 | self.mainbot = False | |
68 | self.rawqueue = deque() | |
69 | ||
70 | def get(self): | |
71 | buf = "" | |
72 | chin = self.s.recv(1) | |
73 | while chin != "\n": | |
74 | buf += chin | |
75 | chin = self.s.recv(1) | |
76 | buf = buf.strip() | |
77 | print "<%d<%s" % (self.id, buf) | |
78 | return buf | |
79 | ||
80 | def sendRaws(self, count=2): | |
81 | if self.online: | |
82 | for i in range(count): | |
83 | try: line = self.rawqueue.popleft() | |
84 | except IndexError: return | |
85 | self.rawnow(line) | |
86 | def raw(self, line): | |
87 | self.rawqueue.append(line) | |
88 | def rawnow(self, line): | |
89 | self.s.sendall(line+"\r\n") | |
90 | print ">%d>%s" % (self.id, line) | |
91 | ||
92 | def msg(self, target, msg): | |
93 | msgs = msg.split("\n") | |
94 | for msg in msgs: | |
95 | if target[0] == '#': | |
96 | self.raw("PRIVMSG %s :%s" % (target, msg)) | |
97 | else: | |
98 | self.raw("NOTICE %s :%s" % (target, msg)) | |
99 | def cmsg(self, cmtype, msg, id=None): | |
100 | if id is None: id = self.id | |
101 | self.msg(cache.ctrl, cache.cmsgs[cmtype] % {'id': id, 'msg': msg}) | |
102 | ||
103 | def joined(self, chname, nick, chlevel=None): | |
104 | chname = chname.lower() | |
105 | if chname not in self.chans and chname not in cache.chans: | |
106 | return | |
107 | ||
108 | self.raw("WHO %s n%%nah" % (nick)) | |
109 | if nick not in cache.users: cache.users[nick] = User(nick) | |
110 | self.chans[chname].joined(cache.users[nick]) | |
111 | if chlevel == "@": | |
112 | self.chans[chname].opped(cache.users[nick]) | |
113 | if chlevel == "+": | |
114 | self.chans[chname].voiced(cache.users[nick]) | |
115 | def parted(self, chname, nick): | |
116 | chname = chname.lower() | |
117 | if nick == self.nick: | |
118 | del self.chans[chname] | |
119 | del cache.chans[chname] | |
120 | elif chname in self.chans: | |
121 | self.chans[chname].parted(cache.users[nick]) | |
122 | def quit(self, nick): | |
123 | if nick in cache.users: | |
124 | user = cache.users[nick] | |
125 | for ch in user.commonchans: | |
126 | cache.chans[ch].parted(user) | |
127 | del cache.users[nick] | |
128 | ||
129 | def disconnect(self): | |
130 | try: cache.mainBot.cmsg('warn', 'Disconnected!', self.id) | |
131 | except: pass | |
132 | try: self.rawnow("QUIT :Disconnected") | |
133 | except: pass | |
134 | try: self.s.shutdown(socket.SHUT_RDWR) | |
135 | except: pass | |
136 | try: self.s.close() | |
137 | except: pass | |
138 | for ch in self.chans.values(): | |
139 | for user in ch.users: | |
140 | ch.parted(user) | |
141 | cache.poll.unregister(self.s.fileno()) | |
142 | self.online = False | |
143 | ||
144 | def joinChans(self): | |
145 | curs = cache.dbc.cursor() | |
146 | curs.execute("SELECT id, chname FROM chans WHERE botid = %s", (self.id,)) | |
147 | row = curs.fetchone() | |
148 | while row is not None: | |
149 | self.raw("JOIN %s" % (row['chname'])) | |
150 | chan = Channel(row['chname'].lower(), self, row['id']) | |
151 | self.chans[row['chname'].lower()] = chan | |
152 | cache.chans[row['chname']] = chan | |
153 | row = curs.fetchone() | |
154 | curs.close() | |
155 | def connect(self, ident): | |
156 | curs = cache.dbc.cursor() | |
157 | curs.execute("SELECT irchost, ircport, ircpass, nick, vhost, realname, authname, authpass FROM bots WHERE id = %s", (self.id,)) | |
158 | row = curs.fetchone() | |
159 | self.nick = row['nick'] | |
160 | self.s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) | |
161 | self.s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) | |
550b7560 JR |
162 | if row['vhost'] is not None: |
163 | self.s.bind((row['vhost'], 0)) | |
b069ba10 JR |
164 | self.s.connect((row['irchost'], row['ircport'])) |
165 | self.rawnow("NICK %s" % (self.nick)) | |
166 | self.rawnow("USER %s * * :%s" % (ident, row['realname'])) | |
167 | curs.close() | |
168 | while True: | |
169 | line = self.get() | |
170 | pieces = line.split() | |
171 | if pieces[0] == "PING": | |
172 | self.rawnow("PONG %s" % (pieces[1])) | |
173 | elif pieces[1] == "433": | |
174 | self.nick = self.nick+str(random.randrange(0,9)) | |
175 | self.rawnow("NICK %s" % (self.nick)) | |
176 | elif pieces[0] == "ERROR": | |
177 | self.online = False | |
178 | return False | |
179 | elif pieces[1] == "376" or pieces[1] == "422": | |
180 | break | |
181 | if row['authname'] is not None and row['authpass'] is not None: | |
182 | self.rawnow("AUTH %s %s" % (row['authname'], row['authpass'])) | |
183 | self.rawnow("MODE %s +ix" % (self.nick)) | |
184 | cache.poll.register(self.s.fileno(), select.POLLIN) | |
185 | cache.botsByFD[self.s.fileno()] = self | |
186 | cache.botsByNick[self.nick] = self | |
187 | self.joinChans() | |
188 | self.online = True | |
189 | return True | |
190 | def __str__(self): return self.nick | |
191 | def __repr__(self): return "<Bot%d: %s>" % (self.id, self.nick) | |
192 | ||
193 | class Cache: | |
550b7560 JR |
194 | # config |
195 | lshost = '0.0.0.0' | |
196 | lsport = 13245 | |
197 | moduledata = '/home/bots/modules/' | |
198 | trigger = '!' | |
199 | cmsgs = { # %(id)d = bot id, %(msg)s = log message. | |
200 | 'debug': "\00303[\037DEBUG\037][%(id)d]: %(msg)s", | |
201 | 'info': "\00312[\037INFO\037][%(id)d]: %(msg)s", | |
202 | 'warn': "\00306[\037WARN\037][%(id)d]: %(msg)s", | |
203 | 'fatal': "\00304[\037FATAL\037][%(id)d]: %(msg)s", | |
204 | } | |
205 | ||
206 | ||
207 | # NOT config | |
b069ba10 JR |
208 | dbc = None |
209 | ls = None | |
210 | admins = {} | |
211 | bots = {} | |
212 | botsByFD = {} | |
213 | botsByNick = {} | |
214 | mainBot = None | |
215 | home = None # homechan | |
216 | ctrl = None # ctrlchan | |
217 | modules = {} | |
218 | unmodules = {} # unloaded | |
219 | timers = {} | |
220 | quitting = False | |
221 | ||
222 | currmod = None | |
223 | cmds = {} | |
224 | nums = {} | |
225 | ||
226 | users = {} | |
227 | chans = {} | |
228 | ||
b069ba10 JR |
229 | def __init__(self): |
230 | global cache | |
231 | cache = self | |
232 | ||
233 | def hooknum(self, num, func): | |
234 | try: self.nums[num].append(func) | |
235 | except: self.nums[num] = [func] | |
236 | def unhooknum(self, num, func): | |
237 | try: self.nums[num].remove(func) | |
238 | except: return False | |
239 | else: return True | |
240 | ||
555a2a31 | 241 | def hookcmd(self, cmdname, level, cmdcallback, minparams, helpcallback, isadmin=False, reqchan=True): |
b069ba10 JR |
242 | self.cmds[cmd.upper()] = {'module': cache.currmod, 'func': func, 'level': level, 'params': params, 'helpfunc': helpfunc, 'isadmin': isadmin, 'reqchan': reqchan} |
243 | def unhookcmd(self, cmd): | |
244 | try: del self.cmds[cmd] | |
245 | except: return False | |
246 | else: return True | |
247 | ||
248 | def gethelp(self, cmd, nick=None, user=None, access=None): | |
249 | if cmd in self.cmds: | |
250 | if self.cmds[cmd]['level'] == 0: | |
251 | return self.cmds[cmd]['helpfunc']() | |
252 | if nick is None and user is None: | |
253 | return self.cmds[cmd]['helpfunc']() | |
254 | elif nick is not None and nick in self.users and self.users[nick].access > self.cmds[cmd]['level']: | |
255 | return self.cmds[cmd]['helpfunc']() | |
256 | elif user is not None and user.access > self.cmds[cmd]['level']: | |
257 | return self.cmds[cmd]['helpfunc']() | |
258 | elif access is not None and access > self.cmds[cmd]['level']: | |
259 | return self.cmds[cmd]['helpfunc']() | |
260 | return None | |
261 | ||
262 | def isbot(self, bid): | |
263 | return (toint(bid) in self.bots) | |
264 | def isonline(self, bid): | |
265 | bid = toint(bid) | |
266 | return (bid in self.bots and self.bots[bid].online) |