Ejemplo n.º 1
0
class BogBot(irc.bot.SingleServerIRCBot):
    def __init__(self, channel, nickname, realname, server, port=6667):
        irc.bot.SingleServerIRCBot.__init__(self, [(server, port)], nickname, realname)
        self.channel = channel
        self.dbcon = DatabaseConnection()

    def on_disconnect(self, c, e):
        raise SystemExit()

    def on_welcome(self, c, e):
        c.join(self.channel)

    def on_nicknameinuse(self, c, e):
        c.nick(c.get_nickname() + "_")

    def on_privmsg(self, c, e):
        self.do_command(e, e.arguments[0])

    def on_pubmsg(self, srvcon, event):
        try:
            if event.arguments[0].strip().startswith("!"):
                self.do_command(event, event.arguments[0][1:])
            else:
                self.process_text(event)
        except Exception as e:
            exc_type, exc_obj, exc_traceback = sys.exc_info()
            tb = traceback.format_list(traceback.extract_tb(exc_traceback)[-1:])[-1]
            tb = ''.join(tb.splitlines())
            msg = "%s: %s  %s" % (exc_type, e.message, tb)
            if len(msg) > 400:
                msg = "%s %s %s" % (msg[:340], "...", msg[-50:])
            self.connection.privmsg("jabr", msg)

    def process_text(self, event):
        message = event.arguments[0]
        if "http" in  message:
            # Add nick/user/host
            hostmask_id = self.add_or_update_hostmask(event.source)

            start = message.find("http")
            end = message.find(" ", start)
            if end == -1:
                url = message[start:]
            else:
                url = message[start:end]

            if "spotify.com" in url:
                spl = SpotifyLookup()
                spotify_meta = spl.lookup(url)
                if spotify_meta is not None:
                    self.connection.notice(event.target, spotify_meta)
                return
            elif "twitter.com" and "status" in url:
                twit_lookup = TwitterLookup()
                twit_meta = twit_lookup.compose_meta(url)
                if twit_meta is not None:
                    self.connection.notice(event.target, twit_meta)
                return

            redirect, idn, title = self._get_url_meta(url)

            # Log url
            if redirect is not None:
                self.add_url(redirect, title, hostmask_id, event.target)
            else:
                self.add_url(url, title, hostmask_id, event.target)

            # Output meta
            if title is not None:
                url_meta = self._compose_url_meta_string(url, redirect,
                                                         idn, title)
                self.connection.notice(event.target, url_meta)

    def _get_url_meta(self, url):
        abort, redirect, idn = self._check_headers(url)
        if abort:
            return None, None, None

        doc = self._get_url_content(url)
        title = self._get_html_title(doc)
        if title is not None and title != "":
            return redirect, idn, title
        return None, None, None

    def _compose_url_meta_string(self, url, redirect, idn, title):
        meta = ""
        if redirect is not None and idn is False:
            meta = "%s )> " % redirect

        if title is not None and title != "":
            meta = "%s%s" % (meta, title)
            return meta

    def _get_html_title(self, doc):
        """
        Parse the string representation ('document') of the web page.
        """
        parsed_doc = lxml.html.fromstring(doc)
        title = parsed_doc.find(".//title")
        if title is not None:
            title_stripped = ''.join(title.text.splitlines())
            return title_stripped.strip()

    def _check_headers(self, url):
        """
        Check size of URL content is within limit. Also check if URL and
        response URL are different, and if the response URL indicates
        that the original URL is a Internationalized Domain Name (IDN).
        """

        response = requests.head(url)
        if response.headers is not None:
            if "content-type" in response.headers:
                if "text/html" not in response.headers['content-type']:
                    self.connection.privmsg("jabr", "No 'text/html' in headers for %s" % url)
                    return True, None, None
            if "content-length" in response.headers:
                # 5.000.000 bytes ~= 5MB
                if int(response.headers['content-length']) > 5000000:
                    self.connection.privmsg("jabr", "Content length too long for %s" % url)
                    return True, None, None
        else:
            self.connection.privmsg("jabr", "No response headers for %s" % url)
            return True, None, None

        if url != response.url:
            if response.url.split('://')[1].startswith('xn--'):
                return False, response.url, True
            return False, response.url, False
        return False, None, False

    def _get_url_content(self, url):
        response = requests.get(url)
        if response.text and response.encoding is not None:
            return response.text.encode(response.encoding)

    def add_or_update_hostmask(self, hostmask_str):
        nick, user, host = self.parse_hostmask(hostmask_str)
        hostmask_id, nick_present = self.is_nick_in_hostmask(nick, user, host)

        if hostmask_id is not None:
            if nick_present:
                #self.connection.privmsg("jabr", "Nickname, username and hostmask already registered.")
                return hostmask_id
            else:
                self.connection.privmsg("jabr", "Username and hostmask already registered, but not nick. Adding %s" % nick)
                return self.dbcon.add_nick(nick, user, host)
        else:
            self.connection.privmsg("jabr", "Username and hostmask not registered. Adding %s %s %s" % (nick, user, host))
            return self.dbcon.add_hostmask(nick, user, host)

    def add_consumption(self, hostmask_id, consumable_str, source=None):
        with self.dbcon.scoped_db_session() as session:
            consumable_qr = session.query(Consumable).\
                            filter(Consumable.name==consumable_str).all() # One?
            if len(consumable_qr) == 0:
                self.connection.privmsg("jabr", "Consumable not registered. Registering.")
                consumable = Consumable(consumable_str)
                session.add(consumable)
            elif len(consumable_qr) == 1:
                self.connection.privmsg("jabr", "The consumable was found in database.")
                consumable = consumable_qr[0]
            else:
                self.connection.privmsg("jabr","ERROR: Several consumables with same name!")

            consumption = Consumption(source, consumable)
            hostmask = session.query(Hostmask).get(hostmask_id)
            hostmask.consumption.append(consumption)

    def add_url(self, url, title, hostmask_id, channel=None):
        with self.dbcon.scoped_db_session() as session:
            url_qr = session.query(URL).filter(URL.url==url).all() # One?
            if len(url_qr) == 0:
                url_obj = URL()
                #self.connection.privmsg("jabr", type(url_obj.hostmask_id))

                url_obj.url = url
                url_obj.title = title
                url_obj.channel = channel
                url_obj.hostmask_id = hostmask_id
                session.add(url_obj)
                msg = "URL %s added by %d" % (url, hostmask_id)
                self.connection.privmsg("jabr", msg)
            elif len(url_qr) == 1:
                msg = "URL %s already exist" % url
                self.connection.privmsg("jabr", msg)
            else:
                msg = "ERROR: %d instances of URL (%s) in DB" % (len(url_qr), url)
                self.connection.privmsg("jabr", msg)

    def do_command(self, event, cmd):
        self.connection.privmsg("jabr", "%s requested command %s" % (event.source.nick, cmd))

        hostmask_id = self.add_or_update_hostmask(event.source)
        self.connection.privmsg("jabr", "Hostmask ID: %s" % hostmask_id)

        if cmd == "kaffe":
            self.add_consumption(hostmask_id, cmd, event.target)
            self.connection.privmsg(event.target, "Coffee added!")
        elif cmd == "brus":
            self.add_consumption(hostmask_id, cmd, event.target)
            self.connection.privmsg(event.target, "Brus added!")
        elif cmd == "halt" and event.source == "[email protected]":
            self.die()
        elif cmd == "stats":
            for chname, chobj in self.channels.items():
                c.notice(nick, "--- Channel statistics ---")
                c.notice(nick, "Channel: " + chname)
                users = chobj.users()
                users.sort()
                c.notice(nick, "Users: " + ", ".join(users))
                opers = chobj.opers()
                opers.sort()
                c.notice(nick, "Opers: " + ", ".join(opers))
                voiced = chobj.voiced()
                voiced.sort()
                c.notice(nick, "Voiced: " + ", ".join(voiced))

    def parse_hostmask(self, hostmask):
        nick = hostmask.split('!', 1)[0]
        user_host = hostmask.split('!', 1)[1].split('@', 1)
        user = user_host[0]
        host = user_host[1]
        return nick, user, host

    def is_nick_in_hostmask(self, nick, user, host):
        with self.dbcon.scoped_ro_db_session() as session:
            try:
                hostmask = session.query(Hostmask).\
                            filter(Hostmask.username==user).\
                            filter(Hostmask.hostname==host).one()
            except MultipleResultsFound, e:
                self.connection.privmsg("jabr",
                    "Multiple hostmasks found for username and hostname. Should not be possible: %s" % e)
            except NoResultFound, e:
                return None, False

            if nick in (nickname.nickname for nickname in hostmask.nickname):
                return hostmask.id, True
            else:
                return hostmask.id, False
