652 lines
27 KiB
Python
652 lines
27 KiB
Python
from flask import Blueprint, render_template, request, redirect, url_for, flash, make_response, jsonify, abort, Response
|
|
import hashlib
|
|
import gzip
|
|
import json
|
|
from PIL import Image
|
|
from app.models.groups import GroupIcon
|
|
from app.models.user_thumbnail import UserThumbnail
|
|
from app.models.asset_thumbnail import AssetThumbnail
|
|
from app.models.place_icon import PlaceIcon
|
|
from app.models.user import User
|
|
from app.models.asset import Asset
|
|
from app.routes.thumbnailer import TakeThumbnail
|
|
from app.util import s3helper
|
|
from app.extensions import redis_controller, csrf
|
|
from config import Config
|
|
from io import BytesIO
|
|
|
|
config = Config()
|
|
ImageRoute = Blueprint('image', __name__)
|
|
|
|
def HandleResolutionCheck(
|
|
WidthParametersName : list[ str ] = [ 'width', 'x' ],
|
|
HeightParametersName : list[ str ] = [ 'height', 'y' ],
|
|
AllowedWidths : list[ int ] = [ 48, 180, 420, 60, 100, 150, 352, 200, 500 ],
|
|
AllowedHeights : list[ int ] = [ 48, 180, 420, 60, 100, 150, 352, 200, 500 ],
|
|
MustBeSquare : bool = True,
|
|
CanRoundToNearest : bool = True
|
|
) -> ( int, int ):
|
|
"""
|
|
Handles resolution checking for images. Aborts the request if the resolution is invalid.
|
|
Must be called in a flask request context.
|
|
|
|
:param WidthParametersName: The names of valid parameters for width.
|
|
:param HeightParametersName: The names of valid parameters for height.
|
|
:param AllowedWidths: The allowed widths.
|
|
:param AllowedHeights: The allowed heights.
|
|
:param MustBeSquare: Whether or not the image must be square.
|
|
:param CanRoundToNearest: Whether or not the image can round to the nearest resolution.
|
|
|
|
:return: ( width, height )
|
|
"""
|
|
Width : int = None
|
|
Height : int = None
|
|
for WidthParameterName in WidthParametersName:
|
|
if WidthParameterName in request.args:
|
|
Width = request.args.get( key=WidthParameterName, default=None, type=int )
|
|
break
|
|
for HeightParameterName in HeightParametersName:
|
|
if HeightParameterName in request.args:
|
|
Height = request.args.get( key=HeightParameterName, default=None, type=int )
|
|
break
|
|
|
|
|
|
if Width is None or Height is None:
|
|
abort( 400 )
|
|
if ( Width not in AllowedWidths or Height not in AllowedHeights ) and not CanRoundToNearest:
|
|
abort( 400 )
|
|
if MustBeSquare and Width != Height:
|
|
abort( 400 )
|
|
|
|
|
|
if CanRoundToNearest:
|
|
if Width not in AllowedWidths:
|
|
Width = min( AllowedWidths, key=lambda x:abs(x-Width) )
|
|
if Height not in AllowedHeights:
|
|
Height = min( AllowedHeights, key=lambda x:abs(x-Height) )
|
|
|
|
return ( Width, Height )
|
|
|
|
def HandleImageResize(
|
|
ImageContentHash : str,
|
|
TargetWidth : int,
|
|
TargetHeight : int,
|
|
CroppedHash : str,
|
|
CacheControl : str = "max-age=120",
|
|
SkipCacheCroppedImage : bool = False,
|
|
ReturnAsJSON : bool = False
|
|
) -> Response:
|
|
"""
|
|
Handles image resizing
|
|
|
|
:param ImageContentHash: The content hash of the image.
|
|
:param TargetWidth: The target width.
|
|
:param TargetHeight: The target height.
|
|
:param CroppedHash: The hash of the cropped image.
|
|
|
|
:return: Flask Response
|
|
"""
|
|
if s3helper.DoesKeyExist(CroppedHash) and not SkipCacheCroppedImage:
|
|
if ReturnAsJSON:
|
|
return jsonify({
|
|
"Final": True,
|
|
"Url": f"{config.CDN_URL}/{CroppedHash}"
|
|
})
|
|
ImageResponse = make_response(redirect(f"{config.CDN_URL}/{CroppedHash}"))
|
|
ImageResponse.headers['Cache-Control'] = CacheControl
|
|
return ImageResponse
|
|
|
|
if not s3helper.DoesKeyExist(ImageContentHash):
|
|
if ReturnAsJSON:
|
|
return jsonify({
|
|
"Final": False,
|
|
"Url": "/static/img/placeholder.png"
|
|
})
|
|
return redirect("/static/img/placeholder.png")
|
|
|
|
ImageContent = BytesIO( s3helper.GetFileFromS3(ImageContentHash) )
|
|
ImageObj = Image.open(ImageContent)
|
|
ImageObj = ImageObj.resize((int(TargetWidth),int(TargetHeight))).convert('RGBA')
|
|
|
|
VirtualFile = BytesIO()
|
|
ImageObj.save(VirtualFile, "PNG")
|
|
VirtualFile.seek(0)
|
|
s3helper.UploadBytesToS3(VirtualFile.getvalue(), CroppedHash, contentType="image/png")
|
|
|
|
if ReturnAsJSON:
|
|
return jsonify({
|
|
"Final": True,
|
|
"Url": f"{config.CDN_URL}/{CroppedHash}"
|
|
})
|
|
ImageResponse = make_response(redirect(f"{config.CDN_URL}/{CroppedHash}"))
|
|
ImageResponse.headers['Cache-Control'] = CacheControl
|
|
return ImageResponse
|
|
|
|
@ImageRoute.route("/avatar-thumbnail/image", methods=["GET"])
|
|
@ImageRoute.route('/Thumbs/Avatar.ashx', methods=['GET'])
|
|
@ImageRoute.route('/thumbs/avatar.ashx', methods=['GET'])
|
|
def avatar():
|
|
userId = request.args.get('userId', default = None, type = int)
|
|
username = request.args.get('username', default = None, type = str)
|
|
|
|
if (userId is None and username is None):
|
|
return redirect("/static/img/placeholder.png")
|
|
|
|
TargetX, TargetY = HandleResolutionCheck(
|
|
WidthParametersName = [ 'x', 'width' ],
|
|
HeightParametersName = [ 'y', 'height' ],
|
|
AllowedWidths = [ 48, 180, 420, 60, 100, 150, 352, 200, 500 ],
|
|
AllowedHeights = [ 48, 180, 420, 60, 100, 150, 352, 200, 500 ],
|
|
MustBeSquare = True,
|
|
CanRoundToNearest = True
|
|
)
|
|
|
|
if username is not None and userId is None:
|
|
UserObj : User = User.query.filter_by(username=username).first()
|
|
if UserObj is None:
|
|
return redirect("/static/img/placeholder.png")
|
|
userId = UserObj.id
|
|
|
|
ThumbnailObj : UserThumbnail = UserThumbnail.query.filter_by(userid=userId).first()
|
|
if ThumbnailObj is None:
|
|
return redirect("/static/img/placeholder.png")
|
|
|
|
ContentHash = ThumbnailObj.full_contenthash
|
|
CroppedHash = hashlib.sha512(f"{ContentHash}-{TargetX}-{TargetY}-v3".encode('utf-8')).hexdigest()
|
|
|
|
return HandleImageResize(
|
|
ImageContentHash = ContentHash,
|
|
TargetWidth = TargetX,
|
|
TargetHeight = TargetY,
|
|
CroppedHash = CroppedHash,
|
|
CacheControl = "max-age=120"
|
|
)
|
|
|
|
@ImageRoute.route('/avatar-thumbnail/json', methods=['GET'])
|
|
def avatar_json():
|
|
userId = request.args.get('userId', None, type=int)
|
|
if userId is None:
|
|
return jsonify({
|
|
"Final": False,
|
|
"Url": "/static/img/placeholder.png"
|
|
})
|
|
|
|
TargetWidth, TargetHeight = HandleResolutionCheck(
|
|
WidthParametersName = [ 'width', 'x' ],
|
|
HeightParametersName = [ 'height', 'y' ],
|
|
AllowedWidths = [ 48, 180, 420, 60, 100, 150, 352, 200, 500 ],
|
|
AllowedHeights = [ 48, 180, 420, 60, 100, 150, 352, 200, 500 ],
|
|
MustBeSquare = True,
|
|
CanRoundToNearest = True
|
|
)
|
|
|
|
thumbnail : UserThumbnail = UserThumbnail.query.filter_by(userid=userId).first()
|
|
if thumbnail is None:
|
|
return jsonify({
|
|
"Final": False,
|
|
"Url": "/static/img/placeholder.png"
|
|
})
|
|
ContentHash = thumbnail.full_contenthash
|
|
CroppedHash = hashlib.sha512(f"{ContentHash}-{TargetWidth}-{TargetHeight}-v3".encode('utf-8')).hexdigest()
|
|
|
|
return HandleImageResize(
|
|
ImageContentHash = ContentHash,
|
|
TargetWidth = TargetWidth,
|
|
TargetHeight = TargetHeight,
|
|
CroppedHash = CroppedHash,
|
|
CacheControl = "max-age=120",
|
|
ReturnAsJSON = True
|
|
)
|
|
|
|
@ImageRoute.route('/headshot-thumbnail/image', methods=['GET'])
|
|
@ImageRoute.route('/Thumbs/Head.ashx', methods=['GET'])
|
|
def head():
|
|
userId = request.args.get('userId', default = None, type = int)
|
|
if userId is None:
|
|
return redirect("/static/img/placeholder.png")
|
|
|
|
TargetX, TargetY = HandleResolutionCheck(
|
|
WidthParametersName = [ 'x', 'width' ],
|
|
HeightParametersName = [ 'y', 'height' ],
|
|
AllowedWidths = [ 48, 180, 420, 60, 100, 150, 352, 200, 500 ],
|
|
AllowedHeights = [ 48, 180, 420, 60, 100, 150, 352, 200, 500 ],
|
|
MustBeSquare = True,
|
|
CanRoundToNearest = True
|
|
)
|
|
|
|
ThumbnailObj : UserThumbnail = UserThumbnail.query.filter_by(userid=userId).first()
|
|
if ThumbnailObj is None:
|
|
return redirect("/static/img/placeholder.png")
|
|
|
|
ContentHash = ThumbnailObj.headshot_contenthash
|
|
CroppedHash = hashlib.sha512(f"{ContentHash}-{TargetX}-{TargetY}-v3".encode('utf-8')).hexdigest()
|
|
|
|
return HandleImageResize(
|
|
ImageContentHash = ContentHash,
|
|
TargetWidth = TargetX,
|
|
TargetHeight = TargetY,
|
|
CroppedHash = CroppedHash,
|
|
CacheControl = "max-age=120"
|
|
)
|
|
|
|
@ImageRoute.route('/asset-thumbnail/json', methods=['GET'])
|
|
def asset_json():
|
|
assetId = request.args.get( 'assetId', None, type=int )
|
|
if assetId is None:
|
|
return jsonify({
|
|
"Final": False,
|
|
"Url": "/static/img/placeholder.png"
|
|
})
|
|
|
|
TargetWidth, TargetHeight = HandleResolutionCheck(
|
|
WidthParametersName = [ 'width', 'x' ],
|
|
HeightParametersName = [ 'height', 'y' ],
|
|
AllowedWidths = [48,180,420,60,100,150,352,396,480,512,576,700,768,640,360,1280,720],
|
|
AllowedHeights = [48,180,420,60,100,150,352,396,480,512,576,700,768,640,360,1280,720],
|
|
MustBeSquare = False,
|
|
CanRoundToNearest = True
|
|
)
|
|
|
|
thumbnailObj : AssetThumbnail = AssetThumbnail.query.filter_by(asset_id=assetId).order_by(AssetThumbnail.asset_version_id.desc()).first()
|
|
if thumbnailObj is None:
|
|
return jsonify({
|
|
"Final": False,
|
|
"Url": "/static/img/placeholder.png"
|
|
})
|
|
if thumbnailObj.moderation_status != 0 or thumbnailObj.asset.moderation_status != 0:
|
|
if thumbnailObj.moderation_status == 2 or thumbnailObj.asset.moderation_status == 2:
|
|
return jsonify({
|
|
"Final": True,
|
|
"Url": "/static/img/ContentDeleted.png"
|
|
})
|
|
return jsonify({
|
|
"Final": False,
|
|
"Url": "/static/img/placeholder.png"
|
|
})
|
|
|
|
ContentHash = thumbnailObj.content_hash
|
|
CroppedHash = hashlib.sha512(f"{ContentHash}-{TargetWidth}-{TargetHeight}-v3".encode('utf-8')).hexdigest()
|
|
|
|
return HandleImageResize(
|
|
ImageContentHash = ContentHash,
|
|
TargetWidth = TargetWidth,
|
|
TargetHeight = TargetHeight,
|
|
CroppedHash = CroppedHash,
|
|
CacheControl = "max-age=120",
|
|
ReturnAsJSON = True
|
|
)
|
|
|
|
@ImageRoute.route('/asset-thumbnail/image', methods=['GET'])
|
|
@ImageRoute.route('/thumbs/asset.ashx', methods=['GET'])
|
|
@ImageRoute.route('/Thumbs/Asset.ashx', methods=['GET'])
|
|
def asset():
|
|
assetId = request.args.get('assetId', default = None, type = int) or request.args.get('assetid', default = None, type = int)
|
|
if assetId is None:
|
|
return redirect("/static/img/placeholder.png")
|
|
|
|
TargetX, TargetY = HandleResolutionCheck(
|
|
WidthParametersName = [ 'x', 'width' ],
|
|
HeightParametersName = [ 'y', 'height' ],
|
|
AllowedWidths = [48,180,420,60,100,150,352,396,480,512,576,700,768,640,360,1280,720],
|
|
AllowedHeights = [48,180,420,60,100,150,352,396,480,512,576,700,768,640,36,1280,720],
|
|
MustBeSquare = False,
|
|
CanRoundToNearest = True
|
|
)
|
|
|
|
ThumbnailObj : AssetThumbnail = AssetThumbnail.query.filter_by(asset_id=assetId).order_by(AssetThumbnail.asset_version_id.desc()).first()
|
|
if ThumbnailObj is None:
|
|
return redirect("/static/img/placeholder.png")
|
|
if ThumbnailObj.moderation_status != 0 or ThumbnailObj.asset.moderation_status != 0:
|
|
if ThumbnailObj.moderation_status == 2 or ThumbnailObj.asset.moderation_status == 2:
|
|
return redirect("/static/img/ContentDeleted.png")
|
|
return redirect("/static/img/placeholder.png")
|
|
ContentHash = ThumbnailObj.content_hash
|
|
CroppedHash = hashlib.sha512(f"{ContentHash}-{TargetX}-{TargetY}-v3".encode('utf-8')).hexdigest()
|
|
|
|
return HandleImageResize(
|
|
ImageContentHash = ContentHash,
|
|
TargetWidth = TargetX,
|
|
TargetHeight = TargetY,
|
|
CroppedHash = CroppedHash,
|
|
CacheControl = "max-age=120"
|
|
)
|
|
|
|
@ImageRoute.route('/Thumbs/GroupIcon.ashx', methods=['GET'])
|
|
def groupicon():
|
|
groupid = request.args.get( key='groupid', default=None, type=int )
|
|
if groupid is None:
|
|
return redirect("/static/img/placeholder.png")
|
|
|
|
TargetX, TargetY = HandleResolutionCheck(
|
|
WidthParametersName = [ 'x', 'width' ],
|
|
HeightParametersName = [ 'y', 'height' ],
|
|
AllowedWidths = [48,180,420,60,100,150,352],
|
|
AllowedHeights = [48,180,420,60,100,150,352],
|
|
MustBeSquare = True,
|
|
CanRoundToNearest = True
|
|
)
|
|
|
|
ThumbnailObj : GroupIcon = GroupIcon.query.filter_by(group_id=groupid).first()
|
|
if ThumbnailObj is None:
|
|
return redirect("/static/img/placeholder.png")
|
|
if ThumbnailObj.moderation_status != 0:
|
|
if ThumbnailObj.moderation_status == 2:
|
|
return redirect("/static/img/ContentDeleted.png")
|
|
return redirect("/static/img/placeholder.png")
|
|
|
|
ContentHash = ThumbnailObj.content_hash
|
|
CroppedHash = hashlib.sha512(f"{ContentHash}-{TargetX}-{TargetY}-v3".encode('utf-8')).hexdigest()
|
|
|
|
return HandleImageResize(
|
|
ImageContentHash = ContentHash,
|
|
TargetWidth = TargetX,
|
|
TargetHeight = TargetY,
|
|
CroppedHash = CroppedHash,
|
|
CacheControl = "max-age=120"
|
|
)
|
|
|
|
@ImageRoute.route('/Thumbs/GameIcon.ashx', methods=['GET'])
|
|
@ImageRoute.route('/Thumbs/PlaceIcon.ashx', methods=['GET'])
|
|
def placeicon():
|
|
assetId = request.args.get('assetId', default = None, type = int) or request.args.get('assetid', default = None, type = int)
|
|
if assetId is None:
|
|
return redirect("/static/img/placeholder.png")
|
|
|
|
TargetX, TargetY = HandleResolutionCheck(
|
|
WidthParametersName = [ 'x', 'width' ],
|
|
HeightParametersName = [ 'y', 'height' ],
|
|
AllowedWidths = [48,180,420,60,100,150,352,324,576],
|
|
AllowedHeights = [48,180,420,60,100,150,352,324,576],
|
|
MustBeSquare = False,
|
|
CanRoundToNearest = True
|
|
)
|
|
|
|
PlaceIconObj : PlaceIcon = PlaceIcon.query.filter_by(placeid=assetId).first()
|
|
if PlaceIconObj is None:
|
|
return redirect("/static/img/placeholder.png")
|
|
if PlaceIconObj.moderation_status != 0 or PlaceIconObj.asset.moderation_status != 0:
|
|
if PlaceIconObj.moderation_status == 2 or PlaceIconObj.asset.moderation_status == 2:
|
|
return redirect("/static/img/ContentDeleted.png")
|
|
return redirect("/static/img/placeholder.png")
|
|
|
|
ContentHash = PlaceIconObj.contenthash
|
|
CroppedHash = hashlib.sha512(f"{ContentHash}-{TargetX}-{TargetY}-v3".encode('utf-8')).hexdigest()
|
|
|
|
return HandleImageResize(
|
|
ImageContentHash = ContentHash,
|
|
TargetWidth = TargetX,
|
|
TargetHeight = TargetY,
|
|
CroppedHash = CroppedHash,
|
|
CacheControl = "max-age=120"
|
|
)
|
|
|
|
@ImageRoute.route('/Game/Tools/ThumbnailAsset.ashx', methods=['GET'])
|
|
def thumbnail_asset():
|
|
ExpectedFormat : str = request.args.get( key='fmt', default='png', type=str )
|
|
AssetId : int = request.args.get( key='aid', default=None, type=int )
|
|
|
|
if AssetId is None:
|
|
return redirect("/static/img/placeholder.png")
|
|
if ExpectedFormat.lower() != "png":
|
|
return redirect("/static/img/placeholder.png")
|
|
|
|
AssetObj : Asset = Asset.query.filter_by(id=AssetId).first()
|
|
if AssetObj is None:
|
|
return redirect("/static/img/placeholder.png")
|
|
|
|
TargetWidth, TargetHeight = HandleResolutionCheck(
|
|
WidthParametersName = [ 'wd', 'width' ],
|
|
HeightParametersName = [ 'ht', 'height' ],
|
|
AllowedWidths = [48,180,420,60,100,150,352,75],
|
|
AllowedHeights = [48,180,420,60,100,150,352,75],
|
|
MustBeSquare = True,
|
|
CanRoundToNearest = True
|
|
)
|
|
|
|
thumbnail : AssetThumbnail = AssetThumbnail.query.filter_by(asset_id=AssetId).order_by(AssetThumbnail.asset_version_id.desc()).first()
|
|
if thumbnail is None:
|
|
TakeThumbnail(AssetId)
|
|
return redirect("/static/img/placeholder.png")
|
|
if thumbnail.moderation_status != 0:
|
|
if thumbnail.moderation_status == 2 or thumbnail.asset.moderation_status == 2:
|
|
return redirect("/static/img/ContentDeleted.png")
|
|
return redirect("/static/img/placeholder.png")
|
|
|
|
ContentHash = thumbnail.content_hash
|
|
CroppedHash = hashlib.sha512(f"{ContentHash}-{TargetWidth}-{TargetHeight}-v3".encode('utf-8')).hexdigest()
|
|
|
|
return HandleImageResize(
|
|
ImageContentHash = ContentHash,
|
|
TargetWidth = TargetWidth,
|
|
TargetHeight = TargetHeight,
|
|
CroppedHash = CroppedHash,
|
|
CacheControl = "max-age=120"
|
|
)
|
|
|
|
import urllib.parse
|
|
|
|
@ImageRoute.route("/v1/batch", methods=["POST"])
|
|
@csrf.exempt
|
|
def BatchImageRequest():
|
|
if request.headers.get("Content-Encoding") == "gzip":
|
|
try:
|
|
data = gzip.decompress(request.data)
|
|
except Exception as e:
|
|
return jsonify({"success": False, "message": "Invalid gzip data"}), 400
|
|
try:
|
|
JSONData = json.loads(data)
|
|
except Exception as e:
|
|
return jsonify({"success": False, "message": "Invalid JSON data"}), 400
|
|
else:
|
|
JSONData = request.json
|
|
if JSONData is None:
|
|
return jsonify({"success": False, "message": "Missing JSON data"}), 400
|
|
|
|
# [{'requestId': 'type=GameIcon&id=1&w=128&h=128&filters=', 'targetId': 1, 'type': 'GameIcon', 'size': '128x128', 'isCircular': False}]
|
|
|
|
if len(JSONData) > 15:
|
|
return jsonify({"success": False, "message": "Too many requests"}), 400
|
|
if len(JSONData) == 0:
|
|
return jsonify({"data":[]}), 200
|
|
|
|
ProcessedRequests = []
|
|
for RequestObj in JSONData:
|
|
if "requestId" not in RequestObj or "targetId" not in RequestObj or "type" not in RequestObj or "size" not in RequestObj:
|
|
continue
|
|
if RequestObj["type"] not in [ "Avatar", "AvatarHeadShot", "GameIcon", "GameThumbnail", "Asset", "GroupIcon"]:
|
|
continue
|
|
|
|
if "x" not in RequestObj["size"]:
|
|
continue
|
|
SplittedSize = RequestObj["size"].split("x")
|
|
if len(SplittedSize) != 2:
|
|
continue
|
|
try:
|
|
TargetWidth = int(SplittedSize[0])
|
|
TargetHeight = int(SplittedSize[1])
|
|
except:
|
|
continue
|
|
|
|
AllowedSizes = [48,180,420,60,100,150,352,396,480,512,576,700,768,640,36,1280,720]
|
|
TargetWidth = min(AllowedSizes, key=lambda x:abs(x-TargetWidth))
|
|
TargetHeight = min(AllowedSizes, key=lambda x:abs(x-TargetHeight))
|
|
|
|
RequestType = RequestObj["type"]
|
|
|
|
if RequestType == "Avatar":
|
|
ThumbnailObj : UserThumbnail = UserThumbnail.query.filter_by(userid=RequestObj["targetId"]).first()
|
|
if ThumbnailObj is None:
|
|
continue
|
|
ContentHash = ThumbnailObj.full_contenthash
|
|
elif RequestType == "AvatarHeadShot":
|
|
ThumbnailObj : UserThumbnail = UserThumbnail.query.filter_by(userid=RequestObj["targetId"]).first()
|
|
if ThumbnailObj is None:
|
|
continue
|
|
ContentHash = ThumbnailObj.headshot_contenthash
|
|
elif RequestType == "GameIcon":
|
|
PlaceIconObj : PlaceIcon = PlaceIcon.query.filter_by(placeid=RequestObj["targetId"]).first()
|
|
if PlaceIconObj is None:
|
|
continue
|
|
ContentHash = PlaceIconObj.contenthash
|
|
elif RequestType == "GameThumbnail" or RequestType == "Asset":
|
|
thumbnailObj : AssetThumbnail = AssetThumbnail.query.filter_by(asset_id=RequestObj["targetId"]).order_by(AssetThumbnail.asset_version_id.desc()).first()
|
|
if thumbnailObj is None:
|
|
continue
|
|
if thumbnailObj.moderation_status != 0:
|
|
continue
|
|
ContentHash = thumbnailObj.content_hash
|
|
elif RequestType == "GroupIcon":
|
|
ThumbnailObj : GroupIcon = GroupIcon.query.filter_by(group_id=RequestObj["targetId"]).first()
|
|
if ThumbnailObj is None:
|
|
continue
|
|
if ThumbnailObj.moderation_status != 0:
|
|
continue
|
|
ContentHash = ThumbnailObj.content_hash
|
|
else:
|
|
continue
|
|
CroppedHash = hashlib.sha512(f"{ContentHash}-{TargetWidth}-{TargetHeight}-v3".encode('utf-8')).hexdigest()
|
|
if not s3helper.DoesKeyExist(CroppedHash):
|
|
if not s3helper.DoesKeyExist(ContentHash):
|
|
continue
|
|
ImageContent = BytesIO( s3helper.GetFileFromS3(ContentHash) )
|
|
ImageObj = Image.open(ImageContent)
|
|
ImageObj = ImageObj.resize((int(TargetWidth),int(TargetHeight))).convert('RGBA')
|
|
|
|
VirtualFile = BytesIO()
|
|
ImageObj.save(VirtualFile, "PNG")
|
|
VirtualFile.seek(0)
|
|
s3helper.UploadBytesToS3(VirtualFile.getvalue(), CroppedHash, contentType="image/png")
|
|
ProcessedRequests.append({
|
|
"requestId": RequestObj["requestId"],
|
|
"targetId": RequestObj["targetId"],
|
|
"state": "Completed",
|
|
"imageUrl": f"{config.CDN_URL}/{CroppedHash}",
|
|
"version": None
|
|
})
|
|
|
|
return jsonify({
|
|
"data": ProcessedRequests
|
|
})
|
|
|
|
@ImageRoute.route("/v1/users/avatar-headshot", methods=["GET"])
|
|
def multi_avatar_headshot():
|
|
userIdsCSV = request.args.get('userIds', default = None, type = str)
|
|
if userIdsCSV is None:
|
|
return jsonify( { "errors": [ { "code": 4, "message": "The requested Ids are invalid, of an invalid type or missing." } ] } ), 400
|
|
|
|
userIds = userIdsCSV.split(",")
|
|
if len(userIds) > 100:
|
|
return jsonify( { "errors": [ { "code": 1, "message": "There are too many requested Ids." } ] } ), 400
|
|
|
|
requestedSize = request.args.get('size', default = "48x48", type = str)
|
|
|
|
if "x" not in requestedSize:
|
|
return jsonify( { "errors": [ { "code": 3, "message": "The requested size is invalid. Please see documentation for valid thumbnail size parameter name and format." } ] } ), 400
|
|
|
|
SplittedSize = requestedSize.split("x")
|
|
if len(SplittedSize) != 2:
|
|
return jsonify( { "errors": [ { "code": 3, "message": "The requested size is invalid. Please see documentation for valid thumbnail size parameter name and format." } ] } ), 400
|
|
|
|
try:
|
|
TargetWidth = int(SplittedSize[0])
|
|
TargetHeight = int(SplittedSize[1])
|
|
|
|
AllowedSizes = [48,180,420,60,100,150,352,396,480,512,576,700,768,640,36,1280,720]
|
|
TargetWidth = min(AllowedSizes, key=lambda x:abs(x-TargetWidth))
|
|
TargetHeight = min(AllowedSizes, key=lambda x:abs(x-TargetHeight))
|
|
except:
|
|
return jsonify( { "errors": [ { "code": 3, "message": "The requested size is invalid. Please see documentation for valid thumbnail size parameter name and format." } ] } ), 400
|
|
|
|
ProcessedRequests = []
|
|
for userId in userIds:
|
|
try:
|
|
userId = int(userId)
|
|
except:
|
|
continue
|
|
ThumbnailObj : UserThumbnail = UserThumbnail.query.filter_by(userid=userId).first()
|
|
if ThumbnailObj is None:
|
|
continue
|
|
ContentHash = ThumbnailObj.headshot_contenthash
|
|
CroppedHash = hashlib.sha512(f"{ContentHash}-{TargetWidth}-{TargetHeight}-v3".encode('utf-8')).hexdigest()
|
|
if not s3helper.DoesKeyExist(CroppedHash):
|
|
if not s3helper.DoesKeyExist(ContentHash):
|
|
continue
|
|
ImageContent = BytesIO( s3helper.GetFileFromS3(ContentHash) )
|
|
ImageObj = Image.open(ImageContent)
|
|
ImageObj = ImageObj.resize((int(TargetWidth),int(TargetHeight))).convert('RGBA')
|
|
|
|
VirtualFile = BytesIO()
|
|
ImageObj.save(VirtualFile, "PNG")
|
|
VirtualFile.seek(0)
|
|
s3helper.UploadBytesToS3(VirtualFile.getvalue(), CroppedHash, contentType="image/png")
|
|
ProcessedRequests.append({
|
|
"targetId": userId,
|
|
"state": "Completed",
|
|
#"imageUrl": f"{config.CDN_URL}/{CroppedHash}", # 2020 Does not allow things like cdn.syntax.eco to be used directly in the "Texture" property so we have to redirect them to an allowed route on the www. domain
|
|
"imageUrl": f"{config.BaseURL}/headshot-thumbnail/image?userId={userId}&x={TargetWidth}&y={TargetHeight}",
|
|
"version": "1"
|
|
})
|
|
|
|
return jsonify({
|
|
"data": ProcessedRequests
|
|
}), 200
|
|
|
|
@ImageRoute.route("/v1/games/icons", methods=["GET"])
|
|
def get_game_icons():
|
|
universeIdsCSV = request.args.get('universeIds', default = None, type = str)
|
|
if universeIdsCSV is None:
|
|
return jsonify( { "errors": [ { "code": 4, "message": "The requested Ids are invalid, of an invalid type or missing." } ] } ), 400
|
|
universeIdsList = universeIdsCSV.split(",")
|
|
if len(universeIdsList) > 100:
|
|
return jsonify( { "errors": [ { "code": 1, "message": "There are too many requested Ids." } ] } ), 400
|
|
|
|
requestedSize = request.args.get('size', default = "50x50", type = str)
|
|
if "x" not in requestedSize:
|
|
return jsonify( { "errors": [ { "code": 3, "message": "The requested size is invalid. Please see documentation for valid thumbnail size parameter name and format." } ] } ), 400
|
|
|
|
SplittedSize = requestedSize.split("x")
|
|
if len(SplittedSize) != 2:
|
|
return jsonify( { "errors": [ { "code": 3, "message": "The requested size is invalid. Please see documentation for valid thumbnail size parameter name and format." } ] } ), 400
|
|
|
|
try:
|
|
TargetWidth = int(SplittedSize[0])
|
|
TargetHeight = int(SplittedSize[1])
|
|
|
|
AllowedSizes = [50, 128, 150, 256, 420, 512]
|
|
TargetWidth = min(AllowedSizes, key=lambda x:abs(x-TargetWidth))
|
|
TargetHeight = min(AllowedSizes, key=lambda x:abs(x-TargetHeight))
|
|
except:
|
|
return jsonify( { "errors": [ { "code": 3, "message": "The requested size is invalid. Please see documentation for valid thumbnail size parameter name and format." } ] } ), 400
|
|
|
|
ProcessedRequests = []
|
|
for universeId in universeIdsList:
|
|
try:
|
|
universeId = int(universeId)
|
|
except:
|
|
continue
|
|
PlaceIconObj : PlaceIcon = PlaceIcon.query.filter_by(placeid=universeId).first()
|
|
if PlaceIconObj is None:
|
|
continue
|
|
ContentHash = PlaceIconObj.contenthash
|
|
CroppedHash = hashlib.sha512(f"{ContentHash}-{TargetWidth}-{TargetHeight}-v3".encode('utf-8')).hexdigest()
|
|
if not s3helper.DoesKeyExist(CroppedHash):
|
|
if not s3helper.DoesKeyExist(ContentHash):
|
|
continue
|
|
ImageContent = BytesIO( s3helper.GetFileFromS3(ContentHash) )
|
|
ImageObj = Image.open(ImageContent)
|
|
ImageObj = ImageObj.resize((int(TargetWidth),int(TargetHeight))).convert('RGBA')
|
|
|
|
VirtualFile = BytesIO()
|
|
ImageObj.save(VirtualFile, "PNG")
|
|
VirtualFile.seek(0)
|
|
s3helper.UploadBytesToS3(VirtualFile.getvalue(), CroppedHash, contentType="image/png")
|
|
ProcessedRequests.append({
|
|
"targetId": universeId,
|
|
"state": "Completed",
|
|
#"imageUrl": f"{config.CDN_URL}/{CroppedHash}"
|
|
"imageUrl": f"{config.BaseURL}/Thumbs/GameIcon.ashx?assetId={str(universeId)}&x={str(TargetWidth)}&y={str(TargetHeight)}"
|
|
})
|
|
|
|
return jsonify({
|
|
"data": ProcessedRequests
|
|
}), 200 |