278 lines
12 KiB
Python
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')
|