Beispiel #1
0
def sessionTokenCall(sessionToken, playerId, methodName, *args):
    playerSession = getPlayerSession(playerId)

    if playerSession is None:
        return getErrorResponse(NO_ACTIVE_SESSION_ERROR)

    if playerSession.method != 'token' or playerSession.token is None:
        return getErrorResponse(TOKEN_METHOD_ERROR);

    if sessionToken == playerSession.token:
        if playerSession.isExpired():
            deletePlayerSession(playerId)
            return getErrorResponse(SESSION_EXPIRED_ERROR)

        method = _getMethodFromName(methodName)
        if method:
            if args is None or len(args) == 0:
                return method(playerId)
            else:
                return method(playerId, *args)

        else:
            return getErrorResponse(UNKNOW_SERVICE_CALL_ERROR)
    else:
        return getErrorResponse(INVALID_SESSION_TOKEN_ERROR)
Beispiel #2
0
def reviewScore(playerId, score, adminMode=False):

    if adminMode:
        admin = getAdmin()
        try:
            admin.playerList.index(playerId)
        except ValueError:
            return getErrorResponse(ADMIN_ONLY)

    scoreValue = score['score']
    scoreTime = score['time']
    playerKey = Key.from_path('Player', playerId)
    reviewSession = ReviewSession.get_by_key_name('reviewSession',
                                                  parent=playerKey)

    if reviewSession is None:
        return {'result': {'message': 'nothing to review'}}

    scoreToReviewKey = ReviewSession.currentScoreToReview.get_value_for_datastore(
        reviewSession)
    # We are done with it
    reviewSession.delete()

    # The above could have potentially be put in a transaction but since there is only one player concerned, it should not matter

    def _increaseNumScoreReviewed():
        playerRecord = Record.get_by_key_name('record', parent=playerKey)
        playerRecord.numScoreReviewed += 1
        playerRecord.put()

    db.run_in_transaction(_increaseNumScoreReviewed)

    try:
        cheaters = db.run_in_transaction(_checkConflicts, scoreToReviewKey,
                                         scoreValue, scoreTime, playerId,
                                         adminMode)
    except TransactionFailedError:
        return getErrorResponse(CHECKCONFLICT_TRANSACTION_FAILURE, 0)

    if cheaters:

        def _cheaterUpdate(cheaterKey):
            cheaterRecord = Record.get_by_key_name('record', parent=cheaterKey)
            cheaterRecord.numCheat += 1
            cheaterRecord.put()

        for cheaterKey in cheaters:
            db.run_in_transaction(_cheaterUpdate,
                                  cheaterKey)  #TODO : check TRANSACTION ERROR

    return {'result': {'message': 'review submited'}}
Beispiel #3
0
def start(playerId):

    def _start():
        playerKey = Key.from_path('Player', playerId)

        rand = random.SystemRandom()
        seed = struct.pack("4L", rand.randint(1, MAX_INT32_VALUE), rand.randint(1, MAX_INT32_VALUE), rand.randint(1, MAX_INT32_VALUE), rand.randint(1, MAX_INT32_VALUE))

        playSession = PlaySession(key_name='playSession', seed=seed, version=config.currentGameMechanicVersion, seedDateTime=datetime.datetime.now(), parent=playerKey)
        playSession.put()

        today = datetime.date.today()
        playerRecord = Record.get_by_key_name('record', parent=playerKey)
        if playerRecord.lastDayPlayed != today:
            playerRecord.numDaysPlayed += 1
            playerRecord.lastDayPlayed = today
            playerRecord.put()

        seedList = struct.unpack("4L", playSession.seed)
        return {'result' : { 'seed' : seedList, 'version': playSession.version } }

    try:
        return db.run_in_transaction(_start)
    except TransactionFailedError:
        return getErrorResponse(START_TRANSACTION_FAILURE, 0)
