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

181 lines
7.5 KiB
Python

from direct.directnotify.DirectNotifyGlobal import directNotify
from direct.showbase.DirectObject import DirectObject
from direct.showbase.PythonUtil import formatTimeExact
from panda3d.core import *
Settings = ScratchPad(
DetectWindow=ConfigVariableDouble('code-redemption-spam-detect-window', 30.).getValue(), # minutes
DetectThreshold=ConfigVariableInt('code-redemption-spam-detect-threshold', 10).getValue(),
FirstPenalty=ConfigVariableDouble('code-redemption-spam-first-penalty', .5).getValue(), # minutes
PenaltyMultiplier=ConfigVariableDouble('code-redemption-spam-penalty-multiplier', 2.).getValue(),
MaxPenaltyDays=ConfigVariableDouble('code-redemption-spam-max-penalty-days', 2.).getValue(),
PenaltyResetDays=ConfigVariableDouble('code-redemption-penalty-reset-days', 7.).getValue(),
)
class TTCodeRedemptionSpamDetector:
notify = directNotify.newCategory('TTCodeRedemptionSpamDetector')
def __init__(self):
self._avId2tracker = {}
if __dev__:
#self._tester = TTCRSDTester(self)
pass
self._cullTask = taskMgr.doMethodLater(10 * 60, self._cullTrackers, uniqueName('cullCodeSpamTrackers'))
def destroy(self):
if __dev__:
#self._tester.destroy()
self._tester = None
def codeSubmitted(self, avId):
if avId not in self._avId2tracker:
self._avId2tracker[avId] = TTCRSDTracker(avId)
self._avId2tracker[avId].codeSubmitted()
def avIsBlocked(self, avId):
tracker = self._avId2tracker.get(avId)
if tracker:
return tracker.avIsBlocked()
return False
def _cullTrackers(self, task=None):
# remove records for avIds that have gone long enough without spamming
avIds = list(self._avId2tracker.keys())
for avId in avIds:
tracker = self._avId2tracker.get(avId)
if tracker.isExpired():
self.notify.debug('culling code redemption spam tracker for %s' % avId)
self._avId2tracker.pop(avId)
return task.again
class TTCRSDTracker:
notify = directNotify.newCategory('TTCodeRedemptionSpamDetector')
def __init__(self, avId):
self._avId = avId
self._timestamps = []
self._lastTimestamp = None
self._penaltyDuration = 0
self._penaltyUntil = 0
def codeSubmitted(self):
now = globalClock.getRealTime()
self.notify.debug('codeSubmitted by %s @ %s' % (self._avId, now))
if self._penaltyActive():
return
self._timestamps.append(now)
self._lastTimestamp = now
self.update()
def isExpired(self):
if self._lastTimestamp is None:
return True
now = globalClock.getRealTime()
# if they've gone for X days without spamming, we can wipe that toon's record
amnestyDelay = Settings.PenaltyResetDays * 24 * 60 * 60
return now > (self._lastTimestamp + amnestyDelay)
def update(self):
self._trimTimestamps()
if (not self._penaltyActive()) and self._overThreshold():
if self._penaltyDuration == 0:
self._penaltyDuration = Settings.FirstPenalty * 60 # seconds/min
else:
self._penaltyDuration = self._penaltyDuration * Settings.PenaltyMultiplier
MaxPenaltySecs = Settings.MaxPenaltyDays * 24 * 60 * 60
if self._penaltyDuration > MaxPenaltySecs:
self._penaltyDuration = MaxPenaltySecs
self._penaltyUntil = globalClock.getRealTime() + self._penaltyDuration
self._timestamps = self._timestamps[Settings.DetectThreshold:]
durationStr = formatTimeExact(self._penaltyDuration)
self.notify.info('time penalty for %s: %s' % (self._avId, durationStr))
def avIsBlocked(self):
self.update()
return self._penaltyActive()
def _trimTimestamps(self):
now = globalClock.getRealTime()
cutoff = now - (Settings.DetectWindow * 60) # seconds/min
while len(self._timestamps):
if self._timestamps[0] < cutoff:
self._timestamps = self._timestamps[1:]
else:
break
def _penaltyActive(self):
return globalClock.getRealTime() < self._penaltyUntil
def _overThreshold(self):
return len(self._timestamps) > Settings.DetectThreshold
if __dev__:
class TTCRSDTester(DirectObject):
notify = directNotify.newCategory('TTCodeRedemptionSpamDetector')
def __init__(self, detector):
self._detector = detector
self._idGen = SerialNumGen()
self.notify.info('starting tests...')
self._thresholdTest()
self._timeoutTest()
def destroy(self):
self._detector = None
def _thresholdTest(self):
avId = next(self._idGen)
for i in range(Settings.DetectThreshold+1):
self._detector.codeSubmitted(avId)
if i < Settings.DetectThreshold:
assert not self._detector.avIsBlocked(avId)
else:
assert self._detector.avIsBlocked(avId)
self.notify.info('threshold test passed.')
def _timeoutTest(self):
avId = next(self._idGen)
for i in range(Settings.DetectThreshold+1):
self._detector.codeSubmitted(avId)
assert self._detector.avIsBlocked(avId)
self._timeoutTestStartT = globalClock.getRealTime()
penaltyDuration = Settings.FirstPenalty * 60
self._timeoutTestEventT = penaltyDuration
self.doMethodLater(Settings.FirstPenalty * 60 * .5, Functor(self._timeoutEarlyTest, avId),
uniqueName('timeoutEarlyTest'))
self.doMethodLater(Settings.FirstPenalty * 60 * 10, Functor(self._timeoutLateTest, avId),
uniqueName('timeoutLateTest'))
def _timeoutEarlyTest(self, avId, task=None):
# only do this test if we didn't chug
if (globalClock.getRealTime() - self._timeoutTestStartT) < (self._timeoutTestEventT * .9):
assert self._detector.avIsBlocked(avId)
return task.done
def _timeoutLateTest(self, avId, task=None):
assert not self._detector.avIsBlocked(avId)
for i in range(Settings.DetectThreshold+1):
self._detector.codeSubmitted(avId)
assert self._detector.avIsBlocked(avId)
self._timeoutLateTestStartT = globalClock.getRealTime()
penaltyDuration = Settings.PenaltyMultiplier * Settings.FirstPenalty * 60
self._timeoutLateTestEventT = penaltyDuration
self.doMethodLater(penaltyDuration * .5, Functor(self._timeoutSecondEarlyTest, avId),
uniqueName('timeoutSecondEarlyTest'))
self.doMethodLater(penaltyDuration * 1.5, Functor(self._timeoutSecondLateTest, avId),
uniqueName('timeoutSecondLateTest'))
return task.done
def _timeoutSecondEarlyTest(self, avId, task=None):
# only do this test if we didn't chug
if (globalClock.getRealTime() - self._timeoutLateTestStartT) < (self._timeoutLateTestEventT * .9):
assert self._detector.avIsBlocked(avId)
return task.done
def _timeoutSecondLateTest(self, avId, task=None):
assert not self._detector.avIsBlocked(avId)
self.notify.info('timeout test passed.')
return task.done