lolbot.py 8.62 KB
Newer Older
1
#! /usr/bin/env python
Jonathan Harker's avatar
Jonathan Harker committed
2
"""
3 4 5 6 7
LOLBOT 2

 - die: Let the bot cease to exist.
 - ask: Ask a MoxQuizz question.
 - list: list some URLs
Jonathan Harker's avatar
Jonathan Harker committed
8 9
"""

10
from __future__ import print_function, unicode_literals
11

12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
import sqlite3
import random
import time
import irc.strings
from irc.bot import SingleServerIRCBot
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker
from models import Log, Url, Model
from pymoxquizz import QuestionBank, Question
from os import listdir, path


DEBUG = True


def debug(msg):
    if DEBUG:
        print(msg)


class LolBot(SingleServerIRCBot):
33
    """
34 35
    An IRC bot to entertain the troops with MoxQuizz questions, log URLs, and
    other shenanigans.
36 37
    """

38 39 40 41 42 43
    qb = list()

    def __init__(self, channel, nickname, server, database, port=6667):
        debug("Instantiating SingleServerIRCBot")
        SingleServerIRCBot.__init__(self, [(server, port)], nickname, nickname)
        self.channel = channel
Brett Wilkins's avatar
Brett Wilkins committed
44

45
        # load some MoxQuizz questions
46 47 48 49 50 51
        qfiles = [f for f in listdir('questions') if path.isfile(path.join('questions', f))]
        debug("Loading MoxQuizz questions")
        for f in qfiles:
            qfile = path.abspath(path.join('questions', f))
            debug("  - from MoxQuizz bank '%s'" % qfile)
            self.qb += QuestionBank(qfile).questions
52
        random.shuffle(self.qb)
53
        self.quiz = 0
54 55
        self.question = None

56
        # connect to the database
57 58
        debug("Connecting to SQLite database '%s'" % database)
        self.dbfile = database
Brett Wilkins's avatar
Brett Wilkins committed
59
        self.dbengine = create_engine('sqlite+pysqlite://', creator=self._get_connection)
60 61
        Model.metadata.bind = self.dbengine
        Model.metadata.create_all()
62
        self.db_session = sessionmaker(bind=self.dbengine)
63

64 65
        self.helptext = "Keeps a list of URLs. Commands: list [n|x-y] - prints the last 10 URLs (or n URLs, or x through y); <url> - adds the URL to the list; help - this message."
        debug("Exiting lolbot constructor")
66

67 68 69 70 71 72
    def _get_connection(self):
        """Creator function for SQLAlchemy."""
        connection = sqlite3.Connection(self.dbfile)
        connection.text_factory = str
        debug("Creating SQLAlchemy connection")
        return connection
73

74 75 76 77
    def on_nicknameinuse(self, connection, event):
        nick = connection.get_nickname()
        debug("Nick '%s' in use, trying '%s_'" % nick)
        connection.nick(nick + "_")
78

79 80 81
    def on_welcome(self, connection, event):
        debug("Joining channel '%s'" % self.channel)
        connection.join(self.channel)
82

83 84
    def on_privmsg(self, connection, event):
        self.do_command(event, event.arguments[0])
85

86
    def on_pubmsg(self, connection, event):
87
        """
88 89 90
        Handle an event on the channel.
        Handle commands addressed to the bot.
        If there's a question, see if it's been answered.
91 92
        """
        try:
93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114
            (nick, message) = event.arguments[0].split(":", 1)
            # handle command, if addressed
            if irc.strings.lower(nick) == irc.strings.lower(self.connection.get_nickname()):
                self.do_command(event, message.strip())
        except ValueError:
            message = event.arguments[0]
            nick = event.source.nick

        # deal with MoxQuizz question
        if self.quiz:
            self.handle_quiz(nick, message)

    def start_quiz(self, nick):
        self.quiz = 0
        self.quiz_scores = dict()
        self.connection.notice(self.channel, 'Quiz begun by %s.' % nick)
        self.quiz_get_next()

    def stop_quiz(self):
        self.quiz = 0
        self.quiz_scores = None
        self.question = None
115

