Beispiel #1
0
 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()
Beispiel #2
0
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)