Beispiel #4
0
def start(playerId):
    def _start():
        playerKey = Key.from_path('Player', playerId)

        rand = random.SystemRandom()
        seed = struct.pack("4L", rand.randint(1, MAX_INT32_VALUE),
                           rand.randint(1, MAX_INT32_VALUE),
                           rand.randint(1, MAX_INT32_VALUE),
                           rand.randint(1, MAX_INT32_VALUE))

        playSession = PlaySession(key_name='playSession',
                                  seed=seed,
                                  version=config.currentGameMechanicVersion,
                                  seedDateTime=datetime.datetime.now(),
                                  parent=playerKey)
        playSession.put()

        today = datetime.date.today()
        playerRecord = Record.get_by_key_name('record', parent=playerKey)
        if playerRecord.lastDayPlayed != today:
            playerRecord.numDaysPlayed += 1
            playerRecord.lastDayPlayed = today
            playerRecord.put()

        seedList = struct.unpack("4L", playSession.seed)
        return {'result': {'seed': seedList, 'version': playSession.version}}

    try:
        return db.run_in_transaction(_start)
    except TransactionFailedError:
        return getErrorResponse(START_TRANSACTION_FAILURE, 0)
Beispiel #5
0
    def _setScore():
        playSession.delete()

        verifiedScore = None
        verifiedScoreWrapper = VerifiedScoreWrapper.get_by_key_name(
            "verifiedScore", parent=playerKey)
        if verifiedScoreWrapper is not None:
            verifiedScore = verifiedScoreWrapper.verified

        if verifiedScore is None or scoreValue > verifiedScore.value or (
                scoreValue == verifiedScore.value
                and scoreTime < verifiedScore.time):
            pendingScore = PendingScore.get_by_key_name("pendingScore",
                                                        parent=playerKey)
            if pendingScore is not None:
                nonVerifiedScore = pendingScore.nonVerified
            else:
                nonVerifiedScore = None

            if nonVerifiedScore is None or scoreValue > nonVerifiedScore.value or (
                    scoreValue == nonVerifiedScore.value
                    and scoreTime < nonVerifiedScore.time):

                try:
                    proofBlob = db.Blob(proof)
                except Exception:
                    proofText = str(proof)
                    proofBlob = db.Blob(proofText)

                nonVerifiedScore = NonVerifiedScore(value=scoreValue,
                                                    time=scoreTime,
                                                    proof=proofBlob,
                                                    seed=seed,
                                                    version=version,
                                                    parent=playerKey)
                nonVerifiedScore.put()
                if pendingScore is None:
                    pendingScore = PendingScore(key_name='pendingScore',
                                                parent=playerKey,
                                                nonVerified=nonVerifiedScore)
                else:
                    conflicts = ReviewConflict.gql(
                        "WHERE ANCESTOR IS :score",
                        score=pendingScore.nonVerified.key()).fetch(
                            100)  # shoud not be more than 2
                    for conflict in conflicts:
                        conflict.delete()
                    pendingScore.nonVerified.delete()
                    pendingScore.nonVerified = nonVerifiedScore
                pendingScore.put()

                return {'result': {'message': 'success'}}
            else:
                pass  # TODO : are you trying to cheat?
        else:
            pass  # TODO : are you trying to cheat?

        return getErrorResponse(SCORE_TOO_SMALL)
Beispiel #6
0
def reviewScore(playerId, score, adminMode=False):

    if adminMode:
        admin = getAdmin()
        try:
            admin.playerList.index(playerId)
        except ValueError:
            return getErrorResponse(ADMIN_ONLY)

    scoreValue = score['score']
    scoreTime = score['time']
    playerKey = Key.from_path('Player', playerId)
    reviewSession = ReviewSession.get_by_key_name('reviewSession', parent=playerKey)

    if reviewSession is None:
        return {'result' : {'message' : 'nothing to review'} }

    scoreToReviewKey = ReviewSession.currentScoreToReview.get_value_for_datastore(reviewSession)
    # We are done with it
    reviewSession.delete()

    # The above could have potentially be put in a transaction but since there is only one player concerned, it should not matter

    def _increaseNumScoreReviewed():
        playerRecord = Record.get_by_key_name('record', parent=playerKey)
        playerRecord.numScoreReviewed += 1
        playerRecord.put()
    db.run_in_transaction(_increaseNumScoreReviewed)

    try:
        cheaters = db.run_in_transaction(_checkConflicts, scoreToReviewKey, scoreValue, scoreTime, playerId, adminMode)
    except TransactionFailedError:
        return getErrorResponse(CHECKCONFLICT_TRANSACTION_FAILURE, 0)

    if cheaters:
        def _cheaterUpdate(cheaterKey):
            cheaterRecord = Record.get_by_key_name('record', parent=cheaterKey)
            cheaterRecord.numCheat+=1
            cheaterRecord.put()

        for cheaterKey in cheaters:
            db.run_in_transaction(_cheaterUpdate,cheaterKey) #TODO : check TRANSACTION ERROR

    return {'result' : {'message' : 'review submited'} }
