12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223 |
- #!/usr/bin/env python
- # Location Bot
- #
- # Douglas Thrift
- #
- # locationbot.py
- import apiclient.discovery
- from ConfigParser import NoOptionError, SafeConfigParser
- from datetime import datetime, timedelta
- import functools
- import getpass
- import inspect
- import ircbot
- import irclib
- import oauth2client.client
- import oauth2client.tools
- 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
- def shortcutting_or(function, values):
- if values:
- for value in values:
- if function(value):
- return True
- return False
- def address_mask(function):
- @functools.wraps(function)
- def address_mask(nick, mask):
- if not function(nick, mask):
- nick, address = nick.split('@', 1)
- try:
- host = socket.gethostbyaddr(address)[0]
- except socket.herror:
- traceback.print_exc()
- else:
- if host != address:
- return function(nick + '@' + host, mask)
- return False
- return True
- return address_mask
- def encode(function):
- @functools.wraps(function)
- def encode(self, target, text):
- return function(self, target, text.encode('utf8'))
- return encode
- irclib.mask_matches = address_mask(reload(irclib).mask_matches)
- irclib.ServerConnection.notice = encode(irclib.ServerConnection.notice)
- irclib.ServerConnection.privmsg = encode(irclib.ServerConnection.privmsg)
- irclib.ServerConnection.privmsg_many = encode(irclib.ServerConnection.privmsg_many)
- class ThreadTimerServiceMixin(object):
- def start(self):
- self.thread.daemon = True
- self.timer = None
- self.timer_lock = threading.Lock()
- self.thread.start()
- def stop(self):
- self.thread.join()
- with self.timer_lock:
- if self.timer is not None:
- self.timer.cancel()
- self.timer.join()
- class Geocoder(ThreadTimerServiceMixin):
- CACHE_DURATION = timedelta(hours = 1)
- def __init__(self, cache = None):
- if cache is None:
- self.cache = {}
- else:
- self.cache = cache
- self.cache_lock = threading.Lock()
- self.thread = threading.Thread(target = self.cache_clean)
- def geocode(self, sensor, coordinates = None, location = None):
- parameters = {'sensor': 'true' if sensor else 'false'}
- with self.cache_lock:
- if coordinates is not None:
- try:
- return self.cache[coordinates][0]
- except KeyError:
- parameters['latlng'] = '%f,%f' % coordinates
- else:
- try:
- return self.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 = 5))
- 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 cache_clean(self):
- now = datetime.utcnow()
- try:
- while now < self.cache_clean_next:
- time.sleep((self.cache_clean_next - now).seconds)
- now = datetime.utcnow()
- except AttributeError:
- pass
- self.cache_clean_next = now.replace(minute = 2, second = 30, microsecond = 0) + self.CACHE_DURATION
- with self.cache_lock:
- for location, (geocode, created) in self.cache.items():
- if now - created >= self.CACHE_DURATION:
- del self.cache[location]
- with self.timer_lock:
- self.timer = threading.Timer((self.cache_clean_next - datetime.utcnow()).seconds, self.cache_clean)
- self.timer.start()
- class Location(object):
- VALUES = ('nick', 'granularity', 'location', 'coordinates', 'accuracy', 'speed', 'heading', 'altitude', 'altitude_accuracy', 'updated')
- def __init__(self, **values):
- if 'row' in values:
- self.values = dict(zip(VALUES, values[row]))
- else:
- self.values = values
- @property
- def url(self):
- if self.granularity == 'best':
- location = '%f,%f' % self.values['coordinates']
- else:
- location = self.values['location']
- return 'http://maps.google.com/maps?' + re.sub('%(2[89cC])', lambda match: chr(int(match.group(1), 16)), urllib.urlencode({'q': '%s (%s)' % (location, self.nick)}))
- @property
- def _accuracy(self):
- return self.values.get('accuracy')
- @property
- def _latitude(self):
- return self.values['coordinates'][0]
- @property
- def _longitude(self):
- return self.values['coordinates'][1]
- def has_changed_from(self, previous):
- if self.location != previous.location:
- return True
- distance = self - previous
- if distance < 60:
- return False
- if self._accuracy is not None and previous._accuracy is not None:
- if distance > (self._accuracy + previous._accuracy) / 2:
- return True
- if distance > min(self._accuracy, previous._accuracy) and self._accuracy < previous._accuracy:
- return True
- return False
- def __get_attr__(self, name):
- if name not in VALUES:
- raise AttributeError("'Location' object has no attribute '%s'" % name)
- value = self.values.get(name)
- if info in ('accuracy', 'altitude', 'altitude_accuracy'):
- return self.distance(value)
- elif info in ('speed',):
- return self.speed(value)
- elif info in ('heading',):
- return self.heading(value)
- else:
- return unicode(value)
- def __unicode__(self):
- try:
- venue = ' at %s' % self.values['venue']
- except KeyError:
- venue = ''
- aux = []
- for name in ('accuracy', 'speed', 'heading', 'altitude', 'altitude_accuracy'):
- if name in self.values:
- aux.append('%s: %s' % (name.replace('_', ' '), getattr(self, name)))
- if aux:
- aux = ' [%s]' % ', '.join(aux)
- else:
- aux = ''
- return '%s is%s in %s%s %s' % (self.nick, venue, self.location, aux, self.url)
- def __sub__(self, other):
- x = (math.cos(self.degrees_to_radians(self._latitude)) + math.cos(self.degrees_to_radians(other._latitude))) * self.longitude_to_nautical_miles(self._longitude - other._longitude) / 2
- y = self.latitude_to_nautical_miles(self._latitude - other._latitude)
- return self.nautical_miles_to_meters(abs(x) ** 2 + abs(y) ** 2)
- @classmethod
- def distance(cls, distance):
- if distance is not None:
- return '%.1f m (%.1f ft)' % (distance, cls.meters_to_feet(distance))
- @classmethod
- def speed(cls, speed):
- if speed is not None:
- return '%.1f m/s (%.1f mph)' % (speed, cls.meters_per_second_to_miles_per_hour(speed))
- @classmethod
- def heading(cls, heading):
- if heading is not None:
- return u'%.1f\xb0 (%s)' % (heading, cls.heading_to_direction(heading))
- @staticmethod
- def degrees_to_radians(degrees):
- return degrees * math.pi / 180
- @staticmethod
- def latitude_to_nautical_miles(latitude):
- return latitude * 60.00721
- @staticmethod
- def longitude_to_nautical_miles(longitude):
- return longitude * 60.10793
- @staticmethod
- def nautical_miles_to_meters(nautical_miles):
- return nauticla_miles * 1852
- @staticmethod
- def meters_to_feet(meters):
- return meters * 3.2808399
- @staticmethod
- def meters_per_second_to_miles_per_hour(meters_per_second):
- return meters_per_second * 2.23693629
- @staticmethod
- 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'
- LOGIN = 0b01
- LOGOUT = 0b10
- ALWAYS = 0b11
- CHANNEL = 0b01
- PRIVATE = 0b10
- BOTH = 0b11
- class Command(object):
- def __init__(self, admin, state, access, arguments, description, function):
- self.admin = admin
- self.state = state
- self.access = access
- self.arguments = arguments
- self.description = description
- self.function = function
- @property
- def is_admin(self):
- return self.admin
- @property
- def is_login(self):
- return bool(self.state & LOGIN)
- @property
- def is_logout(self):
- return bool(self.state & LOGOUT)
- @property
- def is_channel(self):
- return bool(self.access & CHANNEL)
- @property
- def is_private(self):
- return bool(self.access & PRIVATE)
- def __call__(self, *args, **kwargs):
- self.function(*args, **kwargs)
- def command(admin, state, access, arguments, description):
- def command(function):
- cls = inspect.getouterframes(inspect.currentframe())[1][0].f_locals
- cls.setdefault('commands', {})[function.func_name] = Command(admin, state, access, arguments, description, function)
- return function
- return command
- 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')
- 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.geocoder = Geocoder()
- self.__locations = []
- self.__logins = {}
- self.__nick = None
- self.__reloading = False
- else:
- self.geocoder = Geocoder(bot.geocoder.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.__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 shortcutting_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 shortcutting_or(lambda channel: channel.has_user(nick), channels)
- 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()
- @command(False, ALWAYS, PRIVATE, '[command]', 'show this help message')
- def help(self, connection, nick, **kwargs):
- arguments = kwargs.get('arguments')
- command = irclib.irc_lower(arguments.split(None, 1)[0].lstrip('!')) if arguments else None
- connection.privmsg(nick, '\x02command arguments description\x0f')
- def help(name, command):
- connection.privmsg(nick, '%-11s %-23s %s' % (name, command.arguments, command.description))
- if command in self.commands:
- help(command, self.commands[command])
- else:
- for name, command in sorted(self.commands.iteritems()):
- help(name, command)
- @command(False, ALWAYS, BOTH, '[nick]', 'show where everybody or a nick is')
- def status(self, connection, nick, **kwargs):
- pass
- @command(False, LOGOUT, PRIVATE, '[nick] [secret]', 'log in as nick with secret or using masks')
- def login(self, connection, nick, **kwargs):
- pass
- @command(False, LOGOUT, PRIVATE, '[nick] [secret', 'register as nick with secret')
- def register(self, connection, nick, **kwargs):
- pass
- @command(False, LOGIN, PRIVATE, '', 'log out as nick')
- def logout(self, connection, nick, **kwargs):
- pass
- @command(False, LOGIN, PRIVATE, '[variable [value]]', 'display or set variables')
- def set(self, connection, nick, **kwargs):
- pass
- @command(False, LOGIN, PRIVATE, 'variable', 'unset a variable')
- def unset(self, connection, nick, **kwargs):
- pass
- @command(True, ALWAYS, PRIVATE, 'channel', 'join a channel')
- def join(self, connection, nick, **kwargs):
- pass
- @command(True, ALWAYS, PRIVATE, 'channel [message]', 'part from a channel')
- def part(self, connection, nick, **kwargs):
- pass
- @command(True, ALWAYS, PRIVATE, '[message]', 'quit and do not come back')
- def quit(self, connection, nick, **kwargs):
- pass
- @command(True, ALWAYS, PRIVATE, '', 'reload with more up to date code')
- def reload(self, connection, nick, **kwargs):
- pass
- @command(True, ALWAYS, PRIVATE, '', 'quit and join running more up to date code')
- def restart(self, connection, nick, **kwargs):
- pass
- @command(True, ALWAYS, PRIVATE, 'nick|channel message', 'say message to nick or channel')
- def say(self, connection, nick, **kwargs):
- pass
- @command(True, ALWAYS, PRIVATE, '', 'show who is logged in')
- def who(self, connection, nick, **kwargs):
- pass
- def __help(self, connection, nick, admin, login, arguments):
- self.help(connection, nick, admin = admin, login = login, arguments = arguments)
- 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.geocoder.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 KeyError, error:
- print nick, error
- continue
- 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 __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 shortcutting_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.geocoder.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, None, coordinates, None, accuracy, None, speed, None, heading, None, altitude, None, altitude_accuracy, None, updated)
- 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 + '!' + irclib.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()
- services = [self.geocoder]
- for service in services:
- service.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()
- with self.__latitude_timer_lock:
- if self.__latitude_timer is not None:
- self.__latitude_timer.cancel()
- self.__latitude_timer.join()
- for service in services:
- service.stop()
- return not self.__quiting
- def success(self):
- self.connection.privmsg(self.__nick, 'successfully reloaded')
- 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():
- try:
- bot = reload(locationbot).LocationBot(bot)
- except (ImportError, SyntaxError), error:
- bot.error(error)
- else:
- bot.success()
- except KeyboardInterrupt:
- bot.disconnect()
- os.unlink('locationbot.pid')
- # vim: noexpandtab tabstop=4
|