locationbot.py 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732
  1. #!/usr/bin/env python
  2. # Location Bot
  3. #
  4. # Douglas Thrift
  5. #
  6. # $Id$
  7. from ConfigParser import NoOptionError, SafeConfigParser
  8. from datetime import datetime, timedelta
  9. import getpass
  10. import ircbot
  11. import irclib
  12. import os
  13. import psycopg2
  14. import psycopg2.extensions
  15. import pytz
  16. import re
  17. import sys
  18. import time
  19. import threading
  20. import urllib, urllib2
  21. import warnings
  22. try:
  23. import simplejson as json
  24. except ImportError:
  25. import json
  26. class LocationBot(ircbot.SingleServerIRCBot):
  27. def __init__(self, bot = None):
  28. self.__config = SafeConfigParser()
  29. self.__config.read('locationbot.ini')
  30. try:
  31. nick = self.__config.sections()[0]
  32. except IndexError:
  33. sys.exit('No nick configured')
  34. servers = []
  35. try:
  36. for server in self.__config.get(nick, 'servers').split():
  37. server = server.split(':', 2)
  38. if len(server) == 1:
  39. servers.append((server[0], 6667))
  40. else:
  41. host = server[0]
  42. port = int(server[1])
  43. ssl = server[1].startswith('+')
  44. if len(server) == 3:
  45. servers.append((host, port, ssl, server[2]))
  46. else:
  47. servers.append((host, port, ssl))
  48. self.__admins = self.__config.get(nick, 'admins').split()
  49. self.__channels = set(self.__config.get(nick, 'channels').split())
  50. self.__dsn = self.__config.get(nick, 'dsn')
  51. except NoOptionError, error:
  52. sys.exit(error)
  53. try:
  54. self.__hostname = self.__config.get(nick, 'hostname')
  55. except NoOptionError:
  56. self.__hostname = ''
  57. ircbot.SingleServerIRCBot.__init__(self, servers, nick, 'Location Bot')
  58. self.__latitude_timer_lock = threading.Lock()
  59. self.__latitude_timer = None
  60. self.__locations_lock = threading.Lock()
  61. if bot is None:
  62. self.__locations = []
  63. self.__logins = {}
  64. self.__nick = None
  65. self.__reloading = False
  66. else:
  67. irclibobj = self.ircobj.connections[0].irclibobj
  68. self.ircobj.connections[0] = bot.ircobj.connections[0]
  69. self.ircobj.connections[0].irclibobj = irclibobj
  70. self.channels = bot.channels
  71. self.connection = bot.connection
  72. self.__locations = bot.__locations
  73. self.__logins = bot.__logins
  74. self.__nick = bot.__nick
  75. self.__reloading = True
  76. self.__quiting = False
  77. self.__variables = frozenset(['nick', 'secret', 'masks', 'channels', 'timezone', 'location', 'coordinates', 'latitude'])
  78. self.__geocode = self.__variables.intersection(['location', 'coordinates'])
  79. self.__lists = self.__variables.intersection(['masks', 'channels'])
  80. self.__unsetable = self.__variables.difference(['nick', 'secret'])
  81. def __admin(self, nickmask):
  82. return _or(lambda admin: irclib.mask_matches(nickmask, admin), self.__admins)
  83. def __channel(self, nick, exclude = None):
  84. if exclude is not None:
  85. exclude = irclib.irc_lower(exclude)
  86. channels = map(lambda channel: channel[1], filter(lambda channel: irclib.irc_lower(channel[0]) == exclude, self.channels))
  87. else:
  88. channels = self.channels.values()
  89. return _or(lambda channel: channel.has_user(nick), channels)
  90. def __coordinates(self, coordinates):
  91. return ' http://maps.google.com/maps?q=%f,%f' % coordinates if coordinates else ''
  92. def __db(self):
  93. db = psycopg2.connect(self.__dsn)
  94. def point(value, cursor):
  95. if value is not None:
  96. return tuple(map(lambda a: float(a), re.match(r'^\(([^)]+),([^)]+)\)$', value).groups()))
  97. psycopg2.extensions.register_type(psycopg2.extensions.new_type((600,), 'point', point), db)
  98. return db, db.cursor()
  99. def __help(self, connection, nick, admin, login, arguments):
  100. command = irclib.irc_lower(arguments.split(None, 1)[0].lstrip('!')) if arguments else None
  101. commands = {
  102. 'help': ('[command]', 'show this help message'),
  103. 'status': ('[nick]', 'show where everybody or a nick is'),
  104. }
  105. if not login:
  106. commands.update({
  107. 'login': ('[nick] [secret]', 'log in as nick with secret or using masks'),
  108. 'register': ('[nick] secret', 'register as nick with secret'),
  109. })
  110. else:
  111. commands.update({
  112. 'logout': ('', 'log out as nick'),
  113. 'set': ('[variable [value]]', 'display or set variables'),
  114. 'unset': ('variable', 'unset a variable'),
  115. })
  116. if admin:
  117. commands.update({
  118. 'join': ('channel', 'join a channel'),
  119. 'part': ('channel [message]', 'part from a channel'),
  120. 'quit': ('[message]', 'quit and do not come back'),
  121. 'reload': ('', 'reload with more up to date code'),
  122. 'restart': ('', 'quit and join running more up to date code'),
  123. 'say': ('nick|channel message', 'say message to nick or channel'),
  124. 'who': ('', 'show who is logged in'),
  125. })
  126. connection.privmsg(nick, '\x02command arguments description\x0f')
  127. def help(command, arguments, description):
  128. connection.privmsg(nick, '%-11s %-23s %s' % (command, arguments, description))
  129. if command in commands:
  130. help(command, *commands[command])
  131. else:
  132. for command, (arguments, description) in sorted(commands.iteritems()):
  133. help(command, arguments, description)
  134. def __join(self, connection, nick, arguments):
  135. try:
  136. channel = arguments.split(None, 1)[0]
  137. except IndexError:
  138. return self.__help(connection, nick, True, False, 'join')
  139. connection.join(channel)
  140. self.__channels.add(channel)
  141. self.__config.set(self._nickname, 'channels', ' '.join(self.__channels))
  142. self.__write()
  143. connection.privmsg(nick, 'successfully joined channel ("%s")' % channel)
  144. def __latitude(self):
  145. now = datetime.utcnow()
  146. try:
  147. while now < self.__latitude_next:
  148. time.sleep((self.__latitude_next - now).seconds)
  149. now = datetime.utcnow()
  150. except AttributeError:
  151. pass
  152. self.__latitude_next = now.replace(minute = now.minute - now.minute % 5, second = 0, microsecond = 0) + timedelta(minutes = 5)
  153. try:
  154. db, cursor = self.__db()
  155. cursor.execute('select nick, channels, location, latitude from locationbot.nick where latitude is not null order by latitude')
  156. locations = {}
  157. for nick, channels, old_location, latitude in cursor.fetchall():
  158. new_location = locations.get(latitude)
  159. if latitude not in locations:
  160. try:
  161. feature = json.load(urllib2.urlopen('http://www.google.com/latitude/apps/badge/api?' + urllib.urlencode({'user': latitude, 'type': 'json'})))['features'][0]
  162. coordinates = tuple(reversed(feature['geometry']['coordinates']))
  163. properties = feature['properties']
  164. new_location = properties['reverseGeocode']
  165. locations[latitude] = new_location
  166. updated = datetime.fromtimestamp(properties['timeStamp'], pytz.utc)
  167. except Exception, error:
  168. print error
  169. continue
  170. cursor.execute('update locationbot.nick set location = %s, coordinates = point %s, updated = %s where latitude = %s', (new_location, coordinates, updated, latitude))
  171. db.commit()
  172. self.__location(nick, channels, old_location, new_location, coordinates)
  173. except psycopg2.Error, error:
  174. print error
  175. with self.__latitude_timer_lock:
  176. self.__latitude_timer = threading.Timer((self.__latitude_next - datetime.utcnow()).seconds, self.__latitude)
  177. self.__latitude_timer.start()
  178. def __location(self, nick, channels, old_location, new_location, coordinates = None):
  179. if channels and new_location and new_location != old_location:
  180. with self.__locations_lock:
  181. for channel in self.__channels.intersection(channels):
  182. self.__locations.append((nick, channel, new_location, coordinates))
  183. def __login(self, connection, nickmask, nick, arguments = ''):
  184. login = nick
  185. if connection is not None:
  186. arguments = arguments.split(None, 1)
  187. if len(arguments) == 2:
  188. login, secret = arguments
  189. elif len(arguments) == 1:
  190. secret = arguments[0]
  191. else:
  192. secret = None
  193. else:
  194. secret = None
  195. if nick in self.__logins:
  196. login = self.__logins[nick][0]
  197. if connection is not None:
  198. return connection.privmsg(nick, 'already logged in as "%s"' % login)
  199. return login
  200. db, cursor = self.__db()
  201. def success():
  202. connection.privmsg(nick, 'successfully logged in as "%s"' % login)
  203. if secret is not None:
  204. cursor.execute('select true from locationbot.nick where nick = %s and secret = md5(%s)', (login, secret))
  205. if cursor.rowcount == 1:
  206. self.__logins[nick] = (login, nickmask)
  207. return success()
  208. cursor.execute('select nick, masks from locationbot.nick where nick in (%s, %s)', (login, secret if len(arguments) != 2 else None))
  209. for login, masks in cursor.fetchall():
  210. if _or(lambda mask: irclib.mask_matches(nickmask, mask), masks):
  211. self.__logins[nick] = (login, nickmask)
  212. return success() if connection else login
  213. if connection is not None:
  214. return connection.privmsg(nick, 'failed to log in as "%s"' % login)
  215. def __logout(self, connection, nick):
  216. connection.privmsg(nick, 'logged out as "%s"' % self.__logins.pop(nick)[0])
  217. def __part(self, connection, nick, arguments):
  218. arguments = arguments.split(None, 1)
  219. if len(arguments) == 2:
  220. channel, message = arguments
  221. message = ':' + message
  222. elif len(arguments) == 1:
  223. channel = arguments[0]
  224. message = ''
  225. else:
  226. return self.__help(connection, nick, True, False, 'part')
  227. if channel in self.__channels:
  228. connection.part(channel, message)
  229. self.__channels.remove(channel)
  230. self.__config.set(self._nickname, 'channels', ' '.join(self.__channels))
  231. self.__write()
  232. connection.privmsg(nick, 'successfully parted channel ("%s")' % channel)
  233. else:
  234. connection.privmsg(nick, 'not in channel ("%s")' % channel)
  235. def __quit(self, connection, nick, arguments):
  236. self.__reloading = True
  237. self.__quiting = True
  238. connection.privmsg(nick, 'quiting')
  239. self.disconnect(arguments)
  240. def __register(self, connection, nick, arguments):
  241. arguments = arguments.split(None, 1)
  242. if len(arguments) == 2:
  243. login, secret = arguments
  244. elif len(arguments) == 1:
  245. login = nick
  246. secret = arguments[0]
  247. else:
  248. return self.__help(connection, nick, False, False, 'register')
  249. db, cursor = self.__db()
  250. try:
  251. cursor.execute('insert into locationbot.nick (nick, secret) values (%s, md5(%s))', (login, secret))
  252. db.commit()
  253. except psycopg2.IntegrityError:
  254. return connection.privmsg(nick, 'nick ("%s") is already registered' % login)
  255. connection.privmsg(nick, 'nick ("%s") sucessfully registered' % login)
  256. def __reload(self, connection, nick):
  257. self.__nick = nick
  258. self.__reloading = True
  259. connection.privmsg(nick, 'reloading')
  260. def __restart(self, connection):
  261. connection.disconnect('restarting')
  262. os.execvp(sys.argv[0], sys.argv)
  263. def __say(self, connection, nick, arguments):
  264. try:
  265. nick_channel, message = arguments.split(None, 1)
  266. except ValueError:
  267. return self.__help(connection, nick, True, False, 'say')
  268. if irclib.is_channel(nick_channel):
  269. if nick_channel not in self.channels:
  270. return connection.privmsg(nick, 'not in channel ("%s")' % nick_channel)
  271. elif not self.__channel(nick_channel):
  272. return connection.privmsg(nick, 'nick ("%s") not in channel(s)' % nick_channel)
  273. elif nick_channel == connection.get_nickname():
  274. return connection.privmsg(nick, 'nice try')
  275. connection.privmsg(nick_channel, message)
  276. def __set(self, connection, nickmask, nick, login, arguments):
  277. arguments = arguments.split(None, 1)
  278. if len(arguments) == 2:
  279. variable, value = arguments
  280. elif len(arguments) == 1:
  281. variable = arguments[0]
  282. value = None
  283. else:
  284. variable = None
  285. value = None
  286. if variable is not None and variable not in self.__variables:
  287. return self.__unknown_variable(connection, nick, variable)
  288. db, cursor = self.__db()
  289. if value is None:
  290. variables = sorted(self.__variables) if variable is None else [variable]
  291. cursor.execute('select ' + ', '.join(map(lambda variable: "'%s'" % ('*' * 8) if variable == 'secret' else variable, variables)) + ' from locationbot.nick where nick = %s', (login,))
  292. connection.privmsg(nick, '\x02variable value\x0f')
  293. for variable, value in zip(variables, cursor.fetchone()):
  294. connection.privmsg(nick, '%-11s %s' % (variable, ' '.join(value) if isinstance(value, list) else '%f,%f' % value if isinstance(value, tuple) else value))
  295. else:
  296. def invalid(value, variable = variable):
  297. connection.privmsg(nick, 'invalid %s ("%s")' % (variable, value))
  298. if variable in self.__lists:
  299. value = value.split()
  300. if variable == 'channels':
  301. for channel in value:
  302. if not irclib.is_channel(channel) or channel not in self.__channels:
  303. return invalid(channel, 'channel')
  304. elif variable == 'masks':
  305. _mask = re.compile('^.+!.+@.+$')
  306. for mask in value:
  307. if not _mask.match(mask):
  308. return invalid(mask, 'mask')
  309. elif variable == 'latitude':
  310. try:
  311. value = int(re.sub(r'^(?:http://(?:www\.)?google\.com/latitude/apps/badge/api\?user=)?([0-9]+)(?:&type=.*)?$', r'\1', value))
  312. except ValueError:
  313. return invalid(value)
  314. elif variable in self.__geocode:
  315. cursor.execute('select channels, location from locationbot.nick where nick = %s', (login,))
  316. channels, old_location = cursor.fetchone()
  317. parameters = {'sensor': 'false'}
  318. if variable == 'location':
  319. parameters['address'] = value
  320. else:
  321. coordinates = value.split(None, 1)
  322. if len(coordinates) == 1:
  323. coordinates = coordinates[0].split(',', 1)
  324. try:
  325. coordinates = tuple(map(lambda a: float(a), coordinates))
  326. except ValueError:
  327. return invalid(value)
  328. parameters['latlng'] = '%f,%f' % coordinates
  329. geocode = json.load(urllib2.urlopen('http://maps.google.com/maps/api/geocode/json?' + urllib.urlencode(parameters)))
  330. status = geocode['status']
  331. if status != 'OK':
  332. if status == 'ZERO_RESULTS':
  333. return invalid(value)
  334. else:
  335. raise Exception(status)
  336. results = geocode['results']
  337. result = results[0]
  338. new_location = result['formatted_address']
  339. if variable == 'location':
  340. location = result['geometry']['location']
  341. coordinates = (location['lat'], location['lng'])
  342. value = new_location
  343. else:
  344. types = frozenset([
  345. 'country',
  346. 'administrative_area_level_1',
  347. 'administrative_area_level_2',
  348. 'administrative_area_level_3',
  349. 'colloquial_area',
  350. 'locality',
  351. 'sublocality',
  352. 'neighborhood',
  353. ])
  354. for result in results:
  355. if not types.isdisjoint(result['types']):
  356. new_location = result['formatted_address']
  357. break
  358. value = coordinates
  359. elif variable == 'nick':
  360. _nick = value.split(None, 1)
  361. if len(_nick) != 1:
  362. return invalid(value)
  363. elif variable == 'timezone':
  364. if value not in pytz.all_timezones_set:
  365. return invalid(value)
  366. try:
  367. cursor.execute('update locationbot.nick set ' + ('location = %s, coordinates = point %s, updated = now()' if variable in self.__geocode else variable + ' = ' + ('md5(%s)' if variable == 'secret' else '%s')) + ' where nick = %s', (new_location, coordinates, login) if variable in self.__geocode else (value, login))
  368. db.commit()
  369. except psycopg2.IntegrityError:
  370. if variable == 'nick':
  371. return connection.privmsg(nick, 'nick ("%s") is already registered' % value)
  372. raise
  373. if variable == 'nick':
  374. self.__logins[nick] = (value, nickmask)
  375. elif variable in self.__geocode:
  376. self.__location(nick, channels, old_location, new_location, coordinates)
  377. connection.privmsg(nick, 'variable ("%s") successfully set to value ("%s")' % (variable, ' '.join(value) if isinstance(value, list) else '%f,%f' % value if isinstance(value, tuple) else value))
  378. def __status(self, connection, nick, login, arguments):
  379. _nick = arguments.split(None, 1)[0] if arguments else None
  380. db, cursor = self.__db()
  381. cursor.execute('select nick, location, coordinates, updated from locationbot.nick where ' + ('nick = %s and ' if _nick is not None else '') + 'location is not null order by updated desc', (_nick,))
  382. if cursor.rowcount == 0:
  383. return connection.privmsg(nick, 'no location information for ' + ('"%s"' % _nick if _nick is not None else 'anybody'))
  384. locations = cursor.fetchall()
  385. if login is not None:
  386. cursor.execute('select timezone from locationbot.nick where nick = %s and timezone is not null', (login,))
  387. timezone = pytz.timezone(cursor.fetchone()[0]) if cursor.rowcount == 1 else pytz.utc
  388. else:
  389. timezone = pytz.utc
  390. connection.privmsg(nick, '\x02nick location when map\x0f')
  391. for _nick, location, coordinates, updated in locations:
  392. connection.privmsg(nick, '%-23s %-35s %-23s %s' % (_nick, location, timezone.normalize(updated.astimezone(timezone)).strftime('%Y-%m-%d %H:%M %Z'), self.__coordinates(coordinates)))
  393. def __unknown(self, connection, nick, command):
  394. connection.privmsg(nick, 'unknown command ("%s"); try "help"' % command)
  395. def __unknown_variable(self, connection, nick, variable):
  396. connection.privmsg(nick, 'unknown variable ("%s")' % variable)
  397. def __unset(self, connection, nick, login, arguments):
  398. try:
  399. variable = irclib.irc_lower(arguments.split(None, 1)[0])
  400. except IndexError:
  401. return self.__help(connection, nick, False, login, 'unset')
  402. if variable not in self.__unsetable:
  403. if variable in self.__variables:
  404. return connection.privmsg(nick, 'variable ("%s") is not unsetable' % variable)
  405. return self.__unknown_variable(connection, nick, variable)
  406. db, cursor = self.__db()
  407. cursor.execute('update locationbot.nick set ' + ('location = null, coordinates = null, updated = null' if variable in self.__geocode else variable + ' = null') + ' where nick = %s and ' + variable + ' is not null', (login,))
  408. db.commit()
  409. connection.privmsg(nick, 'variable ("%s") %s unset' % (variable, 'successfuly' if cursor.rowcount == 1 else 'already'))
  410. def __who(self, connection, nick):
  411. if self.__logins:
  412. connection.privmsg(nick, '\x02login nick nick mask\x0f')
  413. for login in sorted(self.__logins.values()):
  414. connection.privmsg(nick, '%-23s %s' % login)
  415. else:
  416. connection.privmsg(nick, 'nobody logged in')
  417. def __write(self):
  418. with open('locationbot.ini', 'w') as config:
  419. self.__config.write(config)
  420. def _connect(self):
  421. if len(self.server_list[0]) != 2:
  422. ssl = self.server_list[0][2]
  423. else:
  424. ssl = False
  425. if len(self.server_list[0]) == 4:
  426. password = self.server_list[0][3]
  427. else:
  428. password = None
  429. try:
  430. with warnings.catch_warnings():
  431. warnings.filterwarnings('ignore', r'socket\.ssl\(\) is deprecated\. Use ssl\.wrap_socket\(\) instead\.', DeprecationWarning)
  432. self.connect(self.server_list[0][0], self.server_list[0][1], self._nickname, password, getpass.getuser(), self._realname, localaddress = self.__hostname, ssl = ssl)
  433. except irclib.ServerConnectionError:
  434. pass
  435. def disconnect(self, message = 'oh no!'):
  436. ircbot.SingleServerIRCBot.disconnect(self, message)
  437. def error(self, error):
  438. print error
  439. self.connection.privmsg(self.__nick, 'an error occured')
  440. def get_version(self):
  441. return 'locationbot ' + sys.platform
  442. def on_kick(self, connection, event):
  443. nick = event.arguments()[0]
  444. if not self.__channel(nick, event.target()):
  445. self.__logins.pop(nick, None)
  446. def on_nick(self, connection, event):
  447. nickmask = event.source()
  448. login = self.__logins.pop(irclib.nm_to_n(nickmask), (None,))[0]
  449. if login is not None:
  450. nick = event.target()
  451. self.__logins[nick] = (login, nick + '!' + nm_to_uh(nickmask))
  452. def on_nicknameinuse(self, connection, event):
  453. connection.nick(connection.get_nickname() + '_')
  454. def on_part(self, connection, event):
  455. nick = irclib.nm_to_n(event.source())
  456. if not self.__channel(nick, event.target()):
  457. self.__logins.pop(nick, None)
  458. def on_privmsg(self, connection, event):
  459. nickmask = event.source()
  460. nick = irclib.nm_to_n(nickmask)
  461. admin = self.__admin(nickmask)
  462. if not admin and not self.__channel(nick):
  463. return
  464. try:
  465. login = self.__login(None, nickmask, nick)
  466. try:
  467. command, arguments = event.arguments()[0].split(None, 1)
  468. except ValueError:
  469. command = event.arguments()[0].strip()
  470. arguments = ''
  471. command = irclib.irc_lower(command.lstrip('!'))
  472. if command == 'help':
  473. self.__help(connection, nick, admin, login, arguments)
  474. elif command == 'login':
  475. self.__login(connection, nickmask, nick, arguments)
  476. elif command == 'status':
  477. self.__status(connection, nick, login, arguments)
  478. elif not login and command == 'register':
  479. self.__register(connection, nick, arguments)
  480. elif login and command == 'logout':
  481. self.__logout(connection, nick)
  482. elif login and command == 'set':
  483. self.__set(connection, nickmask, nick, login, arguments)
  484. elif login and command == 'unset':
  485. self.__unset(connection, nick, login, arguments)
  486. elif admin and command == 'join':
  487. self.__join(connection, nick, arguments)
  488. elif admin and command == 'part':
  489. self.__part(connection, nick, arguments)
  490. elif admin and command == 'quit':
  491. self.__quit(connection, nick, arguments)
  492. elif admin and command == 'reload':
  493. self.__reload(connection, nick)
  494. elif admin and command == 'restart':
  495. self.__restart(connection)
  496. elif admin and command == 'say':
  497. self.__say(connection, nick, arguments)
  498. elif admin and command == 'who':
  499. self.__who(connection, nick)
  500. else:
  501. self.__unknown(connection, nick, command)
  502. except Exception, error:
  503. print error
  504. connection.privmsg(nick, 'an error occurred')
  505. def on_quit(self, connection, event):
  506. self.__logins.pop(irclib.nm_to_n(event.source()), None)
  507. def on_welcome(self, connection, event):
  508. for channel in self.__channels:
  509. connection.join(channel)
  510. def start(self):
  511. self.__latitude_thread = threading.Thread(None, self.__latitude)
  512. self.__latitude_thread.daemon = True
  513. self.__latitude_thread.start()
  514. if not self.__reloading:
  515. self._connect()
  516. else:
  517. self.__reloading = False
  518. for channel in self.__channels.symmetric_difference(self.channels.keys()):
  519. if channel in self.__channels:
  520. self.connection.join(channel)
  521. else:
  522. self.connection.part(channel)
  523. while not self.__reloading:
  524. if self.__locations_lock.acquire(False):
  525. if self.__locations and self.__channels.issubset(self.channels.keys()):
  526. for nick, channel, location, coordinates in self.__locations:
  527. self.connection.notice(channel, '%s is in %s' % (nick, location + self.__coordinates(coordinates)))
  528. self.__locations = []
  529. self.__locations_lock.release()
  530. self.ircobj.process_once(0.2)
  531. self.__latitude_thread.join()
  532. with self.__latitude_timer_lock:
  533. if self.__latitude_timer is not None:
  534. self.__latitude_timer.cancel()
  535. self.__latitude_timer.join()
  536. return not self.__quiting
  537. def success(self):
  538. self.connection.privmsg(self.__nick, 'successfully reloaded')
  539. def _or(function, values):
  540. return reduce(lambda a, b: a or b, map(function, values) if values else [False])
  541. if __name__ == '__main__':
  542. os.chdir(os.path.abspath(os.path.dirname(__file__)))
  543. pid = os.fork()
  544. if pid != 0:
  545. with open('locationbot.pid', 'w') as _file:
  546. _file.write('%u\n' % pid)
  547. sys.exit(0)
  548. sys.stdin = open('/dev/null')
  549. sys.stdout = open('locationbot.log', 'a', 1)
  550. sys.stderr = sys.stdout
  551. import locationbot
  552. bot = locationbot.LocationBot()
  553. try:
  554. while bot.start():
  555. import locationbot
  556. try:
  557. bot = reload(locationbot).LocationBot(bot)
  558. except (ImportError, SyntaxError), error:
  559. bot.error(error)
  560. else:
  561. bot.success()
  562. except KeyboardInterrupt:
  563. bot.disconnect()
  564. os.unlink('locationbot.pid')