Beispiel #7
0
def forceReviewTimeUnit(playerId, value):
    admin = getAdmin()
    try:
        admin.playerList.index(playerId)
    except ValueError:
        return getErrorResponse(ADMIN_ONLY)

    oldReviewTimeUnit = getReviewTimeUnit()

    setReviewTimeUnit(value)

    return {'result': {'oldReviewTimeUnit': oldReviewTimeUnit}}
Beispiel #8
0
def forceReviewTimeUnit(playerId, value):
    admin = getAdmin()
    try:
        admin.playerList.index(playerId)
    except ValueError:
        return getErrorResponse(ADMIN_ONLY)
    
    oldReviewTimeUnit = getReviewTimeUnit()
    
    setReviewTimeUnit(value)
    
    return  {'result' : { 'oldReviewTimeUnit' : oldReviewTimeUnit} }
Beispiel #9
0
def signedRequestCall(signedRequest):

    signature, payload = decode_signedRequest(signedRequest)
    data = json.loads(base64_url_decode(payload))

    playerId = data['playerId']
    methodName = data['methodName']
    if 'args' in data:
        args = data['args']
    else:
        args = None

    playerSession = getPlayerSession(playerId)

    if playerSession is None:
        return getErrorResponse(NO_ACTIVE_SESSION_ERROR)

    if playerSession.method != 'signedRequest' or playerSession.secret is None:
        return getErrorResponse(SIGNED_REQUEST_METHOD_ERROR)

    verified = verifySignature(signature, payload, playerSession.secret, 'SHA1') # TODO decide which algorithm to use (haxe need to support SHA256

    if not verified:
        return getErrorResponse(INVALID_SIGNATURE_ERROR)

    if playerSession.isExpired():
        deletePlayerSession(playerId)
        return getErrorResponse(SESSION_EXPIRED_ERROR)

    method = _getMethodFromName(methodName)
    if method:
        if args is None or len(args) == 0:
            return method(playerId)
        else:
            return method(playerId, *args)
    else:
        return getErrorResponse(UNKNOW_SERVICE_CALL_ERROR)
Beispiel #10
0
    def _setScore():
        playSession.delete()

        verifiedScore = None
        verifiedScoreWrapper = VerifiedScoreWrapper.get_by_key_name("verifiedScore", parent=playerKey)
        if verifiedScoreWrapper is not None:
            verifiedScore = verifiedScoreWrapper.verified

        if verifiedScore is None or scoreValue > verifiedScore.value or (scoreValue == verifiedScore.value and scoreTime < verifiedScore.time):
            pendingScore = PendingScore.get_by_key_name("pendingScore", parent=playerKey)
            if pendingScore is not None:
                nonVerifiedScore = pendingScore.nonVerified
            else:
                nonVerifiedScore = None

            if nonVerifiedScore is None or scoreValue > nonVerifiedScore.value or (scoreValue == nonVerifiedScore.value and scoreTime < nonVerifiedScore.time):

                try:
                    proofBlob = db.Blob(proof)
                except Exception:
                    proofText = str(proof)
                    proofBlob = db.Blob(proofText)

                nonVerifiedScore = NonVerifiedScore(value=scoreValue,time=scoreTime,proof=proofBlob,seed=seed,version=version, parent=playerKey)
                nonVerifiedScore.put()
                if pendingScore is None:
                    pendingScore = PendingScore(key_name='pendingScore', parent=playerKey, nonVerified=nonVerifiedScore)
                else:
                    conflicts = ReviewConflict.gql("WHERE ANCESTOR IS :score", score=pendingScore.nonVerified.key()).fetch(100) # shoud not be more than 2
                    for conflict in conflicts:
                        conflict.delete()
                    pendingScore.nonVerified.delete()
                    pendingScore.nonVerified = nonVerifiedScore
                pendingScore.put()

                return {'result' : {'message' : 'success'} }
            else:
                pass # TODO : are you trying to cheat?
        else:
            pass # TODO : are you trying to cheat?

        return getErrorResponse(SCORE_TOO_SMALL)
