historical/toontown-classic.git/toontown/coderedemption/TTCodeRedemptionMgrUD.py
2024-01-16 11:20:27 -06:00

278 lines
12 KiB
Python

from direct.distributed.DistributedObjectGlobalUD import DistributedObjectGlobalUD
from direct.directnotify.DirectNotifyGlobal import directNotify
from direct.task import Task
from otp.distributed import OtpDoGlobals
from toontown.coderedemption.TTCodeRedemptionDB import TTCodeRedemptionDB
from toontown.coderedemption.TTCodeDict import TTCodeDict
from toontown.coderedemption import TTCodeRedemptionConsts
from toontown.coderedemption import TTCodeRedemptionSpamDetector
from panda3d.core import *
import traceback
class TTCodeRedemptionMgrUD(DistributedObjectGlobalUD):
notify = directNotify.newCategory('TTCodeRedemptionMgrUD')
Disabled = ConfigVariableBool('disable-code-redemption', False).getValue()
if __dev__:
TestRedemptionSpamAvIdMin = 6
TestRedemptionSpamAvIdMax = 9999999
TestRedemptions = (
# non-ascii
('\xff', (TTCodeRedemptionConsts.RedeemErrors.CodeDoesntExist, 0)),
# invalid characters
('.', (TTCodeRedemptionConsts.RedeemErrors.CodeDoesntExist, 0)),
)
DisabledTestRedemptions = (
('HWF', (TTCodeRedemptionConsts.RedeemErrors.SystemUnavailable, 0)),
(',', (TTCodeRedemptionConsts.RedeemErrors.SystemUnavailable, 0)),
)
# spam detection
TestSpamRedemptions = (([('!!!', (TTCodeRedemptionConsts.RedeemErrors.CodeDoesntExist, 0)), ] * TTCodeRedemptionSpamDetector.Settings.DetectThreshold) +
[('!!!', (TTCodeRedemptionConsts.RedeemErrors.TooManyAttempts, 0)), ]
)
def __init__(self, air):
DistributedObjectGlobalUD.__init__(self, air)
self._rewardSerialNumGen = SerialNumGen()
self._rewardContextTable = {}
self._redeemContextGen = SerialNumGen()
self._redeemContext2session = {}
self._db = TTCodeRedemptionDB(self.air)
if __debug__:
self._db.runTests()
self._createLotSerialGen = SerialNumGen()
self._createLotId2task = {}
self._randSampleContext2callback = {}
self._randSampleContextGen = SerialMaskedGen((1 << 32)-1)
self._spamDetector = TTCodeRedemptionSpamDetector.TTCodeRedemptionSpamDetector()
self._wantSpamDetect = ConfigVariableBool('want-code-redemption-spam-detect', True).getValue()
if __dev__:
self._testAvId = random.randrange(self.TestRedemptionSpamAvIdMin, self.TestRedemptionSpamAvIdMax)
self._avId2table = {self._testAvId: self.TestRedemptions,
}
self._disabledAvId2table = {self._testAvId: self.DisabledTestRedemptions,
}
if self._wantSpamDetect:
self._spamAvId = random.randrange(self.TestRedemptionSpamAvIdMin, self.TestRedemptionSpamAvIdMax)
self._avId2table[self._spamAvId] = self.TestSpamRedemptions
if __dev__:
def _sendTestRedemptions(self):
assert self.notify.debugCall()
for avId in self._avId2table.keys():
redemptions = self._avId2table[avId]
for i in range(len(redemptions)):
redemption = redemptions[i]
code, results = redemption
self.redeemCodeAiToUd(0, 0, i, code, avId, self._resolveTestRedemption)
def _sendDisabledTestRedemptions(self):
saved = TTCodeRedemptionMgrUD.Disabled
TTCodeRedemptionMgrUD.Disabled = True
for avId in self._disabledAvId2table.keys():
redemptions = self._disabledAvId2table[avId]
for i in range(len(redemptions)):
redemption = redemptions[i]
code, results = redemption
self.redeemCodeAiToUd(0, 0, i, code, avId, self._resolveDisabledTestRedemption)
TTCodeRedemptionMgrUD.Disabled = saved
def _resolveTestRedemption(self, serial, context, avId, result, awardMgrResult):
if avId in self._avId2table:
redemptions = self._avId2table.get(avId)
redemption = redemptions[context]
code, results = redemption
assert result == results[0]
assert awardMgrResult == results[1]
def _resolveDisabledTestRedemption(self, serial, context, avId, result, awardMgrResult):
if avId in self._disabledAvId2table:
redemptions = self._disabledAvId2table.get(avId)
redemption = redemptions[context]
code, results = redemption
assert result == results[0]
assert awardMgrResult == results[1]
def announceGenerate(self):
assert self.notify.debugCall()
DistributedObjectGlobalUD.announceGenerate(self)
if __dev__ and TTCodeRedemptionDB.DoSelfTest:
if not self.Disabled:
self._sendTestRedemptions()
self._sendDisabledTestRedemptions()
def delete(self):
for task in list(self._createLotId2task.values()):
self.removeTask(task)
self._createLotId2task = {}
def createLot(self, manualCode, numCodes, lotName, rewardType, rewardItemId, manualCodeStr, expirationDate):
assert self.notify.debugCall()
if manualCode:
self._db.createManualLot(lotName, manualCodeStr, rewardType, rewardItemId, expirationDate)
results = 'Check lot generation using CHECK_LOT_CODE'
else:
createLotId = self._createLotSerialGen.next()
gen = self._db.createLot(self._requestRandomSamples,
lotName, numCodes,
rewardType, rewardItemId,
expirationDate)
t = self.addTask(self._createLotTask, '%s-createLot-%s' % (self.__class__.__name__, createLotId))
t.createLotId = createLotId
t.gen = gen
self._createLotId2task[createLotId] = t
results = 'Code Generation Task is in queue with ID: %s. Check code generated using CHECK_LOT_CODE' % createLotId
return results
def _createLotTask(self, task):
for result in task.gen:
break
if result is True:
del self._createLotId2task[task.createLotId]
return Task.done
return Task.cont
def _requestRandomSamples(self, callback, numSamples):
assert self.notify.debugCall()
context = self._randSampleContextGen.next()
self._randSampleContext2callback[context] = callback
self.air.dispatchUpdateToGlobalDoId("NonRepeatableRandomSourceUD", "getRandomSamples",
OtpDoGlobals.OTP_DO_ID_TOONTOWN_NON_REPEATABLE_RANDOM_SOURCE,
[self.doId, 'TTCodeRedemptionMgr', context, numSamples])
def getRandomSamplesReply(self, context, samples):
assert self.notify.debugCall()
callback = self._randSampleContext2callback.pop(context)
callback(samples)
def _codeHasInvalidChars(self, code):
return not TTCodeDict.isLegalCode(code)
def _handleRedeemResult(self, context, page, body, replyTo, result, awardMgrResult):
assert self.notify.debugCall()
session = self._redeemContext2session.pop(context)
session.result = result
session.awardMgrResult = awardMgrResult
self._doRedeemResult(body, replyTo, session.avId, session.result, session.awardMgrResult,
session.values, session.errors)
self._reply(page, replyTo)
def redeemCodeAiToUd(self, serial, rmDoId, context, code, senderId, callback=None):
assert self.notify.debugCall()
avId = senderId
# context is supplied by the client and there are no invalid values for it
# code comes from the client and could be any string
try:
result = None
if self.Disabled:
result = TTCodeRedemptionConsts.RedeemErrors.SystemUnavailable
else:
while 1:
try:
code = str(code)
except UnicodeDecodeError as e:
# code is not utf-8-able
self.air.writeServerEvent('suspicious', avId, 'non-utf-8 code redemption: %s' % repr(code))
result = TTCodeRedemptionConsts.RedeemErrors.CodeDoesntExist
break
if self._codeHasInvalidChars(code):
# code has non-letter/digit/dash characters
result = TTCodeRedemptionConsts.RedeemErrors.CodeDoesntExist
break
break
if (result or (not self._db.codeExists(code))):
# check to make sure this avatar isn't submitting incorrect codes too often
self._spamDetector.codeSubmitted(senderId)
if self._wantSpamDetect and self._spamDetector.avIsBlocked(senderId):
self.air.writeServerEvent('suspicious', avId,
'too many invalid code redemption attempts, '
'submission rejected: %s' % (code))
result = TTCodeRedemptionConsts.RedeemErrors.TooManyAttempts
if result is not None:
awardMgrResult = 0
self._handleRedeemCodeAiToUdResult(callback, serial, rmDoId, context, avId, result, awardMgrResult)
else:
"""
'code' came from a client and therefore should be considered to be any potential string
(apart from any checks that have already been done), in particular strings intended
to cause trouble
"""
self._db.redeemCode(code, avId, self, Functor(
self._handleRedeemCodeAiToUdResult, callback, serial, rmDoId, context, avId, ))
except TTCodeRedemptionDB.TryAgainLater as e:
self._warnTryAgainLater(e)
except:
traceback.print_exc()
def _handleRedeemCodeAiToUdResult(self, callback, serial, rmDoId, context, avId, result, awardMgrResult):
assert self.notify.debugCall()
if callback:
callback(serial, context, avId, result, awardMgrResult)
else:
self.air.sendUpdateToDoId('TTCodeRedemptionMgr',
'redeemCodeResultUdToAi',
rmDoId,
[serial, context, avId, result, awardMgrResult]
)
def redeemCode(self, code, avId, callback):
assert self.notify.debugCall()
# callback takes TTCodeRedemptionConsts.RedeemErrors value
return self._db.redeemCode(code, avId, self, callback)
def _giveReward(self, avId, rewardType, rewardItemId, callback):
assert self.notify.debugCall()
# callback takes result
context = self._rewardSerialNumGen.next()
self._rewardContextTable[context] = callback
self.air.dispatchUpdateToGlobalDoId(
"AwardManagerUD", "giveAwardToToon",
OtpDoGlobals.OTP_DO_ID_TOONTOWN_AWARD_MANAGER,
[context, self.doId, "TTCodeRedemptionMgrUD", avId, rewardType, rewardItemId, ])
def giveAwardToToonResult(self, context, result):
assert self.notify.debugCall()
callback = self._rewardContextTable.pop(context)
try:
callback(result)
except TTCodeRedemptionDB.TryAgainLater as e:
self._warnTryAgainLater(e)
def _warnTryAgainLater(self, exception):
# if we catch a TryAgainLater, drop this code submission on the floor. The AI
# will resubmit the code shortly
self.notify.warning('%s' % exception)
self.notify.warning('caught TryAgainLater exception from TTCodeRedemptionDB. Dropping request')