mirror of
https://github.com/oddluck/limnoria-plugins.git
synced 2025-04-26 04:51:09 -05:00
4452 lines
180 KiB
Python
4452 lines
180 KiB
Python
###
|
|
# Copyright (c) 2013, tann <tann@trivialand.org>
|
|
# Copyright (c) 2020, oddluck <oddluck@riseup.net>
|
|
# All rights reserved.
|
|
#
|
|
# Redistribution and use in source and binary forms, with or without
|
|
# modification, are permitted provided that the following conditions are met:
|
|
#
|
|
# * Redistributions of source code must retain the above copyright notice,
|
|
# this list of conditions, and the following disclaimer.
|
|
# * Redistributions in binary form must reproduce the above copyright notice,
|
|
# this list of conditions, and the following disclaimer in the
|
|
# documentation and/or other materials provided with the distribution.
|
|
# * Neither the name of the author of this software nor the name of
|
|
# contributors to this software may be used to endorse or promote products
|
|
# derived from this software without specific prior written consent.
|
|
#
|
|
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
|
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
|
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
|
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
|
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
|
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
|
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
|
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
|
# POSSIBILITY OF SUCH DAMAGE.
|
|
###
|
|
|
|
import supybot.utils as utils
|
|
from supybot.commands import *
|
|
import supybot.plugins as plugins
|
|
import supybot.ircutils as ircutils
|
|
import supybot.callbacks as callbacks
|
|
import supybot.ircdb as ircdb
|
|
import supybot.ircmsgs as ircmsgs
|
|
import supybot.schedule as schedule
|
|
import supybot.log as log
|
|
import supybot.conf as conf
|
|
import os
|
|
import re
|
|
import sqlite3
|
|
import random
|
|
import time
|
|
import datetime
|
|
import unicodedata
|
|
import hashlib
|
|
|
|
#A list with items that are removed when timeout is reached, values must be unique
|
|
class TimeoutList:
|
|
"""
|
|
A dict wrapper used to store timeout values for unique usernames.
|
|
"""
|
|
def __init__(self, timeout):
|
|
self.timeout = timeout
|
|
self.dict = {}
|
|
|
|
def setTimeout(self, timeout):
|
|
self.timeout = timeout
|
|
|
|
def clearTimeout(self):
|
|
for k, t in list(self.dict.items()):
|
|
if t < (time.time() - self.timeout):
|
|
del self.dict[k]
|
|
|
|
def append(self, key):
|
|
self.clearTimeout()
|
|
self.dict[key] = time.time()
|
|
|
|
def has(self, key):
|
|
self.clearTimeout()
|
|
if key in self.dict:
|
|
return True
|
|
return False
|
|
|
|
def getTimeLeft(self, key):
|
|
return self.timeout - (time.time() - self.dict[key])
|
|
|
|
|
|
#Game instance
|
|
class Game:
|
|
"""
|
|
Main game logic, single game instance for each channel.
|
|
"""
|
|
def __init__(self, irc, channel, base):
|
|
# constants
|
|
self.unmaskedChars = " -'\"_=+&%$#@!~`()[]{}?.,<>|\\/:;"
|
|
|
|
# get utilities from base plugin
|
|
self.base = base
|
|
self.games = base.games
|
|
self.storage = base.storage
|
|
self.registryValue = base.registryValue
|
|
self.channel = channel
|
|
self.irc = irc
|
|
self.network = irc.network
|
|
|
|
# Initialize timeout lists
|
|
self.skipTimeoutList = TimeoutList(self.registryValue('skip.skipTime', channel))
|
|
self.hintTimeoutList = TimeoutList(self.registryValue('hints.extraHintTime', channel))
|
|
|
|
# Initialize state variables
|
|
self.state = 'no-question'
|
|
self.stopPending = False
|
|
self.shownHint = False
|
|
self.questionRepeated = False
|
|
|
|
# Initialize game properties
|
|
self.questionID = -1
|
|
self.questionType = ''
|
|
self.question = ''
|
|
self.answers = []
|
|
self.questionPoints = -1
|
|
self.correctPlayers = {}
|
|
self.guessedAnswers = []
|
|
self.skipList = []
|
|
self.streak = 0
|
|
self.lastWinner = ''
|
|
self.hintsCounter = 0
|
|
self.numAsked = 0
|
|
self.lastAnswer = time.time()
|
|
self.roundStartedAt = time.mktime(time.localtime())
|
|
|
|
# activate
|
|
self.loadGameState()
|
|
self.active = True
|
|
|
|
# Remove any old event and start the next question
|
|
self.removeEvent()
|
|
self.nextQuestion()
|
|
|
|
def checkAnswer(self, msg):
|
|
"""
|
|
Check users input to see if answer was given.
|
|
"""
|
|
channel = msg.args[0]
|
|
# is it a user?
|
|
username = self.base.getUsername(msg.nick, msg.prefix)
|
|
usernameCanonical = ircutils.toLower(username)
|
|
correctAnswerFound = False
|
|
correctAnswer = ''
|
|
|
|
attempt = self.normalizeString(msg.args[1])
|
|
|
|
# Check for a correct answer that hasn't already been guessed
|
|
for ans in self.answers:
|
|
normalizedAns = self.normalizeString(ans)
|
|
if normalizedAns == attempt and normalizedAns not in self.guessedAnswers:
|
|
correctAnswerFound = True
|
|
correctAnswer = ans
|
|
|
|
# Immediately return if not a correct answer
|
|
if not correctAnswerFound:
|
|
return
|
|
|
|
dbLocation = self.registryValue('admin.db')
|
|
threadStorage = Storage(dbLocation)
|
|
|
|
timeElapsed = float(time.time() - self.askedAt)
|
|
points = self.questionPoints
|
|
|
|
# Add answer to list so we can cross it out
|
|
if self.guessedAnswers.count(attempt) == 0:
|
|
self.guessedAnswers.append(attempt)
|
|
|
|
# Past first hint? deduct points
|
|
if self.hintsCounter > 1:
|
|
points /= 2 * (self.hintsCounter - 1)
|
|
|
|
# Handle a correct answer for a KAOS question
|
|
if self.questionType == 'kaos':
|
|
if usernameCanonical not in self.correctPlayers:
|
|
self.correctPlayers[usernameCanonical] = 0
|
|
self.correctPlayers[usernameCanonical] += 1
|
|
# KAOS? divide points and convert score to int
|
|
points = int(points / (len(self.answers) + 1))
|
|
self.totalAmountWon += points
|
|
|
|
# Update database with the correct guess for KAOS item
|
|
threadStorage.updateUserLog(username, self.channel, points, 0, 0)
|
|
self.lastAnswer = time.time()
|
|
self.sendMessage('\x02%s\x02 gets \x02%d\x02 points for: \x02%s\x02' % (username, points, correctAnswer))
|
|
|
|
# can show more hints now
|
|
self.shownHint = False
|
|
|
|
# Check if all answers have been answered
|
|
if len(self.guessedAnswers) == len(self.answers):
|
|
self.state = 'post-question'
|
|
self.removeEvent()
|
|
|
|
# Check if question qualifies for bonus points
|
|
bonusPointsText = ''
|
|
if len(self.correctPlayers) >= 2 and len(self.answers) >= 9:
|
|
bonusPoints = self.registryValue('kaos.payoutKAOS', self.channel)
|
|
if bonusPoints > 0:
|
|
for nick in self.correctPlayers:
|
|
threadStorage.updateUserLog(nick, self.channel, bonusPoints, 0, 0)
|
|
self.totalAmountWon += bonusPoints
|
|
bonusPointsText = 'Everyone gets a %d Point Bonus!!' % int(bonusPoints)
|
|
|
|
# Give a special KAOS message
|
|
self.sendMessage('All KAOS answered! %s' % bonusPointsText)
|
|
self.sendMessage('Total Awarded: \x02%d\x02 Points to \x02%d\x02 Players' % (int(self.totalAmountWon), len(self.correctPlayers)))
|
|
|
|
threadStorage.updateQuestionStats(self.questionID, 1, 0)
|
|
|
|
# Handle a correct answer for a regular question
|
|
else:
|
|
self.state = 'post-question'
|
|
self.removeEvent()
|
|
streakBonus = 0
|
|
minStreak = self.registryValue('general.minBreakStreak', channel)
|
|
# update streak info
|
|
if ircutils.toLower(self.lastWinner) != usernameCanonical:
|
|
#streakbreak
|
|
if self.streak > minStreak:
|
|
streakBonus = int(points * .25)
|
|
self.sendMessage('\x02%s\x02 broke \x02%s\x02\'s streak of \x02%d\x02!' % (username, self.lastWinner, self.streak))
|
|
self.lastWinner = username
|
|
self.streak = 1
|
|
else:
|
|
self.streak += 1
|
|
streakBonus = points * .25 * (self.streak-1)
|
|
streakBonus = int(min(streakBonus, points * .25))
|
|
|
|
# Update database
|
|
threadStorage.updateGameStreak(self.channel, self.lastWinner, self.streak)
|
|
threadStorage.updateUserHighestStreak(self.lastWinner, self.streak)
|
|
threadStorage.updateGameLongestStreak(self.channel, username, self.streak)
|
|
threadStorage.updateUserLog(username, self.channel, int(points + streakBonus), 1, timeElapsed)
|
|
threadStorage.updateQuestionStats(self.questionID, 1, 0)
|
|
|
|
# Show congratulatory message
|
|
self.lastAnswer = time.time()
|
|
self.sendMessage('YES, \x02%s\x02 got the correct answer, \x02%s\x02, in \x02%0.4f\x02 seconds for \x02%d(+%d)\x02 points!' % (username, correctAnswer, timeElapsed, points, streakBonus))
|
|
|
|
if self.registryValue('general.showStats', self.channel):
|
|
if self.registryValue('general.globalStats'):
|
|
stat = threadStorage.getUserStat(username, None)
|
|
else:
|
|
stat = threadStorage.getUserStat(username, self.channel)
|
|
|
|
if stat:
|
|
todaysScore = stat['points_day']
|
|
weekScore = stat['points_week']
|
|
monthScore = stat['points_month']
|
|
yearScore = stat['points_year']
|
|
totalScore = stat['points_total']
|
|
recapMessageList = ['\x02%s\x02 has won \x02%d\x02 in a row!' % (username, self.streak)]
|
|
recapMessageList.append(' Total Points')
|
|
recapMessageList.append(' TODAY: \x02%d\x02' % (todaysScore))
|
|
if weekScore > points:
|
|
recapMessageList.append(' this WEEK: \x02%d\x02' % (weekScore))
|
|
if monthScore > points:
|
|
recapMessageList.append(' this MONTH: \x02%d\x02' % (monthScore))
|
|
if yearScore > points:
|
|
recapMessageList.append(' this YEAR: \x02%d\x02' % (yearScore))
|
|
if totalScore > points:
|
|
recapMessageList.append(' & ALL TIME: \x02%d\x02' % (totalScore))
|
|
recapMessage = ''.join(recapMessageList)
|
|
self.sendMessage(recapMessage)
|
|
|
|
if self.state == 'post-question':
|
|
# Check for any pending stops, otherwise queue next question
|
|
if self.stopPending == True:
|
|
self.stop()
|
|
else:
|
|
waitTime = self.registryValue('general.waitTime',self.channel)
|
|
if waitTime < 2:
|
|
waitTime = 2
|
|
log.error('waitTime was set too low (<2 seconds). Setting to 2 seconds')
|
|
waitTime += time.time()
|
|
self.queueEvent(waitTime, self.nextQuestion)
|
|
self.state = 'no-question'
|
|
|
|
def getHintString(self, hintNum=None):
|
|
if hintNum == None:
|
|
hintNum = self.hintsCounter
|
|
hintRatio = self.registryValue('hints.hintRatio') # % to show each hint
|
|
hint = ''
|
|
ratio = float(hintRatio * .01)
|
|
charMask = self.registryValue('hints.charMask', self.channel)
|
|
|
|
# create a string with hints for all of the answers
|
|
if self.questionType == 'kaos':
|
|
for ans in self.answers:
|
|
if ircutils.toLower(ans) not in self.guessedAnswers:
|
|
ans = str(ans)
|
|
hintStr = ''
|
|
if hintNum == 0:
|
|
for char in ans:
|
|
if char in self.unmaskedChars:
|
|
hintStr += char
|
|
else:
|
|
hintStr += charMask
|
|
elif hintNum == 1:
|
|
divider = int(len(ans) * ratio)
|
|
divider = min(divider, 3)
|
|
divider = min(divider, len(ans)-1)
|
|
hintStr += ans[:divider]
|
|
masked = ans[divider:]
|
|
for char in masked:
|
|
if char in self.unmaskedChars:
|
|
hintStr += char
|
|
else:
|
|
hintStr += charMask
|
|
elif hintNum == 2:
|
|
divider = int(len(ans) * ratio)
|
|
divider = min(divider, 3)
|
|
divider = min(divider, len(ans)-1)
|
|
lettersInARow = divider-1
|
|
maskedInARow = 0
|
|
hintStr += ans[:divider]
|
|
ansend = ans[divider:]
|
|
hintsend = ''
|
|
unmasked = 0
|
|
if self.registryValue('hints.vowelsHint', self.channel):
|
|
hintStr += self.getMaskedVowels(ansend, divider-1)
|
|
else:
|
|
hintStr += self.getMaskedRandom(ansend, divider-1)
|
|
hint += ' [{0}]'.format(hintStr)
|
|
else:
|
|
ans = str(self.answers[0])
|
|
if hintNum == 0:
|
|
for char in ans:
|
|
if char in self.unmaskedChars:
|
|
hint += char
|
|
else:
|
|
hint += charMask
|
|
elif hintNum == 1:
|
|
divider = int(len(ans) * ratio)
|
|
divider = min(divider, 3)
|
|
divider = min(divider, len(ans)-1)
|
|
hint += ans[:divider]
|
|
masked = ans[divider:]
|
|
for char in masked:
|
|
if char in self.unmaskedChars:
|
|
hint += char
|
|
else:
|
|
hint += charMask
|
|
elif hintNum == 2:
|
|
divider = int(len(ans) * ratio)
|
|
divider = min(divider, 3)
|
|
divider = min(divider, len(ans)-1)
|
|
lettersInARow = divider-1
|
|
maskedInARow = 0
|
|
hint += ans[:divider]
|
|
ansend = ans[divider:]
|
|
hintsend = ''
|
|
unmasked = 0
|
|
if self.registryValue('hints.vowelsHint', self.channel):
|
|
hint += self.getMaskedVowels(ansend, divider-1)
|
|
else:
|
|
hint += self.getMaskedRandom(ansend, divider-1)
|
|
|
|
return hint.strip()
|
|
|
|
def getMaskedVowels(self, letters, sizeOfUnmasked):
|
|
charMask = self.registryValue('hints.charMask', self.channel)
|
|
hintsList = ['']
|
|
unmasked = 0
|
|
lettersInARow = sizeOfUnmasked
|
|
for char in letters:
|
|
if char in self.unmaskedChars:
|
|
hintsList.append(char)
|
|
elif str.lower(self.removeAccents(char)) in 'aeiou' and unmasked < (len(letters)-1) and lettersInARow < 3:
|
|
hintsList.append(char)
|
|
lettersInARow += 1
|
|
unmasked += 1
|
|
else:
|
|
hintsList.append(charMask)
|
|
lettersInARow = 0
|
|
hints = ''.join(hintsList)
|
|
return hints
|
|
|
|
def getMaskedRandom(self, letters, sizeOfUnmasked):
|
|
charMask = self.registryValue('hints.charMask', self.channel)
|
|
hintRatio = self.registryValue('hints.hintRatio') # % to show each hint
|
|
hints = ''
|
|
unmasked = 0
|
|
maskedInARow = 0
|
|
lettersInARow = sizeOfUnmasked
|
|
for char in letters:
|
|
if char in self.unmaskedChars:
|
|
hints += char
|
|
unmasked += 1
|
|
elif maskedInARow > 2 and unmasked < (len(letters)-1):
|
|
lettersInARow += 1
|
|
unmasked += 1
|
|
maskedInARow = 0
|
|
hints += char
|
|
elif lettersInARow < 3 and unmasked < (len(letters)-1) and random.randint(0,100) < hintRatio:
|
|
lettersInARow += 1
|
|
unmasked += 1
|
|
maskedInARow = 0
|
|
hints += char
|
|
else:
|
|
maskedInARow += 1
|
|
lettersInARow=0
|
|
hints += charMask
|
|
return hints
|
|
|
|
def getExtraHintString(self):
|
|
charMask = self.registryValue('hints.charMask', self.channel)
|
|
ans = self.answers[0]
|
|
hints = ' Extra Hint: \x02\x0312'
|
|
divider = 0
|
|
|
|
if len(ans) < 2:
|
|
divider = 0
|
|
elif self.hintsCounter == 1:
|
|
divider = 1
|
|
elif self.hintsCounter == 2:
|
|
divider = min(int((len(ans) * .25) + 1), 4)
|
|
elif self.hintsCounter == 3:
|
|
divider = min(int((len(ans) * .5) + 1), 6)
|
|
|
|
if divider == len(ans):
|
|
divider -= 1
|
|
|
|
if divider > 0:
|
|
hints += ans[:divider]
|
|
|
|
return hints
|
|
|
|
def getExtraHint(self):
|
|
if self.shownHint == False:
|
|
self.shownHint = True
|
|
self.sendMessage(self.getExtraHintString())
|
|
|
|
def getRemainingKAOS(self):
|
|
if self.shownHint == False:
|
|
self.shownHint = True
|
|
self.sendMessage('\x02\x0312%s' % (self.getHintString(self.hintsCounter-1)))
|
|
|
|
def loadGameState(self):
|
|
gameInfo = self.storage.getGame(self.channel)
|
|
if gameInfo is not None:
|
|
self.numAsked = gameInfo['num_asked']
|
|
self.roundStartedAt = gameInfo['round_started']
|
|
self.lastWinner = gameInfo['last_winner']
|
|
self.streak = int(gameInfo['streak'])
|
|
|
|
def loopEvent(self):
|
|
"""
|
|
Main game/question/hint loop called by event. Decides whether question or hint is needed.
|
|
"""
|
|
# out of hints to give?
|
|
if self.hintsCounter >= 3:
|
|
self.state = 'post-question'
|
|
|
|
if self.questionType == 'kaos':
|
|
# Create a string to show answers missed
|
|
missedAnswers = ''
|
|
for ans in self.answers:
|
|
if ircutils.toLower(ans) not in self.guessedAnswers:
|
|
missedAnswers += ' [{0}]'.format(ans)
|
|
self.sendMessage( """Time's up! No one got \x02%s\x02""" % missedAnswers.strip())
|
|
self.sendMessage("""Correctly Answered: \x02%d\x02 of \x02%d\x02 Total Awarded: \x02%d\x02 Points to \x02%d\x02 Players"""
|
|
% (len(self.guessedAnswers), len(self.answers), int(self.totalAmountWon), len(self.correctPlayers))
|
|
)
|
|
else:
|
|
self.sendMessage( """Time's up! The answer was \x02%s\x02.""" % self.answers[0])
|
|
|
|
self.storage.updateQuestionStats(self.questionID, 0, 1)
|
|
|
|
# Check for any pending stops, otherwise queue next question
|
|
if self.stopPending == True:
|
|
self.stop()
|
|
else:
|
|
waitTime = self.registryValue('general.waitTime', self.channel)
|
|
if waitTime < 2:
|
|
waitTime = 2
|
|
log.error('waitTime was set too low (<2 seconds). Setting to 2 seconds')
|
|
waitTime += time.time()
|
|
self.queueEvent(waitTime, self.nextQuestion)
|
|
self.state = 'no-question'
|
|
else:
|
|
# Give out next hint and queue this event again
|
|
self.showHint()
|
|
if self.questionType == 'kaos':
|
|
hintTime = self.registryValue('kaos.hintKAOS', self.channel)
|
|
else:
|
|
hintTime = self.registryValue('questions.hintTime', self.channel)
|
|
|
|
if hintTime < 2:
|
|
hintTime = 2
|
|
log.error('hintTime was set too low (<2 seconds). Setting to 2 seconds.')
|
|
|
|
hintTime += time.time()
|
|
self.queueEvent(hintTime, self.loopEvent)
|
|
|
|
def showHint(self):
|
|
"""
|
|
Max hints have not been reached, and no answer is found, need more hints
|
|
"""
|
|
hints = self.getHintString(self.hintsCounter)
|
|
self.hintsCounter += 1 #increment hints counter
|
|
self.shownHint = False #reset hint shown
|
|
self.sendMessage(' Hint %s: \x02\x0312%s' % (self.hintsCounter, hints), 1, 9)
|
|
|
|
def nextQuestion(self):
|
|
"""
|
|
Time for a new question
|
|
"""
|
|
inactivityTime = self.registryValue('general.timeout')
|
|
if self.lastAnswer < time.time() - inactivityTime:
|
|
self.sendMessage('Stopping due to inactivity')
|
|
self.stop()
|
|
return
|
|
elif self.stopPending == True:
|
|
self.stop()
|
|
return
|
|
|
|
# Reset and increment question properties
|
|
self.state = 'pre-question'
|
|
del self.skipList[:]
|
|
del self.guessedAnswers[:]
|
|
self.totalAmountWon = 0
|
|
self.correctPlayers.clear()
|
|
self.hintsCounter = 0
|
|
self.numAsked += 1
|
|
|
|
# grab the next question
|
|
numQuestion = self.storage.getNumQuestions()
|
|
if numQuestion == 0:
|
|
self.sendMessage('There are no questions. Stopping. If you are an admin, use the addfile command to add questions to the database.')
|
|
self.stop()
|
|
return
|
|
|
|
# Check if we've asked all questions
|
|
numQuestionsLeftInRound = self.storage.getNumQuestionsNotAsked(self.channel, self.roundStartedAt)
|
|
if numQuestionsLeftInRound == 0:
|
|
self.numAsked = 1
|
|
self.roundStartedAt = time.mktime(time.localtime())
|
|
self.storage.updateGameRoundStarted(self.channel, self.roundStartedAt)
|
|
self.sendMessage('All of the questions have been asked, shuffling and starting over')
|
|
|
|
# Update DB with new round number
|
|
self.storage.updateGame(self.channel, self.numAsked)
|
|
|
|
# Retrieve new question from DB
|
|
retrievedQuestion = self.retrieveQuestion()
|
|
self.questionID = retrievedQuestion['id']
|
|
self.questionType = retrievedQuestion['type']
|
|
self.question = retrievedQuestion['question']
|
|
self.answers = retrievedQuestion['answers']
|
|
self.questionPoints = retrievedQuestion['points']
|
|
|
|
# Store the question and round number so it can be reported
|
|
self.storage.insertGameLog(self.channel, self.numAsked, self.questionID, self.question)
|
|
|
|
# Send question to channel
|
|
self.sendQuestion()
|
|
|
|
# Set state variables after question has been sent
|
|
self.state = 'in-question'
|
|
self.questionRepeated = False
|
|
self.shownHint = False
|
|
self.askedAt = time.time()
|
|
|
|
# Start hint loop
|
|
self.loopEvent()
|
|
|
|
def normalizeString(self, s):
|
|
return str.lower(self.removeExtraSpaces(self.removeAccents(s)))
|
|
|
|
def queueEvent(self, time, event):
|
|
"""
|
|
Schedules a new event.
|
|
"""
|
|
# Schedule a new event to happen at the specified time
|
|
if self.active:
|
|
try:
|
|
schedule.addEvent(event, time, '%s.trivia' % self.channel)
|
|
except AssertionError as e:
|
|
log.error('Unable to queue {0} because another event is already scheduled.'.format(event.__name__))
|
|
|
|
def removeAccents(self, text):
|
|
text = str(text)
|
|
normalized = unicodedata.normalize('NFKD', text)
|
|
normalized = ''.join([c for c in normalized if not unicodedata.combining(c)])
|
|
return normalized
|
|
|
|
def removeExtraSpaces(self, text):
|
|
return utils.str.normalizeWhitespace(text)
|
|
|
|
def repeatQuestion(self):
|
|
if self.questionRepeated == False:
|
|
self.questionRepeated = True
|
|
self.sendQuestion()
|
|
|
|
def removeEvent(self):
|
|
"""
|
|
Remove/cancel trivia timer event
|
|
"""
|
|
# try and remove the current timer and thread, if we fail just carry on
|
|
try:
|
|
schedule.removeEvent('%s.trivia' % self.channel)
|
|
except KeyError:
|
|
pass
|
|
|
|
def retrieveQuestion(self):
|
|
# Retrieve and parse question data from database
|
|
rawData = self.storage.getRandomQuestionNotAsked(self.channel, self.roundStartedAt)
|
|
rawQuestion = rawData['question']
|
|
netTimesAnswered = rawData['num_answered'] - rawData['num_missed']
|
|
questionParts = rawQuestion.split('*')
|
|
|
|
if len(questionParts) > 1:
|
|
question = questionParts[0].strip()
|
|
answers = []
|
|
# Parse question for KAOS
|
|
if ircutils.toLower(question[:4]) == 'kaos':
|
|
questionType = 'kaos'
|
|
for ans in questionParts[1:]:
|
|
if answers.count(ans) == 0: # Filter out duplicate answers
|
|
answers.append(str(ans).strip())
|
|
# Parse question for Unscramble
|
|
elif ircutils.toLower(question[:5]) == 'uword':
|
|
questionType = 'uword'
|
|
ans = questionParts[1]
|
|
answers.append(str(ans).strip())
|
|
shuffledLetters = list(str(ans))
|
|
random.shuffle(shuffledLetters)
|
|
question = 'Unscramble the letters: {0}'.format(' '.join(shuffledLetters))
|
|
# Parse standard question
|
|
else:
|
|
questionType = 'regular'
|
|
for ans in questionParts[1:]:
|
|
answers.append(str(ans).strip())
|
|
|
|
# Calculate base points
|
|
if questionType == 'kaos':
|
|
points = self.registryValue('kaos.defaultKAOS', self.channel) * len(answers)
|
|
else:
|
|
points = self.registryValue('questions.defaultPoints', self.channel)
|
|
|
|
# Calculate additional points
|
|
addPoints = -5 * netTimesAnswered
|
|
addPoints = min(addPoints, 200)
|
|
addPoints = max(addPoints, -200)
|
|
|
|
return {'id': rawData['id'],
|
|
'type': questionType,
|
|
'points': points + addPoints,
|
|
'question': question,
|
|
'answers': answers
|
|
}
|
|
else:
|
|
log.info('Question #%d is invalid.' % rawData['id'])
|
|
# TODO report bad question
|
|
|
|
# default question, everything went wrong with grabbing question
|
|
return {'id': rawData['id'],
|
|
'type': 'kaos',
|
|
'points': 10,
|
|
'question': 'KAOS: The most awesome users in this channel? (This is a panic question, if you see this report this question. it is malformed. Please report immediately.)',
|
|
'answers': ['cars', 'some_weirdo', 'kessa', 'paimun']
|
|
}
|
|
|
|
def sendMessage(self, msg, color=None, bgcolor=None):
|
|
""" <msg>, [<color>], [<bgcolor>]
|
|
helper for game instance to send messages to channel
|
|
"""
|
|
# no color
|
|
self.irc.sendMsg(ircmsgs.privmsg(self.channel, '%s' % msg))
|
|
|
|
def sendQuestion(self):
|
|
question = self.question
|
|
if question[-1:] != '?':
|
|
question += '?'
|
|
|
|
# bold the q, add color
|
|
questionText = '\x02\x0303%s' % (question)
|
|
|
|
# KAOS? report # of answers
|
|
if self.questionType == 'kaos':
|
|
questionText += ' %d possible answers' % (len(self.answers))
|
|
|
|
questionMessageString = ' %s: %s' % (self.numAsked, questionText)
|
|
maxLength = 400
|
|
questionMesagePieces = [questionMessageString[i:i+maxLength] for i in range(0, len(questionMessageString), maxLength)]
|
|
multipleMessages=False
|
|
|
|
for msgPiece in questionMesagePieces:
|
|
if multipleMessages:
|
|
msgPiece = '\x02\x0303%s' % (msgPiece)
|
|
multipleMessages = True
|
|
self.sendMessage(msgPiece, 1, 9)
|
|
|
|
def stop(self):
|
|
"""
|
|
Stop a game in progress
|
|
"""
|
|
# responsible for stopping a timer/thread after being told to stop
|
|
self.active = False
|
|
self.stopPending = False
|
|
self.removeEvent()
|
|
self.sendMessage('Trivia stopped. :\'(')
|
|
channelCanonical = ircutils.toLower(self.channel)
|
|
if self.network in self.games:
|
|
if channelCanonical in self.games[self.network]:
|
|
del self.games[self.network][channelCanonical]
|
|
|
|
|
|
#Storage for users and points using sqlite3
|
|
class Storage:
|
|
"""
|
|
Storage class
|
|
"""
|
|
def __init__(self,loc):
|
|
self.loc = loc
|
|
self.conn = sqlite3.connect(loc, check_same_thread=False) # dont check threads
|
|
# otherwise errors
|
|
self.conn.text_factory = str
|
|
self.conn.row_factory = sqlite3.Row
|
|
|
|
def chunk(self, qs, rows=10000):
|
|
""" Divides the data into 10000 rows each """
|
|
for i in range(0, len(qs), rows):
|
|
yield qs[i:i+rows]
|
|
|
|
def countTemporaryQuestions(self, channel=None):
|
|
c = self.conn.cursor()
|
|
if channel is None:
|
|
c.execute('''SELECT COUNT(*)
|
|
FROM triviatemporaryquestion''')
|
|
else:
|
|
c.execute('''SELECT COUNT(*)
|
|
FROM triviatemporaryquestion
|
|
WHERE channel_canonical=?''',
|
|
(ircutils.toLower(channel),))
|
|
row = c.fetchone()
|
|
c.close()
|
|
return row[0]
|
|
|
|
def countDeletes(self, channel=None):
|
|
c = self.conn.cursor()
|
|
if channel is None:
|
|
c.execute('''SELECT COUNT(*)
|
|
FROM triviadelete''')
|
|
else:
|
|
c.execute('''SELECT COUNT(*)
|
|
FROM triviadelete
|
|
WHERE channel_canonical=?''',
|
|
(ircutils.toLower(channel),))
|
|
row = c.fetchone()
|
|
c.close()
|
|
return row[0]
|
|
|
|
def countEdits(self, channel=None):
|
|
c = self.conn.cursor()
|
|
if channel is None:
|
|
c.execute('''SELECT COUNT(*)
|
|
FROM triviaedit''')
|
|
else:
|
|
c.execute('''SELECT COUNT(*)
|
|
FROM triviaedit
|
|
WHERE channel_canonical=?''',
|
|
(ircutils.toLower(channel),))
|
|
row = c.fetchone()
|
|
c.close()
|
|
return row[0]
|
|
|
|
def countNotMyEdits(self, username, channel=None):
|
|
c = self.conn.cursor()
|
|
if channel is None:
|
|
c.execute('''SELECT COUNT(*)
|
|
FROM triviaedit
|
|
WHERE username<>?''',
|
|
(username,))
|
|
else:
|
|
c.execute('''SELECT COUNT(*)
|
|
FROM triviaedit
|
|
WHERE username<>?
|
|
AND channel_canonical=?''',
|
|
(username,
|
|
ircutils.toLower(channel),))
|
|
row = c.fetchone()
|
|
c.close()
|
|
return row[0]
|
|
|
|
def countMyEdits(self, username, channel=None):
|
|
c = self.conn.cursor()
|
|
if channel is None:
|
|
c.execute('''SELECT COUNT(*)
|
|
FROM triviaedit
|
|
WHERE username=?''',
|
|
(username,))
|
|
else:
|
|
c.execute('''SELECT COUNT(*)
|
|
FROM triviaedit
|
|
WHERE username=?
|
|
AND channel_canonical=?''',
|
|
(username,
|
|
ircutils.toLower(channel),))
|
|
row = c.fetchone()
|
|
c.close()
|
|
return row[0]
|
|
|
|
def countReports(self, channel=None):
|
|
c = self.conn.cursor()
|
|
if channel is None:
|
|
c.execute('''SELECT COUNT(*)
|
|
FROM triviareport''')
|
|
else:
|
|
c.execute('''SELECT COUNT(*)
|
|
FROM triviareport
|
|
WHERE channel_canonical=?''',
|
|
(ircutils.toLower(channel),))
|
|
row = c.fetchone()
|
|
c.close()
|
|
return row[0]
|
|
|
|
def deleteQuestion(self, questionId):
|
|
c = self.conn.cursor()
|
|
test = c.execute('''UPDATE triviaquestion
|
|
SET deleted=1
|
|
WHERE id=?''',
|
|
(questionId,))
|
|
self.conn.commit()
|
|
c.close()
|
|
|
|
def dropActivityTable(self):
|
|
c = self.conn.cursor()
|
|
try:
|
|
c.execute('''DROP TABLE triviaactivity''')
|
|
except:
|
|
pass
|
|
c.close()
|
|
|
|
def dropDeleteTable(self):
|
|
c = self.conn.cursor()
|
|
try:
|
|
c.execute('''DROP TABLE triviadelete''')
|
|
except:
|
|
pass
|
|
c.close()
|
|
|
|
def dropUserTable(self):
|
|
c = self.conn.cursor()
|
|
try:
|
|
c.execute('''DROP TABLE triviausers''')
|
|
except:
|
|
pass
|
|
c.close()
|
|
|
|
def dropLoginTable(self):
|
|
c = self.conn.cursor()
|
|
try:
|
|
c.execute('''DROP TABLE trivialogin''')
|
|
except:
|
|
pass
|
|
c.close()
|
|
|
|
def dropUserLogTable(self):
|
|
c = self.conn.cursor()
|
|
try:
|
|
c.execute('''DROP TABLE triviauserlog''')
|
|
except:
|
|
pass
|
|
c.close()
|
|
|
|
def dropGameTable(self):
|
|
c = self.conn.cursor()
|
|
try:
|
|
c.execute('''DROP table triviagames''')
|
|
except:
|
|
pass
|
|
c.close()
|
|
|
|
def dropGameLogTable(self):
|
|
c = self.conn.cursor()
|
|
try:
|
|
c.execute('''DROP TABLE triviagameslog''')
|
|
c.execute('''DROP INDEX gamelograndomindex''')
|
|
except:
|
|
pass
|
|
c.close()
|
|
|
|
def dropReportTable(self):
|
|
c = self.conn.cursor()
|
|
try:
|
|
c.execute('''DROP TABLE triviareport''')
|
|
except:
|
|
pass
|
|
c.close()
|
|
|
|
def dropQuestionTable(self):
|
|
c = self.conn.cursor()
|
|
try:
|
|
c.execute('''DROP TABLE triviaquestion''')
|
|
c.execute('''DROP INDEX questionrandomindex''')
|
|
except:
|
|
pass
|
|
c.close()
|
|
|
|
def dropTemporaryQuestionTable(self):
|
|
c = self.conn.cursor()
|
|
try:
|
|
c.execute('''DROP TABLE triviatemporaryquestion''')
|
|
except:
|
|
pass
|
|
c.close()
|
|
|
|
def dropEditTable(self):
|
|
c = self.conn.cursor()
|
|
try:
|
|
c.execute('''DROP TABLE triviaedit''')
|
|
except:
|
|
pass
|
|
c.close()
|
|
|
|
def dropLevelTable(self):
|
|
c = self.conn.cursor()
|
|
try:
|
|
c.execute('''DROP TABLE trivialevel''')
|
|
except:
|
|
pass
|
|
c.close()
|
|
|
|
def getRandomQuestionNotAsked(self, channel, roundStart):
|
|
c = self.conn.cursor()
|
|
c.execute('''SELECT *
|
|
FROM triviaquestion
|
|
WHERE deleted=0 AND
|
|
id NOT IN
|
|
(SELECT tl.line_num
|
|
FROM triviagameslog tl
|
|
WHERE tl.channel_canonical=? AND
|
|
tl.asked_at>=?)
|
|
ORDER BY random() LIMIT 1''',
|
|
(ircutils.toLower(channel),roundStart))
|
|
row = c.fetchone()
|
|
c.close()
|
|
return row
|
|
|
|
def getQuestionById(self, id):
|
|
c = self.conn.cursor()
|
|
c.execute('''SELECT *
|
|
FROM triviaquestion
|
|
WHERE id=? LIMIT 1''',
|
|
(id,))
|
|
row = c.fetchone()
|
|
c.close()
|
|
return row
|
|
|
|
def getQuestionByRound(self, roundNumber, channel):
|
|
channel=ircutils.toLower(channel)
|
|
c = self.conn.cursor()
|
|
c.execute('''SELECT *
|
|
FROM triviaquestion
|
|
WHERE id=(SELECT tgl.line_num
|
|
FROM triviagameslog tgl
|
|
WHERE tgl.round_num=? AND
|
|
tgl.channel_canonical=?
|
|
ORDER BY id DESC LIMIT 1)''',
|
|
(roundNumber,channel))
|
|
row = c.fetchone()
|
|
c.close()
|
|
return row
|
|
|
|
def getNumQuestionsNotAsked(self, channel, roundStart):
|
|
c = self.conn.cursor()
|
|
c.execute('''SELECT count(id)
|
|
FROM triviaquestion
|
|
WHERE deleted=0 AND
|
|
id NOT IN
|
|
(SELECT tl.line_num
|
|
FROM triviagameslog tl
|
|
WHERE tl.channel=? AND
|
|
tl.asked_at>=?)''',
|
|
(channel,roundStart))
|
|
row = c.fetchone()
|
|
c.close()
|
|
return row[0]
|
|
|
|
def getUserRank(self, username, channel):
|
|
usernameCanonical = ircutils.toLower(username)
|
|
channelCanonical = None
|
|
if channel is not None:
|
|
channelCanonical = ircutils.toLower(channel)
|
|
dateObject = datetime.date.today()
|
|
day = dateObject.day
|
|
month = dateObject.month
|
|
year = dateObject.year
|
|
data = {}
|
|
|
|
# Retrieve total rank
|
|
query = '''SELECT tr.rank
|
|
FROM (SELECT COUNT(tu2.id)+1 AS rank
|
|
FROM (SELECT id,
|
|
username,
|
|
sum(points_made) AS totalscore
|
|
FROM triviauserlog'''
|
|
arguments = []
|
|
|
|
if channel is not None:
|
|
query = '''%s WHERE channel_canonical=?''' % (query)
|
|
arguments.append(channelCanonical)
|
|
|
|
query = '''%s GROUP BY username_canonical) AS tu2
|
|
WHERE tu2.totalscore > (
|
|
SELECT SUM(points_made)
|
|
FROM triviauserlog
|
|
WHERE username_canonical=?''' % (query)
|
|
arguments.append(usernameCanonical)
|
|
|
|
if channel is not None:
|
|
query = '''%s AND channel_canonical=?''' % (query)
|
|
arguments.append(channelCanonical)
|
|
|
|
query = '''%s )) AS tr
|
|
WHERE EXISTS(
|
|
SELECT *
|
|
FROM triviauserlog
|
|
WHERE username_canonical=?''' % (query)
|
|
arguments.append(usernameCanonical)
|
|
|
|
if channel is not None:
|
|
query = '''%s AND channel_canonical=?''' % (query)
|
|
arguments.append(channelCanonical)
|
|
|
|
query = '''%s ) LIMIT 1''' % (query)
|
|
|
|
c = self.conn.cursor()
|
|
c.execute(query, tuple(arguments))
|
|
row = c.fetchone()
|
|
data['total'] = row[0] if row else 0
|
|
|
|
# Retrieve year rank
|
|
query = '''SELECT tr.rank
|
|
FROM (SELECT COUNT(tu2.id)+1 AS rank
|
|
FROM (SELECT id,
|
|
username,
|
|
SUM(points_made) AS totalscore
|
|
FROM triviauserlog
|
|
WHERE year=?'''
|
|
arguments = [year]
|
|
|
|
if channel is not None:
|
|
query = '''%s AND channel_canonical=?''' % (query)
|
|
arguments.append(channelCanonical)
|
|
|
|
query = '''%s GROUP BY username_canonical) AS tu2
|
|
WHERE tu2.totalscore > (
|
|
SELECT sum(points_made)
|
|
FROM triviauserlog
|
|
WHERE year=? AND
|
|
username_canonical=?''' % (query)
|
|
arguments.append(year)
|
|
arguments.append(usernameCanonical)
|
|
|
|
if channel is not None:
|
|
query = '''%s AND channel_canonical=?''' % (query)
|
|
arguments.append(channelCanonical)
|
|
|
|
query = '''%s )) AS tr
|
|
WHERE EXISTS(
|
|
SELECT *
|
|
FROM triviauserlog
|
|
WHERE year=? AND
|
|
username_canonical=?''' % (query)
|
|
arguments.append(year)
|
|
arguments.append(usernameCanonical)
|
|
|
|
if channel is not None:
|
|
query = '''%s AND channel_canonical=?''' % (query)
|
|
arguments.append(channelCanonical)
|
|
|
|
query = '''%s ) LIMIT 1''' % (query)
|
|
|
|
c.execute(query, tuple(arguments))
|
|
row = c.fetchone()
|
|
data['year'] = row[0] if row else 0
|
|
|
|
# Retrieve month rank
|
|
query = '''SELECT tr.rank
|
|
FROM (SELECT COUNT(tu2.id)+1 AS rank
|
|
FROM (SELECT id,
|
|
username,
|
|
SUM(points_made) AS totalscore
|
|
FROM triviauserlog
|
|
WHERE month=? AND
|
|
year=?'''
|
|
arguments = [month, year]
|
|
|
|
if channel is not None:
|
|
query = '''%s AND channel_canonical=?''' % (query)
|
|
arguments.append(channelCanonical)
|
|
|
|
query = '''%s GROUP BY username_canonical) AS tu2
|
|
WHERE tu2.totalscore > (
|
|
SELECT SUM(points_made)
|
|
FROM triviauserlog
|
|
WHERE month=? AND
|
|
year=? AND
|
|
username_canonical=?''' % (query)
|
|
arguments.append(month)
|
|
arguments.append(year)
|
|
arguments.append(usernameCanonical)
|
|
|
|
if channel is not None:
|
|
query = '''%s AND channel_canonical=?''' % (query)
|
|
arguments.append(channelCanonical)
|
|
|
|
query = '''%s )) AS tr
|
|
WHERE EXISTS(
|
|
SELECT *
|
|
FROM triviauserlog
|
|
WHERE month=? AND
|
|
year=? AND
|
|
username_canonical=?''' % (query)
|
|
arguments.append(month)
|
|
arguments.append(year)
|
|
arguments.append(usernameCanonical)
|
|
|
|
if channel is not None:
|
|
query = '''%s AND channel_canonical=?''' % (query)
|
|
arguments.append(channelCanonical)
|
|
|
|
query = '''%s ) LIMIT 1''' % (query)
|
|
|
|
c.execute(query, tuple(arguments))
|
|
row = c.fetchone()
|
|
data['month'] = row[0] if row else 0
|
|
|
|
# Retrieve week rank
|
|
weekSqlClause = ''
|
|
d = datetime.date.today()
|
|
weekday=d.weekday()
|
|
d -= datetime.timedelta(weekday)
|
|
for i in range(7):
|
|
if i > 0:
|
|
weekSqlClause += ' or '
|
|
weekSqlClause += '''(year=%d AND
|
|
month=%d AND
|
|
day=%d)''' % (d.year, d.month, d.day)
|
|
d += datetime.timedelta(1)
|
|
|
|
weekSql = '''SELECT tr.rank
|
|
FROM (SELECT count(tu2.id)+1 AS rank
|
|
FROM (SELECT id,
|
|
username,
|
|
SUM(points_made) AS totalscore
|
|
FROM triviauserlog
|
|
WHERE ('''
|
|
weekSql += weekSqlClause
|
|
weekSql +=''')'''
|
|
arguments = []
|
|
|
|
if channel is not None:
|
|
weekSql = '''%s AND channel_canonical=?''' % (weekSql)
|
|
arguments.append(channelCanonical)
|
|
|
|
weekSql += '''GROUP BY username_canonical) AS tu2
|
|
WHERE tu2.totalscore > (
|
|
SELECT SUM(points_made)
|
|
FROM triviauserlog
|
|
WHERE username_canonical=?'''
|
|
arguments.append(usernameCanonical)
|
|
|
|
if channel is not None:
|
|
weekSql = '''%s AND channel_canonical=?''' % (weekSql)
|
|
arguments.append(channelCanonical)
|
|
|
|
weekSql += ''' AND ('''
|
|
weekSql += weekSqlClause
|
|
weekSql += '''))) AS tr
|
|
WHERE EXISTS(
|
|
SELECT *
|
|
FROM triviauserlog
|
|
WHERE username_canonical=?'''
|
|
arguments.append(usernameCanonical)
|
|
|
|
if channel is not None:
|
|
weekSql = '''%s AND channel_canonical=?''' % (weekSql)
|
|
arguments.append(channelCanonical)
|
|
|
|
weekSql += ''' AND ('''
|
|
weekSql += weekSqlClause
|
|
weekSql += ''')) LIMIT 1'''
|
|
|
|
c.execute(weekSql, tuple(arguments))
|
|
row = c.fetchone()
|
|
data['week'] = row[0] if row else 0
|
|
|
|
# Retrieve day rank
|
|
query = '''SELECT tr.rank
|
|
FROM (SELECT COUNT(tu2.id)+1 AS rank
|
|
FROM (SELECT id,
|
|
username,
|
|
SUM(points_made) AS totalscore
|
|
FROM triviauserlog
|
|
WHERE day=? AND
|
|
month=? AND
|
|
year=?'''
|
|
arguments = [day, month, year]
|
|
|
|
if channel is not None:
|
|
query = '''%s AND channel_canonical=?''' % (query)
|
|
arguments.append(channelCanonical)
|
|
|
|
query = '''%s GROUP BY username_canonical) AS tu2
|
|
WHERE tu2.totalscore > (
|
|
SELECT SUM(points_made)
|
|
FROM triviauserlog
|
|
WHERE day=? AND
|
|
month=? AND
|
|
year=? AND
|
|
username_canonical=?''' % (query)
|
|
arguments.append(day)
|
|
arguments.append(month)
|
|
arguments.append(year)
|
|
arguments.append(usernameCanonical)
|
|
|
|
if channel is not None:
|
|
query = '''%s and channel_canonical=?''' % (query)
|
|
arguments.append(channelCanonical)
|
|
|
|
query = '''%s )) AS tr
|
|
WHERE EXISTS(
|
|
SELECT *
|
|
FROM triviauserlog
|
|
WHERE day=? AND
|
|
month=? AND
|
|
year=? AND
|
|
username_canonical=?''' % (query)
|
|
arguments.append(day)
|
|
arguments.append(month)
|
|
arguments.append(year)
|
|
arguments.append(usernameCanonical)
|
|
|
|
if channel is not None:
|
|
query = '''%s AND channel_canonical=?''' % (query)
|
|
arguments.append(channelCanonical)
|
|
|
|
query = '''%s ) LIMIT 1''' % (query)
|
|
|
|
c.execute(query, tuple(arguments))
|
|
row = c.fetchone()
|
|
data['day'] = row[0] if row else 0
|
|
|
|
c.close()
|
|
return data
|
|
|
|
def getUserStat(self, username, channel):
|
|
usernameCanonical = ircutils.toLower(username)
|
|
channelCanonical = None
|
|
if channel is not None:
|
|
channelCanonical = ircutils.toLower(channel)
|
|
dateObject = datetime.date.today()
|
|
day = dateObject.day
|
|
month = dateObject.month
|
|
year = dateObject.year
|
|
|
|
c = self.conn.cursor()
|
|
|
|
data = {}
|
|
data['username'] = username
|
|
data['username_canonical'] = usernameCanonical
|
|
|
|
# Retrieve total points and answered
|
|
query = '''SELECT SUM(tl.points_made) AS points,
|
|
SUM(tl.num_answered) AS answered
|
|
FROM triviauserlog tl
|
|
WHERE tl.username_canonical=?'''
|
|
arguments = [usernameCanonical]
|
|
|
|
if channel is not None:
|
|
query = '''%s AND tl.channel_canonical=?''' % (query)
|
|
arguments.append(channelCanonical)
|
|
|
|
query = '''%s LIMIT 1''' % (query)
|
|
|
|
c.execute(query, tuple(arguments))
|
|
row = c.fetchone()
|
|
if row:
|
|
data['points_total'] = row[0]
|
|
data['answer_total'] = row[1]
|
|
|
|
# Retrieve year points and answered
|
|
query = '''SELECT SUM(tl.points_made) AS yearPoints,
|
|
SUM(tl.num_answered) AS yearAnswered
|
|
FROM triviauserlog tl
|
|
WHERE tl.username_canonical=? AND
|
|
tl.year=?'''
|
|
arguments = [usernameCanonical, year]
|
|
|
|
if channel is not None:
|
|
query = '''%s AND tl.channel_canonical=?''' % (query)
|
|
arguments.append(channelCanonical)
|
|
|
|
query = '''%s LIMIT 1''' % (query)
|
|
|
|
c.execute(query, tuple(arguments))
|
|
row = c.fetchone()
|
|
if row:
|
|
data['points_year'] = row[0]
|
|
data['answer_year'] = row[1]
|
|
|
|
# Retrieve month points and answered
|
|
query = '''SELECT SUM(tl.points_made) AS yearPoints,
|
|
SUM(tl.num_answered) AS yearAnswered
|
|
FROM triviauserlog tl
|
|
WHERE tl.username_canonical=? AND
|
|
tl.year=? AND
|
|
tl.month=?'''
|
|
arguments = [usernameCanonical, year, month]
|
|
|
|
if channel is not None:
|
|
query = '''%s AND tl.channel_canonical=?''' % (query)
|
|
arguments.append(channelCanonical)
|
|
|
|
query = '''%s LIMIT 1''' % (query)
|
|
|
|
c.execute(query, tuple(arguments))
|
|
row = c.fetchone()
|
|
if row:
|
|
data['points_month'] = row[0]
|
|
data['answer_month'] = row[1]
|
|
|
|
# Retrieve week points and answered
|
|
query = '''SELECT SUM(tl.points_made) AS yearPoints,
|
|
SUM(tl.num_answered) AS yearAnswered
|
|
FROM triviauserlog tl
|
|
WHERE tl.username_canonical=? AND ('''
|
|
|
|
d = datetime.date.today()
|
|
weekday=d.weekday()
|
|
d -= datetime.timedelta(weekday)
|
|
for i in range(7):
|
|
if i > 0:
|
|
query += ' or '
|
|
query += '''
|
|
(tl.year=%d
|
|
AND tl.month=%d
|
|
AND tl.day=%d)''' % (d.year, d.month, d.day)
|
|
d += datetime.timedelta(1)
|
|
|
|
query += ')'
|
|
arguments = [usernameCanonical]
|
|
|
|
if channel is not None:
|
|
query = '''%s AND tl.channel_canonical=?''' % (query)
|
|
arguments.append(channelCanonical)
|
|
|
|
query = '''%s LIMIT 1''' % (query)
|
|
|
|
c.execute(query, tuple(arguments))
|
|
row = c.fetchone()
|
|
if row:
|
|
data['points_week'] = row[0]
|
|
data['answer_week'] = row[1]
|
|
|
|
# Retrieve day points and answered
|
|
query = '''SELECT SUM(tl.points_made) AS yearPoints,
|
|
SUM(tl.num_answered) AS yearAnswered
|
|
FROM triviauserlog tl
|
|
WHERE tl.username_canonical=? AND
|
|
tl.year=? AND
|
|
tl.month=? AND
|
|
tl.day=?'''
|
|
arguments = [usernameCanonical, year, month, day]
|
|
|
|
if channel is not None:
|
|
query = '''%s AND tl.channel_canonical=?''' % (query)
|
|
arguments.append(channelCanonical)
|
|
|
|
query = '''%s LIMIT 1''' % (query)
|
|
|
|
c.execute(query, tuple(arguments))
|
|
row = c.fetchone()
|
|
if row:
|
|
data['points_day'] = row[0]
|
|
data['answer_day'] = row[1]
|
|
|
|
c.close()
|
|
return data
|
|
|
|
def getUserLevel(self, username, channel):
|
|
usernameCanonical = ircutils.toLower(username)
|
|
channelCanonical = ircutils.toLower(channel)
|
|
|
|
c = self.conn.cursor()
|
|
c.execute('''SELECT level
|
|
FROM trivialevel
|
|
WHERE username_canonical=? AND
|
|
channel_canonical=?''',
|
|
(username_canonical, channel_canonical))
|
|
row = c.fetchone()
|
|
c.close()
|
|
return row[0]
|
|
|
|
def getGame(self, channel):
|
|
channel = ircutils.toLower(channel)
|
|
c = self.conn.cursor()
|
|
c.execute('''SELECT *
|
|
FROM triviagames
|
|
WHERE channel_canonical=?
|
|
LIMIT 1''',
|
|
(channel,))
|
|
row = c.fetchone()
|
|
c.close()
|
|
return row
|
|
|
|
def getNumUser(self, channel):
|
|
channelCanonical = ircutils.toLower(channel)
|
|
c = self.conn.cursor()
|
|
c.execute('''SELECT COUNT(DISTINCT(username_canonical))
|
|
FROM triviauserlog
|
|
WHERE channel_canonical=?''',
|
|
(channelCanonical,))
|
|
row = c.fetchone()
|
|
c.close()
|
|
return row[0]
|
|
|
|
def getNumQuestions(self):
|
|
c = self.conn.cursor()
|
|
c.execute('''SELECT COUNT(*)
|
|
FROM triviaquestion
|
|
WHERE deleted=0''')
|
|
row = c.fetchone()
|
|
c.close()
|
|
return row[0]
|
|
|
|
def getNumKAOS(self):
|
|
c = self.conn.cursor()
|
|
c.execute('''SELECT COUNT(*)
|
|
FROM triviaquestion
|
|
WHERE lower(substr(question,1,4))=?''',
|
|
('kaos',))
|
|
row = c.fetchone()
|
|
c.close()
|
|
return row[0]
|
|
|
|
def getNumActiveThisWeek(self, channel):
|
|
channelCanonical = ircutils.toLower(channel)
|
|
d = datetime.date.today()
|
|
weekday=d.weekday()
|
|
d -= datetime.timedelta(weekday)
|
|
weekSqlString = ''
|
|
for i in range(7):
|
|
if i > 0:
|
|
weekSqlString += ' or '
|
|
weekSqlString += '''
|
|
(tl.year=%d
|
|
AND tl.month=%d
|
|
AND tl.day=%d)''' % (d.year, d.month, d.day)
|
|
d += datetime.timedelta(1)
|
|
c = self.conn.cursor()
|
|
weekSql = '''SELECT COUNT(DISTINCT(tl.username_canonical))
|
|
FROM triviauserlog tl
|
|
WHERE channel_canonical=? AND ('''
|
|
weekSql += weekSqlString
|
|
weekSql += ''')'''
|
|
c.execute(weekSql, (channelCanonical,))
|
|
row = c.fetchone()
|
|
c.close()
|
|
return row[0]
|
|
|
|
def getDeleteById(self, id, channel=None):
|
|
c = self.conn.cursor()
|
|
if channel is None:
|
|
c.execute('''SELECT *
|
|
FROM triviadelete
|
|
WHERE id=? LIMIT 1''',
|
|
(id,))
|
|
else:
|
|
c.execute('''SELECT *
|
|
FROM triviadelete
|
|
WHERE id=? AND
|
|
channel_canonical=?
|
|
LIMIT 1''',
|
|
(id, ircutils.toLower(channel)))
|
|
row = c.fetchone()
|
|
c.close()
|
|
return row
|
|
|
|
def getDeleteTop5(self, page=1, amount=5, channel=None):
|
|
if page < 1:
|
|
page=1
|
|
if amount < 1:
|
|
amount = 5
|
|
page -= 1
|
|
start = page * amount
|
|
c = self.conn.cursor()
|
|
if channel is None:
|
|
c.execute('''SELECT *
|
|
FROM triviadelete
|
|
ORDER BY id ASC LIMIT ?, ?''',
|
|
(start, amount))
|
|
else:
|
|
c.execute('''SELECT *
|
|
FROM triviadelete
|
|
WHERE channel_canonical=?
|
|
ORDER BY id ASC LIMIT ?, ?''',
|
|
(ircutils.toLower(channel), start, amount))
|
|
rows = c.fetchall()
|
|
c.close()
|
|
return rows
|
|
|
|
def getReportById(self, id, channel=None):
|
|
c = self.conn.cursor()
|
|
if channel is None:
|
|
c.execute('''SELECT *
|
|
FROM triviareport
|
|
WHERE id=? LIMIT 1''',
|
|
(id,))
|
|
else:
|
|
c.execute('''SELECT *
|
|
FROM triviareport
|
|
WHERE id=? AND
|
|
channel_canonical=?
|
|
LIMIT 1''',
|
|
(id, ircutils.toLower(channel)))
|
|
row = c.fetchone()
|
|
c.close()
|
|
return row
|
|
|
|
def getReportTop5(self, page=1, amount=5, channel=None):
|
|
if page < 1:
|
|
page=1
|
|
if amount < 1:
|
|
amount = 5
|
|
page -= 1
|
|
start = page * amount
|
|
c = self.conn.cursor()
|
|
if channel is None:
|
|
c.execute('''SELECT *
|
|
FROM triviareport
|
|
ORDER BY id ASC LIMIT ?, ?''',
|
|
(start, amount))
|
|
else:
|
|
c.execute('''SELECT *
|
|
FROM triviareport
|
|
WHERE channel_canonical=?
|
|
ORDER BY id ASC LIMIT ?, ?''',
|
|
(ircutils.toLower(channel), start, amount))
|
|
rows = c.fetchall()
|
|
c.close()
|
|
return rows
|
|
|
|
def getTemporaryQuestionTop5(self, page=1, amount=5, channel=None):
|
|
if page < 1:
|
|
page=1
|
|
if amount < 1:
|
|
amount = 5
|
|
page -= 1
|
|
start = page * amount
|
|
c = self.conn.cursor()
|
|
if channel is None:
|
|
c.execute('''SELECT *
|
|
FROM triviatemporaryquestion
|
|
ORDER BY id ASC LIMIT ?, ?''',
|
|
(start, amount))
|
|
else:
|
|
c.execute('''SELECT *
|
|
FROM triviatemporaryquestion
|
|
WHERE channel_canonical=?
|
|
ORDER BY id ASC LIMIT ?, ?''',
|
|
(ircutils.toLower(channel), start, amount))
|
|
rows = c.fetchall()
|
|
c.close()
|
|
return rows
|
|
|
|
def getTemporaryQuestionById(self, id, channel=None):
|
|
c = self.conn.cursor()
|
|
if channel is None:
|
|
c.execute('''SELECT *
|
|
FROM triviatemporaryquestion
|
|
WHERE id=?
|
|
LIMIT 1''',
|
|
(id,))
|
|
else:
|
|
c.execute('''SELECT *
|
|
FROM triviatemporaryquestion
|
|
WHERE id=? AND
|
|
channel_canonical=?
|
|
LIMIT 1''',
|
|
(id, ircutils.toLower(channel)))
|
|
row = c.fetchone()
|
|
c.close()
|
|
return row
|
|
|
|
def getEditTop5(self, page=1, amount=5, channel=None):
|
|
if page < 1:
|
|
page = 1
|
|
if amount < 1:
|
|
amount = 5
|
|
page -= 1
|
|
start = page * amount
|
|
c = self.conn.cursor()
|
|
if channel is None:
|
|
c.execute('''SELECT *
|
|
FROM triviaedit
|
|
ORDER BY id ASC LIMIT ?, ?''',
|
|
(start, amount))
|
|
else:
|
|
c.execute('''SELECT *
|
|
FROM triviaedit
|
|
WHERE channel_canonical=?
|
|
ORDER BY id ASC LIMIT ?, ?''',
|
|
(ircutils.toLower(channel), start, amount))
|
|
rows = c.fetchall()
|
|
c.close()
|
|
return rows
|
|
|
|
def getNotMyEditTop5(self, username, page=1, amount=5, channel=None):
|
|
if page < 1:
|
|
page = 1
|
|
if amount < 1:
|
|
amount = 5
|
|
page -= 1
|
|
start = page * amount
|
|
c = self.conn.cursor()
|
|
if channel is None:
|
|
c.execute('''SELECT *
|
|
FROM triviaedit
|
|
WHERE username<>?
|
|
ORDER BY id ASC LIMIT ?, ?''',
|
|
(username, start, amount))
|
|
else:
|
|
c.execute('''SELECT *
|
|
FROM triviaedit
|
|
WHERE username<>?
|
|
AND channel_canonical=?
|
|
ORDER BY id ASC LIMIT ?, ?''',
|
|
(username, ircutils.toLower(channel), start, amount))
|
|
rows = c.fetchall()
|
|
c.close()
|
|
return rows
|
|
|
|
def getMyEditTop5(self, username, page=1, amount=5, channel=None):
|
|
if page < 1:
|
|
page = 1
|
|
if amount < 1:
|
|
amount = 5
|
|
page -= 1
|
|
start = page * amount
|
|
c = self.conn.cursor()
|
|
if channel is None:
|
|
c.execute('''SELECT *
|
|
FROM triviaedit
|
|
WHERE username=?
|
|
ORDER BY id ASC LIMIT ?, ?''',
|
|
(username, start, amount))
|
|
else:
|
|
c.execute('''SELECT *
|
|
FROM triviaedit
|
|
WHERE username=?
|
|
AND channel_canonical=?
|
|
ORDER BY id ASC LIMIT ?, ?''',
|
|
(username, ircutils.toLower(channel), start, amount))
|
|
rows = c.fetchall()
|
|
c.close()
|
|
return rows
|
|
|
|
def getEditById(self, id, channel=None):
|
|
c = self.conn.cursor()
|
|
if channel is None:
|
|
c.execute('''SELECT *
|
|
FROM triviaedit
|
|
WHERE id=?
|
|
LIMIT 1''',
|
|
(id,))
|
|
else:
|
|
c.execute('''SELECT *
|
|
FROM triviaedit
|
|
WHERE id=? AND
|
|
channel_canonical=?
|
|
LIMIT 1''',
|
|
(id, ircutils.toLower(channel)))
|
|
row = c.fetchone()
|
|
c.close()
|
|
return row
|
|
|
|
def getNumUserActiveIn(self, channel, timeSeconds):
|
|
channelCanonical = ircutils.toLower(channel)
|
|
epoch = int(time.mktime(time.localtime()))
|
|
dateObject = datetime.date.today()
|
|
day = dateObject.day
|
|
month = dateObject.month
|
|
year = dateObject.year
|
|
c = self.conn.cursor()
|
|
c.execute('''SELECT COUNT(*)
|
|
FROM triviauserlog
|
|
WHERE day=? AND
|
|
month=? AND
|
|
year=? AND
|
|
channel_canonical=? AND
|
|
last_updated>?''',
|
|
(day, month, year, channelCanonical, (epoch-timeSeconds)))
|
|
row = c.fetchone()
|
|
c.close()
|
|
return row[0]
|
|
|
|
def getVersion(self):
|
|
c = self.conn.cursor();
|
|
try:
|
|
c.execute('''SELECT version
|
|
FROM triviainfo''')
|
|
return c.fetchone()
|
|
except:
|
|
pass
|
|
|
|
def gameExists(self, channel):
|
|
channel = ircutils.toLower(channel)
|
|
c = self.conn.cursor()
|
|
c.execute('''SELECT COUNT(id)
|
|
FROM triviagames
|
|
WHERE channel_canonical=?''',
|
|
(channel,))
|
|
row = c.fetchone()
|
|
c.close()
|
|
return row[0] > 0
|
|
|
|
def loginExists(self, username):
|
|
usernameCanonical = ircutils.toLower(username)
|
|
c = self.conn.cursor()
|
|
c.execute('''SELECT COUNT(id)
|
|
FROM trivialogin
|
|
WHERE username_canonical=?''',
|
|
(usernameCanonical,))
|
|
row = c.fetchone()
|
|
c.close()
|
|
return row[0] > 0
|
|
|
|
def insertActivity(self, aType, activity, channel, network, timestamp=None):
|
|
if timestamp is None:
|
|
timestamp = int(time.mktime(time.localtime()))
|
|
channelCanonical = ircutils.toLower(channel)
|
|
c = self.conn.cursor()
|
|
c.execute('''INSERT INTO triviaactivity
|
|
VALUES (NULL, ?, ?, ?, ?, ?, ?)''',
|
|
(aType, activity, channel, channelCanonical, network,
|
|
timestamp))
|
|
self.conn.commit()
|
|
|
|
def insertDelete(self, username, channel, lineNumber, reason):
|
|
usernameCanonical = ircutils.toLower(username)
|
|
channelCanonical = ircutils.toLower(channel)
|
|
c = self.conn.cursor()
|
|
c.execute('''INSERT INTO triviadelete
|
|
VALUES (NULL, ?, ?, ?, ?, ?, ?)''',
|
|
(username, usernameCanonical, lineNumber, channel,
|
|
channelCanonical, reason))
|
|
self.conn.commit()
|
|
|
|
def insertLogin(self, username, salt, isHashed, password, capability):
|
|
usernameCanonical = ircutils.toLower(username)
|
|
if self.loginExists(username):
|
|
return self.updateLogin(username, salt, isHashed, password, capability)
|
|
if not isHashed:
|
|
isHashed = 0
|
|
else:
|
|
isHashed = 1
|
|
c = self.conn.cursor()
|
|
c.execute('''INSERT INTO trivialogin
|
|
VALUES (NULL, ?, ?, ?, ?, ?, ?)''',
|
|
(username, usernameCanonical, salt, isHashed,
|
|
password, capability))
|
|
self.conn.commit()
|
|
|
|
def insertUserLog(self, username, channel, score, numAnswered, timeTaken, day=None, month=None, year=None, epoch=None):
|
|
if day == None and month == None and year == None:
|
|
dateObject = datetime.date.today()
|
|
day = dateObject.day
|
|
month = dateObject.month
|
|
year = dateObject.year
|
|
score = int(score)
|
|
if epoch is None:
|
|
epoch = int(time.mktime(time.localtime()))
|
|
if self.userLogExists(username, channel, day, month, year):
|
|
return self.updateUserLog(username, channel, score, numAnswered, timeTaken, day, month, year, epoch)
|
|
c = self.conn.cursor()
|
|
usernameCanonical = ircutils.toLower(username)
|
|
channelCanonical = ircutils.toLower(channel)
|
|
scoreAvg = 'NULL'
|
|
if numAnswered >= 1:
|
|
scoreAvg = score / numAnswered
|
|
c.execute('''INSERT INTO triviauserlog
|
|
VALUES (NULL, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)''',
|
|
(username, score, numAnswered, day, month, year,
|
|
epoch, timeTaken, scoreAvg, usernameCanonical,
|
|
channel, channelCanonical))
|
|
self.conn.commit()
|
|
c.close()
|
|
|
|
def insertUser(self, username, numEditted=0, numEdittedAccepted=0, numReported=0, numQuestionsAdded=0, numQuestionsAccepted=0):
|
|
usernameCanonical = ircutils.toLower(username)
|
|
if self.userExists(username):
|
|
return self.updateUser(username, numEditted, numEdittedAccepted, numReported, numQuestionsAdded, numQuestionsAccepted)
|
|
c = self.conn.cursor()
|
|
c.execute('''INSERT INTO triviausers
|
|
VALUES (NULL, ?, ?, ?, ?, ?, ?, ?, 0)''',
|
|
(username, numEditted, numEdittedAccepted,
|
|
usernameCanonical, numReported,
|
|
numQuestionsAdded, numQuestionsAccepted))
|
|
self.conn.commit()
|
|
c.close()
|
|
|
|
def insertUserLevel(self, username, channel, level):
|
|
if self.userLevelExists(username, channel):
|
|
return self.updateUserLevel(username, channel, level)
|
|
|
|
usernameCanonical = ircutils.toLower(username)
|
|
channelCanonical = ircutils.toLower(channel)
|
|
|
|
c = self.conn.cursor()
|
|
c.execute('''INSERT INTO trivialevel
|
|
VALUES (?, ?, ?, ?, ?)''',
|
|
(username, usernameCanonical, channel,
|
|
channelCanonical, level))
|
|
self.conn.commit()
|
|
c.close()
|
|
|
|
def insertGame(self, channel, numAsked=0, epoch=None):
|
|
channelCanonical = ircutils.toLower(channel)
|
|
if self.gameExists(channel):
|
|
return self.updateGame(channel, numAsked)
|
|
if epoch is None:
|
|
epoch = int(time.mktime(time.localtime()))
|
|
c = self.conn.cursor()
|
|
c.execute('''INSERT INTO triviagames
|
|
VALUES (NULL, ?, ?, ?, 0, 0, ?, 0, "", "")''',
|
|
(channel, numAsked, epoch, channelCanonical))
|
|
self.conn.commit()
|
|
c.close()
|
|
|
|
def insertGameLog(self, channel, roundNumber, lineNumber, questionText, askedAt=None):
|
|
channelCanonical = ircutils.toLower(channel)
|
|
if askedAt is None:
|
|
askedAt = int(time.mktime(time.localtime()))
|
|
c = self.conn.cursor()
|
|
c.execute('''INSERT INTO triviagameslog
|
|
VALUES (NULL, ?, ?, ?, ?, ?, ?)''',
|
|
(channel, roundNumber, lineNumber, questionText,
|
|
askedAt, channelCanonical))
|
|
self.conn.commit()
|
|
c.close()
|
|
|
|
def insertReport(self, channel, username, reportText, questionNum, reportedAt=None):
|
|
channelCanonical = ircutils.toLower(channel)
|
|
usernameCanonical = ircutils.toLower(username)
|
|
if reportedAt is None:
|
|
reportedAt = int(time.mktime(time.localtime()))
|
|
c = self.conn.cursor()
|
|
c.execute('''INSERT INTO triviareport
|
|
VALUES (NULL, ?, ?, ?, ?, NULL, NULL, ?, ?, ?)''',
|
|
(channel, username, reportText, reportedAt,
|
|
questionNum, usernameCanonical, channelCanonical))
|
|
self.conn.commit()
|
|
c.close()
|
|
|
|
def insertQuestionsBulk(self, questions):
|
|
c = self.conn.cursor()
|
|
#skipped=0
|
|
divData = self.chunk(questions) # divide into 10000 rows each
|
|
for chunk in divData:
|
|
c.executemany('''INSERT INTO triviaquestion
|
|
VALUES (NULL, ?, ?, 0, 0, 0)''',
|
|
chunk)
|
|
self.conn.commit()
|
|
skipped = self.removeDuplicateQuestions()
|
|
c.close()
|
|
return ((len(questions) - skipped), skipped)
|
|
|
|
def insertEdit(self, questionId, questionText, username, channel, createdAt=None):
|
|
c = self.conn.cursor()
|
|
channelCanonical = ircutils.toLower(channel)
|
|
usernameCanonical = ircutils.toLower(username)
|
|
if createdAt is None:
|
|
createdAt = int(time.mktime(time.localtime()))
|
|
c.execute('''INSERT INTO triviaedit
|
|
VALUES (NULL, ?, ?, NULL, ?, ?, ?, ?, ?)''',
|
|
(questionId, questionText, username, channel,
|
|
createdAt, usernameCanonical, channelCanonical))
|
|
self.conn.commit()
|
|
c.close()
|
|
|
|
def insertTemporaryQuestion(self, username, channel, question):
|
|
c = self.conn.cursor()
|
|
channelCanonical = ircutils.toLower(channel)
|
|
usernameCanonical = ircutils.toLower(username)
|
|
c.execute('''INSERT INTO triviatemporaryquestion
|
|
VALUES (NULL, ?, ?, ?, ?, ?)''',
|
|
(username, channel, question, usernameCanonical,
|
|
channelCanonical))
|
|
self.conn.commit()
|
|
c.close()
|
|
|
|
def isQuestionDeleted(self, id):
|
|
c = self.conn.cursor()
|
|
c.execute('''SELECT COUNT(*)
|
|
FROM triviaquestion
|
|
WHERE deleted=1 AND
|
|
id=?''',
|
|
(id,))
|
|
row = c.fetchone()
|
|
c.close()
|
|
return row[0] > 0
|
|
|
|
def isQuestionPendingDeletion(self, id):
|
|
c = self.conn.cursor()
|
|
c.execute('''SELECT COUNT(*)
|
|
FROM triviadelete
|
|
WHERE line_num=?''',
|
|
(id,))
|
|
row = c.fetchone()
|
|
c.close()
|
|
return row[0] > 0
|
|
|
|
def makeActivityTable(self):
|
|
c = self.conn.cursor()
|
|
try:
|
|
c.execute('''CREATE TABLE triviaactivity (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
type TEXT,
|
|
activity TEXT,
|
|
channel TEXT,
|
|
channel_canonical TEXT,
|
|
network TEXT,
|
|
timestamp INTEGER)''')
|
|
except:
|
|
pass
|
|
self.conn.commit()
|
|
c.close()
|
|
|
|
def makeDeleteTable(self):
|
|
c = self.conn.cursor()
|
|
try:
|
|
c.execute('''CREATE TABLE triviadelete (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
username TEXT,
|
|
username_canonical TEXT,
|
|
line_num INTEGER,
|
|
channel TEXT,
|
|
channel_canonical TEXT,
|
|
reason TEXT)''')
|
|
except:
|
|
pass
|
|
self.conn.commit()
|
|
c.close()
|
|
|
|
def makeLevelTable(self):
|
|
c = self.conn.cursor()
|
|
try:
|
|
c.execute('''CREATE TABLE trivialevel (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
username TEXT,
|
|
username_canonical TEXT,
|
|
channel TEXT,
|
|
channel_canonical TEXT,
|
|
level INTEGER)''')
|
|
except:
|
|
pass
|
|
|
|
def makeLoginTable(self):
|
|
c = self.conn.cursor()
|
|
try:
|
|
c.execute('''CREATE TABLE trivialogin (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
username TEXT,
|
|
username_canonical TEXT NOT NULL UNIQUE,
|
|
salt TEXT,
|
|
is_hashed INTEGER NOT NULL DEFAULT 1,
|
|
password TEXT,
|
|
capability TEXT)''')
|
|
except:
|
|
pass
|
|
self.conn.commit()
|
|
c.close()
|
|
|
|
def makeUserTable(self):
|
|
c = self.conn.cursor()
|
|
try:
|
|
c.execute('''CREATE TABLE triviausers (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
username TEXT,
|
|
num_editted INTEGER,
|
|
num_editted_accepted INTEGER,
|
|
username_canonical TEXT NOT NULL UNIQUE,
|
|
num_reported INTEGER,
|
|
num_questions_added INTEGER,
|
|
num_questions_accepted INTEGER,
|
|
highest_streak INTEGER)''')
|
|
except:
|
|
pass
|
|
self.conn.commit()
|
|
c.close()
|
|
|
|
def makeUserLogTable(self):
|
|
c = self.conn.cursor()
|
|
try:
|
|
c.execute('''CREATE TABLE triviauserlog (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
username TEXT,
|
|
points_made INTEGER,
|
|
num_answered INTEGER,
|
|
day INTEGER,
|
|
month INTEGER,
|
|
year INTEGER,
|
|
last_updated INTEGER,
|
|
average_time INTEGER,
|
|
average_score INTEGER,
|
|
username_canonical TEXT,
|
|
channel TEXT,
|
|
channel_canonical TEXT,
|
|
UNIQUE(username_canonical, channel_canonical,
|
|
day, month, year) ON CONFLICT REPLACE)''')
|
|
except:
|
|
pass
|
|
self.conn.commit()
|
|
c.close()
|
|
|
|
def makeGameTable(self):
|
|
c = self.conn.cursor()
|
|
try:
|
|
c.execute('''CREATE TABLE triviagames (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
channel TEXT,
|
|
num_asked INTEGER,
|
|
round_started INTEGER,
|
|
last_winner TEXT,
|
|
streak INTEGER,
|
|
channel_canonical TEXT NOT NULL UNIQUE,
|
|
longest_streak INTEGER,
|
|
longest_streak_holder TEXT,
|
|
longest_streak_holder_canonical TEXT)''')
|
|
except:
|
|
pass
|
|
self.conn.commit()
|
|
c.close()
|
|
|
|
def makeGameLogTable(self):
|
|
c = self.conn.cursor()
|
|
try:
|
|
c.execute('''CREATE TABLE triviagameslog (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
channel TEXT,
|
|
round_num INTEGER,
|
|
line_num INTEGER,
|
|
question TEXT,
|
|
asked_at INTEGER,
|
|
channel_canonical TEXT)''')
|
|
c.execute('''CREATE INDEX gamelograndomindex
|
|
ON triviagameslog (channel, line_num, asked_at))''')
|
|
except:
|
|
pass
|
|
self.conn.commit()
|
|
c.close()
|
|
|
|
def makeInfoTable(self):
|
|
c = self.conn.cursor()
|
|
try:
|
|
c.execute('''CREATE TABLE triviainfo (version INTEGER)''')
|
|
except:
|
|
pass
|
|
|
|
def makeReportTable(self):
|
|
c = self.conn.cursor()
|
|
try:
|
|
c.execute('''CREATE TABLE triviareport (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
channel TEXT,
|
|
username TEXT,
|
|
report_text TEXT,
|
|
reported_at INTEGER,
|
|
fixed_at INTEGER,
|
|
fixed_by TEXT,
|
|
question_num INTEGER,
|
|
username_canonical TEXT,
|
|
channel_canonical TEXT)''')
|
|
except:
|
|
pass
|
|
self.conn.commit()
|
|
c.close()
|
|
|
|
def makeTemporaryQuestionTable(self):
|
|
c = self.conn.cursor()
|
|
try:
|
|
c.execute('''CREATE TABLE triviatemporaryquestion (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
username TEXT,
|
|
channel TEXT,
|
|
question TEXT,
|
|
username_canonical TEXT,
|
|
channel_canonical TEXT)''')
|
|
except:
|
|
pass
|
|
self.conn.commit()
|
|
c.close()
|
|
|
|
def makeQuestionTable(self):
|
|
c = self.conn.cursor()
|
|
try:
|
|
c.execute('''CREATE TABLE triviaquestion (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
question_canonical TEXT,
|
|
question TEXT,
|
|
deleted INTEGER NOT NULL DEFAULT 0,
|
|
num_answered INTEGER,
|
|
num_missed INTEGER)''')
|
|
c.execute('''CREATE INDEX questionrandomindex
|
|
ON triviagameslog (id, deleted))''')
|
|
except:
|
|
pass
|
|
self.conn.commit()
|
|
c.close()
|
|
|
|
def makeEditTable(self):
|
|
c = self.conn.cursor()
|
|
try:
|
|
c.execute('''CREATE TABLE triviaedit (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
question_id INTEGER,
|
|
question TEXT,
|
|
status TEXT,
|
|
username TEXT,
|
|
channel TEXT,
|
|
created_at TEXT,
|
|
username_canonical TEXT,
|
|
channel_canonical TEXT)''')
|
|
except:
|
|
pass
|
|
self.conn.commit()
|
|
c.close()
|
|
|
|
def questionExists(self, question):
|
|
c = self.conn.cursor()
|
|
c.execute('''SELECT COUNT(id)
|
|
FROM triviaquestion
|
|
WHERE question=? OR
|
|
question_canonical=?''',
|
|
(question, question))
|
|
row = c.fetchone()
|
|
c.close()
|
|
return row[0] > 0
|
|
|
|
def questionIdExists(self, id):
|
|
c = self.conn.cursor()
|
|
c.execute('''SELECT COUNT(id)
|
|
FROM triviaquestion
|
|
WHERE id=?''',
|
|
(id,))
|
|
row = c.fetchone()
|
|
c.close()
|
|
return row[0] > 0
|
|
|
|
def removeOldActivity(self,count=100):
|
|
c = self.conn.cursor()
|
|
c.execute('''DELETE FROM triviaactivity
|
|
WHERE id NOT IN (
|
|
SELECT id
|
|
FROM triviaactivity
|
|
ORDER BY id DESC LIMIT ?)''',
|
|
(count,))
|
|
self.conn.commit()
|
|
c.close()
|
|
|
|
def removeDelete(self, deleteId):
|
|
c = self.conn.cursor()
|
|
c.execute('''DELETE FROM triviadelete
|
|
WHERE id=?''',
|
|
(deleteId,))
|
|
self.conn.commit()
|
|
c.close()
|
|
|
|
def removeDuplicateQuestions(self):
|
|
c = self.conn.cursor()
|
|
c.execute('''DELETE FROM triviaquestion
|
|
WHERE id NOT IN (
|
|
SELECT MIN(id)
|
|
FROM triviaquestion
|
|
GROUP BY question_canonical)''')
|
|
num = c.rowcount
|
|
self.conn.commit()
|
|
c.close()
|
|
return num
|
|
|
|
def removeEdit(self, editId):
|
|
c = self.conn.cursor()
|
|
c.execute('''DELETE FROM triviaedit
|
|
WHERE id=?''',
|
|
(editId,))
|
|
self.conn.commit()
|
|
c.close()
|
|
|
|
def removeLogin(self, username):
|
|
usernameCanonical = ircutils.toLower(username)
|
|
c = self.conn.cursor()
|
|
c.execute('''DELETE FROM trivialogin
|
|
WHERE username_canonical=?''',
|
|
(usernameCanonical,))
|
|
self.conn.commit()
|
|
c.close()
|
|
|
|
def removeReport(self, repId):
|
|
c = self.conn.cursor()
|
|
c.execute('''DELETE FROM triviareport
|
|
WHERE id=?''', (repId,))
|
|
self.conn.commit()
|
|
c.close()
|
|
|
|
def removeReportByQuestionNumber(self, id):
|
|
c = self.conn.cursor()
|
|
c.execute('''DELETE FROM triviareport
|
|
WHERE question_num=?''',
|
|
(id,))
|
|
self.conn.commit()
|
|
c.close()
|
|
|
|
def removeEditByQuestionNumber(self, id):
|
|
c = self.conn.cursor()
|
|
c.execute('''DELETE FROM triviaedit
|
|
WHERE question_id=?''',
|
|
(id,))
|
|
self.conn.commit()
|
|
c.close()
|
|
|
|
def removeDeleteByQuestionNumber(self, id):
|
|
c = self.conn.cursor()
|
|
c.execute('''DELETE FROM triviadelete
|
|
WHERE line_num=?''',
|
|
(id,))
|
|
self.conn.commit()
|
|
c.close()
|
|
|
|
def removeTemporaryQuestion(self, id):
|
|
c = self.conn.cursor()
|
|
c.execute('''DELETE FROM triviatemporaryquestion
|
|
WHERE id=?''',
|
|
(id,))
|
|
self.conn.commit()
|
|
c.close()
|
|
|
|
def removeUserLogs(self, username, channel):
|
|
usernameCanonical = ircutils.toLower(username)
|
|
channelCanonical = ircutils.toLower(channel)
|
|
c = self.conn.cursor()
|
|
c.execute('''DELETE FROM triviauserlog
|
|
WHERE username_canonical=? AND
|
|
channel_canonical=?''',
|
|
(usernameCanonical, channelCanonical))
|
|
self.conn.commit()
|
|
c.close()
|
|
|
|
def restoreQuestion(self, id):
|
|
c = self.conn.cursor()
|
|
test = c.execute('''UPDATE triviaquestion
|
|
SET deleted=0
|
|
WHERE id=?''',
|
|
(id,))
|
|
self.conn.commit()
|
|
c.close()
|
|
|
|
def transferUserLogs(self, userFrom, userTo, channel):
|
|
userFromCanonical = ircutils.toLower(userFrom)
|
|
userToCanonical = ircutils.toLower(userTo)
|
|
channelCanonical = ircutils.toLower(channel)
|
|
c = self.conn.cursor()
|
|
c.execute('''UPDATE triviauserlog
|
|
SET num_answered=num_answered+IFNULL(
|
|
(SELECT t3.num_answered
|
|
FROM triviauserlog t3
|
|
WHERE t3.day=triviauserlog.day AND
|
|
t3.month=triviauserlog.month AND
|
|
t3.year=triviauserlog.year AND
|
|
t3.channel_canonical=? AND
|
|
t3.username_canonical=?),0),
|
|
points_made=points_made+IFNULL(
|
|
(SELECT t2.points_made
|
|
FROM triviauserlog t2
|
|
WHERE t2.day=triviauserlog.day AND
|
|
t2.month=triviauserlog.month AND
|
|
t2.year=triviauserlog.year AND
|
|
t2.channel_canonical=? AND
|
|
t2.username_canonical=?),0)
|
|
WHERE id IN (
|
|
SELECT id
|
|
FROM triviauserlog tl
|
|
WHERE channel_canonical=? AND
|
|
username_canonical=? AND
|
|
EXISTS (
|
|
SELECT id
|
|
FROM triviauserlog tl2
|
|
WHERE tl2.day=tl.day AND
|
|
tl2.month=tl.month AND
|
|
tl2.year=tl.year AND
|
|
tl2.channel_canonical=? AND
|
|
tl2.username_canonical=?)
|
|
)
|
|
''', (channelCanonical, userFromCanonical,
|
|
channelCanonical, userFromCanonical,
|
|
channelCanonical, userToCanonical,
|
|
channelCanonical, userFromCanonical))
|
|
|
|
c.execute('''UPDATE triviauserlog
|
|
SET username=?,
|
|
username_canonical=?
|
|
WHERE username_canonical=? AND
|
|
channel_canonical=? AND
|
|
NOT EXISTS (
|
|
SELECT 1
|
|
FROM triviauserlog tl
|
|
WHERE tl.day=triviauserlog.day AND
|
|
tl.month=triviauserlog.month AND
|
|
tl.year=triviauserlog.year AND
|
|
tl.channel_canonical=? AND
|
|
tl.username_canonical=?)''',
|
|
(userTo, userToCanonical, userFromCanonical, channelCanonical,
|
|
channelCanonical, userToCanonical))
|
|
self.conn.commit()
|
|
|
|
self.removeUserLogs(userFrom, channel)
|
|
|
|
def userLogExists(self, username, channel, day, month, year):
|
|
c = self.conn.cursor()
|
|
args = (ircutils.toLower(username),ircutils.toLower(channel),day,month,year)
|
|
c.execute('''SELECT COUNT(id)
|
|
FROM triviauserlog
|
|
WHERE username_canonical=? AND
|
|
channel_canonical=? AND
|
|
day=? AND
|
|
month=? and
|
|
year=?''', args)
|
|
row = c.fetchone()
|
|
c.close()
|
|
return row[0] > 0
|
|
|
|
def userExists(self, username):
|
|
c = self.conn.cursor()
|
|
usr = (ircutils.toLower(username),)
|
|
c.execute('''SELECT COUNT(id)
|
|
FROM triviausers
|
|
WHERE username_canonical=?''', usr)
|
|
row = c.fetchone()
|
|
c.close()
|
|
return row[0] > 0
|
|
|
|
def userLevelExists(self, username, channel):
|
|
usernameCanonical = ircutils.toLower(username)
|
|
channelCanonical = ircutils.toLower(channel)
|
|
|
|
c = self.conn.cursor()
|
|
c.execute('''SELECT COUNT(id)
|
|
FROM trivialevel
|
|
WHERE username_canonical=? AND
|
|
channel_canonical=?''',
|
|
(usernameCanonical, channelCanonical))
|
|
row = c.fetchone()
|
|
c.close()
|
|
return row[0] > 0
|
|
|
|
def updateLogin(self, username, salt, isHashed, password, capability):
|
|
if not self.loginExists(username):
|
|
return self.insertLogin(username, salt, isHashed, password, capability)
|
|
usernameCanonical = ircutils.toLower(username)
|
|
if not isHashed:
|
|
isHashed = 0
|
|
else:
|
|
isHashed = 1
|
|
c = self.conn.cursor()
|
|
c.execute('''UPDATE trivialogin
|
|
SET username=?,
|
|
salt=?,
|
|
is_hashed=?,
|
|
password=?,
|
|
capability=?
|
|
WHERE username_canonical=?''',
|
|
(username, salt, isHashed, password, capability, usernameCanonical))
|
|
self.conn.commit()
|
|
c.close()
|
|
|
|
def updateUserLog(self, username, channel, score, numAnswered, timeTaken, day=None, month=None, year=None, epoch=None):
|
|
if not self.userExists(username):
|
|
self.insertUser(username)
|
|
if day == None and month == None and year == None:
|
|
dateObject = datetime.date.today()
|
|
day = dateObject.day
|
|
month = dateObject.month
|
|
year = dateObject.year
|
|
if epoch is None:
|
|
epoch = int(time.mktime(time.localtime()))
|
|
if not self.userLogExists(username, channel, day, month, year):
|
|
return self.insertUserLog(username, channel, score, numAnswered, timeTaken, day, month, year, epoch)
|
|
c = self.conn.cursor()
|
|
usernameCanonical = ircutils.toLower(username)
|
|
channelCanonical = ircutils.toLower(channel)
|
|
test = c.execute('''UPDATE triviauserlog
|
|
SET username=?,
|
|
points_made=points_made+?,
|
|
average_time=(average_time*(1.0*num_answered/(num_answered+?))+?*(1.0*?/(num_answered+?))),
|
|
average_score=(average_score*(1.0*num_answered/(num_answered+?))+?*(1.0*?/(num_answered+?))),
|
|
num_answered=num_answered+?,
|
|
last_updated=?
|
|
WHERE username_canonical=? AND
|
|
channel_canonical=? AND
|
|
day=? AND
|
|
month=? AND
|
|
year=?''',
|
|
(username, score, numAnswered, timeTaken, numAnswered,
|
|
numAnswered, numAnswered, score, numAnswered, numAnswered,
|
|
numAnswered, epoch, usernameCanonical, channelCanonical,
|
|
day, month, year))
|
|
self.conn.commit()
|
|
c.close()
|
|
|
|
def updateUser(self, username, numEditted=0, numEdittedAccepted=0, numReported=0, numQuestionsAdded=0, numQuestionsAccepted=0):
|
|
if not self.userExists(username):
|
|
return self.insertUser(username, numEditted, numEdittedAccepted, numReported, numQuestionsAdded, numQuestionsAccepted)
|
|
usernameCanonical = ircutils.toLower(username)
|
|
c = self.conn.cursor()
|
|
c.execute('''UPDATE triviausers
|
|
SET username=?,
|
|
num_editted=num_editted+?,
|
|
num_editted_accepted=num_editted_accepted+?,
|
|
num_reported=num_reported+?,
|
|
num_questions_added=num_questions_added+?,
|
|
num_questions_accepted=num_questions_accepted+?
|
|
WHERE username_canonical=?''',
|
|
(username, numEditted, numEdittedAccepted, numReported,
|
|
numQuestionsAdded, numQuestionsAccepted, usernameCanonical))
|
|
self.conn.commit()
|
|
c.close()
|
|
|
|
def updateUserHighestStreak(self, username, streak):
|
|
if not self.userExists(username):
|
|
return self.insertUser(username)
|
|
usernameCanonical = ircutils.toLower(username)
|
|
c = self.conn.cursor()
|
|
c.execute('''UPDATE triviausers
|
|
SET highest_streak=?
|
|
WHERE highest_streak<? AND
|
|
username_canonical=?''',
|
|
(streak, streak, usernameCanonical))
|
|
self.conn.commit()
|
|
c.close()
|
|
|
|
def updateUserLevel(self, username, channel, level):
|
|
if not self.userLevelExists(username, channel):
|
|
return self.insertUserLevel(username, channel, level)
|
|
|
|
usernameCanonical = ircutils.toLower(username)
|
|
channelCanonical = ircutils.toLower(channel)
|
|
|
|
c = self.conn.cursor()
|
|
c.execute('''UPDATE trivialevel
|
|
SET level=?
|
|
WHERE username_canonical=? AND
|
|
channel_canonical=?''',
|
|
(level, usernameCanonical, channelCanonical))
|
|
self.conn.commit()
|
|
c.close()
|
|
|
|
def updateGame(self, channel, numAsked):
|
|
if not self.gameExists(channel):
|
|
return self.insertGame(channel, numAsked)
|
|
c = self.conn.cursor()
|
|
channelCanonical = ircutils.toLower(channel)
|
|
test = c.execute('''UPDATE triviagames
|
|
SET channel=?,
|
|
num_asked=?
|
|
WHERE channel_canonical=?''',
|
|
(channel, numAsked, channelCanonical))
|
|
self.conn.commit()
|
|
c.close()
|
|
|
|
def updateGameLongestStreak(self, channel, lastWinner, streak):
|
|
c = self.conn.cursor()
|
|
channelCanonical = ircutils.toLower(channel)
|
|
lastWinnerCanonical = ircutils.toLower(lastWinner)
|
|
test = c.execute('''UPDATE triviagames
|
|
SET longest_streak=?,
|
|
longest_streak_holder=?,
|
|
longest_streak_holder_canonical=?
|
|
WHERE channel_canonical=? AND
|
|
longest_streak<?''',
|
|
(streak, lastWinner, lastWinnerCanonical,
|
|
channelCanonical, streak))
|
|
self.conn.commit()
|
|
c.close()
|
|
|
|
def updateGameStreak(self, channel, lastWinner, streak):
|
|
if not self.gameExists(channel):
|
|
return self.insertGame(channel, 0, None)
|
|
c = self.conn.cursor()
|
|
channelCanonical = ircutils.toLower(channel)
|
|
test = c.execute('''UPDATE triviagames
|
|
SET last_winner=?,
|
|
streak=?
|
|
WHERE channel_canonical=?''',
|
|
(lastWinner, streak, channelCanonical))
|
|
self.conn.commit()
|
|
c.close()
|
|
|
|
def updateGameRoundStarted(self, channel, lastRoundStarted):
|
|
if not self.gameExists(channel):
|
|
return self.insertGame(channel, numAsked)
|
|
channelCanonical = ircutils.toLower(channel)
|
|
c = self.conn.cursor()
|
|
test = c.execute('''UPDATE triviagames
|
|
SET round_started=?
|
|
WHERE channel_canonical=?''',
|
|
(lastRoundStarted, channelCanonical))
|
|
self.conn.commit()
|
|
c.close()
|
|
|
|
def updateQuestion(self, id, newQuestion):
|
|
c = self.conn.cursor()
|
|
test = c.execute('''UPDATE triviaquestion
|
|
SET question=?
|
|
WHERE id=?''',
|
|
(newQuestion, id))
|
|
self.conn.commit()
|
|
c.close()
|
|
|
|
def updateQuestionStats(self, id, timesAnswered, timesMissed):
|
|
c = self.conn.cursor()
|
|
test = c.execute('''UPDATE triviaquestion
|
|
SET num_answered=num_answered+?,
|
|
num_missed=num_missed+?
|
|
WHERE id=?''',
|
|
(timesAnswered, timesMissed, id))
|
|
self.conn.commit()
|
|
c.close()
|
|
|
|
def viewDayTop10(self, channel, numUpTo=10):
|
|
numUpTo -= 10
|
|
dateObject = datetime.date.today()
|
|
day = dateObject.day
|
|
month = dateObject.month
|
|
year = dateObject.year
|
|
|
|
query = '''SELECT id,
|
|
username,
|
|
SUM(points_made) AS points,
|
|
SUM(num_answered) AS num
|
|
FROM triviauserlog
|
|
WHERE day=? AND
|
|
month=? AND
|
|
year=?'''
|
|
arguments = [day, month, year]
|
|
|
|
if channel is not None:
|
|
channelCanonical = ircutils.toLower(channel)
|
|
query = '''%s AND channel_canonical=?''' % (query)
|
|
arguments.append(channelCanonical)
|
|
|
|
query = '''%s GROUP BY username_canonical
|
|
ORDER BY points DESC LIMIT ?, 10''' % (query)
|
|
arguments.append(numUpTo)
|
|
|
|
c = self.conn.cursor()
|
|
c.execute(query, tuple(arguments))
|
|
rows = c.fetchall()
|
|
c.close()
|
|
return rows
|
|
|
|
def viewAllTimeTop10(self, channel, numUpTo=10):
|
|
numUpTo -= 10
|
|
|
|
query = '''SELECT id,
|
|
username,
|
|
SUM(points_made) AS points,
|
|
SUM(num_answered) AS num
|
|
FROM triviauserlog'''
|
|
arguments = []
|
|
|
|
if channel is not None:
|
|
channelCanonical = ircutils.toLower(channel)
|
|
query = '''%s WHERE channel_canonical=?''' % (query)
|
|
arguments.append(channelCanonical)
|
|
|
|
query = '''%s GROUP BY username_canonical
|
|
ORDER BY points DESC LIMIT ?, 10''' % (query)
|
|
arguments.append(numUpTo)
|
|
|
|
c = self.conn.cursor()
|
|
c.execute(query, tuple(arguments))
|
|
rows = c.fetchall()
|
|
c.close()
|
|
return rows
|
|
|
|
def viewMonthTop10(self, channel, numUpTo=10, year=None, month=None):
|
|
numUpTo -= 10
|
|
d = datetime.date.today()
|
|
if year is None or month is None:
|
|
year = d.year
|
|
month = d.month
|
|
|
|
query = '''SELECT id,
|
|
username,
|
|
SUM(points_made) AS points,
|
|
SUM(num_answered) AS num
|
|
FROM triviauserlog
|
|
WHERE month=? AND
|
|
year=?'''
|
|
arguments = [month, year]
|
|
|
|
if channel is not None:
|
|
channelCanonical = ircutils.toLower(channel)
|
|
query = '''%s AND channel_canonical=?''' % (query)
|
|
arguments.append(channelCanonical)
|
|
|
|
query = '''%s GROUP BY username_canonical
|
|
ORDER BY points DESC LIMIT ?, 10''' % (query)
|
|
arguments.append(numUpTo)
|
|
|
|
c = self.conn.cursor()
|
|
c.execute(query, tuple(arguments))
|
|
rows = c.fetchall()
|
|
c.close()
|
|
return rows
|
|
|
|
def viewYearTop10(self, channel, numUpTo=10, year=None):
|
|
numUpTo -= 10
|
|
d = datetime.date.today()
|
|
if year is None:
|
|
year = d.year
|
|
|
|
query = '''SELECT id,
|
|
username,
|
|
SUM(points_made) AS points,
|
|
SUM(num_answered) AS num
|
|
FROM triviauserlog
|
|
WHERE year=?'''
|
|
arguments = [year]
|
|
|
|
if channel is not None:
|
|
channelCanonical = ircutils.toLower(channel)
|
|
query = '''%s AND channel_canonical=?''' % (query)
|
|
arguments.append(channelCanonical)
|
|
|
|
query = '''%s GROUP BY username_canonical
|
|
ORDER BY points DESC LIMIT ?, 10''' % (query)
|
|
arguments.append(numUpTo)
|
|
|
|
c = self.conn.cursor()
|
|
c.execute(query, tuple(arguments))
|
|
rows = c.fetchall()
|
|
c.close()
|
|
return rows
|
|
|
|
def viewWeekTop10(self, channel, numUpTo=10):
|
|
numUpTo -= 10
|
|
d = datetime.date.today()
|
|
weekday=d.weekday()
|
|
d -= datetime.timedelta(weekday)
|
|
weekSqlString = ''
|
|
for i in range(7):
|
|
if i > 0:
|
|
weekSqlString += ' or '
|
|
weekSqlString += '''
|
|
(year=%d
|
|
AND month=%d
|
|
AND day=%d)''' % (d.year, d.month, d.day)
|
|
d += datetime.timedelta(1)
|
|
|
|
query = '''SELECT id,
|
|
username,
|
|
SUM(points_made) AS points,
|
|
SUM(num_answered) AS num
|
|
FROM triviauserlog
|
|
WHERE (%s)''' % weekSqlString
|
|
arguments = []
|
|
|
|
if channel is not None:
|
|
channelCanonical = ircutils.toLower(channel)
|
|
query = '''%s AND channel_canonical=?''' % (query)
|
|
arguments.append(channelCanonical)
|
|
|
|
query = '''%s GROUP BY username_canonical
|
|
ORDER BY points DESC LIMIT ?, 10''' % (query)
|
|
arguments.append(numUpTo)
|
|
|
|
c = self.conn.cursor()
|
|
c.execute(query, tuple(arguments))
|
|
rows = c.fetchall()
|
|
c.close()
|
|
return rows
|
|
|
|
def wasUserActiveIn(self, username, channel, timeSeconds):
|
|
usernameCanonical = ircutils.toLower(username)
|
|
channelCanonical = ircutils.toLower(channel)
|
|
epoch = int(time.mktime(time.localtime()))
|
|
dateObject = datetime.date.today()
|
|
day = dateObject.day
|
|
month = dateObject.month
|
|
year = dateObject.year
|
|
c = self.conn.cursor()
|
|
c.execute('''SELECT count(*)
|
|
FROM triviauserlog
|
|
WHERE day=? AND
|
|
month=? AND
|
|
year=? AND
|
|
username_canonical=? AND
|
|
channel_canonical=? AND
|
|
last_updated>?''',
|
|
(day, month, year, usernameCanonical,
|
|
channelCanonical, epoch-timeSeconds))
|
|
row = c.fetchone()
|
|
c.close()
|
|
return row[0] > 0
|
|
|
|
|
|
#A log wrapper, ripoff of ChannelLogger
|
|
class Logger:
|
|
def __init__(self, base):
|
|
self.logs = {}
|
|
self.registryValue = base.registryValue
|
|
|
|
def logNameTimestamp(self, channel):
|
|
return time.strftime('%Y-%m-%d')
|
|
|
|
def getLogName(self, channel):
|
|
return '%s.%s.log' % (channel, self.logNameTimestamp(channel))
|
|
|
|
def getLogDir(self, irc, channel):
|
|
logDir = conf.supybot.directories.log.dirize('TriviaTime')
|
|
logDir = os.path.join(logDir, irc.network)
|
|
logDir = os.path.join(logDir, channel)
|
|
timeDir = time.strftime('%B')
|
|
logDir = os.path.join(logDir, timeDir)
|
|
if not os.path.exists(logDir):
|
|
os.makedirs(logDir)
|
|
return logDir
|
|
|
|
def timestamp(self, log):
|
|
format = conf.supybot.log.timestampFormat()
|
|
if format:
|
|
log.write(time.strftime(format))
|
|
log.write(' ')
|
|
|
|
def checkLogNames(self):
|
|
for (irc, logs) in list(self.logs.items()):
|
|
for (channel, log) in list(logs.items()):
|
|
name = self.getLogName(channel)
|
|
if name != log.name:
|
|
log.close()
|
|
del logs[channel]
|
|
|
|
def getLog(self, irc, channel):
|
|
self.checkLogNames()
|
|
try:
|
|
logs = self.logs[irc]
|
|
except KeyError:
|
|
logs = ircutils.IrcDict()
|
|
self.logs[irc] = logs
|
|
if channel in logs:
|
|
return logs[channel]
|
|
else:
|
|
try:
|
|
name = self.getLogName(channel)
|
|
logDir = self.getLogDir(irc, channel)
|
|
log = open(os.path.join(logDir, name), 'a')
|
|
logs[channel] = log
|
|
return log
|
|
except IOError:
|
|
self.log.exception('Error opening log:')
|
|
return self.FakeLog()
|
|
|
|
def doLog(self, irc, channel, s, *args):
|
|
if not self.registryValue('general.logGames'):
|
|
return
|
|
s = format(s, *args)
|
|
channel = self.normalizeChannel(irc, channel)
|
|
log = self.getLog(irc, channel)
|
|
self.timestamp(log)
|
|
s = ircutils.stripFormatting(s)
|
|
log.write(s)
|
|
log.write('\n')
|
|
log.flush()
|
|
|
|
def normalizeChannel(self, irc, channel):
|
|
return ircutils.toLower(channel)
|
|
|
|
class FakeLog(object):
|
|
def flush(self):
|
|
return
|
|
def close(self):
|
|
return
|
|
def write(self, s):
|
|
return
|
|
|
|
|
|
class TriviaTime(callbacks.Plugin):
|
|
"""
|
|
TriviaTime - An enhanced multiplayer and multichannel trivia game for Supybot.
|
|
Includes KAOS: work together to get all the answers before time runs out.
|
|
"""
|
|
threaded = True # enables threading for supybot plugin
|
|
currentDBVersion = 1.2
|
|
|
|
def __init__(self, irc):
|
|
log.info('** TriviaTime loaded! **')
|
|
self.__parent = super(TriviaTime, self)
|
|
self.__parent.__init__(irc)
|
|
|
|
# games info
|
|
self.games = {} # separate game for each channel
|
|
self.voiceTimeouts = TimeoutList(self.registryValue('voice.timeoutVoice'))
|
|
|
|
#Database amend statements for outdated versions
|
|
self.dbamends = {} #Formatted like this: <DBVersion>: "<ALTERSTATEMENT>; <ALTERSTATEMENT>;" (This IS valid SQL as long as we include the semicolons)
|
|
|
|
#logger
|
|
self.logger = Logger(self)
|
|
|
|
# connections
|
|
dbLocation = self.registryValue('admin.db')
|
|
# tuple head, tail ('example/example/', 'filename.txt')
|
|
dbFolder = os.path.split(dbLocation)
|
|
# take folder from split
|
|
dbFolder = dbFolder[0]
|
|
# create the folder
|
|
if not os.path.exists(dbFolder):
|
|
log.info('The database location did not exist, creating folder structure')
|
|
os.makedirs(dbFolder)
|
|
self.storage = Storage(dbLocation)
|
|
#self.storage.dropActivityTable()
|
|
self.storage.makeActivityTable()
|
|
#self.storage.dropUserLogTable()
|
|
self.storage.makeUserLogTable()
|
|
#self.storage.dropGameTable()
|
|
self.storage.makeGameTable()
|
|
#self.storage.dropGameLogTable()
|
|
self.storage.makeGameLogTable()
|
|
#self.storage.dropUserTable()
|
|
self.storage.makeUserTable()
|
|
#self.storage.dropLoginTable()
|
|
self.storage.makeLoginTable()
|
|
#self.storage.dropReportTable()
|
|
self.storage.makeReportTable()
|
|
#self.storage.dropQuestionTable()
|
|
self.storage.makeQuestionTable()
|
|
#self.storage.dropTemporaryQuestionTable()
|
|
self.storage.makeTemporaryQuestionTable()
|
|
#self.storage.dropEditTable()
|
|
self.storage.makeEditTable()
|
|
#self.storage.dropDeleteTable()
|
|
self.storage.makeDeleteTable()
|
|
self.storage.makeInfoTable()
|
|
#self.storage.makeLevelTable()
|
|
#self.storage.dropLevelTable()
|
|
#triviainfo table check
|
|
#if self.storage.isTriviaVersionSet():
|
|
if self.storage.getVersion() != None and self.storage.getVersion() != self.currentDBVersion:
|
|
return
|
|
|
|
def _games(self):
|
|
for (network, games) in list(self.games.items()):
|
|
for (channel, game) in list(games.items()):
|
|
yield game
|
|
|
|
def die(self):
|
|
for game in self._games():
|
|
game.stop()
|
|
|
|
def reset(self):
|
|
for game in self._games():
|
|
game.stop()
|
|
|
|
def doPrivmsg(self, irc, msg):
|
|
"""
|
|
Catches all PRIVMSG, including channels communication
|
|
"""
|
|
username = self.getUsername(msg.nick, msg.prefix)
|
|
usernameCanonical = ircutils.toLower(username)
|
|
channel = msg.args[0]
|
|
# Make sure that it is starting inside of a channel, not in pm
|
|
if not irc.isChannel(channel):
|
|
return
|
|
if callbacks.addressed(irc.nick, msg):
|
|
return
|
|
channelCanonical = ircutils.toLower(channel)
|
|
|
|
extraHintCommand = self.registryValue('commands.extraHint', channel)
|
|
extraHintTime = self.registryValue('hints.extraHintTime', channel)
|
|
game = self.getGame(irc, channel)
|
|
|
|
if game and game.state == 'in-question':
|
|
if msg.args[1] == extraHintCommand: # Check for extra hint command
|
|
if game.questionType == 'kaos':
|
|
game.getRemainingKAOS()
|
|
else:
|
|
if self.registryValue('hints.enableExtraHints', channel):
|
|
game.hintTimeoutList.setTimeout(extraHintTime)
|
|
if game.hintTimeoutList.has(usernameCanonical):
|
|
self.reply(irc, msg, 'You must wait %d seconds to be able to use the extra hint command.' % (game.hintTimeoutList.getTimeLeft(usernameCanonical)), notice=True)
|
|
else:
|
|
game.hintTimeoutList.append(usernameCanonical)
|
|
game.getExtraHint()
|
|
else: # Check the answer
|
|
game.checkAnswer(msg)
|
|
|
|
def doJoin(self, irc, msg):
|
|
username = self.getUsername(msg.nick, msg.prefix)
|
|
channel = msg.args[0]
|
|
self.handleVoice(irc, msg.nick, username, channel)
|
|
|
|
def doNotice(self, irc, msg):
|
|
username = msg.nick
|
|
if msg.args[1][1:5] == "PING":
|
|
pingMsg = msg.args[1][6:]
|
|
pingMsg = pingMsg[:-1]
|
|
pingMsg = pingMsg.split('*', 1)
|
|
if len(pingMsg) == 2:
|
|
pingTime = time.time()-float(pingMsg[0])-1300000000
|
|
channelHash = pingMsg[1]
|
|
channel = ''
|
|
for name in irc.state.channels:
|
|
if channelHash == self.shortHash(ircutils.toLower(name)):
|
|
if username in irc.state.channels[name].users:
|
|
channel = name
|
|
break
|
|
if channel == '':
|
|
irc.sendMsg(ircmsgs.notice(username, 'Ping reply: %0.2f seconds' % (pingTime)))
|
|
else:
|
|
irc.sendMsg(ircmsgs.privmsg(channel, '%s: Ping reply: %0.2f seconds' % (username, pingTime)))
|
|
|
|
def voiceUser(self, irc, nick, username, channel):
|
|
usernameCanonical = ircutils.toLower(username)
|
|
irc.queueMsg(ircmsgs.voice(channel, nick))
|
|
self.voiceTimeouts.append(usernameCanonical)
|
|
|
|
#The following functions are not ready and still in testing. Actually, they haven't even been tested yet. Use at your own risk.
|
|
"""
|
|
def checkLevel(self, irc, nick, username, channel):
|
|
levels = {0:"noob", 1:"Guesser", 2:"Student", 3:"Player", 4:"Master", 5:"Genius", 6:"Distinguished", 7:"Addicted", 8:"Elite", 9:"KAOTIC", 10:"Trivia God"}
|
|
levelMinQuestions = {0:1, 1:50, 2:137, 3:301, 4:500, 5:789, 6:1002, 7:1519, 8:1899, 9:2133, 10:2544}
|
|
usernameCanonical = ircutils.toLower(username)
|
|
if userLevelExists(usernameCanonical):
|
|
currentLevel = getUserLevel(username)
|
|
nextLevel = currentLevel + 1
|
|
if questionsAnswered(usernameCanonical) >= nextLevel(questionCount):
|
|
changeLevel(nextLevel)
|
|
else:
|
|
levelUp()
|
|
|
|
def levelUp():
|
|
if questionsAnswered(usernameCanonical) > levelMinQuestions[10]:
|
|
changeLevel(10)
|
|
elif questionsAnswered(usernameCanonical) > levelMinQuestions[9]:
|
|
changeLevel(9)
|
|
elif questionsAnswered(usernameCanonical) > levelMinQuestions[8]:
|
|
changeLevel(8)
|
|
elif questionsAnswered(usernameCanonical) > levelMinQuestions[7]:
|
|
changelevel(7)
|
|
elif questionsAnswered(usernameCanonical) > levelMinQuestions[6]:
|
|
changelevel(6)
|
|
elif questionsAnswered(usernameCanonical) > levelMinQuestions[5]:
|
|
changelevel(5)
|
|
elif questionsAnswered(usernameCanonical) > levelMinQuestions[4]
|
|
changelevel(4)
|
|
elif questionsAnswered(usernameCanonical) > levelMinQuestions[3]:
|
|
changelevel(3)
|
|
elif questionsAnswered(usernameCanonical) > levelMinQuestions[2]:
|
|
changelevel(2)
|
|
elif questionsAnswered(usernameCanonical) > levelMinQuestions[1]:
|
|
changelevel(1)
|
|
elif questionsAnswered(usernameCanonical) > levelMinQuestions[0]:
|
|
changelLevel(0)
|
|
|
|
Storage.getUserStat()
|
|
|
|
def changeLevel(self, irc, nick, username, channel):
|
|
usernameCanonical = ircutils.toLower(username)
|
|
USERLEVEL = NEWUSERLEVEL
|
|
irc.sendMsg(ircmsgs.privmsg(channel, 'Congratulations %s, you\'ve answered %d questions and leveled up to %s!' % (username, questionsAnswered(usernameCanonical), levels[newlevel])))
|
|
#reward points levelMinQuestions[level] * 5
|
|
if self.registryValue('general.globalStats'):
|
|
stat = threadStorage.getUserStat(username, None)
|
|
else:
|
|
stat = threadStorage.getUserStat(username, channel)
|
|
|
|
"""
|
|
def handleVoice(self, irc, nick, username, channel):
|
|
if not self.registryValue('voice.enableVoice'):
|
|
return
|
|
|
|
timeoutVoice = self.registryValue('voice.timeoutVoice')
|
|
self.voiceTimeouts.setTimeout(timeoutVoice)
|
|
usernameCanonical = ircutils.toLower(username)
|
|
dbLocation = self.registryValue('admin.db')
|
|
threadStorage = Storage(dbLocation)
|
|
if self.registryValue('general.globalStats'):
|
|
stat = threadStorage.getUserStat(username, None)
|
|
rank = threadStorage.getUserRank(username, None)
|
|
else:
|
|
stat = threadStorage.getUserStat(username, channel)
|
|
rank = threadStorage.getUserRank(username, channel)
|
|
|
|
if stat and not self.voiceTimeouts.has(usernameCanonical):
|
|
numTopToVoice = self.registryValue('voice.numTopToVoice')
|
|
minPointsVoiceYear = self.registryValue('voice.minPointsVoiceYear')
|
|
minPointsVoiceMonth = self.registryValue('voice.minPointsVoiceMonth')
|
|
minPointsVoiceWeek = self.registryValue('voice.minPointsVoiceWeek')
|
|
|
|
if rank['year'] <= numTopToVoice and stat['points_year'] >= minPointsVoiceYear:
|
|
self.voiceUser(irc, nick, username, channel)
|
|
irc.sendMsg(ircmsgs.privmsg(channel, 'Giving voice to %s for being MVP this YEAR (#%d)' % (nick, rank['year'])))
|
|
elif rank['month'] <= numTopToVoice and stat['points_month'] >= minPointsVoiceMonth:
|
|
self.voiceUser(irc, nick, username, channel)
|
|
irc.sendMsg(ircmsgs.privmsg(channel, 'Giving voice to %s for being MVP this MONTH (#%d)' % (nick, rank['month'])))
|
|
elif rank['week'] <= numTopToVoice and stat['points_week'] >= minPointsVoiceWeek:
|
|
self.voiceUser(irc, nick, username, channel)
|
|
irc.sendMsg(ircmsgs.privmsg(channel, 'Giving voice to %s for being MVP this WEEK (#%d)' % (nick, rank['week'])))
|
|
|
|
def addZeroWidthSpace(self, text):
|
|
if len(text) <= 1:
|
|
return text
|
|
s = '%s\u200b%s' % (text[:1], text[1:])
|
|
return s
|
|
|
|
def shortHash(self, text):
|
|
hashText = hashlib.sha1(text).hexdigest()
|
|
hashText = self.numToBase94(int(hashText, 16), 8)
|
|
return hashText
|
|
|
|
def numToBase94(self, n, maxChars):
|
|
chars = '!"#$%&\'() +,-./0123456789:;<=>?@ABCDEFGHUJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~'
|
|
L = []
|
|
for i in range(maxChars):
|
|
L.append(chars[n % len(chars)])
|
|
n = int(n / len(chars))
|
|
return ''.join(L)
|
|
|
|
def addActivity(self, activityType, activityText, channel, irc, storage=None):
|
|
if storage is None:
|
|
dbLocation = self.registryValue('admin.db')
|
|
threadStorage = Storage(dbLocation)
|
|
else:
|
|
threadStorage = storage
|
|
threadStorage.removeOldActivity()
|
|
threadStorage.insertActivity(activityType, activityText, channel, irc.network)
|
|
|
|
def deleteGame(self, irc, channel):
|
|
channelCanonical = ircutils.toLower(channel)
|
|
if irc.network in self.games:
|
|
if channelCanonical in self.games[irc.network]:
|
|
del self.games[irc.network][channelCanonical]
|
|
if len(self.games[irc.network]) < 1:
|
|
del self.games[irc.network]
|
|
|
|
def createGame(self, irc, channel):
|
|
if irc.network not in self.games:
|
|
self.games[irc.network] = {}
|
|
channelCanonical = ircutils.toLower(channel)
|
|
newGame = Game(irc, channel, self)
|
|
if newGame.active == True:
|
|
self.games[irc.network][channelCanonical] = newGame
|
|
|
|
def getGame(self, irc, channel):
|
|
channelCanonical = ircutils.toLower(channel)
|
|
if irc.network in self.games:
|
|
if channelCanonical in self.games[irc.network]:
|
|
return self.games[irc.network][channelCanonical]
|
|
return None
|
|
|
|
def isTriviaMod(self, hostmask, channel):
|
|
channel = ircutils.toLower(channel)
|
|
cap = self.getTriviaCapability(hostmask, channel)
|
|
return cap in ['{0},{1}'.format(channel,'triviamod'),
|
|
'{0},{1}'.format(channel,'triviaadmin'), 'owner']
|
|
|
|
def isTriviaAdmin(self, hostmask, channel):
|
|
channel = ircutils.toLower(channel)
|
|
cap = self.getTriviaCapability(hostmask, channel)
|
|
return cap in ['{0},{1}'.format(channel,'triviaadmin'), 'owner']
|
|
|
|
def getTriviaCapability(self, hostmask, channel):
|
|
if ircdb.users.hasUser(hostmask):
|
|
channel = ircutils.toLower(channel)
|
|
caps = list(ircdb.users.getUser(hostmask).capabilities)
|
|
triviamod = '{0},{1}'.format(channel,'triviamod')
|
|
triviaadmin = '{0},{1}'.format(channel,'triviaadmin')
|
|
|
|
# If multiple capabilities exist, pick the most important
|
|
if 'owner' in caps:
|
|
return 'owner'
|
|
elif triviaadmin in caps:
|
|
return triviaadmin
|
|
elif triviamod in caps:
|
|
return triviamod
|
|
else:
|
|
return 'user'
|
|
|
|
return None
|
|
|
|
def getUsername(self, nick, hostmask):
|
|
username = nick
|
|
try:
|
|
#rootcoma!~rootcomaa@unaffiliated/rootcoma
|
|
user = ircdb.users.getUser(hostmask)
|
|
username = user.name
|
|
except KeyError:
|
|
pass
|
|
return username
|
|
|
|
def reply(self, irc, msg, outstr, notice=False, prefixNick=True):
|
|
if ircutils.isChannel(msg.args[0]) and not notice:
|
|
target = msg.args[0]
|
|
else:
|
|
target = msg.nick
|
|
|
|
if notice:
|
|
output = ircmsgs.notice
|
|
else:
|
|
output = ircmsgs.privmsg
|
|
|
|
if prefixNick == False or ircutils.isNick(target):
|
|
irc.sendMsg(output(target, outstr))
|
|
else:
|
|
irc.sendMsg(output(target, '%s: %s' % (msg.nick, outstr)))
|
|
irc.noReply()
|
|
|
|
def acceptdelete(self, irc, msg, arg, channel, num):
|
|
"""[<channel>] <num>
|
|
Accept a question deletion.
|
|
Channel is only required when using the command outside of a channel.
|
|
"""
|
|
hostmask = msg.prefix
|
|
username = self.getUsername(msg.nick, hostmask)
|
|
if self.isTriviaMod(hostmask, channel) == False:
|
|
irc.reply('You must be a TriviaMod in {0} to use this command.'.format(channel))
|
|
return
|
|
|
|
dbLocation = self.registryValue('admin.db')
|
|
threadStorage = Storage(dbLocation)
|
|
if self.registryValue('general.globalstats'):
|
|
delete = threadStorage.getDeleteById(num)
|
|
else:
|
|
delete = threadStorage.getDeleteById(num, channel)
|
|
|
|
if delete:
|
|
if username == delete['username'] and self.isTriviaMod(hostmask, channel) == False:
|
|
irc.reply('You cannot accept your own deletion request.')
|
|
else:
|
|
questionNumber = delete['line_num']
|
|
irc.reply('Question #%d deleted!' % questionNumber)
|
|
threadStorage.removeReportByQuestionNumber(questionNumber)
|
|
threadStorage.removeEditByQuestionNumber(questionNumber)
|
|
threadStorage.deleteQuestion(questionNumber)
|
|
threadStorage.removeDelete(num)
|
|
threadStorage.removeDeleteByQuestionNumber(questionNumber)
|
|
self.logger.doLog(irc, channel, "%s accepted delete# %i, question #%i deleted" % (msg.nick, num, questionNumber))
|
|
activityText = '%s deleted a question, approved by %s' % (delete['username'], msg.nick)
|
|
self.addActivity('delete', activityText, channel, irc, threadStorage)
|
|
else:
|
|
if self.registryValue('general.globalstats'):
|
|
irc.error('Unable to find delete #{0}.'.format(num))
|
|
else:
|
|
irc.error('Unable to find delete #{0} in {1}.'.format(num, channel))
|
|
acceptdelete = wrap(acceptdelete, ['channel', 'int'])
|
|
|
|
def acceptedit(self, irc, msg, arg, channel, num):
|
|
"""[<channel>] <num>
|
|
Accept a question edit, and remove edit.
|
|
Channel is only required when using the command outside of a channel.
|
|
"""
|
|
hostmask = msg.prefix
|
|
username = self.getUsername(msg.nick, hostmask)
|
|
if self.isTriviaMod(hostmask, channel) == False:
|
|
irc.reply('You must be a TriviaMod in {0} to use this command.'.format(channel))
|
|
return
|
|
|
|
dbLocation = self.registryValue('admin.db')
|
|
threadStorage = Storage(dbLocation)
|
|
if self.registryValue('general.globalstats'):
|
|
edit = threadStorage.getEditById(num)
|
|
else:
|
|
edit = threadStorage.getEditById(num, channel)
|
|
|
|
if edit:
|
|
if username == edit['username'] and self.isTriviaMod(hostmask, channel) == False:
|
|
irc.reply('You cannot accept your own edit.')
|
|
else:
|
|
question = threadStorage.getQuestionById(edit['question_id'])
|
|
questionOld = question['question'] if question else ''
|
|
threadStorage.updateQuestion(edit['question_id'], edit['question'])
|
|
threadStorage.updateUser(edit['username'], 0, 1)
|
|
threadStorage.removeEdit(edit['id'])
|
|
threadStorage.removeReportByQuestionNumber(edit['question_id'])
|
|
|
|
irc.reply('Question #%d updated!' % edit['question_id'])
|
|
self.logger.doLog(irc, channel, "%s accepted edit# %i, question #%i edited NEW: '%s' OLD '%s'" % (msg.nick, num, edit['question_id'], edit['question'], questionOld))
|
|
activityText = '%s edited a question, approved by %s' % (edit['username'], msg.nick)
|
|
self.addActivity('edit', activityText, channel, irc, threadStorage)
|
|
irc.sendMsg(ircmsgs.notice(msg.nick, 'NEW: %s' % (edit['question'])))
|
|
if questionOld != '':
|
|
irc.sendMsg(ircmsgs.notice(msg.nick, 'OLD: %s' % (questionOld)))
|
|
else:
|
|
irc.error('Question could not be found for this edit')
|
|
else:
|
|
if self.registryValue('general.globalstats'):
|
|
irc.error('Unable to find edit #{0}.'.format(num))
|
|
else:
|
|
irc.error('Unable to find edit #{0} in {1}.'.format(num, channel))
|
|
acceptedit = wrap(acceptedit, ['channel', 'int'])
|
|
|
|
def acceptnew(self, irc, msg, arg, channel, num):
|
|
"""[<channel>] <num>
|
|
Accept a new question, and add it to the database.
|
|
Channel is only required when using the command outside of a channel.
|
|
"""
|
|
hostmask = msg.prefix
|
|
username = self.getUsername(msg.nick, hostmask)
|
|
if self.isTriviaMod(hostmask, channel) == False:
|
|
irc.reply('You must be a TriviaMod in {0} to use this command.'.format(channel))
|
|
return
|
|
|
|
dbLocation = self.registryValue('admin.db')
|
|
threadStorage = Storage(dbLocation)
|
|
if self.registryValue('general.globalstats'):
|
|
q = threadStorage.getTemporaryQuestionById(num)
|
|
else:
|
|
q = threadStorage.getTemporaryQuestionById(num, channel)
|
|
|
|
if q:
|
|
if username == q['username']:
|
|
irc.reply('You cannot accept your own new question.')
|
|
else:
|
|
threadStorage.updateUser(q['username'], 0, 0, 0, 0, 1)
|
|
threadStorage.insertQuestionsBulk([(q['question'], q['question'])])
|
|
threadStorage.removeTemporaryQuestion(q['id'])
|
|
irc.reply('Question accepted!')
|
|
self.logger.doLog(irc, channel, "%s accepted new question #%i, '%s'" % (msg.nick, num, q['question']))
|
|
activityText = '%s added a new question, approved by %s' % (q['username'], msg.nick)
|
|
self.addActivity('new', activityText, channel, irc, threadStorage)
|
|
else:
|
|
if self.registryValue('general.globalstats'):
|
|
irc.error('Unable to find new question #{0}.'.format(num))
|
|
else:
|
|
irc.error('Unable to find new question #{0} in {1}.'.format(num, channel))
|
|
acceptnew = wrap(acceptnew, ['channel', 'int'])
|
|
|
|
def add(self, irc, msg, arg, user, channel, question):
|
|
"""[<channel>] <question text>
|
|
Adds a question to the database.
|
|
Channel is only required when using the command outside of a channel.
|
|
"""
|
|
username = msg.nick
|
|
charMask = self.registryValue('hints.charMask', channel)
|
|
if charMask not in question:
|
|
irc.error('The question must include the separating character %s ' % (charMask))
|
|
return
|
|
dbLocation = self.registryValue('admin.db')
|
|
threadStorage = Storage(dbLocation)
|
|
threadStorage.updateUser(username, 0, 0, 0, 1)
|
|
threadStorage.insertTemporaryQuestion(username, channel, question)
|
|
irc.reply('Thank you for adding your question to the question database, it is awaiting approval. ')
|
|
self.logger.doLog(irc, channel, "%s added new question: '%s'" % (username, question))
|
|
add = wrap(add, ['user', 'channel', 'text'])
|
|
|
|
def addfile(self, irc, msg, arg, filename):
|
|
"""[<filename>]
|
|
Add a file of questions to the question database,
|
|
filename defaults to configured question file.
|
|
"""
|
|
if filename is None:
|
|
filename = self.registryValue('admin.file')
|
|
try:
|
|
filesLines = open(filename).readlines()
|
|
except:
|
|
irc.error('Could not open file to add to database. Make sure it exists on the server.')
|
|
return
|
|
irc.reply('Adding questions from %s to database.. This may take a few minutes' % filename)
|
|
insertList = []
|
|
channel = msg.args[0]
|
|
for line in filesLines:
|
|
insertList.append((str(line).strip(),str(line).strip()))
|
|
dbLocation = self.registryValue('admin.db')
|
|
threadStorage = Storage(dbLocation)
|
|
info = threadStorage.insertQuestionsBulk(insertList)
|
|
irc.reply('Successfully added %d questions, skipped %d' % (info[0], info[1]))
|
|
self.logger.doLog(irc, channel, "%s added question file: '%s', added: %i, skipped: %i" % (msg.nick, filename, info[0], info[1]))
|
|
addfile = wrap(addfile, ['owner', optional('text')])
|
|
|
|
def authweb(self, irc, msg, arg, channel):
|
|
"""[<channel>]
|
|
This registers triviamods and triviaadmins on the website.
|
|
Use this command again if the account password has changed.
|
|
Channel is only required when using the command outside of a channel.
|
|
"""
|
|
hostmask = msg.prefix
|
|
capability = self.getTriviaCapability(hostmask, channel)
|
|
if capability is None or capability == 'user':
|
|
irc.reply('You must be a TriviaMod in {0} to use this command.'.format(channel))
|
|
return
|
|
|
|
user = ircdb.users.getUser(hostmask)
|
|
salt = ''
|
|
password = ''
|
|
isHashed = user.hashed
|
|
if isHashed:
|
|
(salt, password) = user.password.split('|')
|
|
else:
|
|
password = user.password
|
|
|
|
dbLocation = self.registryValue('admin.db')
|
|
threadStorage = Storage(dbLocation)
|
|
info = threadStorage.insertLogin(user.name, salt, isHashed, password, capability)
|
|
irc.reply('Success, updated your web access login.')
|
|
self.logger.doLog(irc, channel, "%s authed for web access" % (user.name))
|
|
authweb = wrap(authweb, ['channel'])
|
|
|
|
def clearpoints(self, irc, msg, arg, channel, username):
|
|
"""[<channel>] <username>
|
|
Deletes all of a users points, and removes all their records.
|
|
Channel is only required when using the command outside of a channel.
|
|
"""
|
|
hostmask = msg.prefix
|
|
if self.isTriviaAdmin(hostmask, channel) == False:
|
|
irc.reply('You must be a TriviaAdmin in {0} to use this command.'.format(channel))
|
|
return
|
|
|
|
dbLocation = self.registryValue('admin.db')
|
|
threadStorage = Storage(dbLocation)
|
|
threadStorage.removeUserLogs(username, channel)
|
|
irc.reply('Removed all points from {0} in {1}.'.format(username, channel))
|
|
self.logger.doLog(irc, channel, '{0} cleared points for {1} in {2}.'.format(msg.nick, username, channel))
|
|
clearpoints = wrap(clearpoints, ['channel', 'nick'])
|
|
|
|
def day(self, irc, msg, arg, channel, num):
|
|
"""[<channel>] [<number>]
|
|
Displays the top scores of the day.
|
|
Parameter is optional, display up to that number. (eg 20 - display 11-20)
|
|
Channel is only required when using the command outside of a channel.
|
|
"""
|
|
if num is not None:
|
|
num = max(num, 10)
|
|
else:
|
|
num = 10
|
|
offset = num-9
|
|
dbLocation = self.registryValue('admin.db')
|
|
threadStorage = Storage(dbLocation)
|
|
if self.registryValue('general.globalStats'):
|
|
tops = threadStorage.viewDayTop10(None, num)
|
|
else:
|
|
tops = threadStorage.viewDayTop10(channel, num)
|
|
|
|
topsList = ['Today\'s Top {0}-{1} Players: '.format(offset, num)]
|
|
if tops:
|
|
for i in range(len(tops)):
|
|
topsList.append('\x02 #%d:\x02 %s %d ' % ((i+offset) , self.addZeroWidthSpace(tops[i]['username']), tops[i]['points']))
|
|
else:
|
|
topsList.append('No players')
|
|
topsText = ''.join(topsList)
|
|
self.reply(irc, msg, topsText, prefixNick=False)
|
|
day = wrap(day, ['channel', optional('int')])
|
|
|
|
def delete(self, irc, msg, arg, user, channel, t, id, reason):
|
|
"""[<channel>] [<type "R" or "Q">] <question id> [<reason>]
|
|
Deletes a question from the database. Type decides whether to delete
|
|
by round number (r), or question number (q) (default round).
|
|
Channel is only required when using the command outside of a channel.
|
|
"""
|
|
hostmask = msg.prefix
|
|
username = self.getUsername(msg.nick, hostmask)
|
|
dbLocation = self.registryValue('admin.db')
|
|
threadStorage = Storage(dbLocation)
|
|
|
|
# Search for question ID if deletion is by 'round'
|
|
if t is None or str.lower(t) == "round":
|
|
q = threadStorage.getQuestionByRound(id, channel)
|
|
if q:
|
|
id = q['id']
|
|
else:
|
|
irc.error('Could not find that round #{0}.'.format(id))
|
|
return
|
|
|
|
if not threadStorage.questionIdExists(id):
|
|
irc.error('That question does not exist.')
|
|
elif threadStorage.isQuestionDeleted(id):
|
|
irc.error('That question is already deleted.')
|
|
elif threadStorage.isQuestionPendingDeletion(id):
|
|
irc.error('That question is already pending deletion.')
|
|
else:
|
|
threadStorage.insertDelete(username, channel, id, reason)
|
|
irc.reply('Question %d marked for deletion and pending review.' % id)
|
|
self.logger.doLog(irc, channel, "%s marked question #%i for deletion" % (username, id))
|
|
delete = wrap(delete, ['user', 'channel', optional(('literal',("question", "QUESTION", "ROUND", "round"))),'int', optional('text')])
|
|
|
|
def edit(self, irc, msg, arg, user, channel, num, question):
|
|
"""[<channel>] <question number> <corrected text>
|
|
Correct a question by providing the question number and the corrected text.
|
|
Channel is only required when using the command outside of a channel.
|
|
"""
|
|
username = self.getUsername(msg.nick, msg.prefix)
|
|
dbLocation = self.registryValue('admin.db')
|
|
threadStorage = Storage(dbLocation)
|
|
q = threadStorage.getQuestionById(num)
|
|
if q:
|
|
questionParts = question.split('*')
|
|
if len(questionParts) < 2:
|
|
oldQuestionParts = q['question'].split('*')
|
|
questionParts.extend(oldQuestionParts[1:])
|
|
question = questionParts[0]
|
|
for part in questionParts[1:]:
|
|
question += '*'
|
|
question += part
|
|
if question == q['question']:
|
|
irc.error('Your edit does not change the original question.')
|
|
else:
|
|
threadStorage.insertEdit(num, question, username, channel)
|
|
threadStorage.updateUser(username, 1, 0)
|
|
irc.reply('Success! Submitted edit for further review.')
|
|
irc.sendMsg(ircmsgs.notice(msg.nick, 'NEW: %s' % (question)))
|
|
irc.sendMsg(ircmsgs.notice(msg.nick, 'OLD: %s' % (q['question'])))
|
|
self.logger.doLog(irc, channel, "%s edited question #%i: OLD: '%s' NEW: '%s'" % (username, num, q['question'], question))
|
|
else:
|
|
irc.error('Question does not exist')
|
|
edit = wrap(edit, ['user', 'channel', 'int', 'text'])
|
|
|
|
def givepoints(self, irc, msg, arg, channel, username, points, days):
|
|
"""[<channel>] <username> <points> [<daysAgo>]
|
|
Give a user points, last argument is optional amount of days in past to add records.
|
|
Channel is only required when using the command outside of a channel.
|
|
"""
|
|
hostmask = msg.prefix
|
|
if self.isTriviaAdmin(hostmask, channel) == False:
|
|
irc.reply('You must be a TriviaAdmin in {0} to use this command.'.format(channel))
|
|
return
|
|
elif points == 0:
|
|
irc.error("You cannot give 0 points.")
|
|
return
|
|
|
|
username = self.getUsername(username, username)
|
|
day=None
|
|
month=None
|
|
year=None
|
|
if days is not None:
|
|
d = datetime.date.today()
|
|
d -= datetime.timedelta(days)
|
|
day = d.day
|
|
month = d.month
|
|
year = d.year
|
|
dbLocation = self.registryValue('admin.db')
|
|
threadStorage = Storage(dbLocation)
|
|
threadStorage.updateUserLog(username, channel, points, 0, 0, day, month, year)
|
|
irc.reply('Added {0} points to {1} in {2}.'.format(points, username, channel))
|
|
self.logger.doLog(irc, channel, '{0} gave {1} points to {2} in {3}.'.format(msg.nick, points, username, channel))
|
|
givepoints = wrap(givepoints, ['channel', 'nick', 'int', optional('int')])
|
|
|
|
def listdeletes(self, irc, msg, arg, channel, page):
|
|
"""[<channel>] [<page>]
|
|
List questions pending deletion.
|
|
Channel is only required when using the command outside of a channel.
|
|
"""
|
|
hostmask = msg.prefix
|
|
if self.isTriviaMod(hostmask, channel) == False:
|
|
irc.reply('You must be at least a TriviaMod in {0} to use this command.'.format(channel))
|
|
return
|
|
|
|
# Grab list from the database
|
|
dbLocation = self.registryValue('admin.db')
|
|
threadStorage = Storage(dbLocation)
|
|
if self.registryValue('general.globalstats'):
|
|
count = threadStorage.countDeletes()
|
|
else:
|
|
count = threadStorage.countDeletes(channel)
|
|
pages = int(count / 5) + int(count % 5 > 0)
|
|
if page is not None:
|
|
page = max(1, min(page, pages))
|
|
else:
|
|
page = 1
|
|
if self.registryValue('general.globalstats'):
|
|
deletes = threadStorage.getDeleteTop5(page)
|
|
else:
|
|
deletes = threadStorage.getDeleteTop5(page, channel=channel)
|
|
|
|
# Output list
|
|
if count < 1:
|
|
if self.registryValue('general.globalstats'):
|
|
irc.reply('No deletes found.')
|
|
else:
|
|
irc.reply('No deletes found in {0}.'.format(channel))
|
|
else:
|
|
irc.reply('Showing page %i of %i' % (page, pages))
|
|
for delete in deletes:
|
|
question = threadStorage.getQuestionById(delete['line_num'])
|
|
questionText = question['question'] if question else 'Question not found'
|
|
irc.reply('Delete #%d, by %s Question #%d: %s, Reason:%s'%(delete['id'], delete['username'], delete['line_num'], questionText, delete['reason']))
|
|
irc.reply('Use the showdelete command to see more information')
|
|
listdeletes = wrap(listdeletes, ['channel', optional('int')])
|
|
|
|
def listalledits(self, irc, msg, arg, channel, page):
|
|
"""[<channel>] [<page>]
|
|
List all edits pending approval (even your own).
|
|
Channel is only required when using the command outside of a channel.
|
|
"""
|
|
hostmask = msg.prefix
|
|
if self.isTriviaMod(hostmask, channel) == False:
|
|
irc.reply('You must be at least a TriviaMod in {0} to use this command.'.format(channel))
|
|
return
|
|
|
|
# Grab list from the database
|
|
dbLocation = self.registryValue('admin.db')
|
|
threadStorage = Storage(dbLocation)
|
|
if self.registryValue('general.globalstats'):
|
|
count = threadStorage.countEdits()
|
|
else:
|
|
count = threadStorage.countEdits(channel)
|
|
pages = int(count / 5) + int(count % 5 > 0)
|
|
if page is not None:
|
|
page = max(1, min(page, pages))
|
|
else:
|
|
page = 1
|
|
if self.registryValue('general.globalstats'):
|
|
edits = threadStorage.getEditTop5(page)
|
|
else:
|
|
edits = threadStorage.getEditTop5(page, channel=channel)
|
|
|
|
# Output list
|
|
if count < 1:
|
|
if self.registryValue('general.globalstats'):
|
|
irc.reply('No edits found.')
|
|
else:
|
|
irc.reply('No edits found in {0}.'.format(channel))
|
|
else:
|
|
irc.reply('Showing page %i of %i' % (page, pages))
|
|
for edit in edits:
|
|
irc.reply('Edit #%d, Question #%d, NEW:%s'%(edit['id'], edit['question_id'], edit['question']))
|
|
irc.reply('Use the showedit command to see more information')
|
|
listalledits = wrap(listalledits, ['channel', optional('int')])
|
|
|
|
def listmyedits(self, irc, msg, arg, channel, page):
|
|
"""[<channel>] [<page>]
|
|
List only your own edits pending approval.
|
|
Channel is only required when using the command outside of a channel.
|
|
"""
|
|
hostmask = msg.prefix
|
|
if self.isTriviaMod(hostmask, channel) == False:
|
|
irc.reply('You must be at least a TriviaMod in {0} to use this command.'.format(channel))
|
|
return
|
|
|
|
# Grab list from the database
|
|
dbLocation = self.registryValue('admin.db')
|
|
threadStorage = Storage(dbLocation)
|
|
username = self.getUsername(msg.nick, msg.prefix)
|
|
if self.registryValue('general.globalstats'):
|
|
count = threadStorage.countMyEdits(username)
|
|
else:
|
|
count = threadStorage.countMyEdits(username, channel)
|
|
pages = int(count / 5) + int(count % 5 > 0)
|
|
if page is not None:
|
|
page = max(1, min(page, pages))
|
|
else:
|
|
page = 1
|
|
if self.registryValue('general.globalstats'):
|
|
edits = threadStorage.getMyEditTop5(username, page)
|
|
else:
|
|
edits = threadStorage.getMyEditTop5(username, page, channel=channel)
|
|
|
|
# Output list
|
|
if count < 1:
|
|
if self.registryValue('general.globalstats'):
|
|
irc.reply('No edits found.')
|
|
else:
|
|
irc.reply('No edits found in {0}.'.format(channel))
|
|
else:
|
|
irc.reply('Showing page %i of %i' % (page, pages))
|
|
for edit in edits:
|
|
irc.reply('Edit #%d, Question #%d, NEW:%s'%(edit['id'], edit['question_id'], edit['question']))
|
|
irc.reply('Use the showedit command to see more information')
|
|
listmyedits = wrap(listmyedits, ['channel', optional('int')])
|
|
|
|
def listedits(self, irc, msg, arg, channel, page):
|
|
"""[<channel>] [<page>]
|
|
List edits pending approval (by default does not include your own edits)
|
|
Channel is only required when using the command outside of a channel.
|
|
"""
|
|
hostmask = msg.prefix
|
|
if self.isTriviaMod(hostmask, channel) == False:
|
|
irc.reply('You must be at least a TriviaMod in {0} to use this command.'.format(channel))
|
|
return
|
|
|
|
# Grab list from the database
|
|
dbLocation = self.registryValue('admin.db')
|
|
threadStorage = Storage(dbLocation)
|
|
username = self.getUsername(msg.nick, msg.prefix)
|
|
if self.registryValue('general.globalstats'):
|
|
count = threadStorage.countNotMyEdits(username)
|
|
else:
|
|
count = threadStorage.countNotMyEdits(username, channel)
|
|
pages = int(count / 5) + int(count % 5 > 0)
|
|
if page is not None:
|
|
page = max(1, min(page, pages))
|
|
else:
|
|
page = 1
|
|
if self.registryValue('general.globalstats'):
|
|
edits = threadStorage.getNotMyEditTop5(username, page)
|
|
else:
|
|
edits = threadStorage.getNotMyEditTop5(username, page, channel=channel)
|
|
|
|
# Output list
|
|
if count < 1:
|
|
if self.registryValue('general.globalstats'):
|
|
irc.reply('No edits found.')
|
|
else:
|
|
irc.reply('No edits found in {0}.'.format(channel))
|
|
else:
|
|
irc.reply('Showing page %i of %i' % (page, pages))
|
|
for edit in edits:
|
|
irc.reply('Edit #%d, Question #%d, NEW:%s'%(edit['id'], edit['question_id'], edit['question']))
|
|
irc.reply('Use the showedit command to see more information')
|
|
listedits = wrap(listedits, ['channel', optional('int')])
|
|
|
|
def listreports(self, irc, msg, arg, user, channel, page):
|
|
"""[<channel>] [<page>]
|
|
List reports pending edit.
|
|
Channel is only required when using the command outside of a channel.
|
|
"""
|
|
# Grab list from the database
|
|
dbLocation = self.registryValue('admin.db')
|
|
threadStorage = Storage(dbLocation)
|
|
if self.registryValue('general.globalstats'):
|
|
count = threadStorage.countReports()
|
|
else:
|
|
count = threadStorage.countReports(channel)
|
|
pages = int(count / 5) + int(count % 5 > 0)
|
|
if page is not None:
|
|
page = max(1, min(page, pages))
|
|
else:
|
|
page = 1
|
|
if self.registryValue('general.globalstats'):
|
|
reports = threadStorage.getReportTop5(page)
|
|
else:
|
|
reports = threadStorage.getReportTop5(page, channel=channel)
|
|
|
|
# Output list
|
|
if count < 1:
|
|
if self.registryValue('general.globalstats'):
|
|
irc.reply('No reports found.')
|
|
else:
|
|
irc.reply('No reports found in {0}.'.format(channel))
|
|
else:
|
|
irc.reply('Showing page %i of %i' % (page, pages))
|
|
for report in reports:
|
|
irc.reply('Report #%d \'%s\' by %s on %s Q#%d '%(report['id'], report['report_text'], report['username'], report['channel'], report['question_num']))
|
|
irc.reply('Use the showreport command to see more information')
|
|
listreports = wrap(listreports, ['user', 'channel', optional('int')])
|
|
|
|
def listnew(self, irc, msg, arg, channel, page):
|
|
"""[<channel>] [<page>]
|
|
List questions awaiting approval.
|
|
Channel is only required when using the command outside of a channel.
|
|
"""
|
|
hostmask = msg.prefix
|
|
if self.isTriviaMod(hostmask, channel) == False:
|
|
irc.reply('You must be at least a TriviaMod in {0} to use this command.'.format(channel))
|
|
return
|
|
|
|
# Grab list from the database
|
|
dbLocation = self.registryValue('admin.db')
|
|
threadStorage = Storage(dbLocation)
|
|
if self.registryValue('general.globalstats'):
|
|
count = threadStorage.countTemporaryQuestions()
|
|
else:
|
|
count = threadStorage.countTemporaryQuestions(channel)
|
|
pages = int(count / 5) + int(count % 5 > 0)
|
|
if page is not None:
|
|
page = max(1, min(page, pages))
|
|
else:
|
|
page = 1
|
|
if self.registryValue('general.globalstats'):
|
|
q = threadStorage.getTemporaryQuestionTop5(page)
|
|
else:
|
|
q = threadStorage.getTemporaryQuestionTop5(page, channel=channel)
|
|
|
|
# Output list
|
|
if count < 1:
|
|
if self.registryValue('general.globalstats'):
|
|
irc.reply('No new questions found.')
|
|
else:
|
|
irc.reply('No new questions found in {0}.'.format(channel))
|
|
else:
|
|
irc.reply('Showing page %i of %i' % (page, pages))
|
|
for ques in q:
|
|
irc.reply('Temp Q #%d: %s'%(ques['id'], ques['question']))
|
|
irc.reply('Use the shownew to see more information')
|
|
listnew = wrap(listnew, ['channel', optional('int')])
|
|
|
|
def info(self, irc, msg, arg, channel):
|
|
"""[<channel>]
|
|
Get TriviaTime information, how many questions/users in database, time, etc.
|
|
Channel is only required when using the command outside of a channel.
|
|
"""
|
|
dbLocation = self.registryValue('admin.db')
|
|
threadStorage = Storage(dbLocation)
|
|
totalUsersEver = threadStorage.getNumUser(channel)
|
|
numActiveThisWeek = threadStorage.getNumActiveThisWeek(channel)
|
|
infoText = ' TriviaTime fork for #trivia on Snoonet: https://github.com/oddluck/limnoria-plugins'
|
|
self.reply(irc, msg, infoText, prefixNick=False)
|
|
infoText = '\x02 %d Users\x02 on scoreboard with \x02%d Active This Week\x02' % (totalUsersEver, numActiveThisWeek)
|
|
self.reply(irc, msg, infoText, prefixNick=False)
|
|
numKaos = threadStorage.getNumKAOS()
|
|
numQuestionTotal = threadStorage.getNumQuestions()
|
|
infoText = '\x02 %d Questions\x02 and \x02%d KAOS\x02 (\x02%d Total\x02) in the database ' % ((numQuestionTotal-numKaos), numKaos, numQuestionTotal)
|
|
self.reply(irc, msg, infoText, prefixNick=False)
|
|
info = wrap(info, ['channel'])
|
|
|
|
def ping(self, irc, msg, arg):
|
|
"""
|
|
Check your ping time to the bot. The client must respond correctly to pings.
|
|
"""
|
|
channel = msg.args[0]
|
|
channelHash = self.shortHash(ircutils.toLower(channel))
|
|
username = msg.nick
|
|
irc.sendMsg(ircmsgs.privmsg(username, '\x01PING %0.2f*%s\x01' % (time.time()-1300000000, channelHash)))
|
|
ping = wrap(ping)
|
|
|
|
def me(self, irc, msg, arg, channel):
|
|
"""[<channel>]
|
|
Get your rank, score & questions asked for day, month, year.
|
|
Channel is only required when using the command outside of a channel.
|
|
"""
|
|
username = self.getUsername(msg.nick, msg.prefix)
|
|
identified = ircdb.users.hasUser(msg.prefix)
|
|
dbLocation = self.registryValue('admin.db')
|
|
threadStorage = Storage(dbLocation)
|
|
|
|
if self.registryValue('general.globalStats'):
|
|
stat = threadStorage.getUserStat(username, None)
|
|
rank = threadStorage.getUserRank(username, None)
|
|
else:
|
|
stat = threadStorage.getUserStat(username, channel)
|
|
rank = threadStorage.getUserRank(username, channel)
|
|
|
|
if not stat:
|
|
errorMessage = 'You do not have any points.'
|
|
identifyMessage = ''
|
|
if not identified:
|
|
identifyMessage = ' You should identify to keep track of your score more accurately.'
|
|
irc.reply('%s%s' % (errorMessage, identifyMessage))
|
|
else:
|
|
hasPoints = False
|
|
infoList = ['%s\'s Stats: Points (answers)' % (self.addZeroWidthSpace(stat['username']))]
|
|
if rank['day'] is not None and stat['points_day'] is not None and stat['answer_day'] is not None:
|
|
if rank['day'] > 0 or stat['points_day'] > 0 or stat['answer_day'] > 0:
|
|
hasPoints = True
|
|
infoList.append(' \x02Today:\x02 #%d %d (%d)' % (rank['day'], stat['points_day'], stat['answer_day']))
|
|
if rank['week'] is not None and stat['points_week'] is not None and stat['answer_week'] is not None:
|
|
if rank['week'] > 0 or stat['points_week'] > 0 or stat['answer_week'] > 0:
|
|
hasPoints = True
|
|
infoList.append(' \x02This Week:\x02 #%d %d (%d)' % (rank['week'], stat['points_week'], stat['answer_week']))
|
|
if rank['month'] is not None and stat['points_month'] is not None and stat['answer_month'] is not None:
|
|
if rank['month'] > 0 or stat['points_month'] > 0 or stat['answer_week'] > 0:
|
|
hasPoints = True
|
|
infoList.append(' \x02This Month:\x02 #%d %d (%d)' % (rank['month'], stat['points_month'], stat['answer_month']))
|
|
if rank['year'] is not None and stat['points_year'] is not None and stat['answer_year'] is not None:
|
|
if rank['year'] > 0 or stat['points_year'] > 0 or stat['answer_year'] > 0:
|
|
hasPoints = True
|
|
infoList.append(' \x02This Year:\x02 #%d %d (%d)' % (rank['year'], stat['points_year'], stat['answer_year']))
|
|
if rank['total'] is not None and stat['points_total'] is not None and stat['answer_total'] is not None:
|
|
if rank['total'] > 0 or stat['points_total'] > 0 or stat['answer_total'] > 0:
|
|
hasPoints = True
|
|
infoList.append(' \x02All Time:\x02 #%d %d (%d)' % (rank['total'], stat['points_total'], stat['answer_total']))
|
|
if not hasPoints:
|
|
infoList = ['%s: You do not have any points.' % (username)]
|
|
if not identified:
|
|
infoList.append(' You should identify to keep track of your score more accurately.')
|
|
infoText = ''.join(infoList)
|
|
self.reply(irc, msg, infoText, prefixNick=False)
|
|
me = wrap(me, ['channel'])
|
|
|
|
def month(self, irc, msg, arg, channel, num):
|
|
"""[<channel>] [<number>]
|
|
Displays the top ten scores of the month.
|
|
Parameter is optional, display up to that number. (eg 20 - display 11-20)
|
|
Channel is only required when using the command outside of a channel.
|
|
"""
|
|
if num is not None:
|
|
num = max(num, 10)
|
|
else:
|
|
num = 10
|
|
offset = num-9
|
|
dbLocation = self.registryValue('admin.db')
|
|
threadStorage = Storage(dbLocation)
|
|
if self.registryValue('general.globalStats'):
|
|
tops = threadStorage.viewMonthTop10(None, num)
|
|
else:
|
|
tops = threadStorage.viewMonthTop10(channel, num)
|
|
|
|
topsList = ['This Month\'s Top {0}-{1} Players: '.format(offset, num)]
|
|
if tops:
|
|
for i in range(len(tops)):
|
|
topsList.append('\x02 #%d:\x02 %s %d ' % ((i+offset) , self.addZeroWidthSpace(tops[i]['username']), tops[i]['points']))
|
|
else:
|
|
topsList.append('No players')
|
|
topsText = ''.join(topsList)
|
|
self.reply(irc, msg, topsText, prefixNick=False)
|
|
month = wrap(month, ['channel', optional('int')])
|
|
|
|
def next(self, irc, msg, arg, channel):
|
|
"""
|
|
Skip to the next question immediately.
|
|
This can only be used by a user with a certain streak, set in the config.
|
|
"""
|
|
username = self.getUsername(msg.nick, msg.prefix)
|
|
minStreak = self.registryValue('general.nextMinStreak', channel)
|
|
game = self.getGame(irc, channel)
|
|
|
|
# Sanity checks
|
|
# 1. Is trivia running?
|
|
# 2. Is question is still being asked?
|
|
# 3. Is caller the streak holder?
|
|
# 4. Is streak high enough?
|
|
if game is None or game.active == False:
|
|
self.reply(irc, msg, 'Trivia is not currently running.')
|
|
elif game.state != 'no-question':
|
|
self.reply(irc, msg, 'You must wait until the current question is over.')
|
|
elif ircutils.toLower(game.lastWinner) != ircutils.toLower(username):
|
|
self.reply(irc, msg, 'You are not currently the streak holder.')
|
|
elif game.streak < minStreak:
|
|
self.reply(irc, msg, 'You do not have a large enough streak yet (%i of %i).' % (game.streak, minStreak))
|
|
else:
|
|
game.removeEvent()
|
|
self.reply(irc, msg, 'Onto the next question!', prefixNick=False)
|
|
game.nextQuestion()
|
|
next = wrap(next, ['onlyInChannel'])
|
|
|
|
def rmedit(self, irc, msg, arg, channel, num):
|
|
"""[<channel>] <int>
|
|
Deny a question edit.
|
|
Channel is only required when using the command outside of a channel.
|
|
"""
|
|
hostmask = msg.prefix
|
|
if self.isTriviaMod(hostmask, channel) == False:
|
|
irc.reply('You must be at least a TriviaMod in {0} to use this command.'.format(channel))
|
|
return
|
|
|
|
dbLocation = self.registryValue('admin.db')
|
|
threadStorage = Storage(dbLocation)
|
|
if self.registryValue('general.globalstats'):
|
|
edit = threadStorage.getEditById(num)
|
|
else:
|
|
edit = threadStorage.getEditById(num, channel)
|
|
|
|
if edit:
|
|
threadStorage.removeEdit(edit['id'])
|
|
irc.reply('Edit %d removed!' % edit['id'])
|
|
self.logger.doLog(irc, channel, "%s removed edit# %i, for question #%i, text: %s" % (msg.nick, edit['id'], edit['question_id'], edit['question']))
|
|
else:
|
|
if self.registryValue('general.globalstats'):
|
|
irc.error('Unable to find edit #{0}.'.format(num))
|
|
else:
|
|
irc.error('Unable to find edit #{0} in {1}.'.format(num, channel))
|
|
rmedit = wrap(rmedit, ['channel', 'int'])
|
|
|
|
def rmdelete(self, irc, msg, arg, channel, num):
|
|
"""[<channel>] <int>
|
|
Deny a deletion request.
|
|
Channel is only required when using the command outside of a channel.
|
|
"""
|
|
hostmask = msg.prefix
|
|
if self.isTriviaMod(hostmask, channel) == False:
|
|
irc.reply('You must be at least a TriviaMod in {0} to use this command.'.format(channel))
|
|
return
|
|
|
|
dbLocation = self.registryValue('admin.db')
|
|
threadStorage = Storage(dbLocation)
|
|
if self.registryValue('general.globalstats'):
|
|
delete = threadStorage.getDeleteById(num)
|
|
else:
|
|
delete = threadStorage.getDeleteById(num, channel)
|
|
|
|
if delete:
|
|
threadStorage.removeDelete(num)
|
|
irc.reply('Delete %d removed!' % num)
|
|
self.logger.doLog(irc, channel, "%s removed delete# %i, for question #%i, reason was '%s'" % (msg.nick, num, delete['line_num'], delete['reason']))
|
|
else:
|
|
if self.registryValue('general.globalstats'):
|
|
irc.error('Unable to find delete #{0}.'.format(num))
|
|
else:
|
|
irc.error('Unable to find delete #{0} in {1}.'.format(num, channel))
|
|
rmdelete = wrap(rmdelete, ['channel', 'int'])
|
|
|
|
def rmreport(self, irc, msg, arg, channel, num):
|
|
"""[<channel>] <report num>
|
|
Delete a report.
|
|
Channel is only required when using the command outside of a channel.
|
|
"""
|
|
hostmask = msg.prefix
|
|
if self.isTriviaMod(hostmask, channel) == False:
|
|
irc.reply('You must be at least a TriviaMod in {0} to use this command.'.format(channel))
|
|
return
|
|
|
|
dbLocation = self.registryValue('admin.db')
|
|
threadStorage = Storage(dbLocation)
|
|
if self.registryValue('general.globalstats'):
|
|
report = threadStorage.getReportById(num)
|
|
else:
|
|
report = threadStorage.getReportById(num, channel)
|
|
|
|
if report:
|
|
threadStorage.removeReport(report['id'])
|
|
irc.reply('Report %d removed!' % report['id'])
|
|
self.logger.doLog(irc, channel, "%s removed report# %i, for question #%i text was %s" % (msg.nick, report['id'], report['question_num'], report['report_text']))
|
|
else:
|
|
if self.registryValue('general.globalstats'):
|
|
irc.error('Unable to find report #{0}.'.format(num))
|
|
else:
|
|
irc.error('Unable to find report #{0} in {1}.'.format(num, channel))
|
|
rmreport = wrap(rmreport, ['channel', 'int'])
|
|
|
|
def rmnew(self, irc, msg, arg, channel, num):
|
|
"""[<channel>] <int>
|
|
Deny a new question.
|
|
Channel is only required when using the command outside of a channel.
|
|
"""
|
|
hostmask = msg.prefix
|
|
if self.isTriviaMod(hostmask, channel) == False:
|
|
irc.reply('You must be at least a TriviaMod in {0} to use this command.'.format(channel))
|
|
return
|
|
|
|
dbLocation = self.registryValue('admin.db')
|
|
threadStorage = Storage(dbLocation)
|
|
if self.registryValue('general.globalstats'):
|
|
q = threadStorage.getTemporaryQuestionById(num)
|
|
else:
|
|
q = threadStorage.getTemporaryQuestionById(num, channel)
|
|
|
|
if q:
|
|
threadStorage.removeTemporaryQuestion(q['id'])
|
|
irc.reply('Temp question #%d removed!' % q['id'])
|
|
self.logger.doLog(irc, channel, "%s removed new question #%i, '%s'" % (msg.nick, q['id'], q['question']))
|
|
else:
|
|
if self.registryValue('general.globalstats'):
|
|
irc.error('Unable to find new question #{0}.'.format(num))
|
|
else:
|
|
irc.error('Unable to find new question #{0} in {1}.'.format(num, channel))
|
|
rmnew = wrap(rmnew, ['channel', 'int'])
|
|
|
|
def repeat(self, irc, msg, arg, channel):
|
|
"""
|
|
Repeat the current question.
|
|
"""
|
|
game = self.getGame(irc, channel)
|
|
|
|
# Sanity checks
|
|
# 1. Is trivia running?
|
|
# 2. Is a question being asked?
|
|
if game is None or game.active == False:
|
|
self.reply(irc, msg, 'Trivia is not currently running.')
|
|
elif game.state != 'in-question':
|
|
self.reply(irc, msg, 'No question is currently being asked.')
|
|
else:
|
|
game.repeatQuestion()
|
|
irc.noReply()
|
|
repeat = wrap(repeat, ['onlyInChannel'])
|
|
|
|
def report(self, irc, msg, arg, user, channel, round, text):
|
|
"""[channel] <round> <report text>
|
|
Provide a report for a bad question. Be sure to include the round number and the problem(s).
|
|
To edit the question, input a regex substitution for the report text. To delete the
|
|
question, input 'delete' for the report text. Any other report text will be treated as a
|
|
regular report.
|
|
Channel is a optional parameter which is only needed when reporting outside of the channel
|
|
"""
|
|
username = self.getUsername(msg.nick, msg.prefix)
|
|
channelCanonical = ircutils.toLower(channel)
|
|
game = self.getGame(irc, channel)
|
|
if game and text[:2] == 's/' and game.numAsked == round and game.state != 'no-question':
|
|
irc.reply('Sorry, you must wait until the current question is over to make a regex report.')
|
|
return
|
|
|
|
dbLocation = self.registryValue('admin.db')
|
|
threadStorage = Storage(dbLocation)
|
|
question = threadStorage.getQuestionByRound(round, channel)
|
|
if question:
|
|
if text[:2] == 's/': # Regex substitution
|
|
regex = text[2:].split('/')
|
|
if len(regex) > 1:
|
|
threadStorage.updateUser(username, 1, 0)
|
|
pattern = regex[0]
|
|
repl = regex[1]
|
|
try:
|
|
newQuestionText = re.sub(pattern, repl, question['question'])
|
|
except:
|
|
irc.error('Unable to process this regex substitution.')
|
|
return
|
|
|
|
if newQuestionText == question['question']: # Ignore if no substitutions made
|
|
irc.error('This regex substitution expression does not change the original question.')
|
|
else:
|
|
threadStorage.insertEdit(question['id'], newQuestionText, username, channel)
|
|
irc.reply('Regex substitution detected: Question edited!')
|
|
irc.sendMsg(ircmsgs.notice(username, 'NEW: %s' % (newQuestionText)))
|
|
irc.sendMsg(ircmsgs.notice(username, 'OLD: %s' % (question['question'])))
|
|
self.logger.doLog(irc, channel, "%s edited question #%i, NEW: '%s', OLD: '%s'" % (msg.nick, question['id'], newQuestionText, question['question']))
|
|
else: # Incomplete expression
|
|
irc.error('Incomplete regex substitution expression.')
|
|
elif str.lower(utils.str.normalizeWhitespace(text))[:6] == 'delete': # Delete
|
|
if not threadStorage.questionIdExists(question['id']):
|
|
irc.error('That question does not exist.')
|
|
elif threadStorage.isQuestionDeleted(question['id']):
|
|
irc.error('That question is already deleted.')
|
|
elif threadStorage.isQuestionPendingDeletion(question['id']):
|
|
irc.error('That question is already pending deletion.')
|
|
else:
|
|
reason = utils.str.normalizeWhitespace(text)[6:]
|
|
reason = utils.str.normalizeWhitespace(reason)
|
|
irc.reply('Marked question for deletion.')
|
|
self.logger.doLog(irc, channel, "%s marked question #%i for deletion" % (msg.nick, question['id']))
|
|
threadStorage.insertDelete(username, channel, question['id'], reason)
|
|
else: # Regular report
|
|
threadStorage.updateUser(username, 0, 0, 1)
|
|
threadStorage.insertReport(channel, username, text, question['id'])
|
|
self.logger.doLog(irc, channel, "%s reported question #%i, Text: '%s'" % (msg.nick, question['id'], text))
|
|
irc.reply('Your report has been submitted!')
|
|
else:
|
|
irc.error('Sorry, round %d could not be found in the database.' % (round))
|
|
report = wrap(report, ['user', 'channel', 'int', 'text'])
|
|
|
|
def restorequestion(self, irc, msg, arg, channel, questionNum):
|
|
"""[<channel>] <Question num>
|
|
Restore a deleted question.
|
|
Channel is only required when using the command outside of a channel.
|
|
"""
|
|
hostmask = msg.prefix
|
|
if self.isTriviaMod(hostmask, channel) == False:
|
|
irc.reply('You must be at least a TriviaMod in {0} to use this command.'.format(channel))
|
|
return
|
|
|
|
username = msg.nick
|
|
dbLocation = self.registryValue('admin.db')
|
|
threadStorage = Storage(dbLocation)
|
|
if not threadStorage.questionIdExists(questionNum):
|
|
irc.error('That question does not exist.')
|
|
return
|
|
if not threadStorage.isQuestionDeleted(questionNum):
|
|
irc.error('That question was not deleted.')
|
|
return
|
|
threadStorage.restoreQuestion(questionNum)
|
|
irc.reply('Question %d restored!' % questionNum)
|
|
self.logger.doLog(irc, channel, "%s restored question #%i" % (username, questionNum))
|
|
restorequestion = wrap(restorequestion, ['channel', 'int'])
|
|
|
|
def skip(self, irc, msg, arg, channel):
|
|
"""
|
|
Skip the current question and start the next. Rate-limited. Requires a certain percentage of active players to skip.
|
|
"""
|
|
username = self.getUsername(msg.nick, msg.prefix)
|
|
usernameCanonical = ircutils.toLower(username)
|
|
|
|
dbLocation = self.registryValue('admin.db')
|
|
threadStorage = Storage(dbLocation)
|
|
timeSeconds = self.registryValue('skip.skipActiveTime', channel)
|
|
totalActive = threadStorage.getNumUserActiveIn(channel, timeSeconds)
|
|
channelCanonical = ircutils.toLower(channel)
|
|
game = self.getGame(irc, channel)
|
|
|
|
# Sanity checks
|
|
if game is None or game.active == False:
|
|
self.reply(irc, msg, 'Trivia is not currently running.')
|
|
elif game.state != 'in-question':
|
|
self.reply(irc, msg, 'No question is currently being asked.')
|
|
elif not threadStorage.wasUserActiveIn(username, channel, timeSeconds):
|
|
self.reply(irc, msg, 'Only users who have answered a question in the last %s seconds can vote to skip.' % (timeSeconds))
|
|
elif usernameCanonical in game.skipList:
|
|
self.reply(irc, msg, 'You can only vote to skip once.')
|
|
else:
|
|
# Ensure the game's skip timeout is set? and then check the user
|
|
skipSeconds = self.registryValue('skip.skipTime', channel)
|
|
game.skipTimeoutList.setTimeout(skipSeconds)
|
|
if game.skipTimeoutList.has(usernameCanonical):
|
|
self.reply(irc, msg, 'You must wait %d seconds to be able to skip again.' % (game.skipTimeoutList.getTimeLeft(usernameCanonical)), notice=True)
|
|
return
|
|
|
|
# Update skip count
|
|
game.skipList.append(usernameCanonical)
|
|
game.skipTimeoutList.append(usernameCanonical)
|
|
self.reply(irc, msg, '%s voted to skip this question.' % username, (len(game.skipList), totalActive), prefixNick=False)
|
|
skipPercent = len(game.skipList)/(totalActive*1.0)
|
|
|
|
# Check if skip threshold has been reached
|
|
if skipPercent >= self.registryValue('skip.skipThreshold', channel):
|
|
game.removeEvent()
|
|
self.reply(irc, msg, 'Skipped question! (%d of %d voted)' % (len(game.skipList), totalActive), prefixNick=False)
|
|
game.nextQuestion()
|
|
skip = wrap(skip, ['onlyInChannel'])
|
|
|
|
def stats(self, irc, msg, arg, channel, username):
|
|
""" [<channel>] <username>
|
|
Show a player's rank, score & questions asked for day, month, and year.
|
|
Channel is only required when using the command outside of a channel.
|
|
"""
|
|
dbLocation = self.registryValue('admin.db')
|
|
threadStorage = Storage(dbLocation)
|
|
if self.registryValue('general.globalStats'):
|
|
stat = threadStorage.getUserStat(username, None)
|
|
rank = threadStorage.getUserRank(username, None)
|
|
else:
|
|
stat = threadStorage.getUserStat(username, channel)
|
|
rank = threadStorage.getUserRank(username, channel)
|
|
|
|
if not stat:
|
|
irc.reply("User not found in database.")
|
|
else:
|
|
hasPoints = False
|
|
infoList = ['%s\'s Stats: Points (answers)' % (self.addZeroWidthSpace(stat['username']))]
|
|
if rank['day'] is not None and stat['points_day'] is not None and stat['answer_day'] is not None:
|
|
if rank['day'] > 0 or stat['points_day'] > 0 or stat['answer_day'] > 0:
|
|
hasPoints = True
|
|
infoList.append(' \x02Today:\x02 #%d %d (%d)' % (rank['day'], stat['points_day'], stat['answer_day']))
|
|
if rank['week'] is not None and stat['points_week'] is not None and stat['answer_week'] is not None:
|
|
if rank['week'] > 0 or stat['points_week'] > 0 or stat['answer_week'] > 0:
|
|
hasPoints = True
|
|
infoList.append(' \x02This Week:\x02 #%d %d (%d)' % (rank['week'], stat['points_week'], stat['answer_week']))
|
|
if rank['month'] is not None and stat['points_month'] is not None and stat['answer_month'] is not None:
|
|
if rank['month'] > 0 or stat['points_month'] > 0 or stat['answer_month'] > 0:
|
|
hasPoints = True
|
|
infoList.append(' \x02This Month:\x02 #%d %d (%d)' % (rank['month'], stat['points_month'], stat['answer_month']))
|
|
if rank['year'] is not None and stat['points_year'] is not None and stat['answer_year'] is not None:
|
|
if rank['year'] > 0 or stat['points_year'] > 0 or stat['answer_year'] > 0:
|
|
hasPoints = True
|
|
infoList.append(' \x02This Year:\x02 #%d %d (%d)' % (rank['year'], stat['points_year'], stat['answer_year']))
|
|
if rank['total'] is not None and stat['points_total'] is not None and stat['answer_total'] is not None:
|
|
if rank['total'] > 0 or stat['points_total'] > 0 or stat['answer_total'] > 0:
|
|
hasPoints = True
|
|
infoList.append(' \x02All Time:\x02 #%d %d (%d)' % (rank['total'], stat['points_total'], stat['answer_total']))
|
|
if not hasPoints:
|
|
infoList = ['%s: %s does not have any points.' % (msg.nick, username)]
|
|
infoText = ''.join(infoList)
|
|
self.reply(irc, msg, infoText, prefixNick=False)
|
|
stats = wrap(stats, ['channel', 'nick'])
|
|
|
|
def showdelete(self, irc, msg, arg, channel, num):
|
|
"""[<channel>] [<temp question #>]
|
|
Show a deleteion request pending approval.
|
|
Channel is only required when using the command outside of a channel.
|
|
"""
|
|
hostmask = msg.prefix
|
|
if self.isTriviaMod(hostmask, channel) == False:
|
|
irc.reply('You must be at least a TriviaMod in {0} to use this command.'.format(channel))
|
|
return
|
|
|
|
if num is not None:
|
|
dbLocation = self.registryValue('admin.db')
|
|
threadStorage = Storage(dbLocation)
|
|
if self.registryValue('general.globalstats'):
|
|
delete = threadStorage.getDeleteById(num)
|
|
else:
|
|
delete = threadStorage.getDeleteById(num, channel)
|
|
|
|
if delete:
|
|
question = threadStorage.getQuestionById(delete['line_num'])
|
|
questionText = question['question'] if question else 'Question not found'
|
|
irc.reply('Delete #%d, by %s Question #%d: %s, Reason: %s'%(delete['id'], delete['username'], delete['line_num'], questionText, delete['reason']))
|
|
else:
|
|
if self.registryValue('general.globalstats'):
|
|
irc.error('Unable to find delete #{0}.'.format(num))
|
|
else:
|
|
irc.error('Unable to find delete #{0} in {1}.'.format(num, channel))
|
|
else:
|
|
self.listdeletes(irc, msg, [channel])
|
|
showdelete = wrap(showdelete, ['channel', optional('int')])
|
|
|
|
def showquestion(self, irc, msg, arg, user, channel, num):
|
|
"""[<channel>] <num>
|
|
Search question database for question at line number.
|
|
Channel is only necessary when editing from outside of the channel.
|
|
"""
|
|
dbLocation = self.registryValue('admin.db')
|
|
threadStorage = Storage(dbLocation)
|
|
question = threadStorage.getQuestionById(num)
|
|
if question:
|
|
if question['deleted'] == 1:
|
|
irc.reply('Info: This question is currently deleted.')
|
|
irc.reply('Question #%d: %s' % (num, question['question']))
|
|
else:
|
|
irc.error('Question not found')
|
|
showquestion = wrap(showquestion, ['user', 'channel', 'int'])
|
|
|
|
def showround(self, irc, msg, arg, user, channel, round):
|
|
"""[<channel>] <round>
|
|
Show what question was asked during gameplay.
|
|
Channel is only necessary when editing from outside of the channel.
|
|
"""
|
|
game = self.getGame(irc, channel)
|
|
if game and round == game.numAsked and game.state != 'no-question':
|
|
irc.error('The current question can\'t be displayed until it is over.')
|
|
else:
|
|
dbLocation = self.registryValue('admin.db')
|
|
threadStorage = Storage(dbLocation)
|
|
question = threadStorage.getQuestionByRound(round, channel)
|
|
if question:
|
|
irc.reply('Round %d: Question #%d: %s' % (round, question['id'], question['question']))
|
|
else:
|
|
irc.error('Round not found')
|
|
showround = wrap(showround, ['user', 'channel', 'int'])
|
|
|
|
def showreport(self, irc, msg, arg, user, channel, num):
|
|
"""[<channel>] [<report num>]
|
|
Shows report information, if number is provided one record is shown, otherwise the last 3 are.
|
|
Channel is only necessary when editing from outside of the channel.
|
|
"""
|
|
if num is not None:
|
|
dbLocation = self.registryValue('admin.db')
|
|
threadStorage = Storage(dbLocation)
|
|
if self.registryValue('general.globalstats'):
|
|
report = threadStorage.getReportById(num)
|
|
else:
|
|
report = threadStorage.getReportById(num, channel)
|
|
|
|
if report:
|
|
irc.reply('Report #%d \'%s\' by %s on %s Q#%d '%(report['id'], report['report_text'], report['username'], report['channel'], report['question_num']))
|
|
question = threadStorage.getQuestionById(report['question_num'])
|
|
if question:
|
|
irc.reply('Question #%d: %s' % (question['id'], question['question']))
|
|
else:
|
|
irc.error('Question could not be found.')
|
|
else:
|
|
if self.registryValue('general.globalstats'):
|
|
irc.reply('Unable to find report #{0}.'.format(num))
|
|
else:
|
|
irc.reply('Unable to find report #{0} in {1}.'.format(num, channel))
|
|
else:
|
|
self.listreports(irc, msg, [channel])
|
|
showreport = wrap(showreport, ['user', 'channel', optional('int')])
|
|
|
|
def showedit(self, irc, msg, arg, channel, num):
|
|
"""[<channel>] [<edit num>]
|
|
Show top 3 edits, or provide edit num to view one.
|
|
Channel is only required when using the command outside of a channel.
|
|
"""
|
|
hostmask = msg.prefix
|
|
if self.isTriviaMod(hostmask, channel) == False:
|
|
irc.reply('You must be at least a TriviaMod in {0} to use this command.'.format(channel))
|
|
return
|
|
|
|
if num is not None:
|
|
dbLocation = self.registryValue('admin.db')
|
|
threadStorage = Storage(dbLocation)
|
|
if self.registryValue('general.globalstats'):
|
|
edit = threadStorage.getEditById(num)
|
|
else:
|
|
edit = threadStorage.getEditById(num, channel)
|
|
|
|
if edit:
|
|
question = threadStorage.getQuestionById(edit['question_id'])
|
|
irc.reply('Edit #%d by %s, Question #%d'%(edit['id'], edit['username'], edit['question_id']))
|
|
irc.reply('NEW:%s' %(edit['question']))
|
|
if question:
|
|
irc.reply('OLD:%s' % (question['question']))
|
|
else:
|
|
irc.error('Question could not be found for this edit')
|
|
else:
|
|
if self.registryValue('general.globalstats'):
|
|
irc.error('Unable to find edit #{0}.'.format(num))
|
|
else:
|
|
irc.error('Unable to find edit #{0} in {1}.'.format(num, channel))
|
|
else:
|
|
self.listedits(irc, msg, [channel])
|
|
showedit = wrap(showedit, ['channel', optional('int')])
|
|
|
|
def shownew(self, irc, msg, arg, channel, num):
|
|
"""[<channel>] [<temp question #>]
|
|
Show questions awaiting approval
|
|
Channel is only required when using the command outside of a channel.
|
|
"""
|
|
hostmask = msg.prefix
|
|
if self.isTriviaMod(hostmask, channel) == False:
|
|
irc.reply('You must be at least a TriviaMod in {0} to use this command.'.format(channel))
|
|
return
|
|
|
|
if num is not None:
|
|
dbLocation = self.registryValue('admin.db')
|
|
threadStorage = Storage(dbLocation)
|
|
if self.registryValue('general.globalstats'):
|
|
q = threadStorage.getTemporaryQuestionById(num)
|
|
else:
|
|
q = threadStorage.getTemporaryQuestionById(num, channel)
|
|
|
|
if q:
|
|
irc.reply('Temp Q #%d by %s: %s'%(q['id'], q['username'], q['question']))
|
|
else:
|
|
if self.registryValue('general.globalstats'):
|
|
irc.error('Unable to find new question #{0}.'.format(num))
|
|
else:
|
|
irc.error('Unable to find new question #{0} in {1}.'.format(num, channel))
|
|
else:
|
|
self.listnew(irc, msg, [channel])
|
|
shownew = wrap(shownew, ['channel', optional('int')])
|
|
|
|
def start(self, irc, msg, args, channel):
|
|
"""
|
|
Begins a round of Trivia inside the current channel.
|
|
"""
|
|
game = self.getGame(irc, channel)
|
|
|
|
# Sanity checks
|
|
# 1. Is trivia running?
|
|
# 2. Is the previous trivia session still in the shutdown phase?
|
|
# 3. Is there a stop pending?
|
|
if game is None:
|
|
# create a new game
|
|
self.reply(irc, msg, 'Another epic round of trivia is about to begin.', prefixNick=False)
|
|
self.createGame(irc, channel)
|
|
elif game.active == False:
|
|
self.reply(irc, msg, 'Please wait for the previous game instance to stop.')
|
|
elif game.stopPending == True:
|
|
game.stopPending = False
|
|
self.reply(irc, msg, 'Pending stop aborted', prefixNick=False)
|
|
else:
|
|
self.reply(irc, msg, 'Trivia has already been started.')
|
|
start = wrap(start, ['onlyInChannel'])
|
|
|
|
def stop(self, irc, msg, args, user, channel):
|
|
"""
|
|
Stops the current Trivia round.
|
|
"""
|
|
game = self.getGame(irc, channel)
|
|
|
|
# Sanity checks
|
|
# 1. Is trivia running?
|
|
# 2. Is a question being asked?
|
|
# 2.1 Is a stop pending?
|
|
if game is None or game.active == False:
|
|
self.reply(irc, msg, 'Game is already stopped.')
|
|
elif game.state != 'no-question':
|
|
if game.stopPending == True:
|
|
self.reply(irc, msg, 'Trivia is already pending stop.')
|
|
else:
|
|
game.stopPending = True
|
|
self.reply(irc, msg, 'Trivia will now stop after this question.', prefixNick=False)
|
|
else:
|
|
game.stop()
|
|
irc.noReply()
|
|
stop = wrap(stop, ['user', 'onlyInChannel'])
|
|
|
|
def time(self, irc, msg, arg):
|
|
"""
|
|
Figure out what time/day it is on the server.
|
|
"""
|
|
timeStr = time.asctime(time.localtime())
|
|
self.reply(irc, msg, 'The current server time appears to be {0}'.format(timeStr), prefixNick=False)
|
|
time = wrap(time)
|
|
|
|
def top(self, irc, msg, arg, channel, num):
|
|
"""[<channel>] [<number>]
|
|
Displays the top scores of all time.
|
|
Parameter is optional, display up to that number. (eg 20 - display 11-20)
|
|
Channel is only required when using the command outside of a channel.
|
|
"""
|
|
if num is not None:
|
|
num = max(num, 10)
|
|
else:
|
|
num = 10
|
|
offset = num-9
|
|
dbLocation = self.registryValue('admin.db')
|
|
threadStorage = Storage(dbLocation)
|
|
if self.registryValue('general.globalStats'):
|
|
tops = threadStorage.viewAllTimeTop10(None, num)
|
|
else:
|
|
tops = threadStorage.viewAllTimeTop10(channel, num)
|
|
|
|
topsList = ['All Time Top {0}-{1} Players: '.format(offset, num)]
|
|
if tops:
|
|
for i in range(len(tops)):
|
|
topsList.append('\x02 #%d:\x02 %s %d ' % ((i+offset) , self.addZeroWidthSpace(tops[i]['username']), tops[i]['points']))
|
|
else:
|
|
topsList.append('No players')
|
|
topsText = ''.join(topsList)
|
|
self.reply(irc, msg, topsText, prefixNick=False)
|
|
top = wrap(top, ['channel', optional('int')])
|
|
|
|
def transferpoints(self, irc, msg, arg, channel, userfrom, userto):
|
|
"""[<channel>] <userfrom> <userto>
|
|
Transfers all points and records from one user to another.
|
|
Channel is only required when using the command outside of a channel.
|
|
"""
|
|
hostmask = msg.prefix
|
|
if self.isTriviaAdmin(hostmask, channel) == False:
|
|
irc.reply('You must be a TriviaAdmin in {0} to use this command.'.format(channel))
|
|
return
|
|
|
|
userfrom = userfrom
|
|
userto = userto
|
|
dbLocation = self.registryValue('admin.db')
|
|
threadStorage = Storage(dbLocation)
|
|
threadStorage.transferUserLogs(userfrom, userto, channel)
|
|
irc.reply('Transferred all records from {0} to {1} in {2}.'.format(userfrom, userto, channel))
|
|
self.logger.doLog(irc, channel, '{0} transferred records from {1} to {2} in {3}.'.format(msg.nick, userfrom, userto, channel))
|
|
transferpoints = wrap(transferpoints, ['channel', 'nick', 'nick'])
|
|
|
|
def week(self, irc, msg, arg, channel, num):
|
|
"""[<channel>] [<number>]
|
|
Displays the top scores of the week.
|
|
Parameter is optional, display up to that number. (eg 20 - display 11-20)
|
|
Channel is only required when using the command outside of a channel.
|
|
"""
|
|
if num is not None:
|
|
num = max(num, 10)
|
|
else:
|
|
num = 10
|
|
offset = num-9
|
|
dbLocation = self.registryValue('admin.db')
|
|
threadStorage = Storage(dbLocation)
|
|
if self.registryValue('general.globalStats'):
|
|
tops = threadStorage.viewWeekTop10(None, num)
|
|
else:
|
|
tops = threadStorage.viewWeekTop10(channel, num)
|
|
|
|
topsList = ['This Week\'s Top {0}-{1} Players: '.format(offset, num)]
|
|
if tops:
|
|
for i in range(len(tops)):
|
|
topsList.append('\x02 #%d:\x02 %s %d ' % ((i+offset) , self.addZeroWidthSpace(tops[i]['username']), tops[i]['points']))
|
|
else:
|
|
topsList.append('No players')
|
|
topsText = ''.join(topsList)
|
|
self.reply(irc, msg, topsText, prefixNick=False)
|
|
week = wrap(week, ['channel', optional('int')])
|
|
|
|
def year(self, irc, msg, arg, channel, num):
|
|
"""[<channel>] [<number>]
|
|
Displays the top scores of the year.
|
|
Parameter is optional, display up to that number. (eg 20 - display 11-20)
|
|
Channel is only required when using the command outside of a channel.
|
|
"""
|
|
if num is not None:
|
|
num = max(num, 10)
|
|
else:
|
|
num = 10
|
|
offset = num-9
|
|
dbLocation = self.registryValue('admin.db')
|
|
threadStorage = Storage(dbLocation)
|
|
if self.registryValue('general.globalStats'):
|
|
tops = threadStorage.viewYearTop10(None, num)
|
|
else:
|
|
tops = threadStorage.viewYearTop10(channel, num)
|
|
|
|
topsList = ['This Year\'s Top {0}-{1} Players: '.format(offset, num)]
|
|
if tops:
|
|
for i in range(len(tops)):
|
|
topsList.append('\x02 #%d:\x02 %s %d ' % ((i+offset) , self.addZeroWidthSpace(tops[i]['username']), tops[i]['points']))
|
|
else:
|
|
topsList.append('No players')
|
|
topsText = ''.join(topsList)
|
|
self.reply(irc, msg, topsText, prefixNick=False)
|
|
year = wrap(year, ['channel', optional('int')])
|
|
|
|
|
|
Class = TriviaTime
|
|
# vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79:
|