Beispiel #11
0
def getHighestNonApprovedScore(playerId):
    admin = getAdmin()
    try:
        admin.playerList.index(playerId)
    except ValueError:
        return getErrorResponse(ADMIN_ONLY)


    scoreToApprove = db.GqlQuery("SELECT * FROM VerifiedScore WHERE approvedByAdmin = False ORDER BY value DESC").get()

    if scoreToApprove is not None:
        playerKey = Key.from_path('Player', playerId)
        approveSession = ApproveSession(key_name='approveSession', currentScoreToApprove=scoreToApprove, parent=playerKey)
        approveSession.put()

        seedList = struct.unpack("4L", scoreToApprove.seed)
        return {'result' : { 'proof' : scoreToApprove.proof, 'seed' : seedList} }


    return {'result' : { 'message' : 'Nothing to approve for now'} }
Beispiel #12
0
def getHighestNonApprovedScore(playerId):
    admin = getAdmin()
    try:
        admin.playerList.index(playerId)
    except ValueError:
        return getErrorResponse(ADMIN_ONLY)

    scoreToApprove = db.GqlQuery(
        "SELECT * FROM VerifiedScore WHERE approvedByAdmin = False ORDER BY value DESC"
    ).get()

    if scoreToApprove is not None:
        playerKey = Key.from_path('Player', playerId)
        approveSession = ApproveSession(key_name='approveSession',
                                        currentScoreToApprove=scoreToApprove,
                                        parent=playerKey)
        approveSession.put()

        seedList = struct.unpack("4L", scoreToApprove.seed)
        return {'result': {'proof': scoreToApprove.proof, 'seed': seedList}}

    return {'result': {'message': 'Nothing to approve for now'}}
Beispiel #13
0
def getOwnHighScore(playerId):

    def _getOwnHighScore():
        score = None
        playerKey = Key.from_path('Player', playerId)
        bestScore = PendingScore.get_by_key_name("pendingScore", parent=playerKey)
        if bestScore is None:
            bestScore = VerifiedScoreWrapper.get_by_key_name("verifiedScore", parent=playerKey)
            if bestScore is not None:
                score = bestScore.verified
        else:
            score = bestScore.nonVerified

        if score is None:
            return {'result' : { 'score' : 0, 'time' : 0} }

        return {'result' : { 'score' : score.value, 'time' : score.time} }

    try:
        return db.run_in_transaction(_getOwnHighScore)
    except TransactionFailedError:
        return getErrorResponse(OWNHIGHSCORE_TRANSACTION_FAILURE, 0)
Beispiel #14
0
def getOwnHighScore(playerId):
    def _getOwnHighScore():
        score = None
        playerKey = Key.from_path('Player', playerId)
        bestScore = PendingScore.get_by_key_name("pendingScore",
                                                 parent=playerKey)
        if bestScore is None:
            bestScore = VerifiedScoreWrapper.get_by_key_name("verifiedScore",
                                                             parent=playerKey)
            if bestScore is not None:
                score = bestScore.verified
        else:
            score = bestScore.nonVerified

        if score is None:
            return {'result': {'score': 0, 'time': 0}}

        return {'result': {'score': score.value, 'time': score.time}}

    try:
        return db.run_in_transaction(_getOwnHighScore)
    except TransactionFailedError:
        return getErrorResponse(OWNHIGHSCORE_TRANSACTION_FAILURE, 0)
