def __init__(self,irc): self.__parent = super(SeLoger, self) self.__parent.__init__(irc) self.backend = SqliteSeLogerDB(self.log) self.gettingLockLock = threading.Lock() self.locks = {} self.graph = Pyasciigraph()
class SeLoger(callbacks.Plugin): """This plugin search and alerts you in query if new ads are available. Use "sladd" for a new search. Use "sllist" to list you current search. Use "sldisable" to remove an old search.""" threaded = True def __init__(self,irc): self.__parent = super(SeLoger, self) self.__parent.__init__(irc) self.backend = SqliteSeLogerDB(self.log) self.gettingLockLock = threading.Lock() self.locks = {} self.graph = Pyasciigraph() ### the external methods def sladdrent(self, irc, msg, args, pc, min_surf, max_price, nb_pieces): """usage: sladd_rent <postal code> <min surface> <max price> [<nb_pieces>] Adds a new rent search for you ( /!\ many messages in the first batch ) """ user = irc.msg.nick self._addSearch(str(user), str(pc), str(min_surf), str(max_price), '1', str(nb_pieces)) msg='Done sladd' irc.reply(msg,to=user,private=True) sladdrent = wrap(sladdrent, ['int', 'int', 'int', 'int']) def slhelp(self, irc, msg, args): """usage: slhelp display the help for this module """ user = irc.msg.nick help_content= { 'slhelp' : 'Help for this module:', 'sladdrent <postal code> <min surface> <max price> <min_num_room>': 'Adding a new rent search:', 'sladdbuy <postal code> <min surface> <max price> <min_num_room>': 'Adding a new buy search:', 'sllist': 'List your active searches:', 'sldisable <search ID>': 'Remove the given search (use sllist to get <search ID>):', 'slstatrent <postal code|\'all\'>': 'Print some stats about \'rent\' searches:', 'slstatbuy <postal code|\'all\'>': 'print some stats about \'buy\' searches:', } for cmd in help_content: msg = ircutils.underline(help_content[cmd]) irc.reply(msg,to=user,private=True) msg = ircutils.mircColor(str(cmd), 8) irc.reply(msg,to=user,private=True) #msg = '' #irc.reply(msg,to=user,private=True) slhelp = wrap(slhelp) def sladdbuy(self, irc, msg, args, pc, min_surf, max_price, nb_pieces): """usage: sladd_buy <postal code> <min surface> <max price> [<nb_pieces>] Adds a new buy search for you ( /!\ many messages in the first batch ) """ user = irc.msg.nick self._addSearch(str(user), str(pc), str(min_surf), str(max_price), '2', str(nb_pieces)) msg='Done sladd' irc.reply(msg,to=user,private=True) sladdbuy = wrap(sladdbuy, ['int', 'int', 'int', 'int']) def sldisable(self, irc, msg, args, id_search): """usage: sldisable <id_search> Disables a search """ user = irc.msg.nick self._disableSearch(user, id_search) msg='Done sldisable' irc.reply(msg,to=user,private=True) sldisable = wrap(sldisable, ['text']) def sllist(self, irc, msg, args): """usage: sllist list all your searches """ user = irc.msg.nick self._listSearch(user, irc) msg='Done sllist' irc.reply(msg,to=user,private=True) sllist = wrap(sllist) def slstatrent(self, irc, msg, args, pc): """usage: slstatrent <postal code|'all'> give you some stats about your rent searches. Specify 'all' (no filter), or a specific postal code """ user = irc.msg.nick self._gen_stat_rooms(user, irc, pc, '1') self._gen_stat_surface(user, irc, pc, '1') msg='Done slstatrent' irc.reply(msg,to=user,private=True) slstatrent = wrap(slstatrent, ['text']) def slstatbuy(self, irc, msg, args, pc): """usage: slstatbuy <postal code|'all'> give you some stats about your buy searches. Specify 'all' (no filter), or a specific postal code """ user = irc.msg.nick self._gen_stat_rooms(user, irc, pc, '2') self._gen_stat_surface(user, irc, pc, '2') msg='Done slstatbuy' irc.reply(msg,to=user,private=True) slstatbuy = wrap(slstatbuy, ['text']) def colors(self, irc, msg, args): for color in range(16): msg = ircutils.mircColor(str(color), color) irc.reply(msg) colors = wrap(colors) ### The internal methods def _print_stats(self, user, irc, stats): """ small function to print a list of line in different color """ #empty line for lisibility msg = ' ' irc.reply(msg,to=user,private=True) #list of colors we use (order matters) colors = [ 15, 14, 10, 3, 7, 2, 6, 5 ] colors_len = len(colors) color = 0 for line in stats: msg = ircutils.mircColor(line, colors[color]) color = (color + 1) % colors_len irc.reply(msg,to=user,private=True) def _gen_stat_rooms(self, user, irc, pc, ad_type): """internal function generating stats about the number of rooms """ #we get all the ads of the user (with a filter on the postal code) ads = self.backend.get_all(user, pc, ad_type) #if we have nothing to make stats on if len(ads) == 0: msg = 'no stats about number of rooms available' irc.reply(msg,to=user,private=True) return number_ads_by_room = {} surface_by_room = {} price_by_room = {} surface_by_room = {} list_surface = [] list_price = [] list_number = [] for ad in ads: rooms = ad['nbPiece'] #we increment 'n (rooms)' if rooms in number_ads_by_room: number_ads_by_room[rooms] += 1 else: number_ads_by_room[rooms] = 1 #we add the price to the corresponding field if rooms in price_by_room: price_by_room[rooms] += float(ad['prix']) else: price_by_room[rooms] = float(ad['prix']) #we add the surface to the corresponding field if rooms in surface_by_room: surface_by_room[rooms] += float(ad['surface']) else: surface_by_room[rooms] = float(ad['surface']) #we generate the list of tuples for rooms in sorted(surface_by_room, key=int): #the list for number of ads by number of rooms list_number.append(( rooms + ' room(s)', number_ads_by_room[rooms])) #calcul of the avrage surface for this number of rooms surface_by_room[rooms] = surface_by_room[rooms] \ / number_ads_by_room[rooms] list_surface.append(( rooms + ' room(s)', int(surface_by_room[rooms]))) #calcul of the avrage price for this number of rooms price_by_room[rooms] = price_by_room[rooms] \ / number_ads_by_room[rooms] list_price.append(( rooms + ' room(s)', int(price_by_room[rooms]))) #we print all that graph_number = self.graph.graph(u'number of ads by room', list_number) self._print_stats(user, irc, graph_number) graph_surface = self.graph.graph(u'surface by room', list_surface) self._print_stats(user, irc, graph_surface) graph_price = self.graph.graph(u'price by room', list_price) self._print_stats(user, irc, graph_price) def _get_step(self, ads, id_row, number_of_steps): """internal function generating a step for numerical range """ mini = float(ads[0][id_row]) maxi = float(ads[0][id_row]) for ad in ads: value = float(ad[id_row]) if value > maxi: maxi = value if value < mini: mini = value return max(1, int((maxi - mini) / number_of_steps)) def _gen_stat_surface(self, user, irc, pc, ad_type): """internal function generating stats about the surface """ #we get all the ads of the user (with a filter on the postal code) ads = self.backend.get_all(user, pc, ad_type) #if we have nothing to make stats on if len(ads) == 0: msg = 'no stats about surface available' irc.reply(msg,to=user,private=True) return number_ads_by_range = {} rent_by_range = {} price_by_range = {} list_rent = [] list_price = [] list_number = [] number_of_steps = 7 #we calcul the step of the range (max step is 5) step = min(self._get_step(ads, 'surface', number_of_steps), 5) for ad in ads: surface_range = str(int(float(ad['surface']) / step)) #we count the number of ads by range if surface_range in number_ads_by_range: number_ads_by_range[surface_range] += 1 else: number_ads_by_range[surface_range] = 1 #we add the rent to the corresponding range if surface_range in rent_by_range: rent_by_range[surface_range] += float(ad['prix']) else: rent_by_range[surface_range] = float(ad['prix']) #we add the rent per square meter to the corresponding range if surface_range in price_by_range: price_by_range[surface_range] += float(ad['prix']) \ / float(ad['surface']) else: price_by_range[surface_range] = float(ad['prix']) \ / float(ad['surface']) #we generate the list of tuples to print for surface_range in sorted(number_ads_by_range, key=int): #calcul of the label label = str( int(surface_range) * step) + \ ' to ' +\ str((int(surface_range) + 1) * step) #number of ads by range list_number.append(( label, number_ads_by_range[surface_range])) #calcul of mid rent by range mid_rent = int(rent_by_range[surface_range] \ / number_ads_by_range[surface_range]) list_rent.append(( label, mid_rent)) #calcul of mid rent per square meter by range mid_price = int(price_by_range[surface_range] \ / number_ads_by_range[surface_range]) list_price.append(( label, mid_price)) #we print all these stats graph_number = self.graph.graph(u'number of ads by surface range', list_number) self._print_stats(user, irc, graph_number) graph_rent = self.graph.graph(u'price by surface range', list_rent) self._print_stats(user, irc, graph_rent) graph_price = self.graph.graph(u'price per square meter by surface range', list_price) self._print_stats(user, irc, graph_price) def __call__(self, irc, msg): """black supybot magic... at least for me """ self.__parent.__call__(irc, msg) irc = callbacks.SimpleProxy(irc, msg) t = threading.Thread(None,self._print, None, (irc,)) t.start() def _update_db(self): """direct call to do_search from the backend class it gets the new ads from SeLoger """ self.backend.do_searches() def _acquireLock(self, url, blocking=True): """Lock handler for the threads """ try: self.gettingLockLock.acquire() try: lock = self.locks[url] except KeyError: lock = threading.RLock() self.locks[url] = lock return lock.acquire(blocking=blocking) finally: self.gettingLockLock.release() def _releaseLock(self, url): """Lock handler for the threads """ self.locks[url].release() def _print(self,irc): """This function updates the database and prints any new results to each user """ if self._acquireLock('print', blocking=False): self._update_db() ads = self.backend.get_new() total = len(ads) counter = 1 for ad in ads: self._print_ad(ad, irc, counter, total) counter += 1 #we search every 5 minutes time.sleep(300) self._releaseLock('print') def _reformat_date(self, date): """small function reformatting the date from SeLoger """ d = datetime.datetime.strptime(date, '%Y-%m-%dT%H:%M:%S') return d.strftime('%d/%m/%Y %H:%M') def _print_ad(self,ad,irc, counter, total): """this function prints one ad """ #user needs to be an ascii string, not unicode user = str(ad['owner_id']) #empty line for lisibility msg = 'new ad %d/%d' % (counter, total) irc.reply(msg,to=user,private=True) #printing the pric, number of rooms and surface price = ircutils.mircColor('Prix: ' + ad['prix'] + ad['prixUnite'],8) rooms = ircutils.mircColor('Pieces: ' + ad['nbPiece'],4) surface = ircutils.mircColor( 'Surface: ' + ad['surface'] + ad['surfaceUnite'], 13 ) msg = price + ' | ' + rooms + ' | ' + surface irc.reply(msg,to=user,private=True) #printing the city, the postal code and date of the ad city = ircutils.mircColor('Ville: ' + ad['ville'], 11) cp = ircutils.mircColor('Code postal: ' + ad['cp'], 12) date = ircutils.mircColor( 'Date ajout: ' + self._reformat_date(ad['dtCreation']), 11 ) msg = city + ' | ' + cp + ' | ' + date irc.reply(msg,to=user,private=True) #printing a googlemaps url to see where it is (data not accurate) msg = ircutils.mircColor( 'Localisation: https://maps.google.com/maps?q=' \ + ad['latitude'] + '+' + ad['longitude'], 3 ) irc.reply(msg,to=user,private=True) #printing "Proximite" info msg = ircutils.mircColor('Proximite: ' + ad['proximite'],2) irc.reply(msg,to=user,private=True) #print the description msg = u'Description: ' + ad['descriptif'] #\n creates some mess when we print them, so we remove them. msg = re.sub(r'\n', r' ', msg) irc.reply(msg,to=user,private=True) #printing the permanent link of the ad msg = ircutils.mircColor('Lien: ' + ad['permaLien'],9) irc.reply(msg,to=user,private=True) #one more time, an empty line for lisibility msg = ' ' irc.reply(msg,to=user,private=True) self.log.debug('printing ad %s of %s ', ad['idAnnonce'], user) def _addSearch(self, user, pc, min_surf, max_price, ad_type, nb_pieces): """this function adds a search""" self.backend.add_search(user, pc, min_surf, max_price, ad_type, nb_pieces) def _disableSearch(self, user, id_search): """this function disables a search""" self.backend.disable_search(id_search,user) def _listSearch(self, user, irc): """this function list the current searches of a user""" searches = self.backend.get_search(user) for search in searches: id_search = ircutils.mircColor("ID: " + search['search_id'], 8) surface = ircutils.mircColor("Surface >= " + search['min_surf'], 4) loyer = ircutils.mircColor("Loyer/Prix <= " + search['max_price'], 13) cp = ircutils.mircColor("Code Postal == " + search['cp'], 11) if search['ad_type'] == '2': ad_type = '2 (achat)' elif search['ad_type'] == '1': ad_type = '1 (location)' else: ad_type = search['ad_type'] + ' (inconnu)' type_ad = ircutils.mircColor("Type d'annonce == " + ad_type, 14) nb_pieces = ircutils.mircColor("Pieces >= " + search['nb_pieces'], 12) msg = id_search + " | " + surface + " | " + loyer + " | " + cp + " | " + type_ad + " | " + nb_pieces irc.reply(msg,to=user,private=True)