# avatar.roblox.com import logging from flask import Blueprint, render_template, request, redirect, url_for, flash, session, abort, jsonify, make_response from app.util import auth from app.extensions import db, csrf, limiter, user_limiter from flask_wtf.csrf import CSRFError, generate_csrf from app.routes.thumbnailer import TakeUserThumbnail from app.models.user_avatar import UserAvatar from app.models.user_avatar_asset import UserAvatarAsset from app.models.user import User from app.models.asset import Asset from app.models.userassets import UserAsset from app.enums.AssetType import AssetType AvatarAPIRoute = Blueprint('avatarapi', __name__, url_prefix='/') csrf.exempt(AvatarAPIRoute) @AvatarAPIRoute.errorhandler(CSRFError) def handle_csrf_error(e): ErrorResponse = make_response(jsonify({ "errors": [ { "code": 0, "message": "Token Validation Failed" } ] })) ErrorResponse.status_code = 403 ErrorResponse.headers["x-csrf-token"] = generate_csrf() return ErrorResponse @AvatarAPIRoute.errorhandler(429) def handle_ratelimit_reached(e): return jsonify({ "errors": [ { "code": 9, "message": "The flood limit has been exceeded." } ] }), 429 @AvatarAPIRoute.before_request def before_request(): if "Roblox/" not in request.user_agent.string: csrf.protect() @AvatarAPIRoute.route("/v1/avatar", methods=["GET"]) @auth.authenticated_required_api @limiter.limit("60/minute") def get_authenticated_user_avatar(): AuthenticatedUser : User = auth.GetCurrentUser() UserAvatarObject : UserAvatar = UserAvatar.query.filter_by(user_id = AuthenticatedUser.id).first() if UserAvatarObject is None: UserAvatarObject = UserAvatar(AuthenticatedUser.id) db.session.add(UserAvatarObject) db.session.commit() UserAvatarAssetObject : list[UserAvatarAsset] = UserAvatarAsset.query.filter_by(user_id = AuthenticatedUser.id).all() WearingAssets : list[dict] = [] for AvatarAsset in UserAvatarAssetObject: AssetObject : Asset = AvatarAsset.asset WearingAssets.append({ "id": AvatarAsset.asset_id, "name": AssetObject.name, "assetType": { "id": AssetObject.asset_type.value, "name": AssetObject.asset_type.name }, "currentVersionId": AssetObject.id }) return jsonify({ "scales": { "height": UserAvatarObject.height_scale, "width": UserAvatarObject.width_scale, "head": UserAvatarObject.head_scale, "depth": 1, "proportion": UserAvatarObject.proportion_scale, "bodyType": UserAvatarObject.body_type_scale }, "playerAvatarType": "R6" if not UserAvatarObject.r15 else "R15", "bodyColors": { "headColorId": UserAvatarObject.head_color_id, "torsoColorId": UserAvatarObject.torso_color_id, "rightArmColorId": UserAvatarObject.right_arm_color_id, "leftArmColorId": UserAvatarObject.left_arm_color_id, "rightLegColorId": UserAvatarObject.right_leg_color_id, "leftLegColorId": UserAvatarObject.left_leg_color_id }, "assets": WearingAssets, "defaultShirtApplied": False, "defaultPantsApplied": False, "emotes": [] }) @AvatarAPIRoute.route("/v1/avatar/metadata", methods=["GET"]) @auth.authenticated_required_api @limiter.limit("60/minute") def get_avatar_editor_metadata(): return jsonify({ "enableDefaultClothingMessage": False, "isAvatarScaleEmbeddedInTab": True, "isBodyTypeScaleOutOfTab": True, "scaleHeightIncrement": 0.05, "scaleWidthIncrement": 0.05, "scaleHeadIncrement": 0.05, "scaleProportionIncrement": 0.05, "scaleBodyTypeIncrement": 0.05, "supportProportionAndBodyType": True, "showDefaultClothingMessageOnPageLoad": False, "areThreeDeeThumbsEnabled": False, "isAvatarWearingApiCallsLockingOnFrontendEnabled": True, "isOutfitHandlingOnFrontendEnabled": True, "isJustinUiChangesEnabled": True, "isCategoryReorgEnabled": True, "LCEnabledInEditorAndCatalog": True, "isLCCompletelyEnabled": True }) @AvatarAPIRoute.route("/v1/avatar-rules", methods=["GET"]) @auth.authenticated_required_api @limiter.limit("60/minute") def get_avatar_editor_rules(): return jsonify({"playerAvatarTypes":["R6","R15"],"scales":{"height":{"min":0.9,"max":1.05,"increment":0.01},"width":{"min":0.7,"max":1,"increment":0.01},"head":{"min":0.95,"max":1,"increment":0.01},"proportion":{"min":0,"max":1,"increment":0.01},"bodyType":{"min":0,"max":1,"increment":0.01}},"wearableAssetTypes":[{"maxNumber":1,"id":18,"name":"Face"},{"maxNumber":1,"id":19,"name":"Gear"},{"maxNumber":1,"id":17,"name":"Head"},{"maxNumber":1,"id":29,"name":"Left Arm"},{"maxNumber":1,"id":30,"name":"Left Leg"},{"maxNumber":1,"id":12,"name":"Pants"},{"maxNumber":1,"id":28,"name":"Right Arm"},{"maxNumber":1,"id":31,"name":"Right Leg"},{"maxNumber":1,"id":11,"name":"Shirt"},{"maxNumber":1,"id":2,"name":"T-Shirt"},{"maxNumber":1,"id":27,"name":"Torso"},{"maxNumber":1,"id":48,"name":"Climb Animation"},{"maxNumber":1,"id":49,"name":"Death Animation"},{"maxNumber":1,"id":50,"name":"Fall Animation"},{"maxNumber":1,"id":51,"name":"Idle Animation"},{"maxNumber":1,"id":52,"name":"Jump Animation"},{"maxNumber":1,"id":53,"name":"Run Animation"},{"maxNumber":1,"id":54,"name":"Swim Animation"},{"maxNumber":1,"id":55,"name":"Walk Animation"},{"maxNumber":1,"id":56,"name":"Pose Animation"},{"maxNumber":0,"id":61,"name":"Emote Animation"},{"maxNumber":3,"id":8,"name":"Hat"},{"maxNumber":5,"id":41,"name":"Hair Accessory"},{"maxNumber":5,"id":42,"name":"Face Accessory"},{"maxNumber":1,"id":43,"name":"Neck Accessory"},{"maxNumber":1,"id":44,"name":"Shoulder Accessory"},{"maxNumber":1,"id":45,"name":"Front Accessory"},{"maxNumber":1,"id":46,"name":"Back Accessory"},{"maxNumber":1,"id":47,"name":"Waist Accessory"},{"maxNumber":1,"id":72,"name":"Dress Skirt Accessory"},{"maxNumber":1,"id":67,"name":"Jacket Accessory"},{"maxNumber":1,"id":70,"name":"Left Shoe Accessory"},{"maxNumber":1,"id":71,"name":"Right Shoe Accessory"},{"maxNumber":1,"id":66,"name":"Pants Accessory"},{"maxNumber":1,"id":65,"name":"Shirt Accessory"},{"maxNumber":1,"id":69,"name":"Shorts Accessory"},{"maxNumber":1,"id":68,"name":"Sweater Accessory"},{"maxNumber":1,"id":64,"name":"T-Shirt Accessory"},{"maxNumber":1,"id":76,"name":"Eyebrow Accessory"},{"maxNumber":1,"id":77,"name":"Eyelash Accessory"},{"maxNumber":1,"id":78,"name":"Mood Animation"},{"maxNumber":1,"id":79,"name":"Dynamic Head"}],"bodyColorsPalette":[{"brickColorId":361,"hexColor":"#564236","name":"Dirt brown"},{"brickColorId":192,"hexColor":"#694028","name":"Reddish brown"},{"brickColorId":217,"hexColor":"#7C5C46","name":"Brown"},{"brickColorId":153,"hexColor":"#957977","name":"Sand red"},{"brickColorId":359,"hexColor":"#AF9483","name":"Linen"},{"brickColorId":352,"hexColor":"#C7AC78","name":"Burlap"},{"brickColorId":5,"hexColor":"#D7C59A","name":"Brick yellow"},{"brickColorId":101,"hexColor":"#DA867A","name":"Medium red"},{"brickColorId":1007,"hexColor":"#A34B4B","name":"Dusty Rose"},{"brickColorId":1014,"hexColor":"#AA5500","name":"CGA brown"},{"brickColorId":38,"hexColor":"#A05F35","name":"Dark orange"},{"brickColorId":18,"hexColor":"#CC8E69","name":"Nougat"},{"brickColorId":125,"hexColor":"#EAB892","name":"Light orange"},{"brickColorId":1030,"hexColor":"#FFCC99","name":"Pastel brown"},{"brickColorId":133,"hexColor":"#D5733D","name":"Neon orange"},{"brickColorId":106,"hexColor":"#DA8541","name":"Bright orange"},{"brickColorId":105,"hexColor":"#E29B40","name":"Br. yellowish orange"},{"brickColorId":1017,"hexColor":"#FFAF00","name":"Deep orange"},{"brickColorId":24,"hexColor":"#F5CD30","name":"Bright yellow"},{"brickColorId":334,"hexColor":"#F8D96D","name":"Daisy orange"},{"brickColorId":226,"hexColor":"#FDEA8D","name":"Cool yellow"},{"brickColorId":141,"hexColor":"#27462D","name":"Earth green"},{"brickColorId":1021,"hexColor":"#3A7D15","name":"Camo"},{"brickColorId":28,"hexColor":"#287F47","name":"Dark green"},{"brickColorId":37,"hexColor":"#4B974B","name":"Bright green"},{"brickColorId":310,"hexColor":"#5B9A4C","name":"Shamrock"},{"brickColorId":317,"hexColor":"#7C9C6B","name":"Moss"},{"brickColorId":119,"hexColor":"#A4BD47","name":"Br. yellowish green"},{"brickColorId":1011,"hexColor":"#002060","name":"Navy blue"},{"brickColorId":1012,"hexColor":"#2154B9","name":"Deep blue"},{"brickColorId":1010,"hexColor":"#0000FF","name":"Really blue"},{"brickColorId":23,"hexColor":"#0D69AC","name":"Bright blue"},{"brickColorId":305,"hexColor":"#527CAE","name":"Steel blue"},{"brickColorId":102,"hexColor":"#6E99CA","name":"Medium blue"},{"brickColorId":45,"hexColor":"#B4D2E4","name":"Light blue"},{"brickColorId":107,"hexColor":"#008F9C","name":"Bright bluish green"},{"brickColorId":1018,"hexColor":"#12EED4","name":"Teal"},{"brickColorId":1027,"hexColor":"#9FF3E9","name":"Pastel blue-green"},{"brickColorId":1019,"hexColor":"#00FFFF","name":"Toothpaste"},{"brickColorId":1013,"hexColor":"#04AFEC","name":"Cyan"},{"brickColorId":11,"hexColor":"#80BBDC","name":"Pastel Blue"},{"brickColorId":1024,"hexColor":"#AFDDFF","name":"Pastel light blue"},{"brickColorId":104,"hexColor":"#6B327C","name":"Bright violet"},{"brickColorId":1023,"hexColor":"#8C5B9F","name":"Lavender"},{"brickColorId":321,"hexColor":"#A75E9B","name":"Lilac"},{"brickColorId":1015,"hexColor":"#AA00AA","name":"Magenta"},{"brickColorId":1031,"hexColor":"#6225D1","name":"Royal purple"},{"brickColorId":1006,"hexColor":"#B480FF","name":"Alder"},{"brickColorId":1026,"hexColor":"#B1A7FF","name":"Pastel violet"},{"brickColorId":21,"hexColor":"#C4281C","name":"Bright red"},{"brickColorId":1004,"hexColor":"#FF0000","name":"Really red"},{"brickColorId":1032,"hexColor":"#FF00BF","name":"Hot pink"},{"brickColorId":1016,"hexColor":"#FF66CC","name":"Pink"},{"brickColorId":330,"hexColor":"#FF98DC","name":"Carnation pink"},{"brickColorId":9,"hexColor":"#E8BAC8","name":"Light reddish violet"},{"brickColorId":1025,"hexColor":"#FFC9C9","name":"Pastel orange"},{"brickColorId":364,"hexColor":"#5A4C42","name":"Dark taupe"},{"brickColorId":351,"hexColor":"#BC9B5D","name":"Cork"},{"brickColorId":1008,"hexColor":"#C1BE42","name":"Olive"},{"brickColorId":29,"hexColor":"#A1C48C","name":"Medium green"},{"brickColorId":1022,"hexColor":"#7F8E64","name":"Grime"},{"brickColorId":151,"hexColor":"#789082","name":"Sand green"},{"brickColorId":135,"hexColor":"#74869D","name":"Sand blue"},{"brickColorId":1020,"hexColor":"#00FF00","name":"Lime green"},{"brickColorId":1028,"hexColor":"#CCFFCC","name":"Pastel green"},{"brickColorId":1009,"hexColor":"#FFFF00","name":"New Yeller"},{"brickColorId":1029,"hexColor":"#FFFFCC","name":"Pastel yellow"},{"brickColorId":1003,"hexColor":"#111111","name":"Really black"},{"brickColorId":26,"hexColor":"#1B2A35","name":"Black"},{"brickColorId":199,"hexColor":"#635F62","name":"Dark stone grey"},{"brickColorId":194,"hexColor":"#A3A2A5","name":"Medium stone grey"},{"brickColorId":1002,"hexColor":"#CDCDCD","name":"Mid gray"},{"brickColorId":208,"hexColor":"#E5E4DF","name":"Light stone grey"},{"brickColorId":1,"hexColor":"#F2F3F3","name":"White"},{"brickColorId":1001,"hexColor":"#F8F8F8","name":"Institutional white"}],"basicBodyColorsPalette":[{"brickColorId":364,"hexColor":"#5A4C42","name":"Dark taupe"},{"brickColorId":217,"hexColor":"#7C5C46","name":"Brown"},{"brickColorId":359,"hexColor":"#AF9483","name":"Linen"},{"brickColorId":18,"hexColor":"#CC8E69","name":"Nougat"},{"brickColorId":125,"hexColor":"#EAB892","name":"Light orange"},{"brickColorId":361,"hexColor":"#564236","name":"Dirt brown"},{"brickColorId":192,"hexColor":"#694028","name":"Reddish brown"},{"brickColorId":351,"hexColor":"#BC9B5D","name":"Cork"},{"brickColorId":352,"hexColor":"#C7AC78","name":"Burlap"},{"brickColorId":5,"hexColor":"#D7C59A","name":"Brick yellow"},{"brickColorId":153,"hexColor":"#957977","name":"Sand red"},{"brickColorId":1007,"hexColor":"#A34B4B","name":"Dusty Rose"},{"brickColorId":101,"hexColor":"#DA867A","name":"Medium red"},{"brickColorId":1025,"hexColor":"#FFC9C9","name":"Pastel orange"},{"brickColorId":330,"hexColor":"#FF98DC","name":"Carnation pink"},{"brickColorId":135,"hexColor":"#74869D","name":"Sand blue"},{"brickColorId":305,"hexColor":"#527CAE","name":"Steel blue"},{"brickColorId":11,"hexColor":"#80BBDC","name":"Pastel Blue"},{"brickColorId":1026,"hexColor":"#B1A7FF","name":"Pastel violet"},{"brickColorId":321,"hexColor":"#A75E9B","name":"Lilac"},{"brickColorId":107,"hexColor":"#008F9C","name":"Bright bluish green"},{"brickColorId":310,"hexColor":"#5B9A4C","name":"Shamrock"},{"brickColorId":317,"hexColor":"#7C9C6B","name":"Moss"},{"brickColorId":29,"hexColor":"#A1C48C","name":"Medium green"},{"brickColorId":105,"hexColor":"#E29B40","name":"Br. yellowish orange"},{"brickColorId":24,"hexColor":"#F5CD30","name":"Bright yellow"},{"brickColorId":334,"hexColor":"#F8D96D","name":"Daisy orange"},{"brickColorId":199,"hexColor":"#635F62","name":"Dark stone grey"},{"brickColorId":1002,"hexColor":"#CDCDCD","name":"Mid gray"},{"brickColorId":1001,"hexColor":"#F8F8F8","name":"Institutional white"}],"minimumDeltaEBodyColorDifference":11.4,"proportionsAndBodyTypeEnabledForUser":True,"defaultClothingAssetLists":{"defaultShirtAssetIds":[855776103,855760101,855766176,855777286,855768342,855779323,855773575,855778084],"defaultPantAssetIds":[855783877,855780360,855781078,855782781,855781508,855785499,855782253,855784936]},"bundlesEnabledForUser":False,"emotesEnabledForUser":False}) @AvatarAPIRoute.route("/v1/avatar/set-player-avatar-type", methods=["POST"]) @auth.authenticated_required_api @limiter.limit("30/minute") @user_limiter.limit("30/minute") def set_player_avatar_type(): AuthenticatedUser : User = auth.GetCurrentUser() if not request.is_json: return jsonify({"errors": [{"code": 0, "message": "Invalid JSON"}]}), 400 if "playerAvatarType" not in request.json: return jsonify({"errors": [{"code": 0, "message": "Invalid JSON"}]}), 400 if request.json["playerAvatarType"] not in ["R6", "R15"]: return jsonify({"errors": [{"code": 0, "message": "Invalid JSON"}]}), 400 UserAvatarObject : UserAvatar = UserAvatar.query.filter_by(user_id = AuthenticatedUser.id).first() if UserAvatarObject is None: UserAvatarObject = UserAvatar(AuthenticatedUser.id) db.session.add(UserAvatarObject) db.session.commit() UserAvatarObject.r15 = True if request.json["playerAvatarType"] == "R15" else False db.session.commit() TakeUserThumbnail(AuthenticatedUser.id) return jsonify({ "success": True }) @AvatarAPIRoute.route("/v1/avatar/set-scales", methods=["POST"]) @auth.authenticated_required_api @limiter.limit("30/minute") @user_limiter.limit("30/minute") def set_player_avatar_scale(): AuthenticatedUser : User = auth.GetCurrentUser() if not request.is_json: return jsonify({"errors": [{"code": 0, "message": "Invalid JSON"}]}), 400 if "height" not in request.json or "width" not in request.json or "head" not in request.json or "proportion" not in request.json or "bodyType" not in request.json: return jsonify({"errors": [{"code": 0, "message": "Invalid JSON"}]}), 400 AvatarScales = {} try: AvatarScales["height"] = float(request.json["height"]) AvatarScales["width"] = float(request.json["width"]) AvatarScales["head"] = float(request.json["head"]) AvatarScales["proportion"] = float(request.json["proportion"]) AvatarScales["bodyType"] = float(request.json["bodyType"]) except ValueError: return jsonify({"errors": [{"code": 0, "message": "Invalid JSON"}]}), 400 if AvatarScales["height"] < 0.9 or AvatarScales["height"] > 1.05 or AvatarScales["width"] < 0.7 or AvatarScales["width"] > 1 or AvatarScales["head"] < 0.95 or AvatarScales["head"] > 1 or AvatarScales["proportion"] < 0 or AvatarScales["proportion"] > 1 or AvatarScales["bodyType"] < 0 or AvatarScales["bodyType"] > 1: return jsonify({"errors": [{"code": 0, "message": "Invalid JSON"}]}), 400 UserAvatarObject : UserAvatar = UserAvatar.query.filter_by(user_id = AuthenticatedUser.id).first() if UserAvatarObject is None: UserAvatarObject = UserAvatar(AuthenticatedUser.id) db.session.add(UserAvatarObject) db.session.commit() UserAvatarObject.height_scale = AvatarScales["height"] UserAvatarObject.width_scale = AvatarScales["width"] UserAvatarObject.head_scale = AvatarScales["head"] UserAvatarObject.proportion_scale = AvatarScales["proportion"] UserAvatarObject.body_type_scale = AvatarScales["bodyType"] db.session.commit() TakeUserThumbnail(AuthenticatedUser.id) return jsonify({ "success": True }) AllowedBodyColors = [361, 192, 217, 153, 359, 352, 5, 101, 1007, 1014, 38, 18, 125, 1030, 133, 106, 105, 1017, 24, 334, 226, 141, 1021, 28, 37, 310, 317, 119, 1011, 1012, 1010, 23, 305, 102, 45, 107, 1018, 1027, 1019, 1013, 11, 1024, 104, 1023, 321, 1015, 1031, 1006, 1026, 21, 1004, 1032, 1016, 330, 9, 1025, 364, 351, 1008, 29, 1022, 151, 135, 1020, 1028, 1009, 1029, 1003, 26, 199, 194, 1002, 208, 1, 1001] @AvatarAPIRoute.route("/v1/avatar/set-body-colors", methods=["POST"]) @auth.authenticated_required_api @limiter.limit("30/minute") @user_limiter.limit("30/minute") def set_player_avatar_body_colors(): AuthenticatedUser : User = auth.GetCurrentUser() if not request.is_json: return jsonify({"errors": [{"code": 0, "message": "Invalid JSON"}]}), 400 if "headColorId" not in request.json or "torsoColorId" not in request.json or "rightArmColorId" not in request.json or "leftArmColorId" not in request.json or "rightLegColorId" not in request.json or "leftLegColorId" not in request.json: return jsonify({"errors": [{"code": 0, "message": "Invalid JSON"}]}), 400 if not isinstance(request.json["headColorId"], int) or not isinstance(request.json["torsoColorId"], int) or not isinstance(request.json["rightArmColorId"], int) or not isinstance(request.json["leftArmColorId"], int) or not isinstance(request.json["rightLegColorId"], int) or not isinstance(request.json["leftLegColorId"], int): return jsonify({"errors": [{"code": 0, "message": "Invalid JSON"}]}), 400 if request.json["headColorId"] not in AllowedBodyColors or request.json["torsoColorId"] not in AllowedBodyColors or request.json["rightArmColorId"] not in AllowedBodyColors or request.json["leftArmColorId"] not in AllowedBodyColors or request.json["rightLegColorId"] not in AllowedBodyColors or request.json["leftLegColorId"] not in AllowedBodyColors: return jsonify({"errors": [{"code": 0, "message": "Invalid JSON"}]}), 400 UserAvatarObject : UserAvatar = UserAvatar.query.filter_by(user_id = AuthenticatedUser.id).first() if UserAvatarObject is None: UserAvatarObject = UserAvatar(AuthenticatedUser.id) db.session.add(UserAvatarObject) db.session.commit() UserAvatarObject.head_color_id = request.json["headColorId"] UserAvatarObject.torso_color_id = request.json["torsoColorId"] UserAvatarObject.right_arm_color_id = request.json["rightArmColorId"] UserAvatarObject.left_arm_color_id = request.json["leftArmColorId"] UserAvatarObject.right_leg_color_id = request.json["rightLegColorId"] UserAvatarObject.left_leg_color_id = request.json["leftLegColorId"] db.session.commit() TakeUserThumbnail(AuthenticatedUser.id) return jsonify({ "success": True }) @AvatarAPIRoute.route("/v1/avatar/set-wearing-assets", methods=["POST"]) @auth.authenticated_required_api @limiter.limit("30/minute") @user_limiter.limit("30/minute") def set_player_avatar_wearing_assets(): AuthenticatedUser : User = auth.GetCurrentUser() if not request.is_json: return jsonify({"errors": [{"code": 0, "message": "Invalid JSON"}]}), 400 if "assetIds" not in request.json: return jsonify({"errors": [{"code": 0, "message": "Invalid JSON"}]}), 400 if not isinstance(request.json["assetIds"], list): return jsonify({"errors": [{"code": 0, "message": "Invalid JSON"}]}), 400 InvalidAssetIds : list[int] = [] AssetTypeCounter : dict[int, int] = {} AllowedAssetIds : list[int] = [] for AssetId in request.json["assetIds"]: try: AssetId = int(AssetId) except ValueError: return jsonify({"errors": [{"code": 0, "message": "Invalid JSON"}]}), 400 if AssetId in InvalidAssetIds or AssetId in AllowedAssetIds: continue UserAssetObj : UserAsset = UserAsset.query.filter_by(userid = AuthenticatedUser.id, assetid = AssetId).first() if UserAssetObj is None: InvalidAssetIds.append(AssetId) continue AssetObj : Asset = UserAssetObj.asset if AssetObj.moderation_status != 0: InvalidAssetIds.append(AssetId) continue if AssetObj.asset_type.value not in [2,8,11,12,17,18,19,27,28,29,30,31,32,41,42,43,44,45,46,47,57,58]: return jsonify({"errors": [{"code": 3, "message": "Invalid AssetId"}]}), 400 if AssetObj.asset_type.value not in AssetTypeCounter: AssetTypeCounter[AssetObj.asset_type.value] = 0 if AssetTypeCounter[AssetObj.asset_type.value] >= 1 and AssetObj.asset_type != AssetType.Hat: return jsonify({"errors": [{"code": 3, "message": "Invalid AssetId"}]}), 400 if AssetTypeCounter[AssetObj.asset_type.value] >= 3 and AssetObj.asset_type == AssetType.Hat: return jsonify({"errors": [{"code": 3, "message": "Invalid AssetId"}]}), 400 AssetTypeCounter[AssetObj.asset_type.value] += 1 AllowedAssetIds.append(AssetId) UserAvatarAsset.query.filter_by(user_id = AuthenticatedUser.id).delete() db.session.commit() for AssetId in AllowedAssetIds: NewAvatarAssetObj : UserAvatarAsset = UserAvatarAsset( user_id = AuthenticatedUser.id, asset_id = AssetId ) db.session.add(NewAvatarAssetObj) db.session.commit() TakeUserThumbnail(AuthenticatedUser.id) return jsonify({ "invalidAssetIds": InvalidAssetIds, "success": True })