Beispiel #15
0
def setScore(playerId, score):

    scoreValue = score['score']
    scoreTime = score['time']
    proof = score['proof']

    playerKey = Key.from_path('Player', playerId)

    playSession = PlaySession.get_by_key_name('playSession', parent=playerKey)
    if playSession is None:
        return getErrorResponse(NO_PLAYER_SESSION)

    seed = playSession.seed
    version = playSession.version
    seedDateTime = playSession.seedDateTime

    now = datetime.datetime.now()

    # TODO : investigate: should we consider the player as cheater for this two exception  ?
    if seedDateTime + datetime.timedelta(milliseconds=scoreTime) > now:
        return getErrorResponse(NOT_ENOUGH_TIME)

    maxScoreTime = scoreTime + MINIMUM_TIME
    if seedDateTime + datetime.timedelta(milliseconds=maxScoreTime) < now:
        return getErrorResponse(TOO_MUCH_TIME)


    def _setScore():
        playSession.delete()

        verifiedScore = None
        verifiedScoreWrapper = VerifiedScoreWrapper.get_by_key_name("verifiedScore", parent=playerKey)
        if verifiedScoreWrapper is not None:
            verifiedScore = verifiedScoreWrapper.verified

        if verifiedScore is None or scoreValue > verifiedScore.value or (scoreValue == verifiedScore.value and scoreTime < verifiedScore.time):
            pendingScore = PendingScore.get_by_key_name("pendingScore", parent=playerKey)
            if pendingScore is not None:
                nonVerifiedScore = pendingScore.nonVerified
            else:
                nonVerifiedScore = None

            if nonVerifiedScore is None or scoreValue > nonVerifiedScore.value or (scoreValue == nonVerifiedScore.value and scoreTime < nonVerifiedScore.time):

                try:
                    proofBlob = db.Blob(proof)
                except Exception:
                    proofText = str(proof)
                    proofBlob = db.Blob(proofText)

                nonVerifiedScore = NonVerifiedScore(value=scoreValue,time=scoreTime,proof=proofBlob,seed=seed,version=version, parent=playerKey)
                nonVerifiedScore.put()
                if pendingScore is None:
                    pendingScore = PendingScore(key_name='pendingScore', parent=playerKey, nonVerified=nonVerifiedScore)
                else:
                    conflicts = ReviewConflict.gql("WHERE ANCESTOR IS :score", score=pendingScore.nonVerified.key()).fetch(100) # shoud not be more than 2
                    for conflict in conflicts:
                        conflict.delete()
                    pendingScore.nonVerified.delete()
                    pendingScore.nonVerified = nonVerifiedScore
                pendingScore.put()

                return {'result' : {'message' : 'success'} }
            else:
                pass # TODO : are you trying to cheat?
        else:
            pass # TODO : are you trying to cheat?

        return getErrorResponse(SCORE_TOO_SMALL)

    try:
        return db.run_in_transaction(_setScore)
    except TransactionFailedError:
        return getErrorResponse(SETSCORE_TRANSACTION_FAILURE, 0)