116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164
    def quiz_get_next(self):
        self.quiz += 1
        self.question = random.choice(self.qb)
        print(str(self.question.question))
        self.connection.notice(self.channel, str(self.question.question))

    def quiz_award_points(self, nick):
        score = "%s point" % self.question.score
        if self.question.score != 1:
            score += "s"

        self.connection.notice(self.channel, 'Correct! The answer was %s. %s scores %s.' % (self.question.answer, nick, score))
        if nick not in self.quiz_scores.keys():
            self.quiz_scores[nick] = 0
        self.quiz_scores[nick] += self.question.score

    def quiz_check_win(self, nick):
        if self.quiz_scores[nick] == 10:
            self.connection.notice(self.channel, '%s wins with 10 points!' % nick)
            self.quiz_scoreboard()
            self.stop_quiz()

    def quiz_scoreboard(self):
        self.connection.notice(self.channel, 'Scoreboard:')
        for nick in self.quiz_scores.keys():
            score = "%s point" % self.quiz_scores[nick]
            if self.quiz_scores[nick] != 1:
                score += "s"
            self.connection.notice(self.channel, '%s has %s.' % (nick, score))

    def handle_quiz(self, nick, message):
        # bail if there's no quiz or unanswered question.
        if not self.quiz or not isinstance(self.question, Question):
            return

        # see if anyone answered correctly.
        if self.question.attempt(message):
            self.quiz_award_points(nick)
            self.quiz_check_win(nick)
            # if nobody has won, carry on
            if self.quiz:
                self.quiz_get_next()

    def do_command(self, e, cmd):
        """
        Handle bot commands.
        """
        nick = e.source.nick
        c = self.connection
165

166 167
        if cmd == "die":
            self.die()
168

169 170
        elif cmd == 'help':
            c.notice(nick, self.helptext)
171

172 173
        elif cmd == 'status':
            c.notice(nick, "I know %s questions." % len(self.qb))
174

175 176 177 178 179
        elif cmd == 'halt' or cmd == 'quit':
            if self.quiz:
                self.quiz_scoreboard()
                self.stop_quiz()
                c.notice(self.channel, "Quiz halted by %s. Use ask to start a new one." % nick)
180
            else:
181 182 183 184 185 186 187 188 189 190 191
                c.notice(self.channel, "No quiz running.")

        elif cmd == 'ask':
            if self.quiz:
                c.notice(self.channel, "Quiz is running. Use halt or quit to stop.")
                c.notice(self.channel, str(self.question.question))
            elif isinstance(self.question, Question):
                c.notice(self.channel, "There is an unanswered question.")
                c.notice(self.channel, str(self.question.question))
            else:
                self.start_quiz(nick)
192

193 194 195 196
        elif cmd == 'revolt':
            if isinstance(self.question, Question):
                c.notice(self.channel, "Fine, the answer is: %s" % self.question.answer)
                self.quiz_get_next()
197

198 199
        elif cmd.startswith('urls') or cmd.startswith('list'):
            db = self.db_session()
200
            try:
201
                (listcmd, n) = cmd.split(" ", 1)
202
            except ValueError:
203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230
                n = '5'

            n = n.strip()
            if n == "all":
                rows = db.query(Url).order_by(Url.timestamp.desc())
            elif n.find("-") > 0:
                (x, y) = n.split("-", 1)
                try:
                    x = abs(int(x))
                    y = abs(int(y))
                    if y < x:
                        x, y = y, x
                except ValueError as ex:
                    c.notice(nick, "Give me a number or a range of numbers, e.g. list 5 or list 11-20")
                    raise ex
                rows = db.query(Url).order_by(Url.timestamp.desc())[x - 1: y]
            else:
                try:
                    n = abs(int(n))
                except ValueError as ex:
                    c.notice(nick, "Give me a number or a range of numbers, e.g. list 5 or list 11-20")
                    raise ex
                rows = db.query(Url).order_by(Url.timestamp.desc())[:n]

            for url in rows:
                line = "%s %s" % (url.url, url.title)
                c.notice(nick, line)
                time.sleep(1)
231

232 233
        else:
            c.notice(nick, "Not understood: " + cmd)
234 235


236 237 238 239
def main():
    import sys
    if len(sys.argv) != 5:
        print("Usage: lolbot2.py <server[:port]> <channel> <nickname> <db>")
240
        sys.exit(1)
241

242 243 244 245 246 247 248 249 250 251 252 253 254
    s = sys.argv[1].split(":", 1)
    server = s[0]
    if len(s) == 2:
        try:
            port = int(s[1])
        except ValueError:
            print("Error: Erroneous port.")
            sys.exit(1)
    else:
        port = 6667
    channel = sys.argv[2]
    nickname = sys.argv[3]
    database = sys.argv[4]
255

256
    debug("Parameters: server=%s port=%s nickname=%s channel=%s database=%s" % (server, port, nickname, channel, database))
257

258 259 260
    irc.client.ServerConnection.buffer_class = irc.buffer.LenientDecodingLineBuffer
    bot = LolBot(channel, nickname, server, database, port)
    bot.start()
261

262

Jonathan Harker's avatar
Jonathan Harker committed
263
if __name__ == "__main__":
264
    main()