mirror of
https://github.com/oddluck/limnoria-plugins.git
synced 2025-04-26 04:51:09 -05:00
5540 lines
195 KiB
Python
5540 lines
195 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 * 0.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 * 0.25 * (self.streak - 1)
|
|
streakBonus = int(min(streakBonus, points * 0.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 * 0.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) * 0.25) + 1), 4)
|
|
elif self.hintsCounter == 3:
|
|
divider = min(int((len(ans) * 0.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:
|