Beispiel #16
0
def approveScore(playerId, score):
    admin = getAdmin()
    try:
        admin.playerList.index(playerId)
    except ValueError:
        return getErrorResponse(ADMIN_ONLY)

    scoreValue = score['score']
    scoreTime = score['time']
    playerKey = Key.from_path('Player', playerId)
    approveSession = ApproveSession.get_by_key_name('approveSession', parent=playerKey)

    if approveSession is None:
        return getErrorResponse(NOTHING_TO_REVIEW)

    scoreToApproveKey = ApproveSession.currentScoreToApprove.get_value_for_datastore(approveSession)
    # We are done with it
    approveSession.delete()

    def _approveScore():
        scoreToApprove = db.get(scoreToApproveKey)
        if scoreToApprove is None:
            return {'cheater' : None, 'nonCheaters': []}

        approvedPlayerKey = scoreToApprove.parent_key()

        cheater = None
        nonCheaters = []
        if scoreToApprove.value == scoreValue and scoreToApprove.time == scoreTime:
            scoreToApprove.approvedByAdmin = True
            scoreToApprove.put()
        else:
            approvedPlayerRecord = Record.get_by_key_name('record', parent=approvedPlayerKey)
            approvedPlayerRecord.numCheat += 1
            approvedPlayerRecord.put()
            cheater = Key.from_path('Player', scoreToApprove.verifier)
            for nonCheaterId in scoreToApprove.conflictingReviewers:
                nonCheaters.append(Key.from_path('Player', nonCheaterId))
            scoreToApprove.delete()
            db.delete(Key.from_path('VerifiedScoreWrapper', 'verifiedScore', parent = approvedPlayerKey))

        return {'cheater' : cheater, 'nonCheaters': nonCheaters}

    try:
        result = db.run_in_transaction(_approveScore)
    except TransactionFailedError:
        return getErrorResponse(APPROVESCORE_TRANSACTION_FAILURE, 0)

    cheater = result['cheater']
    nonCheaters = result['nonCheaters']

    if cheater is not None:
        cheaterRecord = Record.get_by_key_name('record', parent=cheater)
        cheaterRecord.numCheat+=1
        cheaterRecord.put()

    if nonCheaters:
        def _nonCheaterUpdate(nonCheaterKey):
            cheaterRecord = Record.get_by_key_name('record', parent=nonCheaterKey)
            cheaterRecord.numCheat-=1
            cheaterRecord.put()

        for nonCheaterKey in nonCheaters:
            db.run_in_transaction(_nonCheaterUpdate,nonCheaterKey)

    return {'result' : { 'message' : 'approvement submited'} }
Beispiel #17
0
def setScore(playerId, score):

    scoreValue = score['score']
    scoreTime = score['time']
    proof = score['proof']

    playerKey = Key.from_path('Player', playerId)

    playSession = PlaySession.get_by_key_name('playSession', parent=playerKey)
    if playSession is None:
        return getErrorResponse(NO_PLAYER_SESSION)

    seed = playSession.seed
    version = playSession.version
    seedDateTime = playSession.seedDateTime

    now = datetime.datetime.now()

    # TODO : investigate: should we consider the player as cheater for this two exception  ?
    if seedDateTime + datetime.timedelta(milliseconds=scoreTime) > now:
        return getErrorResponse(NOT_ENOUGH_TIME)

    maxScoreTime = scoreTime + MINIMUM_TIME
    if seedDateTime + datetime.timedelta(milliseconds=maxScoreTime) < now:
        return getErrorResponse(TOO_MUCH_TIME)

    def _setScore():
        playSession.delete()

        verifiedScore = None
        verifiedScoreWrapper = VerifiedScoreWrapper.get_by_key_name(
            "verifiedScore", parent=playerKey)
        if verifiedScoreWrapper is not None:
            verifiedScore = verifiedScoreWrapper.verified

        if verifiedScore is None or scoreValue > verifiedScore.value or (
                scoreValue == verifiedScore.value
                and scoreTime < verifiedScore.time):
            pendingScore = PendingScore.get_by_key_name("pendingScore",
                                                        parent=playerKey)
            if pendingScore is not None:
                nonVerifiedScore = pendingScore.nonVerified
            else:
                nonVerifiedScore = None

            if nonVerifiedScore is None or scoreValue > nonVerifiedScore.value or (
                    scoreValue == nonVerifiedScore.value
                    and scoreTime < nonVerifiedScore.time):

                try:
                    proofBlob = db.Blob(proof)
                except Exception:
                    proofText = str(proof)
                    proofBlob = db.Blob(proofText)

                nonVerifiedScore = NonVerifiedScore(value=scoreValue,
                                                    time=scoreTime,
                                                    proof=proofBlob,
                                                    seed=seed,
                                                    version=version,
                                                    parent=playerKey)
                nonVerifiedScore.put()
                if pendingScore is None:
                    pendingScore = PendingScore(key_name='pendingScore',
                                                parent=playerKey,
                                                nonVerified=nonVerifiedScore)
                else:
                    conflicts = ReviewConflict.gql(
                        "WHERE ANCESTOR IS :score",
                        score=pendingScore.nonVerified.key()).fetch(
                            100)  # shoud not be more than 2
                    for conflict in conflicts:
                        conflict.delete()
                    pendingScore.nonVerified.delete()
                    pendingScore.nonVerified = nonVerifiedScore
                pendingScore.put()

                return {'result': {'message': 'success'}}
            else:
                pass  # TODO : are you trying to cheat?
        else:
            pass  # TODO : are you trying to cheat?

        return getErrorResponse(SCORE_TOO_SMALL)

    try:
        return db.run_in_transaction(_setScore)
    except TransactionFailedError:
        return getErrorResponse(SETSCORE_TRANSACTION_FAILURE, 0)