Ejemplo n.º 2
0
class BogBot(irc.bot.SingleServerIRCBot):
    def __init__(self, channel, nickname, realname, server, port=6667):
        irc.bot.SingleServerIRCBot.__init__(self, [(server, port)], nickname,
                                            realname)
        self.channel = channel
        self.dbcon = DatabaseConnection()

    def on_disconnect(self, c, e):
        raise SystemExit()

    def on_welcome(self, c, e):
        c.join(self.channel)

    def on_nicknameinuse(self, c, e):
        c.nick(c.get_nickname() + "_")

    def on_privmsg(self, c, e):
        self.do_command(e, e.arguments[0])

    def on_pubmsg(self, srvcon, event):
        try:
            if event.arguments[0].strip().startswith("!"):
                self.do_command(event, event.arguments[0][1:])
            else:
                self.process_text(event)
        except Exception as e:
            exc_type, exc_obj, exc_traceback = sys.exc_info()
            tb = traceback.format_list(
                traceback.extract_tb(exc_traceback)[-1:])[-1]
            tb = ''.join(tb.splitlines())
            msg = "%s: %s  %s" % (exc_type, e.message, tb)
            if len(msg) > 400:
                msg = "%s %s %s" % (msg[:340], "...", msg[-50:])
            self.connection.privmsg("jabr", msg)

    def process_text(self, event):
        message = event.arguments[0]
        if "http" in message:
            # Add nick/user/host
            hostmask_id = self.add_or_update_hostmask(event.source)

            start = message.find("http")
            end = message.find(" ", start)
            if end == -1:
                url = message[start:]
            else:
                url = message[start:end]

            if "spotify.com" in url:
                spl = SpotifyLookup()
                spotify_meta = spl.lookup(url)
                if spotify_meta is not None:
                    self.connection.notice(event.target, spotify_meta)
                return
            elif "twitter.com" and "status" in url:
                twit_lookup = TwitterLookup()
                twit_meta = twit_lookup.compose_meta(url)
                if twit_meta is not None:
                    self.connection.notice(event.target, twit_meta)
                return

            redirect, idn, title = self._get_url_meta(url)

            # Log url
            if redirect is not None:
                self.add_url(redirect, title, hostmask_id, event.target)
            else:
                self.add_url(url, title, hostmask_id, event.target)

            # Output meta
            if title is not None:
                url_meta = self._compose_url_meta_string(
                    url, redirect, idn, title)
                self.connection.notice(event.target, url_meta)

    def _get_url_meta(self, url):
        abort, redirect, idn = self._check_headers(url)
        if abort:
            return None, None, None

        doc = self._get_url_content(url)
        title = self._get_html_title(doc)
        if title is not None and title != "":
            return redirect, idn, title
        return None, None, None

    def _compose_url_meta_string(self, url, redirect, idn, title):
        meta = ""
        if redirect is not None and idn is False:
            meta = "%s )> " % redirect

        if title is not None and title != "":
            meta = "%s%s" % (meta, title)
            return meta

    def _get_html_title(self, doc):
        """
        Parse the string representation ('document') of the web page.
        """
        parsed_doc = lxml.html.fromstring(doc)
        title = parsed_doc.find(".//title")
        if title is not None:
            title_stripped = ''.join(title.text.splitlines())
            return title_stripped.strip()

    def _check_headers(self, url):
        """
        Check size of URL content is within limit. Also check if URL and
        response URL are different, and if the response URL indicates
        that the original URL is a Internationalized Domain Name (IDN).
        """

        response = requests.head(url)
        if response.headers is not None:
            if "content-type" in response.headers:
                if "text/html" not in response.headers['content-type']:
                    self.connection.privmsg(
                        "jabr", "No 'text/html' in headers for %s" % url)
                    return True, None, None
            if "content-length" in response.headers:
                # 5.000.000 bytes ~= 5MB
                if int(response.headers['content-length']) > 5000000:
                    self.connection.privmsg(
                        "jabr", "Content length too long for %s" % url)
                    return True, None, None
        else:
            self.connection.privmsg("jabr", "No response headers for %s" % url)
            return True, None, None

        if url != response.url:
            if response.url.split('://')[1].startswith('xn--'):
                return False, response.url, True
            return False, response.url, False
        return False, None, False

    def _get_url_content(self, url):
        response = requests.get(url)
        if response.text and response.encoding is not None:
            return response.text.encode(response.encoding)

    def add_or_update_hostmask(self, hostmask_str):
        nick, user, host = self.parse_hostmask(hostmask_str)
        hostmask_id, nick_present = self.is_nick_in_hostmask(nick, user, host)

        if hostmask_id is not None:
            if nick_present:
                #self.connection.privmsg("jabr", "Nickname, username and hostmask already registered.")
                return hostmask_id
            else:
                self.connection.privmsg(
                    "jabr",
                    "Username and hostmask already registered, but not nick. Adding %s"
                    % nick)
                return self.dbcon.add_nick(nick, user, host)
        else:
            self.connection.privmsg(
                "jabr",
                "Username and hostmask not registered. Adding %s %s %s" %
                (nick, user, host))
            return self.dbcon.add_hostmask(nick, user, host)

    def add_consumption(self, hostmask_id, consumable_str, source=None):
        with self.dbcon.scoped_db_session() as session:
            consumable_qr = session.query(Consumable).\
                            filter(Consumable.name==consumable_str).all() # One?
            if len(consumable_qr) == 0:
                self.connection.privmsg(
                    "jabr", "Consumable not registered. Registering.")
                consumable = Consumable(consumable_str)
                session.add(consumable)
            elif len(consumable_qr) == 1:
                self.connection.privmsg(
                    "jabr", "The consumable was found in database.")
                consumable = consumable_qr[0]
            else:
                self.connection.privmsg(
                    "jabr", "ERROR: Several consumables with same name!")

            consumption = Consumption(source, consumable)
            hostmask = session.query(Hostmask).get(hostmask_id)
            hostmask.consumption.append(consumption)

    def add_url(self, url, title, hostmask_id, channel=None):
        with self.dbcon.scoped_db_session() as session:
            url_qr = session.query(URL).filter(URL.url == url).all()  # One?
            if len(url_qr) == 0:
                url_obj = URL()
                #self.connection.privmsg("jabr", type(url_obj.hostmask_id))

                url_obj.url = url
                url_obj.title = title
                url_obj.channel = channel
                url_obj.hostmask_id = hostmask_id
                session.add(url_obj)
                msg = "URL %s added by %d" % (url, hostmask_id)
                self.connection.privmsg("jabr", msg)
            elif len(url_qr) == 1:
                msg = "URL %s already exist" % url
                self.connection.privmsg("jabr", msg)
            else:
                msg = "ERROR: %d instances of URL (%s) in DB" % (len(url_qr),
                                                                 url)
                self.connection.privmsg("jabr", msg)

    def do_command(self, event, cmd):
        self.connection.privmsg(
            "jabr", "%s requested command %s" % (event.source.nick, cmd))

        hostmask_id = self.add_or_update_hostmask(event.source)
        self.connection.privmsg("jabr", "Hostmask ID: %s" % hostmask_id)

        if cmd == "kaffe":
            self.add_consumption(hostmask_id, cmd, event.target)
            self.connection.privmsg(event.target, "Coffee added!")
        elif cmd == "brus":
            self.add_consumption(hostmask_id, cmd, event.target)
            self.connection.privmsg(event.target, "Brus added!")
        elif cmd == "halt" and event.source == "[email protected]":
            self.die()
        elif cmd == "stats":
            for chname, chobj in self.channels.items():
                c.notice(nick, "--- Channel statistics ---")
                c.notice(nick, "Channel: " + chname)
                users = chobj.users()
                users.sort()
                c.notice(nick, "Users: " + ", ".join(users))
                opers = chobj.opers()
                opers.sort()
                c.notice(nick, "Opers: " + ", ".join(opers))
                voiced = chobj.voiced()
                voiced.sort()
                c.notice(nick, "Voiced: " + ", ".join(voiced))

    def parse_hostmask(self, hostmask):
        nick = hostmask.split('!', 1)[0]
        user_host = hostmask.split('!', 1)[1].split('@', 1)
        user = user_host[0]
        host = user_host[1]
        return nick, user, host

    def is_nick_in_hostmask(self, nick, user, host):
        with self.dbcon.scoped_ro_db_session() as session:
            try:
                hostmask = session.query(Hostmask).\
                            filter(Hostmask.username==user).\
                            filter(Hostmask.hostname==host).one()
            except MultipleResultsFound, e:
                self.connection.privmsg(
                    "jabr",
                    "Multiple hostmasks found for username and hostname. Should not be possible: %s"
                    % e)
            except NoResultFound, e:
                return None, False

            if nick in (nickname.nickname for nickname in hostmask.nickname):
                return hostmask.id, True
            else:
                return hostmask.id, False