93
93
PIDGIN_LOGS_PATH = "~/.purple/logs"
97
97
def __init__(self, options):
98
98
self.options = options
99
99
self.logger = logging.getLogger(app_name+"."+module_name)
100
100
self.loadConfigData()
102
102
def loadConfigData(self):
105
105
self.config = PidginConfig()
107
107
if self.options.config != None:
108
108
# load the config based on options passed in from the main app
109
109
configfilepath = self.options.config
111
111
# load plugin config from home directory of the user
112
112
configfilepath = os.path.join(os.path.expanduser('~'), ".config/"+app_name+"/"+module_name+".config")
114
114
if os.path.exists(configfilepath):
116
116
self.logger.info("Loading config settings from \"%s\""%configfilepath)
118
118
for line in fileinput.input(os.path.expanduser(configfilepath)):
119
119
line = line.strip()
120
120
if len(line) > 0 and line[0:1] != "#": # ignore commented lines or empty ones
122
122
name = line.split("=")[0].strip().upper() # config setting name on the left of =
123
123
value = line.split("=")[1].split("#")[0].strip() # config value on the right of = (minus any trailing comments)
125
125
if len(value) > 0:
126
126
if name == "HEADERTEMPLATE":
127
self.config.HEADERTEMPLATE = self.getTypedValue(value, "string")
127
self.config.HEADERTEMPLATE = getTypedValue(value, "string")
128
128
elif name == "TEMPLATE":
129
self.config.TEMPLATE = self.getTypedValue(value, "string")
129
self.config.TEMPLATE = getTypedValue(value, "string")
130
130
elif name == "ONLINEONLY":
131
self.config.ONLINEONLY = self.getTypedValue(value, "boolean")
131
self.config.ONLINEONLY = getTypedValue(value, "boolean")
132
132
elif name == "OFFLINEONLY":
133
self.config.OFFLINEONLY = self.getTypedValue(value, "boolean")
133
self.config.OFFLINEONLY = getTypedValue(value, "boolean")
134
134
elif name == "AVAILABLEONLY":
135
self.config.AVAILABLEONLY = self.getTypedValue(value, "boolean")
135
self.config.AVAILABLEONLY = getTypedValue(value, "boolean")
136
136
elif name == "INCLUDELIST":
137
self.config.INCLUDELIST = self.getTypedValue(value, "string")
137
self.config.INCLUDELIST = getTypedValue(value, "string")
138
138
elif name == "IGNORELIST":
139
self.config.IGNORELIST = self.getTypedValue(value, "string")
139
self.config.IGNORELIST = getTypedValue(value, "string")
140
140
elif name == "LIMIT":
141
self.config.LIMIT = self.getTypedValue(value, "integer")
141
self.config.LIMIT = getTypedValue(value, "integer")
142
142
elif name == "SORTBYLOGACTIVITY":
143
self.config.SORTBYLOGACTIVITY = self.getTypedValue(value, "boolean")
143
self.config.SORTBYLOGACTIVITY = getTypedValue(value, "boolean")
144
144
elif name == "DEFAULTBUDDYICON":
145
self.config.DEFAULTBUDDYICON = self.getTypedValue(value, "string")
145
self.config.DEFAULTBUDDYICON = getTypedValue(value, "string")
147
147
self.logger.error("Unknown option in config file: " + name)
149
149
self.logger.info("Config data file %s not found, using defaults and setting up config file for next time" % configfilepath)
151
151
userconfigpath = os.path.join(os.path.expanduser('~'), ".config/"+app_name+"/")
152
152
configsource = os.path.join(app_path, "config/"+module_name+".config")
154
154
if os.path.exists(userconfigpath) == False:
155
155
os.makedirs(userconfigpath)
157
157
shutil.copy(configsource, configfilepath)
159
159
except Exception, e:
160
160
self.logger.error(e.__str__()+"\n"+traceback.format_exc())
162
def getTypedValue(self, value, expectedtype):
165
if len(value.strip(" ")) == 0:
168
elif value.lower() == "true":
169
if expectedtype == "boolean":
172
self.logger.error("Expected type was '%s', but the value '%s' was given"%(expectedtype, value))
174
elif value.lower() == "false":
175
if expectedtype == "boolean":
178
self.logger.error("Expected type was '%s', but the value '%s' was given"%(expectedtype, value))
180
elif self.isNumeric(value) == True:
181
if expectedtype == "integer":
184
self.logger.error("Expected type was '%s', but the value '%s' was given"%(expectedtype, value))
189
except (TypeError, ValueError):
190
self.logger.error("Cannot convert '%s' to expected type of '%s'"%(value,expectedtype))
193
162
def testDBus(self, bus, interface):
194
163
obj = bus.get_object('org.freedesktop.DBus', '/org/freedesktop/DBus')
195
164
dbus_iface = dbus.Interface(obj, 'org.freedesktop.DBus')
242
211
defaultbuddyicon = "file://%s"%os.path.expanduser(self.config.DEFAULTBUDDYICON)
244
213
defaultbuddyicon = ""
246
215
self.logger.info("Calling dbus interface for IM data")
248
217
# Iterate through every active account
249
218
for acctID in iface.PurpleAccountsGetAllActive():
251
220
# get all the buddies associated with that account
252
221
buddies = iface.PurpleFindBuddies(acctID,"")
254
223
for buddyid in buddies:
256
225
groupid = iface.PurpleBuddyGetGroup(buddyid)
258
227
# get initial data
259
228
alias = iface.PurpleBuddyGetAlias(buddyid)
260
229
name = iface.PurpleBuddyGetName(buddyid)
261
230
online = iface.PurpleBuddyIsOnline(buddyid)
262
231
group = iface.PurpleGroupGetName(groupid)
264
233
if self.ignoreGroup(group) == False:
266
235
if self.includeGroup(group) == True:
270
239
if self.config.ONLINEONLY == True:
279
248
if addBuddy == True:
281
250
# determine whether to show this buddies details based on onlineonly option
282
251
#if online == 1 or self.config.ONLINEONLY == False:
286
255
# retrieve buddy status
287
256
presenceid = iface.PurpleBuddyGetPresence(buddyid)
288
257
status = iface.PurplePresenceGetActiveStatus(presenceid)
290
259
# get extended message if set
291
status_message = self.getCleanText(iface.PurpleStatusGetAttrString(status, "message")) # getCleanText needed for google encoded text
260
status_message = getCleanText(iface.PurpleStatusGetAttrString(status, "message")) # getCleanText needed for google encoded text
293
262
# get the status type, id and text
294
263
statustype = iface.PurpleStatusGetType(status)
295
264
statusid = iface.PurpleStatusTypeGetPrimitive(statustype)
296
statustypetext = iface.PurpleStatusTypeGetId(statustype)
265
statustypetext = iface.PurpleStatusTypeGetId(statustype)
299
268
if self.isBuddyChatting(name,iface):
300
269
statusicon = self.ICON_CHATTING
303
272
if statusid == self.STATUS_AVAILABLE:
304
273
statusicon = self.ICON_AVAILABLE
307
276
elif statusid == self.STATUS_UNAVAILABLE:
308
277
statusicon = self.ICON_UNAVAILABLE
310
279
elif statusid == self.STATUS_INVISIBLE:
311
280
statusicon = self.ICON_INVISIBLE
313
282
elif statusid == self.STATUS_AWAY or statusid == self.STATUS_EXTENDED_AWAY:
314
283
statusicon = self.ICON_AWAY
316
285
elif statusid == self.STATUS_MOBILE:
317
286
statusicon = self.ICON_MOBILE
319
288
elif statusid == self.STATUS_TUNE:
320
289
#TODO: need icon for STATUS_TUNE whatever that is...
321
290
statusicon = self.ICON_AVAILABLE
323
292
else: # catch all in case no match on statustype, guess available
324
293
statusicon = self.ICON_AVAILABLE
326
statusicon = self.ICON_OFFLINE
295
statusicon = self.ICON_OFFLINE
328
297
status = "file://%s/images/pidginicons/%s.png"%(app_path, statusicon)
330
299
# determine whether to show this buddies details based on availableonly option
331
300
if available == 1 or self.config.AVAILABLEONLY == False:
333
302
# get buddies icon, if not there then default icon used if setup
334
303
icon = defaultbuddyicon
335
304
iconid = iface.PurpleBuddyGetIcon(buddyid)
344
313
if purplestoredimageid <> 0:
346
315
#iconpath = iface.PurpleImgstoreGetFilename(purplestoredimageid)
347
#icon = "file://"+urllib.quote(iconpath)
316
#icon = "file://"+urllib.quote(iconpath)
349
318
# get latest activity datetime
350
319
activitydatetime = self.getBuddyActivityDatetime(self.PIDGIN_LOGS_PATH, name)
352
321
self.logger.info("Adding IM data for buddy: '%s', group: '%s', status: '%s'"%(alias, group, statustypetext))
354
323
# ready text for html output
355
name = self.getHTMLText(name)
356
alias = self.getHTMLText(alias)
357
group = self.getHTMLText(group)
358
status_message = self.getHTMLText(status_message)
324
name = getHTMLText(name)
325
alias = getHTMLText(alias)
326
group = getHTMLText(group)
327
status_message = getHTMLText(status_message)
360
329
pidginData = PidginData(name, alias, group, status, status_message, icon, activitydatetime)
361
330
pidginDataList.append(pidginData)
365
334
del remote_object
453
422
basefolder = os.path.expanduser(basefolder)
454
423
latestmodifieddatetime = datetime(2000, 1, 1, 0, 0)
456
425
# if the folder passed in is non existent return an empty list
457
426
if os.path.exists(basefolder):
459
428
# for each file or folder
460
429
for root, dirs, files in os.walk(basefolder):
463
432
if dir == buddyname:
465
434
buddypath = os.path.join(root,dir)
466
435
moddatetime = self.getLatestLogFileModificationDatetime(buddypath, latestmodifieddatetime)
467
436
if moddatetime > latestmodifieddatetime:
468
437
latestmodifieddatetime = moddatetime
469
438
return latestmodifieddatetime
471
440
return latestmodifieddatetime
473
442
def getLatestLogFileModificationDatetime(self, folderpath, latestmodifieddatetime):
475
444
for file in os.listdir(folderpath):
476
445
filepath = os.path.join(folderpath,file)
477
446
if os.path.isfile(filepath):
478
447
modifieddate = datetime.fromtimestamp(os.stat(filepath)[stat.ST_MTIME])
479
448
if modifieddate > latestmodifieddatetime:
480
449
latestmodifieddatetime = modifieddate
482
451
return latestmodifieddatetime
484
453
def isBuddyChatting(self,name,iface):
487
456
convname = iface.PurpleConversationGetName(imid)
488
457
if convname == name:
493
462
def ignoreBuddy(self, name, alias):
495
464
if self.config.IGNORELIST != None:
497
466
# for each text in the ignore list, should we be ignoring the buddy
498
467
for ignore in self.config.IGNORELIST.split(","):
499
468
# has the buddy been found in the list item
500
469
if name.lower().find(ignore.lower()) != -1 or alias.lower().find(ignore.lower()) != -1:
508
477
def includeBuddy(self, name, alias):
510
479
if self.config.INCLUDELIST != None:
512
481
# for each text in the ignore list, should we be ignoring the buddy
513
482
for include in self.config.INCLUDELIST.split(","):
514
483
# has the buddy been found in the list item
515
484
if name.lower().find(include.lower()) != -1 or alias.lower().find(include.lower()) != -1:
523
492
def ignoreGroup(self, group):
525
494
if self.config.IGNORELIST != None:
527
496
# for each text in the ignore list, should we be ignoring the buddy in the group
528
497
for ignore in self.config.IGNORELIST.split(","):
529
498
# has the group been found in the list item
530
499
if group.lower().find(ignore.lower()) != -1:
538
507
def includeGroup(self, group):
540
509
if self.config.INCLUDELIST != None:
542
511
# for each text in the ignore list, should we be ignoring the buddy in the group
543
512
for include in self.config.INCLUDELIST.split(","):
544
513
# has the group been found in the list item
545
514
if group.lower().find(include.lower()) != -1:
553
def getHTMLText(self,text):
556
for char in text: #html:
558
htmlentities.append(char)
560
htmlentities.append('&%s;' % codepoint2name[ord(char)])
561
html = "".join(htmlentities)
563
html = html.replace("\n","<br>\n") # switch out new line for html breaks
568
def getCleanText(self,html):
571
text = text.replace("\n","") # remove new lines from html
572
text = text.replace("'","'") # workaround for shitty xml codes not compliant with html
573
text = text.replace("<br>","\n") # switch out html breaks for new line
574
text = re.sub('<(.|\n)+?>','',text) # remove any html tags
575
text = re.sub('&(%s);' % '|'.join(name2codepoint), lambda m: chr(name2codepoint[m.group(1)]), text)
580
def isNumeric(self, string):
582
dummy = float(string)
587
522
def getHTML(options):
588
523
output = Output(options)
589
524
html = output.getOutput()
593
528
# to enable testing in isolation
594
529
if __name__ == "__main__":
596
531
parser = OptionParser()
597
parser.add_option("--noheader", dest="noheader", default=False, action="store_true", help=u"Turn off header output. This will override any header template setting to be nothing")
532
parser.add_option("--noheader", dest="noheader", default=False, action="store_true", help=u"Turn off header output. This will override any header template setting to be nothing")
598
533
parser.add_option("--headertemplate", dest="headertemplate", type="string", metavar="FILE", help=u"Override the header template for the plugin, default or config based template ignored.")
599
534
parser.add_option("--template", dest="template", type="string", metavar="FILE", help=u"Override the template for the plugin, default or config based template ignored.")
600
535
parser.add_option("--verbose", dest="verbose", default=False, action="store_true", help=u"Outputs verbose info to the terminal")
601
536
parser.add_option("--version", dest="version", default=False, action="store_true", help=u"Displays the version of the script.")
602
parser.add_option("--logfile", dest="logfile", type="string", metavar="FILE", help=u"If a filepath is set, the script logs to the filepath.")
537
parser.add_option("--logfile", dest="logfile", type="string", metavar="FILE", help=u"If a filepath is set, the script logs to the filepath.")
604
539
(options, args) = parser.parse_args()
606
541
output = Output(options)
607
542
html = output.getOutput()