Beispiel #18
0
def getRandomScore(playerId):

    playerKey = Key.from_path('Player', playerId)

    playerRecord = Record.get_by_key_name('record', parent=playerKey)

    # do not review if you are a cheater
    if playerRecord.numCheat > 0:
        return getErrorResponse(CHEATER_BLOCKED)

    reviewTimeUnitMilliseconds = getReviewTimeUnit()
    reviewTimeUnit = datetime.timedelta(milliseconds=reviewTimeUnitMilliseconds)
    now =datetime.datetime.now()
    oldEnoughTime = now - reviewTimeUnit

    delay = 2000 + random.random() * 5000  + ceil(reviewTimeUnitMilliseconds * (1 + random.random() * 2))
    if delay > MAX_INT32_VALUE:
        delay = MAX_INT32_VALUE

    def _updateLastReviewAttemptDateTime():
        if playerRecord.lastReviewAttemptDateTime is not None and playerRecord.lastReviewAttemptDateTime > oldEnoughTime:
            # TODO : check whethe rthis randomize stuff is good or not:
            return {'result' : {'message' : 'You already posted enough reviews, retry later', 'retry' : delay } }
            # could be 2 * reviewTimeUnit / config.nbPlayerPerTimeUnit

        playerRecord.lastReviewAttemptDateTime = datetime.datetime.now()
        playerRecord.put()
        return None

    reviewSession = ReviewSession.get_by_key_name('reviewSession', parent=playerKey)
    if reviewSession is None:

        try:
            result = db.run_in_transaction(_updateLastReviewAttemptDateTime)
        except TransactionFailedError:
            result = getErrorResponse(LASTREVIEWUPDATE_TRANSACTION_FAILURE, 0)

        if result is not None:
            return result

        # do not allow reviewer to jump on a just posted review. basically the reviewer should have lots of potential review to take from and other reviewer shoudl compete with
        potentialScoresToReview = db.GqlQuery("SELECT * FROM NonVerifiedScore WHERE dateTime < :oldEnoughTime ORDER BY dateTime ASC", oldEnoughTime=oldEnoughTime).fetch(5)

        scoreToReview = None
        for score in potentialScoresToReview:
            if score.parent_key() != playerKey:
                try:
                    score.conflictingReviewers.index(playerId)
                except ValueError: # the current reviewer did not review this score yet
                    scoreToReview = score
                    break

        if scoreToReview is None:
            return {'result' : {'message' : 'Nothing to review for now', 'retry' : delay } }


        reviewSession = ReviewSession(key_name='reviewSession', currentScoreToReview=scoreToReview, parent=playerKey)
        reviewSession.put()
    else:
        try:
            scoreToReview = reviewSession.currentScoreToReview
        except ReferencePropertyResolveError:
            scoreToReview = None
            
    # in case score has been approved just now, it could have been removed
    if scoreToReview is not None:
        seedList = struct.unpack("4L", scoreToReview.seed)
        return {'result' : { 'proof' : scoreToReview.proof, 'seed' : seedList, 'version' : scoreToReview.version} }

    return {'result' : {'message' : 'Nothing to review for now (just done)', 'retry' : delay} }