syntaxwebsite/app/routes/jobreporthandler.py

708 lines
32 KiB
Python

from flask import Blueprint, render_template, request, redirect, url_for, flash, make_response, jsonify, abort
from app.extensions import db, redis_controller, csrf, get_remote_address
import requests
import logging
import json
import gzip
import json
import math
from datetime import datetime, timedelta
from app.models.user import User
from app.models.asset import Asset
from app.models.usereconomy import UserEconomy
from app.models.gameservers import GameServer
from app.models.placeservers import PlaceServer
from app.models.placeserver_players import PlaceServerPlayer
from app.models.place import Place
from app.models.groups import Group
from app.models.game_session_log import GameSessionLog
from app.models.universe import Universe
from app.models.user_ban import UserBan
from app.services.economy import IncrementTargetBalance
from app.services.gameserver_comm import perform_post
from app.pages.home.home import InsertRecentlyPlayed
from app.enums.TransactionType import TransactionType
from app.enums.PlaceYear import PlaceYear
from app.enums.BanType import BanType
from app.util.placeinfo import ClearPlayingCountCache, GetPlayingCount
from app.util.transactions import CreateTransaction
from config import Config
config = Config()
JobReportHandler = Blueprint('jobreporthandler', __name__, url_prefix='/')
def isValidAuthorizationToken( authtoken : str) -> GameServer:
if authtoken is None:
return None
RequestAddress = get_remote_address()
GameServerObject = GameServer.query.filter_by( accessKey = authtoken, serverIP = RequestAddress ).first()
return GameServerObject
class InvalidGZIPData(Exception):
pass
class InvalidJSONData(Exception):
pass
def IncrementPlaceVisits( PlaceObj : Place ):
PlaceObj.visitcount += 1
UniverseObj : Universe = Universe.query.filter_by( id = PlaceObj.parent_universe_id ).first()
if UniverseObj is None:
return
UniverseObj.visit_count += 1
db.session.commit()
def ParsePayloadData(throwException : bool = True):
"""
Handles RCC Post Data and returns the JSON data
"""
if request.headers.get("Content-Encoding") == "gzip":
try:
data = gzip.decompress(request.data)
except Exception as e:
raise InvalidGZIPData("Invalid gzip data")
try:
JSONData = json.loads(data)
except Exception as e:
raise InvalidJSONData("Invalid JSON data")
else:
JSONData = request.json
if JSONData is None:
raise InvalidJSONData("Invalid JSON data")
return JSONData
def EvictPlayer( PlaceServerObject : PlaceServer, UserId : int ):
PlaceObj : Place = Place.query.filter_by( placeid = PlaceServerObject.serverPlaceId ).first()
MasterServer : GameServer | None = GameServer.query.filter_by(serverId=PlaceServerObject.originServerId).first()
try:
if PlaceObj.placeyear in [PlaceYear.Eighteen, PlaceYear.Twenty, PlaceYear.Sixteen]:
if PlaceObj.placeyear in [PlaceYear.Eighteen, PlaceYear.Twenty]:
ExecutionScript = f"""{{
"Mode": "EvictPlayer",
"MessageVersion": 1,
"Settings": {{
"PlayerId": {str(UserId)}
}}
}}"""
elif PlaceObj.placeyear in [PlaceYear.Sixteen]:
ExecutionScript = f"""for _, Player in pairs(game:GetService("Players"):GetPlayers()) do if Player.UserId == {UserId} then Player:Kick("Disconnected from game, possibly due to game joined from another device") end end"""
else:
raise Exception("PlaceYear is not compatible")
ExecuteScriptRequest = perform_post(
TargetGameserver = MasterServer,
Endpoint = "Execute",
JSONData = {
"jobid": str(PlaceServerObject.serveruuid),
"script": ExecutionScript,
"arguments": []
}
)
if ExecuteScriptRequest.status_code != 200:
raise Exception(f"Unexpected Status Code: {ExecuteScriptRequest.status_code}, {ExecuteScriptRequest.content}")
elif PlaceObj.placeyear in [PlaceYear.Fourteen]:
redis_controller.set(f"EvictPlayerRequest:{PlaceServerObject.serveruuid}:{UserId}", 1, ex=60)
StartTime = datetime.utcnow()
while redis_controller.exists(f"EvictPlayerRequest:{PlaceServerObject.serveruuid}:{UserId}"):
if (datetime.utcnow() - StartTime).total_seconds() > 50:
raise Exception("Failed to evict player")
else:
raise Exception("PlaceYear is not compatible")
except Exception as e:
logging.error(f"EvictPlayer failed to send request to kick player, {e}")
@JobReportHandler.before_request
def before_request():
requesterAddress = get_remote_address()
if requesterAddress is None:
return abort(404)
gameserverObj : GameServer = GameServer.query.filter_by(serverIP=requesterAddress).first()
if gameserverObj is None:
return abort(404)
if "UserRequest" in request.headers.get( key = "accesskey", default = "" ):
return jsonify({
"success": False,
"message": "Invalid request"
}), 400
@JobReportHandler.route('/internal/gameserver/reportshutdown', methods=['POST'])
@csrf.exempt
def reportshutdown():
try:
JSONData = ParsePayloadData()
except InvalidGZIPData:
return jsonify({"status": "error", "message": "Invalid gzip data"}),400
except InvalidJSONData:
return jsonify({"status": "error", "message": "Invalid JSON data"}),400
except Exception as e:
return jsonify({"status": "error", "message": "Unknown error occured while parsing data"}),400
if "AuthToken" not in JSONData:
return jsonify({"status": "error", "message": "Invalid JSON data"}),400
PlaceServerOwner : GameServer | None = isValidAuthorizationToken(JSONData["AuthToken"])
if PlaceServerOwner is None:
return jsonify({"status": "error", "message": "Invalid authorization token"}),400
placeId = JSONData["PlaceId"]
jobId = JSONData["JobId"]
PlaceServerObject : PlaceServer = PlaceServer.query.filter_by(serverPlaceId=placeId, serveruuid=jobId).first()
if PlaceServerObject is None:
return jsonify({"status": "error", "message": "Invalid place server"}),400
try:
logging.info(f"CloseJob : Closing {jobId} for place [{placeId}] because server reports shutdown")
perform_post(
TargetGameserver = PlaceServerOwner,
Endpoint = "CloseJob",
JSONData = {
"jobid": jobId,
}
)
except Exception as e:
logging.error(f"Failed to close job ( {jobId} ) for place ( {placeId} ), {e}")
PlaceServerOwner : GameServer = GameServer.query.filter_by(serverId=PlaceServerObject.originServerId).first()
PlaceServerPlayers = PlaceServerPlayer.query.filter_by(serveruuid=jobId).all()
for PlaceServerPlayerObject in PlaceServerPlayers:
db.session.delete(PlaceServerPlayerObject)
db.session.delete(PlaceServerObject)
db.session.commit()
PlaceObj : Place = Place.query.filter_by(placeid=placeId).first()
if PlaceObj is not None:
ClearPlayingCountCache(PlaceObj)
return jsonify({"status": "success"}),200
@JobReportHandler.route('/internal/gameserver/reportstats', methods=['POST'])
@csrf.exempt
def reportstats():
try:
JSONData = ParsePayloadData()
except InvalidGZIPData:
return jsonify({"status": "error", "message": "Invalid gzip data"}),400
except InvalidJSONData:
return jsonify({"status": "error", "message": "Invalid JSON data"}),400
except Exception as e:
return jsonify({"status": "error", "message": "Unknown error occured while parsing data"}),400
if "AuthToken" not in JSONData:
return jsonify({"status": "error", "message": "Invalid JSON data"}),400
PlaceServerOwner = isValidAuthorizationToken(JSONData["AuthToken"])
if PlaceServerOwner is None:
return jsonify({"status": "error", "message": "Invalid authorization token"}),400
placeId = JSONData["PlaceId"]
jobId = JSONData["JobId"]
serverAliveTime = JSONData["ServerAliveTime"]
PlaceServerObject : PlaceServer = PlaceServer.query.filter_by(serverPlaceId=placeId, serveruuid=jobId).first()
if PlaceServerObject is None:
return jsonify({"status": "success"}),200
PlaceServerObject.lastping = datetime.utcnow()
PlaceServerObject.serverRunningTime = serverAliveTime
db.session.commit()
return jsonify({"status": "success"}),200
@JobReportHandler.route('/internal/gameserver/reportplacevalidation', methods=['POST'])
@csrf.exempt
def reportplacevalidation():
try:
JSONData = ParsePayloadData()
except InvalidGZIPData:
return jsonify({"status": "error", "message": "Invalid gzip data"}),400
except InvalidJSONData:
return jsonify({"status": "error", "message": "Invalid JSON data"}),400
except Exception as e:
return jsonify({"status": "error", "message": "Unknown error occured while parsing data"}),400
if "AuthToken" not in JSONData:
return jsonify({"status": "error", "message": "Invalid JSON data"}),400
PlaceServerOwner = isValidAuthorizationToken(JSONData["AuthToken"])
if PlaceServerOwner is None:
return jsonify({"status": "error", "message": "Invalid authorization token"}),400
ValidationRequestId = JSONData["ReqId"]
LoadSuccess = JSONData["LoadSuccess"]
ErrorMessage = None
if "ErrorMessage" in JSONData:
ErrorMessage = JSONData["ErrorMessage"]
redis_controller.set(f"ValidatePlaceFileRequest:{ValidationRequestId}", json.dumps({
"valid": LoadSuccess,
"error": ErrorMessage
}), ex=600)
logging.info(f"Place validation request ( {ValidationRequestId} ) has been completed")
return jsonify({"status": "success"}),200
def HandleUserTimePlayed( UserObj : User, Timeplayed : int, serverUUID : str = None, placeId : int = None ):
"""
For every 40 seconds the user has played a game we give them 1 ticket
however we limit the tickets to 100 per day and the user account must be
more than 2 days old
"""
if serverUUID is not None and placeId is not None:
GameSessionLogObject : GameSessionLog = GameSessionLog.query.filter_by(serveruuid=serverUUID, place_id=placeId, user_id=UserObj.id).first()
if GameSessionLogObject is None:
GameSessionLogObject = GameSessionLog(
user_id = UserObj.id,
serveruuid = serverUUID,
place_id = placeId,
joined_at = datetime.utcnow() - timedelta(seconds=Timeplayed),
left_at = datetime.utcnow()
)
db.session.add(GameSessionLogObject)
else:
GameSessionLogObject.left_at = datetime.utcnow()
db.session.commit()
if datetime.utcnow() - timedelta(days=2) < UserObj.created:
return
RawTicketsEarned = math.floor(Timeplayed / 40)
CurrentDay = datetime.utcnow().day
TicketsEarnedToday = redis_controller.get(f"UserTicketsEarned:{UserObj.id}:{CurrentDay}")
if TicketsEarnedToday is None:
TicketsEarnedToday = 0
else:
TicketsEarnedToday = int(TicketsEarnedToday)
if TicketsEarnedToday >= 500:
return
TicketsToGive = 500 - TicketsEarnedToday
if RawTicketsEarned > TicketsToGive:
RawTicketsEarned = TicketsToGive
if RawTicketsEarned <= 0:
return
IncrementTargetBalance(UserObj, RawTicketsEarned, 1)
CreateTransaction(
Reciever = UserObj,
Sender = User.query.filter_by(id=1).first(),
CurrencyAmount = RawTicketsEarned,
CurrencyType = 1,
TransactionType = TransactionType.BuildersClubStipend,
AssetId = None,
CustomText = f"Played game for {str(round(Timeplayed,1))} seconds"
)
AmountOfTicketsEarnedToday = TicketsEarnedToday + RawTicketsEarned
redis_controller.set(f"UserTicketsEarned:{UserObj.id}:{CurrentDay}", AmountOfTicketsEarnedToday, ex=86400)
@JobReportHandler.route('/internal/gameserver/verifyplayer', methods=['POST'])
@csrf.exempt
def verifyplayer():
try:
JSONData = ParsePayloadData()
except InvalidGZIPData:
return jsonify({"status": "error", "message": "Invalid gzip data"}),400
except InvalidJSONData:
return jsonify({"status": "error", "message": "Invalid JSON data"}),400
except Exception as e:
return jsonify({"status": "error", "message": "Unknown error occured while parsing data"}),400
if "AuthToken" not in JSONData:
return jsonify({"status": "error", "message": "Invalid JSON data"}),400
PlaceServerOwner = isValidAuthorizationToken(JSONData["AuthToken"])
if PlaceServerOwner is None:
return jsonify({"status": "error", "message": "Invalid authorization token"}),400
jobId = JSONData["JobId"]
PlaceServerObject : PlaceServer = PlaceServer.query.filter_by(serveruuid=jobId).first()
if PlaceServerObject is None:
return jsonify({"status": "error", "message": "Invalid place server"}),400
PlaceServerObject.lastping = datetime.utcnow()
PlaceObject : Place = Place.query.filter_by(placeid=PlaceServerObject.serverPlaceId).first()
if PlaceObject is None:
return jsonify({"status": "error", "message": "Invalid place"}),400
UserId = JSONData["UserId"]
UserObject : User = User.query.filter_by(id=UserId).first()
if UserObject is None or UserObject.accountstatus != 1:
return jsonify({"status": "error", "message": "Invalid user", "authenticated": False}), 200
if "Username" not in JSONData or "CharacterAppearance" not in JSONData or "VerificationTicket" not in JSONData:
return jsonify({"status": "error", "message": "Invalid JSON data", "authenticated": False}), 200
Username = JSONData["Username"]
CharacterAppearance = JSONData["CharacterAppearance"]
VerificationTicket = JSONData["VerificationTicket"]
authKeyName = f"joinashx-auth:{str(jobId)}:{str(UserId)}:{str(PlaceObject.placeid)}:{VerificationTicket}"
if not redis_controller.exists(authKeyName):
return jsonify({"status": "error", "message": "Invalid join request", "authenticated": False}), 200
JoinInfo = json.loads(redis_controller.get(authKeyName))
if JoinInfo is None:
return jsonify({"status": "error", "message": "Invalid join request", "authenticated": False}), 200
redis_controller.delete(authKeyName)
if JoinInfo["CharacterAppearance"] != CharacterAppearance or JoinInfo["Username"] != Username:
return jsonify({"status": "error", "message": "Invalid join request", "authenticated": False}), 200
return jsonify({"status": "success", "message": "Valid join request", "authenticated": True}), 200
@JobReportHandler.route('/internal/gameserver/reportplayers', methods=['POST'])
@csrf.exempt
def reportplayers():
try:
JSONData = ParsePayloadData()
except InvalidGZIPData:
return jsonify({"status": "error", "message": "Invalid gzip data"}),400
except InvalidJSONData:
return jsonify({"status": "error", "message": "Invalid JSON data"}),400
except Exception as e:
return jsonify({"status": "error", "message": "Unknown error occured while parsing data"}),400
if "AuthToken" not in JSONData:
return jsonify({"status": "error", "message": "Invalid JSON data"}),400
PlaceServerOwner = isValidAuthorizationToken(JSONData["AuthToken"])
if PlaceServerOwner is None:
return jsonify({"status": "error", "message": "Invalid authorization token"}),400
jobId = JSONData["JobId"]
players = JSONData["Players"] # Array of players in the server which each player is a dictionary ( { "UserId": 1, "Name": "test" } )
playerCount = len(players)
PlaceServerObject : PlaceServer = PlaceServer.query.filter_by(serveruuid=jobId).first()
if PlaceServerObject is None:
return jsonify({"status": "error", "message": "Invalid place server"}),400
PlaceServerObject.lastping = datetime.utcnow()
PlaceObject : Place = Place.query.filter_by(placeid=PlaceServerObject.serverPlaceId).first()
if PlaceObject is None:
return jsonify({"status": "error", "message": "Invalid place"}),400
AssetObject : Asset = Asset.query.filter_by(id=PlaceObject.placeid).first()
CreatorObject : User | Group = None
if AssetObject.creator_type == 0:
CreatorObject = User.query.filter_by(id=AssetObject.creator_id).first()
else:
CreatorObject = Group.query.filter_by(id=AssetObject.creator_id).first()
BadPlayers = [] # Array of players which must be kicked from the server
for player in players:
UserObject : User = User.query.filter_by(id=player["UserId"]).first()
if UserObject is None or UserObject.username != player["Name"] or UserObject.accountstatus != 1:
logging.info(f"/internal/gameserver/reportplayers - {jobId} - Invalid Player ( {player['UserId']} ) - {player['Name']}")
BadPlayers.append(player["UserId"])
playerCount -= 1
continue
if redis_controller.exists(f"EvictPlayerRequest:{jobId}:{player['UserId']}"):
redis_controller.delete(f"EvictPlayerRequest:{jobId}:{player['UserId']}")
logging.info(f"/internal/gameserver/reportplayers - {jobId} - Player ( {player['UserId']} ) requested kick")
BadPlayers.append(player["UserId"])
playerCount -= 1
continue
PlaceServerPlayerObject : PlaceServerPlayer = PlaceServerPlayer.query.filter_by(userid=player["UserId"]).first()
if PlaceServerPlayerObject is None:
if not redis_controller.exists(f"allow_join:{str(player['UserId'])}:{PlaceObject.placeid}:{str(jobId)}"):
logging.info(f"/internal/gameserver/reportplayers - {jobId} - Player ( {player['UserId']} ) is not allowed to join")
BadPlayers.append(player["UserId"])
playerCount -= 1
continue
PlaceServerPlayerObject = PlaceServerPlayer(serveruuid=jobId, userid=player["UserId"], joinTime= datetime.utcnow())
IncrementPlaceVisits(PlaceObject)
if CreatorObject is not None:
IncrementTargetBalance(CreatorObject, 1, 1)
UserObject.lastonline = datetime.utcnow()
InsertRecentlyPlayed(UserObj = UserObject, PlaceId = PlaceObject.placeid)
db.session.add(PlaceServerPlayerObject)
else:
UserId = player["UserId"]
if str(PlaceServerPlayerObject.serveruuid) == jobId:
PlaceServerPlayerObject.lastHeartbeat = datetime.utcnow()
UserObject.lastonline = datetime.utcnow()
else:
OtherPlaceServerObj = PlaceServer.query.filter_by(serveruuid=PlaceServerPlayerObj.serveruuid).first()
if OtherPlaceServerObj is not None:
EvictPlayer( OtherPlaceServerObj, UserId )
TotalTimePlayed = (datetime.utcnow() - PlaceServerPlayerObject.joinTime).total_seconds()
UserObj : User = User.query.filter_by(id=PlaceServerPlayerObject.userid).first()
HandleUserTimePlayed(UserObj, TotalTimePlayed, serverUUID=jobId, placeId=PlaceObject.placeid)
db.session.delete(PlaceServerPlayerObj)
PlaceServerPlayerObj = PlaceServerPlayer(serveruuid=jobId, userid=UserId, joinTime= datetime.utcnow())
IncrementPlaceVisits(PlaceObject)
if CreatorObject is not None:
IncrementTargetBalance(CreatorObject, 1, 1)
UserObject.lastonline = datetime.utcnow()
InsertRecentlyPlayed(UserObj = UserObject, PlaceId = PlaceObject.placeid)
PlaceServerPlayers = PlaceServerPlayer.query.filter_by(serveruuid=jobId).all()
for PlaceServerPlayerObject in PlaceServerPlayers:
playerFound = False
for player in players:
if PlaceServerPlayerObject.userid == player["UserId"]:
playerFound = True
break
if playerFound == False:
TotalTimePlayed = (datetime.utcnow() - PlaceServerPlayerObject.joinTime).total_seconds()
UserObj : User = User.query.filter_by(id=PlaceServerPlayerObject.userid).first()
HandleUserTimePlayed(UserObj, TotalTimePlayed, serverUUID=jobId, placeId=PlaceObject.placeid)
db.session.delete(PlaceServerPlayerObject)
PlaceServerObject.playerCount = playerCount
db.session.commit()
ClearPlayingCountCache(PlaceObject)
return jsonify({"status": "success", "bad": BadPlayers}),200
@JobReportHandler.route('/internal/gameserver/reportfailure', methods=['POST'])
@csrf.exempt
def reportfailure():
# TODO: Implement this
return jsonify({"status": "success"}),200
# 2018+ endpoints
@JobReportHandler.route("/v2/CreateOrUpdate/", methods=['POST'])
@csrf.exempt
def CreateOrUpdate():
try:
JSONData = ParsePayloadData()
except InvalidGZIPData:
return jsonify({"status": "error", "message": "Invalid gzip data"}),400
except InvalidJSONData:
return jsonify({"status": "error", "message": "Invalid JSON data"}),400
except Exception as e:
return jsonify({"status": "error", "message": "Unknown error occured while parsing data"}),400
RequestHost = request.headers.get('Host')
if RequestHost is None:
return abort(404)
if not RequestHost.startswith("gameinstances.api."):
return abort(404)
AccessKey = request.args.get( key = 'apiKey', default = None, type = str)
JobId = request.args.get( key = 'gameId', default = None, type = str)
if AccessKey is None or JobId is None:
return abort(404)
# Newer RCCs uses temporary access keys to report their game status and stuff
if not redis_controller.exists(f"GameServerAccessKey:{AccessKey}:{JobId}"):
logging.warning(f"Invalid access key ( {AccessKey} ) for server ( {JobId} )")
return abort(404)
PlaceServerObj : PlaceServer = PlaceServer.query.filter_by(serveruuid=JobId).first()
if PlaceServerObj is None:
originServerUUID = redis_controller.get(f"place:{JobId}:origin")
if originServerUUID is not None:
PlaceServerOwner : GameServer = GameServer.query.filter_by(serverId=originServerUUID).first()
if PlaceServerOwner is not None:
logging.info(f"CloseJob : Closing {JobId} because server does not exist in database")
perform_post(
TargetGameserver = PlaceServerOwner,
Endpoint = "CloseJob",
JSONData = {
"jobid": JobId,
}
)
logging.warning(f"Invalid server ( {JobId} )")
return abort(404)
PlaceServerOwner : GameServer = GameServer.query.filter_by(serverId=PlaceServerObj.originServerId).first()
PlaceObject : Place = Place.query.filter_by(placeid=PlaceServerObj.serverPlaceId).first()
if PlaceObject is None:
logging.warning(f"Invalid place ( {PlaceServerObj.serverPlaceId} ) for server ( {JobId} )")
return jsonify({"status": "error", "message": "Invalid place"}),400
AssetObject : Asset = Asset.query.filter_by(id=PlaceObject.placeid).first()
CreatorObject : User | Group = None
if AssetObject.creator_type == 0:
CreatorObject = User.query.filter_by(id=AssetObject.creator_id).first()
else:
CreatorObject = Group.query.filter_by(id=AssetObject.creator_id).first()
PlaceServerObj.lastping = datetime.utcnow()
GameSessions : list = JSONData["GameSessions"]
if GameSessions is None:
return jsonify({"status": "error", "message": "Invalid JSON data"}),400
for GameSession in GameSessions:
UserId = GameSession["UserId"]
UserObject : User = User.query.filter_by(id=UserId).first()
if UserObject is None or UserObject.accountstatus != 1:
EvictPlayer( PlaceServerObj, UserId )
logging.warning(f"User ( {UserId} ) is not a valid user, on server ( {PlaceServerObj.serveruuid} )")
continue
PlaceServerPlayerObj : PlaceServerPlayer = PlaceServerPlayer.query.filter_by(serveruuid=JobId, userid=UserId).first()
if PlaceServerPlayerObj is None:
PlaceServerPlayerObj = PlaceServerPlayer(serveruuid=JobId, userid=UserId, joinTime=datetime.utcnow())
db.session.add(PlaceServerPlayerObj)
IncrementPlaceVisits(PlaceObject)
if CreatorObject is not None:
IncrementTargetBalance(CreatorObject, 1, 1)
UserObject.lastonline = datetime.utcnow()
InsertRecentlyPlayed(UserObj = UserObject, PlaceId = PlaceObject.placeid)
else:
if str(PlaceServerPlayerObj.serveruuid) == JobId:
PlaceServerPlayerObj.lastHeartbeat = datetime.utcnow()
UserObject.lastonline = datetime.utcnow()
else:
OtherPlaceServerObj = PlaceServer.query.filter_by(serveruuid=PlaceServerPlayerObj.serveruuid).first()
if OtherPlaceServerObj is not None:
EvictPlayer( OtherPlaceServerObj, UserId )
TotalTimePlayed = (datetime.utcnow() - PlaceServerPlayerObject.joinTime).total_seconds()
UserObj : User = User.query.filter_by(id=PlaceServerPlayerObject.userid).first()
HandleUserTimePlayed(UserObj, TotalTimePlayed, serverUUID=JobId, placeId=PlaceObject.placeid)
db.session.delete(PlaceServerPlayerObj)
PlaceServerPlayerObj = PlaceServerPlayer(serveruuid=JobId, userid=UserId, joinTime= datetime.utcnow())
IncrementPlaceVisits(PlaceObject)
if CreatorObject is not None:
IncrementTargetBalance(CreatorObject, 1, 1)
UserObject.lastonline = datetime.utcnow()
InsertRecentlyPlayed(UserObj = UserObject, PlaceId = PlaceObject.placeid)
PlaceServerPlayers = PlaceServerPlayer.query.filter_by(serveruuid=JobId).all()
for PlaceServerPlayerObject in PlaceServerPlayers:
playerFound = False
for GameSession in GameSessions:
if PlaceServerPlayerObject.userid == GameSession["UserId"]:
playerFound = True
break
if playerFound == False:
TotalTimePlayed = (datetime.utcnow() - PlaceServerPlayerObject.joinTime).total_seconds()
UserObj : User = User.query.filter_by(id=PlaceServerPlayerObject.userid).first()
HandleUserTimePlayed(UserObj, TotalTimePlayed, serverUUID=JobId, placeId=PlaceObject.placeid)
db.session.delete(PlaceServerPlayerObject)
if PlaceServerObj.playerCount == 0 and len(GameSessions) == 0 and PlaceServerObj.serverRunningTime > 60:
logging.info(f"CloseJob : Closing {JobId} for place [{PlaceServerObj.serverPlaceId}] because there was no players in the server for more than 60 seconds")
perform_post(
TargetGameserver = PlaceServerOwner,
Endpoint = "CloseJob",
JSONData = {
"jobid": JobId,
}
)
logging.info(f"Server ( {JobId} ) has been shutdown because there was no players in the server")
db.session.delete(PlaceServerObj)
db.session.commit()
return jsonify({"status": "success"}),200
PlaceServerObj.serverRunningTime = int(float(request.args.get('gameTime', '1', type=str))) + 1
PlaceServerObj.playerCount = len(GameSessions)
db.session.commit()
ClearPlayingCountCache(PlaceObject)
return jsonify({"status": "success"}),200
@JobReportHandler.route("/v2.0/Refresh", methods=['POST'])
@csrf.exempt
def Refresh():
return jsonify({"status": "success"}),200 # We don't care about this endpoint since the above endpoint will handle it
@JobReportHandler.route("/v1/Close/", methods=['POST'])
@csrf.exempt
def CloseJob():
AccessKey = request.args.get( key = 'apiKey', default = None, type = str)
JobId = request.args.get( key = 'gameId', default = None, type = str)
if AccessKey is None or JobId is None:
return abort(404)
if not redis_controller.exists(f"GameServerAccessKey:{AccessKey}:{JobId}"):
logging.warning(f"Invalid access key ( {AccessKey} ) for server ( {JobId} )")
return abort(404)
PlaceServerObj : PlaceServer = PlaceServer.query.filter_by(serveruuid=JobId).first()
if PlaceServerObj is not None:
AllPlaceServerPlayers : list[PlaceServerPlayer] = PlaceServerPlayer.query.filter_by(serveruuid=JobId).all()
for PlaceServerPlayerObj in AllPlaceServerPlayers:
TotalTimePlayed = (datetime.utcnow() - PlaceServerPlayerObj.joinTime).total_seconds()
UserObj : User = User.query.filter_by(id=PlaceServerPlayerObj.userid).first()
HandleUserTimePlayed(UserObj, TotalTimePlayed, serverUUID=JobId, placeId=PlaceServerObj.serverPlaceId)
db.session.delete(PlaceServerPlayerObj)
db.session.delete(PlaceServerObj)
db.session.commit()
return jsonify({"status": "success"}),200
@JobReportHandler.route("/game/report-water-sys", methods=['GET'])
@csrf.exempt
def ReportCheatersHandler():
RequestHost = request.headers.get('Host')
if RequestHost is None:
return abort(404)
if not RequestHost.startswith("gameinstances.api."):
return abort(404)
ReportingUserId = request.args.get( key = 'UserID', default = None, type = int)
if ReportingUserId is None:
return abort(404)
ReportingMessage = request.args.get( key = 'Message', default = None, type = str)
if ReportingMessage is None:
return abort(404)
AccessKey = request.args.get( key = 'AccessKey', default = '', type = str )
if "UserRequest" in AccessKey:
return abort(404)
ReportingRemoteAddress = get_remote_address()
ReportingGameServer : GameServer = GameServer.query.filter_by(serverIP=ReportingRemoteAddress).first()
if ReportingGameServer is None:
return abort(404)
UserObj : User = User.query.filter_by(id=ReportingUserId).first()
if UserObj is None:
return abort(404)
BannableErrorCodes = {
"carol": "Lua vm hooked (20)",
"murdle": "Cheat Engine Stable Methods (0)",
"olivia": "Debugger found (10)"
}
isBanned = False
if ReportingMessage.lower() in BannableErrorCodes:
LastestUserBanObj : UserBan = UserBan.query.filter_by(userid=UserObj.id, acknowledged = False).order_by(UserBan.id.desc()).first()
if LastestUserBanObj is None and UserObj.accountstatus == 1:
NewUserBanObj = UserBan(
userid = UserObj.id,
author_userid = 1,
ban_type = BanType.Deleted,
reason = "Exploiting in games is not tolerated on SYNTAX",
moderator_note = f"Automatic ban, received detection from gameserver. Error Code: {BannableErrorCodes[ReportingMessage.lower()]} / {BannableErrorCodes[ReportingMessage.lower()]}",
expires_at = None
)
db.session.add(NewUserBanObj)
UserObj.accountstatus = 3
db.session.commit()
isBanned = True
try:
requests.post(
url = config.CHEATER_REPORTS_DISCORD_WEBHOOK,
json = {
"content": f"Received Cheater Report from GameServer {ReportingGameServer.serverName} ( {ReportingGameServer.serverId} ) for User {UserObj.username} ( {UserObj.id} )\n```{ReportingMessage}```\n Was the user banned? **{isBanned}**",
"username": "Cheater Reports"
}
)
except Exception as e:
logging.error(f"jobreporthandler : ReportCheatersHandler: Failed to send cheater report to discord, {e}")
return "OK", 200
@JobReportHandler.route("/Game/ClientPresence.ashx", methods=['GET'])
def ClientPresence(): # Does nothing
return "OK", 200