12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031 |
- #!/usr/bin/env python
- # Location Bot
- #
- # Douglas Thrift
- #
- # locationbot.py
- from ConfigParser import NoOptionError, SafeConfigParser
- from datetime import datetime, timedelta
- import getpass
- import ircbot
- import irclib
- import oauth2 as oauth
- import os
- import psycopg2
- import psycopg2.extensions
- import pytz
- import re
- import socket
- import sys
- import time
- import threading
- import traceback
- import urllib, urllib2
- import urlparse
- import warnings
- import math
- try:
- import simplejson as json
- except ImportError:
- import json
- class LocationBot(ircbot.SingleServerIRCBot):
- def __init__(self, bot = None):
- self.__config = SafeConfigParser()
- self.__config.read('locationbot.ini')
- try:
- nick = self.__config.sections()[0]
- except IndexError:
- sys.exit('No nick configured')
- servers = []
- try:
- for server in self.__config.get(nick, 'servers').split():
- server = server.split(':', 2)
- if len(server) == 1:
- servers.append((server[0], 6667))
- else:
- host = server[0]
- port = int(server[1])
- ssl = server[1].startswith('+')
- if len(server) == 3:
- servers.append((host, port, ssl, server[2]))
- else:
- servers.append((host, port, ssl))
- self.__admins = self.__config.get(nick, 'admins').split()
- self.__channels = set(self.__config.get(nick, 'channels').split())
- self.__dsn = self.__config.get(nick, 'dsn')
- self.__latitude_client_id = self.__config.get(nick, 'latitude_client_id')
- self.__latitude_consumer = oauth.Consumer(key = self.__latitude_client_id, secret = self.__config.get(nick, 'latitude_client_secret'))
- except NoOptionError, error:
- sys.exit(error)
- try:
- self.__hostname = self.__config.get(nick, 'hostname')
- except NoOptionError:
- self.__hostname = ''
- ircbot.SingleServerIRCBot.__init__(self, servers, nick, 'Location Bot')
- if bot is None:
- self.__geocode_cache = {}
- self.__locations = []
- self.__logins = {}
- self.__nick = None
- self.__reloading = False
- else:
- self.__geocode_cache = bot.__geocode_cache
- irclibobj = self.ircobj.connections[0].irclibobj
- self.ircobj.connections[0] = bot.ircobj.connections[0]
- self.ircobj.connections[0].irclibobj = irclibobj
- self.channels = bot.channels
- self.connection = bot.connection
- self.__locations = bot.__locations
- self.__logins = bot.__logins
- self.__nick = bot.__nick
- self.__reloading = True
- self.__geocode_cache_lock = threading.Lock()
- self.__geocode_cache_clean_timer_lock = threading.Lock()
- self.__geocode_cache_clean_timer = None
- self.__latitude_granularities = frozenset(['city', 'best'])
- self.__latitude_timer_lock = threading.Lock()
- self.__latitude_timer = None
- self.__locations_lock = threading.Lock()
- self.__quiting = False
- self.__timeout = 5
- self.__variables = frozenset(['nick', 'secret', 'masks', 'channels', 'timezone', 'location', 'coordinates', 'latitude'])
- self.__geocode_variables = self.__variables.intersection(['location', 'coordinates'])
- self.__lists = self.__variables.intersection(['masks', 'channels'])
- self.__unsetable = self.__variables.difference(['nick', 'secret'])
- def __admin(self, nickmask):
- return _or(lambda admin: irclib.mask_matches(nickmask, admin), self.__admins)
- def __channel(self, nick, exclude = None):
- if exclude is not None:
- exclude = irclib.irc_lower(exclude)
- channels = map(lambda channel: channel[1], filter(lambda channel: irclib.irc_lower(channel[0]) == exclude, self.channels))
- else:
- channels = self.channels.values()
- return _or(lambda channel: channel.has_user(nick), channels)
- def __distance(self, distance):
- if distance is not None:
- return '%.1f m (%.1f ft)' % (distance, _meters_to_feet(distance))
- def __db(self):
- db = psycopg2.connect(self.__dsn)
- def point(value, cursor):
- if value is not None:
- return tuple(map(lambda a: float(a), re.match(r'^\(([^)]+),([^)]+)\)$', value).groups()))
- psycopg2.extensions.register_type(psycopg2.extensions.new_type((600,), 'point', point), db)
- return db, db.cursor()
- def __geocode(self, sensor, coordinates = None, location = None):
- parameters = {'sensor': 'true' if sensor else 'false'}
- with self.__geocode_cache_lock:
- if coordinates is not None:
- try:
- return self.__geocode_cache[coordinates][0]
- except KeyError:
- parameters['latlng'] = '%f,%f' % coordinates
- else:
- try:
- return self.__geocode_cache[coordinates][0]
- except KeyError:
- parameters['address'] = location
- geocode = json.load(urllib2.urlopen('http://maps.google.com/maps/api/geocode/json?' + urllib.urlencode(parameters), timeout = self.__timeout))
- status = geocode['status']
- if status != 'OK':
- if coordinates is not None and status == 'ZERO_RESULTS':
- return self.__geocode(sensor, location = parameters['latlng'])
- else:
- raise Exception(status)
- results = geocode['results']
- def _result():
- _location = result['geometry']['location']
- geocode = (_location['lat'], _location['lng']), result['formatted_address']
- with self.__geocode_cache_lock:
- self.__geocode_cache[coordinates if coordinates is not None else location] = (geocode, datetime.utcnow())
- return geocode
- types = frozenset([
- 'country',
- 'administrative_area_level_1',
- 'administrative_area_level_2',
- 'administrative_area_level_3',
- 'colloquial_area',
- 'locality',
- 'sublocality',
- 'neighborhood',
- ])
- for result in results:
- if not types.isdisjoint(result['types']):
- return _result()
- result = results[0]
- return _result()
- def __geocode_cache_clean(self):
- now = datetime.utcnow()
- try:
- while now < self.__geocode_cache_clean_next:
- time.sleep((self.__geocode_cache_clean_next - now).seconds)
- now = datetime.utcnow()
- except AttributeError:
- self.__geocode_cache_length = timedelta(hours = 1)
- self.__geocode_cache_clean_next = now.replace(minute = 2, second = 30, microsecond = 0) + self.__geocode_cache_length
- with self.__geocode_cache_lock:
- for location, (geocode, created) in self.__geocode_cache.items():
- if now - created >= self.__geocode_cache_length:
- del self.__geocode_cache[location]
- with self.__geocode_cache_clean_timer_lock:
- self.__geocode_cache_clean_timer = threading.Timer((self.__geocode_cache_clean_next - datetime.utcnow()).seconds, self.__latitude)
- self.__geocode_cache_clean_timer.start()
- def __heading(self, heading):
- if heading is not None:
- return (u'%.1f\xb0 (%s)' % (heading, _heading_to_direction(heading))).encode('latin1')
- def __help(self, connection, nick, admin, login, arguments):
- command = irclib.irc_lower(arguments.split(None, 1)[0].lstrip('!')) if arguments else None
- commands = {
- 'help': ('[command]', 'show this help message'),
- 'status': ('[nick]', 'show where everybody or a nick is'),
- }
- if not login:
- commands.update({
- 'login': ('[nick] [secret]', 'log in as nick with secret or using masks'),
- 'register': ('[nick] secret', 'register as nick with secret'),
- })
- else:
- commands.update({
- 'logout': ('', 'log out as nick'),
- 'set': ('[variable [value]]', 'display or set variables'),
- 'unset': ('variable', 'unset a variable'),
- })
- if admin:
- commands.update({
- 'join': ('channel', 'join a channel'),
- 'part': ('channel [message]', 'part from a channel'),
- 'quit': ('[message]', 'quit and do not come back'),
- 'reload': ('', 'reload with more up to date code'),
- 'restart': ('', 'quit and join running more up to date code'),
- 'say': ('nick|channel message', 'say message to nick or channel'),
- 'who': ('', 'show who is logged in'),
- })
- connection.privmsg(nick, '\x02command arguments description\x0f')
- def help(command, arguments, description):
- connection.privmsg(nick, '%-11s %-23s %s' % (command, arguments, description))
- if command in commands:
- help(command, *commands[command])
- else:
- for command, (arguments, description) in sorted(commands.iteritems()):
- help(command, arguments, description)
- def __join(self, connection, nick, arguments):
- try:
- channel = arguments.split(None, 1)[0]
- except IndexError:
- return self.__help(connection, nick, True, False, 'join')
- connection.join(channel)
- self.__channels.add(channel)
- self.__config.set(self._nickname, 'channels', ' '.join(self.__channels))
- self.__write()
- connection.privmsg(nick, 'successfully joined channel ("%s")' % channel)
- def __latitude(self, granularity = None, token = None, secret = None):
- if granularity is not None:
- response, content = oauth.Client(self.__latitude_consumer, oauth.Token(token, secret), timeout = self.__timeout).request('https://www.googleapis.com/latitude/v1/currentLocation?' + urllib.urlencode({'granularity': granularity}), 'GET')
- if int(response['status']) != 200:
- raise Exception(content.strip())
- data = json.loads(content)['data']
- coordinates = (data['latitude'], data['longitude'])
- return datetime.fromtimestamp(int(data['timestampMs']) / 1e3, pytz.utc), coordinates, data.get('accuracy'), data.get('speed'), data.get('heading'), data.get('altitude'), data.get('altitudeAccuracy'), self.__geocode(False, coordinates = coordinates)[1]
- now = datetime.utcnow()
- try:
- while now < self.__latitude_next:
- time.sleep((self.__latitude_next - now).seconds)
- now = datetime.utcnow()
- except AttributeError:
- pass
- self.__latitude_next = now.replace(minute = now.minute - now.minute % 5, second = 0, microsecond = 0) + timedelta(minutes = 5)
- try:
- db, cursor = self.__db()
- cursor.execute('select nick, channels, latitude.granularity, location, coordinates, accuracy, speed, heading, altitude, altitude_accuracy, updated, token, latitude.secret from locationbot.nick join locationbot.latitude using (id)')
- for nick, channels, granularity, old_location, old_coordinates, old_accuracy, old_speed, old_heading, old_altitude, old_altitude_accuracy, old_updated, token, secret in cursor.fetchall():
- try:
- updated, coordinates, accuracy, speed, heading, altitude, altitude_accuracy, location = self.__latitude(granularity, token, secret)
- except Exception, error:
- traceback.print_exc()
- continue
- cursor.execute('update locationbot.nick set granularity = %s, location = %s, coordinates = point %s, accuracy = %s, speed = %s, heading = %s, altitude = %s, altitude_accuracy = %s, updated = %s where nick = %s', (granularity, location, coordinates, accuracy, speed, heading, altitude, altitude_accuracy, updated, nick))
- db.commit()
- self.__location(nick, channels, granularity, old_location, location, old_coordinates, coordinates, old_accuracy, accuracy, old_speed, speed, old_heading, heading, old_altitude, altitude, old_altitude_accuracy, altitude_accuracy, old_updated, updated)
- except psycopg2.Error, error:
- traceback.print_exc()
- with self.__latitude_timer_lock:
- self.__latitude_timer = threading.Timer((self.__latitude_next - datetime.utcnow()).seconds, self.__latitude)
- self.__latitude_timer.start()
- def __location(self, nick, channels, granularity, old_location, new_location, old_coordinates, new_coordinates, old_accuracy, new_accuracy, old_speed, new_speed, old_heading, new_heading, old_altitude, new_altitude, old_altitude_accuracy, new_altitude_accuracy, old_updated, new_updated):
- def calc_distance(lat1, lon1, lat2, lon2): # returns meters
- nauticalMilePerLat = 60.00721
- nauticalMilePerLongitude = 60.10793
- rad = math.pi / 180.0
- milesPerNauticalMile = 1.15078
- metersPerNauticalMile = 1852
- yDistance = (lat2 - lat1) * nauticalMilePerLat
- xDistance = (math.cos(lat1 * rad) + math.cos(lat2 * rad)) * \
- (lon2 - lon1) * (nauticalMilePerLongitude / 2)
- distance = math.sqrt( yDistance**2 + xDistance**2 )
- return distance * metersPerNauticalMile
- def make_update_noise():
- with self.__locations_lock:
- for channel in self.__channels.intersection(channels):
- self.__locations.append((nick, channel, granularity, new_location, new_coordinates, new_accuracy, new_speed, new_heading, new_altitude, new_altitude_accuracy))
- if channels:
- if new_location != old_location: # old method
- return make_update_noise()
- if old_accuracy is not None and new_accuracy is not None:
- distance = calc_distance(old_coordinates[0], old_coordinates[1],
- new_coordinates[0], new_coordinates[1])
- avg_radius = (old_accuracy + new_accuracy) / 2
- if distance > avg_radius:
- return make_update_noise()
- min_radius = min(old_accuracy, new_accuracy)
- if distance > min_radius and new_accuracy < old_accuracy:
- return make_update_noise()
- def __login(self, connection, nickmask, nick, arguments = ''):
- login = nick
- if connection is not None:
- arguments = arguments.split(None, 1)
- if len(arguments) == 2:
- login, secret = arguments
- elif len(arguments) == 1:
- secret = arguments[0]
- else:
- secret = None
- else:
- secret = None
- if nick in self.__logins:
- login = self.__logins[nick][0]
- if connection is not None:
- return connection.privmsg(nick, 'already logged in as "%s"' % login)
- return login
- db, cursor = self.__db()
- def success():
- connection.privmsg(nick, 'successfully logged in as "%s"' % login)
- if secret is not None:
- cursor.execute('select true from locationbot.nick where nick = %s and secret = md5(%s)', (login, secret))
- if cursor.rowcount == 1:
- self.__logins[nick] = (login, nickmask)
- return success()
- cursor.execute('select nick, masks from locationbot.nick where nick in (%s, %s)', (login, secret if len(arguments) != 2 else None))
- for login, masks in cursor.fetchall():
- if _or(lambda mask: irclib.mask_matches(nickmask, mask), masks):
- self.__logins[nick] = (login, nickmask)
- return success() if connection else login
- if connection is not None:
- return connection.privmsg(nick, 'failed to log in as "%s"' % login)
- def __logout(self, connection, nick):
- connection.privmsg(nick, 'logged out as "%s"' % self.__logins.pop(nick)[0])
- def __part(self, connection, nick, arguments):
- arguments = arguments.split(None, 1)
- if len(arguments) == 2:
- channel, message = arguments
- message = ':' + message
- elif len(arguments) == 1:
- channel = arguments[0]
- message = ''
- else:
- return self.__help(connection, nick, True, False, 'part')
- if channel in self.__channels:
- connection.part(channel, message)
- self.__channels.remove(channel)
- self.__config.set(self._nickname, 'channels', ' '.join(self.__channels))
- self.__write()
- connection.privmsg(nick, 'successfully parted channel ("%s")' % channel)
- else:
- connection.privmsg(nick, 'not in channel ("%s")' % channel)
- def __quit(self, connection, nick, arguments):
- self.__reloading = True
- self.__quiting = True
- connection.privmsg(nick, 'quiting')
- self.disconnect(arguments)
- def __register(self, connection, nick, arguments):
- arguments = arguments.split(None, 1)
- if len(arguments) == 2:
- login, secret = arguments
- elif len(arguments) == 1:
- login = nick
- secret = arguments[0]
- else:
- return self.__help(connection, nick, False, False, 'register')
- db, cursor = self.__db()
- try:
- cursor.execute('insert into locationbot.nick (nick, secret) values (%s, md5(%s))', (login, secret))
- db.commit()
- except psycopg2.IntegrityError:
- return connection.privmsg(nick, 'nick ("%s") is already registered' % login)
- connection.privmsg(nick, 'nick ("%s") sucessfully registered' % login)
- def __reload(self, connection, nick):
- self.__nick = nick
- self.__reloading = True
- connection.privmsg(nick, 'reloading')
- def __restart(self, connection):
- connection.disconnect('restarting')
- os.execvp(sys.argv[0], sys.argv)
- def __say(self, connection, nick, arguments):
- try:
- nick_channel, message = arguments.split(None, 1)
- except ValueError:
- return self.__help(connection, nick, True, False, 'say')
- if irclib.is_channel(nick_channel):
- if nick_channel not in self.channels:
- return connection.privmsg(nick, 'not in channel ("%s")' % nick_channel)
- elif not self.__channel(nick_channel):
- return connection.privmsg(nick, 'nick ("%s") not in channel(s)' % nick_channel)
- elif nick_channel == connection.get_nickname():
- return connection.privmsg(nick, 'nice try')
- connection.privmsg(nick_channel, message)
- connection.privmsg(nick, 'successfully sent message ("%s") to nick/channel ("%s")' % (message, nick_channel))
- def __set(self, connection, nickmask, nick, login, arguments):
- arguments = arguments.split(None, 1)
- if len(arguments) == 2:
- variable, value = arguments
- elif len(arguments) == 1:
- variable = arguments[0]
- value = None
- else:
- variable = None
- value = None
- if variable is not None and variable not in self.__variables:
- return self.__unknown_variable(connection, nick, variable)
- db, cursor = self.__db()
- if value is None:
- variables = sorted(self.__variables) if variable is None else [variable]
- cursor.execute('select ' + ', '.join(map(lambda variable: "'%s'" % ('*' * 8) if variable == 'secret' else 'latitude.granularity, latitude.authorized' if variable == 'latitude' else variable, variables)) + ' from locationbot.nick left join locationbot.latitude using (id) where nick = %s', (login,))
- values = list(cursor.fetchone())
- try:
- index = variables.index('latitude')
- values[index:index + 2] = ['%s (%s)' % (values[index], 'authorized' if values[index + 1] else 'unauthorized')]
- except ValueError:
- pass
- connection.privmsg(nick, '\x02variable value\x0f')
- for variable, value in zip(variables, values):
- connection.privmsg(nick, '%-11s %s' % (variable, ' '.join(value) if isinstance(value, list) else '%f,%f' % value if isinstance(value, tuple) else value))
- else:
- def invalid(value, variable = variable):
- connection.privmsg(nick, 'invalid %s ("%s")' % (variable, value))
- if variable in self.__lists:
- value = value.split()
- if variable == 'channels':
- for channel in value:
- if not irclib.is_channel(channel) or channel not in self.__channels:
- return invalid(channel, 'channel')
- elif variable == 'masks':
- _mask = re.compile('^.+!.+@.+$')
- for mask in value:
- if not _mask.match(mask):
- return invalid(mask, 'mask')
- elif variable == 'latitude':
- if value in self.__latitude_granularities:
- response, content = oauth.Client(self.__latitude_consumer, timeout = self.__timeout).request('https://www.google.com/accounts/OAuthGetRequestToken', 'POST', urllib.urlencode({
- 'scope': 'https://www.googleapis.com/auth/latitude',
- 'oauth_callback': 'oob',
- }))
- if int(response['status']) != 200:
- raise Exception(content.strip())
- authorized = False
- else:
- cursor.execute('select channels, location, latitude.granularity, token, latitude.secret from locationbot.nick join locationbot.latitude using (id) where nick = %s and authorized = false', (login,))
- if cursor.rowcount == 0:
- return invalid(value)
- channels, old_location, granularity, token, secret = cursor.fetchone()
- token = oauth.Token(token, secret)
- token.set_verifier(value)
- response, content = oauth.Client(self.__latitude_consumer, token, timeout = self.__timeout).request('https://www.google.com/accounts/OAuthGetAccessToken', 'GET')
- status = int(response['status'])
- if status == 400:
- return invalid(value)
- elif status != 200:
- raise Exception(content.strip())
- authorized = True
- data = dict(urlparse.parse_qsl(content))
- token = data['oauth_token']
- secret = data['oauth_token_secret']
- if not authorized:
- connection.privmsg(nick, 'go to https://www.google.com/latitude/apps/OAuthAuthorizeToken?' + urllib.urlencode({
- 'domain': self.__latitude_client_id,
- 'granularity': value,
- 'oauth_token': token,
- }))
- elif variable in self.__geocode_variables:
- cursor.execute('select channels, location, latitude.granularity, token, latitude.secret, authorized from locationbot.nick left join locationbot.latitude using (id) where nick = %s', (login,))
- channels, old_location, granularity, token, secret, authorized = cursor.fetchone()
- if variable == 'location':
- coordinates = None
- granularity = 'city'
- location = value
- else:
- coordinates = value.split(None, 1)
- if len(coordinates) == 1:
- coordinates = coordinates[0].split(',', 1)
- try:
- coordinates = tuple(map(lambda a: float(a), coordinates))
- except ValueError:
- return invalid(value)
- for coordinate in coordinates:
- if not -180.0 <= coordinate <= 180.0:
- return invalid(value)
- location = None
- geocode = self.__geocode(False, coordinates, location)
- new_location = geocode[1]
- if variable == 'location':
- coordinates = geocode[0]
- value = new_location
- else:
- value = coordinates
- if authorized:
- response, content = oauth.Client(self.__latitude_consumer, oauth.Token(token, secret), timeout = self.__timeout).request('https://www.googleapis.com/latitude/v1/currentLocation?' + urllib.urlencode({'granularity': granularity}), 'POST', json.dumps({'data': {
- 'kind': 'latitude#location',
- 'latitude': coordinates[0],
- 'longitude': coordinates[1],
- }}), {'Content-Type': 'application/json'})
- if int(response['status']) != 200:
- raise Exception(content.strip())
- accuracy = speed = heading = altitude = altitude_accuracy = None
- elif variable == 'nick':
- _nick = value.split(None, 1)
- if len(_nick) != 1:
- return invalid(value)
- elif variable == 'timezone':
- if value not in pytz.all_timezones_set:
- return invalid(value)
- try:
- if variable in self.__geocode_variables:
- cursor.execute('update locationbot.nick set granularity = %s, location = %s, coordinates = point %s, accuracy = %s, speed = %s, heading = %s, altitude = %s, altitude_accuracy = %s, updated = now() where nick = %s', (granularity, new_location, coordinates, accuracy, speed, heading, altitude, altitude_accuracy, login))
- elif variable == 'latitude':
- if authorized:
- cursor.execute('update locationbot.latitude set token = %s, secret = %s, authorized = %s from locationbot.nick where latitude.id = nick.id and nick = %s', (token, secret, authorized, login))
- else:
- cursor.execute('delete from locationbot.latitude using locationbot.nick where latitude.id = nick.id and nick = %s', (login,))
- cursor.execute('insert into locationbot.latitude (id, granularity, token, secret, authorized) select id, %s, %s, %s, %s from locationbot.nick where nick = %s', (value, token, secret, authorized, login))
- else:
- cursor.execute('update locationbot.nick set ' + variable + ' = ' + ('md5(%s)' if variable == 'secret' else '%s') + ' where nick = %s', (value, login))
- db.commit()
- except psycopg2.IntegrityError:
- if variable == 'nick':
- return connection.privmsg(nick, 'nick ("%s") is already registered' % value)
- raise
- 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))
- if variable == 'nick':
- self.__logins[nick] = (value, nickmask)
- elif variable in self.__geocode_variables or variable == 'latitude' and authorized:
- if variable == 'latitude':
- updated, coordinates, accuracy, speed, heading, altitude, altitude_accuracy, new_location = self.__latitude(granularity, token, secret)
- cursor.execute('update locationbot.nick set granularity = %s, location = %s, coordinates = point %s, accuracy = %s, speed = %s, heading = %s, altitude = %s, altitude_accuracy = %s, updated = %s where nick = %s', (granularity, new_location, coordinates, accuracy, speed, heading, altitude, altitude_accuracy, updated, login))
- self.__location(login, channels, granularity, old_location, new_location, coordinates, accuracy, speed, heading, altitude, altitude_accuracy)
- def __speed(self, speed):
- if speed is not None:
- return '%.1f m/s (%.1f mph)' % (speed, _meters_per_second_to_miles_per_hour(speed))
- def __status(self, connection, nick, login, arguments):
- _nick = arguments.split(None, 1)[0] if arguments else None
- db, cursor = self.__db()
- cursor.execute('select nick, granularity, location, coordinates, accuracy, speed, heading, altitude, altitude_accuracy, updated from locationbot.nick where ' + ('nick = %s and ' if _nick is not None else '') + 'location is not null order by updated desc', (_nick,))
- if cursor.rowcount == 0:
- return connection.privmsg(nick, 'no location information for ' + ('"%s"' % _nick if _nick is not None else 'anybody'))
- locations = cursor.fetchall()
- if login is not None:
- cursor.execute('select timezone from locationbot.nick where nick = %s and timezone is not null', (login,))
- timezone = pytz.timezone(cursor.fetchone()[0]) if cursor.rowcount == 1 else pytz.utc
- else:
- timezone = pytz.utc
- connection.privmsg(nick, '\x02%-24s%-36s%-24s%-24s%-16s%-24s%-24s%-24s%s\x0f' % ('nick', 'location', 'accuracy', 'speed', 'heading', 'altitude', 'altitude accuracy', 'when', 'map'))
- for _nick, granularity, location, coordinates, accuracy, speed, heading, altitude, altitude_accuracy, updated in locations:
- connection.privmsg(nick, '%-23s %-35s %-23s %-23s %-15s %-23s %-23s %-23s %s' % (_nick, location, self.__distance(accuracy), self.__speed(speed), self.__heading(heading), self.__distance(altitude), self.__distance(altitude_accuracy), timezone.normalize(updated.astimezone(timezone)).strftime('%Y-%m-%d %H:%M %Z'), self.__url(_nick, granularity, location, coordinates)))
- def __unknown(self, connection, nick, command):
- connection.privmsg(nick, 'unknown command ("%s"); try "help"' % command)
- def __unknown_variable(self, connection, nick, variable):
- connection.privmsg(nick, 'unknown variable ("%s")' % variable)
- def __unset(self, connection, nick, login, arguments):
- try:
- variable = irclib.irc_lower(arguments.split(None, 1)[0])
- except IndexError:
- return self.__help(connection, nick, False, login, 'unset')
- if variable not in self.__unsetable:
- if variable in self.__variables:
- return connection.privmsg(nick, 'variable ("%s") is not unsetable' % variable)
- return self.__unknown_variable(connection, nick, variable)
- db, cursor = self.__db()
- cursor.execute('update locationbot.nick set ' + ('location = null, coordinates = null, updated = null' if variable in self.__geocode_variables else variable + ' = null') + ' where nick = %s and ' + variable + ' is not null', (login,))
- db.commit()
- connection.privmsg(nick, 'variable ("%s") %s unset' % (variable, 'successfuly' if cursor.rowcount == 1 else 'already'))
- def __url(self, nick, granularity, location, coordinates):
- if granularity == 'best':
- location = '%f,%f' % coordinates
- return 'http://maps.google.com/maps?' + re.sub('%(2[cC])', lambda match: chr(int(match.group(1), 16)), urllib.urlencode({'q': '%s (%s)' % (location, nick)}))
- def __who(self, connection, nick):
- if self.__logins:
- connection.privmsg(nick, '\x02login nick nick mask\x0f')
- for login in sorted(self.__logins.values()):
- connection.privmsg(nick, '%-23s %s' % login)
- else:
- connection.privmsg(nick, 'nobody logged in')
- def __write(self):
- with open('locationbot.ini', 'w') as config:
- self.__config.write(config)
- def _connect(self):
- if len(self.server_list[0]) != 2:
- ssl = self.server_list[0][2]
- else:
- ssl = False
- if len(self.server_list[0]) == 4:
- password = self.server_list[0][3]
- else:
- password = None
- try:
- with warnings.catch_warnings():
- warnings.filterwarnings('ignore', r'socket\.ssl\(\) is deprecated\. Use ssl\.wrap_socket\(\) instead\.', DeprecationWarning)
- self.connect(self.server_list[0][0], self.server_list[0][1], self._nickname, password, getpass.getuser(), self._realname, localaddress = self.__hostname, ssl = ssl)
- except irclib.ServerConnectionError:
- pass
- def disconnect(self, message = 'oh no!'):
- ircbot.SingleServerIRCBot.disconnect(self, message)
- def error(self, error):
- traceback.print_exc()
- self.connection.privmsg(self.__nick, 'an error occured')
- def get_version(self):
- return 'locationbot ' + sys.platform
- def on_kick(self, connection, event):
- nick = event.arguments()[0]
- if not self.__channel(nick, event.target()):
- self.__logins.pop(nick, None)
- def on_nick(self, connection, event):
- nickmask = event.source()
- login = self.__logins.pop(irclib.nm_to_n(nickmask), (None,))[0]
- if login is not None:
- nick = event.target()
- self.__logins[nick] = (login, nick + '!' + nm_to_uh(nickmask))
- def on_nicknameinuse(self, connection, event):
- connection.nick(connection.get_nickname() + '_')
- def on_part(self, connection, event):
- nick = irclib.nm_to_n(event.source())
- if not self.__channel(nick, event.target()):
- self.__logins.pop(nick, None)
- def on_privmsg(self, connection, event):
- nickmask = event.source()
- nick = irclib.nm_to_n(nickmask)
- admin = self.__admin(nickmask)
- if not admin and not self.__channel(nick):
- return
- try:
- login = self.__login(None, nickmask, nick)
- try:
- command, arguments = event.arguments()[0].split(None, 1)
- except ValueError:
- command = event.arguments()[0].strip()
- arguments = ''
- command = irclib.irc_lower(command.lstrip('!'))
- if command == 'help':
- self.__help(connection, nick, admin, login, arguments)
- elif command == 'login':
- self.__login(connection, nickmask, nick, arguments)
- elif command == 'status':
- self.__status(connection, nick, login, arguments)
- elif not login and command == 'register':
- self.__register(connection, nick, arguments)
- elif login and command == 'logout':
- self.__logout(connection, nick)
- elif login and command == 'set':
- self.__set(connection, nickmask, nick, login, arguments)
- elif login and command == 'unset':
- self.__unset(connection, nick, login, arguments)
- elif admin and command == 'join':
- self.__join(connection, nick, arguments)
- elif admin and command == 'part':
- self.__part(connection, nick, arguments)
- elif admin and command == 'quit':
- self.__quit(connection, nick, arguments)
- elif admin and command == 'reload':
- self.__reload(connection, nick)
- elif admin and command == 'restart':
- self.__restart(connection)
- elif admin and command == 'say':
- self.__say(connection, nick, arguments)
- elif admin and command == 'who':
- self.__who(connection, nick)
- else:
- self.__unknown(connection, nick, command)
- except Exception, error:
- traceback.print_exc()
- connection.privmsg(nick, 'an error occurred')
- def on_quit(self, connection, event):
- self.__logins.pop(irclib.nm_to_n(event.source()), None)
- def on_welcome(self, connection, event):
- for channel in self.__channels:
- connection.join(channel)
- def start(self):
- latitude_thread = threading.Thread(None, self.__latitude)
- latitude_thread.daemon = True
- latitude_thread.start()
- geocode_cache_clean_thread = threading.Thread(None, self.__geocode_cache_clean)
- geocode_cache_clean_thread.daemon = True
- geocode_cache_clean_thread.start()
- if not self.__reloading:
- self._connect()
- else:
- self.__reloading = False
- for channel in self.__channels.symmetric_difference(self.channels.keys()):
- if channel in self.__channels:
- self.connection.join(channel)
- else:
- self.connection.part(channel)
- ping_next = datetime.utcnow() + timedelta(minutes = 1)
- while not self.__reloading:
- try:
- now = datetime.utcnow()
- if now >= ping_next:
- self.connection.ping(self.connection.server)
- ping_next = now + timedelta(minutes = 1)
- if self.__locations_lock.acquire(False):
- if self.__locations and self.__channels.issubset(self.channels.keys()):
- for nick, channel, granularity, location, coordinates, accuracy, speed, heading, altitude, altitude_accuracy in self.__locations:
- aux = []
- if accuracy is not None:
- aux.append('accuracy: ' + self.__distance(accuracy))
- if speed is not None:
- aux.append('speed: ' + self.__speed(speed))
- if heading is not None:
- aux.append(u'heading: ' + self.__heading(heading))
- if altitude is not None:
- aux.append('altitude: ' + self.__distance(altitude))
- if altitude_accuracy is not None:
- aux.append('altitude accuracy: ' + self.__distance(altitude_accuracy))
- if aux:
- aux = ' [%s]' % ', '.join(aux)
- else:
- aux = ''
- self.connection.notice(channel, '%s is in %s%s %s' % (nick, location, aux, self.__url(nick, granularity, location, coordinates)))
- self.__locations = []
- self.__locations_lock.release()
- except irclib.ServerNotConnectedError:
- self.jump_server()
- self.ircobj.process_once(0.2)
- latitude_thread.join()
- geocode_cache_clean_thread.join()
- with self.__latitude_timer_lock:
- if self.__latitude_timer is not None:
- self.__latitude_timer.cancel()
- self.__latitude_timer.join()
- with self.__geocode_cache_clean_timer_lock:
- if self.__geocode_cache_clean_timer is not None:
- self.__geocode_cache_clean_timer.cancel()
- self.__geocode_cache_clean_timer.join()
- return not self.__quiting
- def success(self):
- self.connection.privmsg(self.__nick, 'successfully reloaded')
- class _AddressMask(object):
- def __init__(self, function):
- self.function = function
- def __call__(self, nick, mask):
- if not self.function(nick, mask):
- nick, address = nick.split('@', 1)
- try:
- host = socket.gethostbyaddr(address)[0]
- except socket.herror:
- pass
- else:
- if host != address:
- return self.function(nick + '@' + host, mask)
- return False
- return True
- irclib.mask_matches = _AddressMask(reload(irclib).mask_matches)
- def _meters_per_second_to_miles_per_hour(meters_per_second):
- return meters_per_second * 2.23693629
- def _meters_to_feet(meters):
- return meters * 3.2808399
- def _heading_to_direction(heading):
- heading %= 360
- if 348.75 < heading or heading <= 11.25:
- return 'N'
- elif 11.25 < heading <= 33.75:
- return 'NNE'
- elif 33.75 < heading <= 56.25:
- return 'NE'
- elif 56.25 < heading <= 78.75:
- return 'ENE'
- elif 78.75 < heading <= 101.25:
- return 'E'
- elif 101.25 < heading <= 123.75:
- return 'ESE'
- elif 123.75 < heading <= 146.25:
- return 'SE'
- elif 146.25 < heading <= 168.75:
- return 'SSE'
- elif 168.75 < heading <= 191.25:
- return 'S'
- elif 191.25 < heading <= 213.75:
- return 'SSW'
- elif 213.75 < heading <= 236.25:
- return 'SW'
- elif 236.25 < heading <= 258.75:
- return 'WSW'
- elif 258.75 < heading <= 281.25:
- return 'W'
- elif 281.25 < heading <= 303.75:
- return 'WNW'
- elif 303.75 < heading <= 326.25:
- return 'NW'
- else:
- return 'NNW'
- def _or(function, values):
- if values:
- for value in values:
- if function(value):
- return True
- return False
- if __name__ == '__main__':
- os.chdir(os.path.abspath(os.path.dirname(__file__)))
- pid = os.fork()
- if pid != 0:
- with open('locationbot.pid', 'w') as _file:
- _file.write('%u\n' % pid)
- sys.exit(0)
- sys.stdin = open('/dev/null')
- sys.stdout = open('locationbot.log', 'a', 1)
- sys.stderr = sys.stdout
- import locationbot
- bot = locationbot.LocationBot()
- try:
- while bot.start():
- import locationbot
- try:
- bot = reload(locationbot).LocationBot(bot)
- except (ImportError, SyntaxError), error:
- bot.error(error)
- else:
- bot.success()
- except KeyboardInterrupt:
- bot.disconnect()
- os.unlink('locationbot.pid')
|