disable debug output
[quasselfs.git] / quasseltool.py
blob82b631103d893a6dcb3282136e1913bcacd340c2
1 #!/usr/bin/env python
2 # -*- coding: utf-8 -*-
4 #license blarghs
6 """This library provides useful quassel related tools
8 Documentation bogus here"""
10 import sqlite3
11 import time
12 import exceptions
13 import os
14 import re
16 class _conf:
17 dbpath=("~/.config/quassel-irc.org/quassel-storage.sqlite", "~/.quassel/quassel-storage.sqlite")
19 """
20 Buffer Types
21 0 InvalidBuffer = 0x00,
22 1 StatusBuffer = 0x01,
23 2 ChannelBuffer = 0x02,
24 4 QueryBuffer = 0x04,
25 8 GroupBuffer = 0x08
27 Message Types
28 1 Plain = 0x0001,
29 2 Notice = 0x0002,
30 4 Action = 0x0004,
31 8 Nick = 0x0008,
32 16 Mode = 0x0010,
33 32 Join = 0x0020,
34 64 Part = 0x0040,
35 128 Quit = 0x0080,
36 256 Kick = 0x0100,
37 512 Kill = 0x0200,
38 1024 Server = 0x0400,
39 2048 Info = 0x0800,
40 4096 Error = 0x1000,
41 8192 DayChange = 0x2000
43 Message Flags (mostly internal, but may be useful)
44 0 None = 0x00,
45 1 Self = 0x01,
46 2 Highlight = 0x02,
47 4 Redirected = 0x04,
48 8 ServerMsg = 0x08,
49 128 Backlog = 0x80
50 """
51 class QuasselError(exceptions.Exception):
52 """base class for all quassel related exceptions"""
53 pass
55 class NotFound(QuasselError):
56 """Generic error when something was not found in a getter
58 methods that throw this should be clear enough to guess the meaning"""
59 pass
61 class Logformat:
62 """Template class for log formatters, do not use directly
64 row is a list with elements (type, flags, time, sender, message)
65 type is message type, mostly for internal use
66 flags is unneeded currently
67 time is the time as unix timestamp
68 sender is the sender as nick!ident@hostmask
69 message is the message
71 All output producing methods must return strings
73 if you don't override methods, they will be forwarded to others()
74 which can be used like an else-statement"""
76 def __init__(self):
77 """if overriding, always call parent unless you know what you're doing
79 NOTE: Includes language specfic code for an english core"""
80 #TODO: these strings are language specific for an english core
81 self.topicchangere = re.compile('(\S+) has changed topic for (\S+) to: "(.*)"$')
82 self.topicjoinre = re.compile('Topic for (\S+) is "(.*)"$')
83 self.topicjoin2re = re.compile('Topic set by (\S+) on (\S+ \S+ \d+ \S+)')
85 def format(self, row):
86 """wrapper for the methods, most child classes should not touch this
88 in most cases, you will call this method with a row to get a meaningful representation of that row"""
89 if row[0] == 1:
90 return self.message(row)
91 elif row[0] == 2:
92 return self.notice(row)
93 elif row[0] == 4:
94 return self.action(row)
95 elif row[0] == 8:
96 return self.nick(row)
97 elif row[0] == 16:
98 return self.mode(row)
99 elif row[0] == 32:
100 return self.join(row)
101 elif row[0] == 64:
102 return self.part(row)
103 elif row[0] == 128:
104 return self.quit(row)
105 elif row[0] == 256:
106 return self.kick(row)
107 elif row[0] == 1024:
108 return self._server(row)
109 elif row[0] == 2048:
110 return self.info(row)
111 elif row[0] == 4096:
112 return self.error(row)
113 elif row[0] == 8192:
114 return self.daychange(row)
115 else:
116 return self.others(row)
118 def _server(self, row):
119 match = self.topicchangere.search(row[4])
120 if not match is None:
121 (who, where, what) = match.groups()
122 return self.topicchange(row[2], who, where, what)
123 else:
124 match = self.topicjoinre.search(row[4])
125 if not match is None:
126 (where, what) = match.groups()
127 return self.topicjoin1(row[2], where, what)
128 else:
129 match = self.topicjoin2re.search(row[4])
130 if not match is None:
131 (who, when) = match.groups()
132 return self.topicjoin2(row[2], who, when)
133 else:
134 return self.server(row)
136 def message(self, row):
137 """defines format of regular messages"""
138 return self.others(row)
140 def notice(self, row):
141 """defines format of notices"""
142 return self.others(row)
144 def action(self, row):
145 """defines format of actions (/me)"""
146 return self.others(row)
148 def nick(self, row):
149 """defines format of nickchanges"""
150 return self.others(row)
152 def mode(self, row):
153 """defines format of modechanges"""
154 return self.others(row)
156 def join(self, row):
157 """defines format of joins (including self)"""
158 return self.others(row)
160 def part(self, row):
161 """defines format of parts (including self)"""
162 return self.others(row)
164 def quit(self, row):
165 """defines format of quits (including self)"""
166 return self.others(row)
168 def kick(self, row):
169 """defines format of kicks (including self)"""
170 return self.others(row)
172 def server(self, row):
173 """defines format of server messages not including topic related messages"""
174 return self.others(row)
176 def topicchange(self, time, user, channel, topic):
177 """defines format of generic topic changes"""
178 return self.others(row)
180 def topicjoin1(self, time, channel, topic):
181 """defines format of channel join topic message, part one"""
182 return self.others(row)
184 def topicjoin2(self, time, user, logtime):
185 """defines format of channel join topic message, part two"""
186 return self.others(row)
188 def info(self, row):
189 """defines format of info lines"""
190 return self.others(row)
192 def error(self, row):
193 """defines format of error lines"""
194 return self.others(row)
196 def daychange(self, row):
197 """defines format of daychange message, mostly quassel specific"""
198 return self.others(row)
200 def others(self, row):
201 """defines format of all types that you didn't override in your child, used to catch unhandled types"""
202 return ""
204 class Logformat_mIRC (Logformat):
205 def __init__(self, buffer):
206 self.mynick = ""
207 self.buffer = buffer
208 Logformat.__init__(self)
211 def _now(self, timestamp):
212 return time.strftime("[%H:%M.%S]", time.localtime(timestamp))
214 def _now2(self, timestamp):
215 return time.strftime("%a %b %d %H:%M:%S %Y", time.localtime(timestamp))
217 def message(self, row):
218 sender = row[3].split("!")
219 return "%s <%s> %s\n"%(self._now(row[2]), sender[0], row[4])
221 def notice(self, row):
222 sender = row[3].split("!")
223 return "%s -%s- %s\n"%(self._now(row[2]), sender[0], row[4])
225 def action(self, row):
226 sender = row[3].split("!")
227 return "%s * %s %s\n"%(self._now(row[2]), sender[0], row[4])
229 def nick(self, row):
230 if row[3] == row[4]:
231 #own nick
232 if self.mynick == "":
233 #session start
234 return "\nSession Start: %s\nSession Ident: %s\n"%(self._now2(row[2]), self.buffer)
235 else:
236 return "%s *** %s is now known as %s\n"%(self._now(row[2]), self.mynick, row[4])
237 self.mynick = row[4]
238 else:
239 #Other people
240 sender = row[3].split("!")
241 return "%s *** %s is now known as %s\n"%(self._now(row[2]), sender[0], row[4])
244 def mode(self, row):
245 sender = row[3].split("!")
246 return "%s *** %s sets mode: %s\n"%(self._now(row[2]), sender[0], " ".join(row[4].split(" ")[1:]))
248 def join(self, row):
249 sender = row[3].split("!")
250 if self.mynick == "":
251 #own nick
252 self.mynick = sender[0]
253 return "\nSession Start: %s\nSession Ident: %s\n"%(self._now2(row[2]), self.buffer)
254 else:
255 return "%s *** %s (%s) has joined %s\n"%(self._now(row[2]), sender[0], sender[1], row[4])
257 def part(self, row):
258 sender = row[3].split("!")
259 if sender[0] == self.mynick:
260 self.mynick = ""
261 return "Session Close: %s\n"%self._now2(row[2])
262 else:
263 return "%s *** %s (%s) has left %s\n"%(self._now(row[2]), sender[0], sender[1], self.buffer)
265 def quit(self, row):
266 sender = row[3].split("!")
267 if sender[0] == self.mynick:
268 self.mynick = ""
269 return "Session Close: %s\n"%self._now2(row[2])
270 else:
271 return "%s *** %s (%s) Quit (%s)\n"%(self._now(row[2]), sender[0], sender[1], row[4])
273 def kick(self, row):
274 sender = row[3].split("!")
275 msg = row[4].split(" ")
276 reason = " ".join(msg[1:])
277 return "%s *** %s was kicked by %s (%s)\n"%(self._now(row[2]), msg[0], sender[0], reason)
279 def server(self, row):
280 return "%s *** %s\n"%(self._now(row[2]), row[4])
282 def topicchange(self, time, user, channel, topic):
283 return "%s *** %s changes topic to '%s'\n"%(self._now(time), user, topic)
285 def topicjoin1(self, time, channel, topic):
286 return "%s *** Topic is '%s\0'\n"%(self._now(time), topic)
288 def topicjoin2(self, time, user, logtime):
289 return "%s *** Set by %s on %s\n"%(self._now(time), user, logtime)
291 def mirc_color(num, num2=None):
292 newnum = str(num).rjust(2, "0")
293 if num2 is None:
294 return "%c%s"%(3, newnum)
295 else:
296 new2 = str(num2).rjust(2, "0")
297 return "%c%s.%s"%(3, newnum, new2)
299 class Logutil:
300 _dbpath = None
302 def __init__(self, dbpath=""):
303 """returns a new Logutil instance.
304 DB connection is shared between all logutils
305 if dbpath is empty, try default paths (should work on most unixes)"""
306 #if Logutil._con is None:
307 if dbpath=="":
308 self._open()
309 else:
310 if Logutil._dbpath is None:
311 Logutil._dbpath = os.path.expanduser(dbpath)
312 self.dbpath = os.path.expanduser(dbpath)
313 #self.con = sqlite3.connect(os.path.expanduser(dbpath))
314 #print (Logutil._dbpath)
315 #print (self.dbpath)
317 def _open(self):
318 #TODO: POS Code, replace with something sane when sober
319 #print ("open")
320 for path in _conf.dbpath:
321 if os.path.isfile(os.path.expanduser(path)):
322 if Logutil._dbpath is None:
323 Logutil._dbpath = os.path.expanduser(path)
324 self.dbpath = os.path.expanduser(path)
327 def _time_to_unix(self, timestr):
328 #Date
329 year = 0
330 month = 0
331 day = 0
332 hour = 0
333 minute = 0
334 second = 0
335 match = re.compile("(\d{2,4})-(\d{2}).(\d{2})").search(timestr)
336 if not match is None:
337 (year, month, day) = match.groups()
338 match = None
339 match = re.compile("(\d{2}):(\d{2}):(\d{2})").search(timestr)
340 if not match is None:
341 (hour, minute, second) = match.groups()
342 return time.mktime((int(year), int(month), int(day), int(hour), int(minute), int(second), -1, 0, -1))
344 def cursor(self):
345 """for thread safety, we need to do many connects for now"""
346 con = sqlite3.connect(self.dbpath)
347 return con.cursor()
349 def get_users(self):
350 query="""SELECT username FROM quasseluser"""
351 cur = self.cursor()
352 cur.execute(query)
353 return [str(user[0]) for user in cur.fetchall()]
355 def is_user(self, username):
356 try:
357 return username in self.get_users()
358 except:
359 return False
361 def get_networks(self, username):
362 query = """
363 SELECT networkname
364 FROM network
365 JOIN quasseluser ON network.userid = quasseluser.userid
366 WHERE username=?
368 cur = self.cursor()
369 cur.execute(query, (username, ))
370 return [str(network[0]) for network in cur.fetchall()]
372 def is_network(self, username, network):
373 try:
374 return network in self.get_networks(username)
375 except:
376 return False
378 def get_buffers(self, username, network):
379 query = """
380 SELECT buffername
381 FROM buffer
382 JOIN network ON buffer.networkid = network.networkid
383 , quasseluser ON buffer.userid = quasseluser.userid
384 WHERE username=?
385 AND networkname=?
387 nwquery = """
388 SELECT networkname
389 FROM network
390 JOIN quasseluser ON network.userid = quasseluser.userid
391 WHERE username=?
393 cur = self.cursor()
394 if network is None:
395 result = {}
396 for nw in cur.execute(nwquery, (username, )):
397 cur.execute(query, (username, nw[0]))
398 result[str(nw[0])] = [str(buffer[0]) for buffer in cur.fetchall()]
399 else:
400 cur.execute(query, (username, network))
401 result = [str(buf[0]) for buf in cur.fetchall()]
402 return result
404 def is_buffer(self, username, network, buffer):
405 try:
406 return buffer in self.get_buffers(username, network)
407 except:
408 return False
410 def getlog(self, username, network, buffer, outstream, fmt=Logformat_mIRC(buffer), counter=None):
411 """gets a logfile
412 outstream must support .write() for strings
413 fmt must be a Logformat child"""
414 cur = self.cursor()
415 query = """
416 SELECT type, flags, time, sender, message
417 FROM backlog
418 JOIN sender ON backlog.senderid = sender.senderid
419 , buffer ON backlog.bufferid = buffer.bufferid
420 , network ON buffer.networkid = network.networkid
421 , quasseluser ON buffer.userid = quasseluser.userid
422 WHERE username=?
423 AND networkname=?
424 AND buffername=?
426 cur.execute(query, (username, network, buffer))
427 all = cur.fetchall()
428 if not counter is None:
429 counter.set_max(len(all))
430 counter.start()
431 for row in all:
432 if not counter is None:
433 counter.next()
434 outstream.write(fmt.format(row))
435 #print fmt.format(row),