789 lines
30 KiB
C#
789 lines
30 KiB
C#
using Microsoft.AspNetCore;
|
|
using Microsoft.AspNetCore.Http.HttpResults;
|
|
using Microsoft.AspNetCore.Mvc;
|
|
using Microsoft.Extensions.Configuration.UserSecrets;
|
|
using Newtonsoft.Json;
|
|
using SixLabors.ImageSharp.Metadata;
|
|
using SixLabors.ImageSharp.PixelFormats;
|
|
using SixLabors.ImageSharp;
|
|
using Vanadium.Auth;
|
|
using Vanadium.Classes;
|
|
using Vanadium.Classes.DBs;
|
|
using SixLabors.ImageSharp.Processing;
|
|
using SixLabors.ImageSharp.Formats.Png;
|
|
using Vanadium.Classes.DBs.DBClasses;
|
|
using Vanadium.Classes.Rooms;
|
|
using static Vanadium.Classes.ServerConfig;
|
|
using Vanadium.Utils;
|
|
using Vanadium.Utils.NotiController;
|
|
using System.Net.WebSockets;
|
|
using System.Security.Cryptography.X509Certificates;
|
|
using System.Text;
|
|
using System.Text.RegularExpressions;
|
|
using System.Text.Json;
|
|
using System.Xml;
|
|
using static ImageMetadataDB;
|
|
using static Vanadium.Classes.DBs.DBClasses.EventDBClasses;
|
|
using static Vanadium.Classes.DBs.DBClasses.FriendsDBClasses;
|
|
using static Vanadium.Classes.DBs.DBClasses.PlayerDBClasses;
|
|
|
|
namespace Vanadium.Controllers
|
|
{
|
|
[ApiController]
|
|
public class ImageController : ControllerBase
|
|
{
|
|
|
|
|
|
internal async Task SendImageHileWebhook(
|
|
long accountId,
|
|
string? message,
|
|
string? imageName = null,
|
|
string? headersPretty = null)
|
|
{
|
|
|
|
var embed = new Dictionary<string, object?>
|
|
{
|
|
["title"] = $"ID = {accountId}",
|
|
["color"] = 3066993,
|
|
["fields"] = new object[]
|
|
{
|
|
new { name = "Headers", value = headersPretty ?? Request.Headers.ToString(), inline = false },
|
|
new { name = "IP", value = HttpContext.Connection.RemoteIpAddress?.ToString() ?? "Unknown", inline = false },
|
|
new { name = "Image (in /Data/BadImages look at your own risk)", value = imageName ?? "No image provided", inline = true }
|
|
},
|
|
["footer"] = new { text = message ?? "No message provided" }
|
|
};
|
|
|
|
var payload = new
|
|
{
|
|
embeds = new[] { embed }
|
|
};
|
|
|
|
try
|
|
{
|
|
var content = new StringContent(
|
|
System.Text.Json.JsonSerializer.Serialize(payload),
|
|
Encoding.UTF8, "application/json");
|
|
await APIController._webhookClient.PostAsync(
|
|
"https://discord.com/api/webhooks/1544865393091547267/1ntP_8icrIw2O0OiLEt1Rk05lWANxOJksouFVQIcKLu9q9dFIBqSSxPBIkGn6KZz9dbp",
|
|
content);
|
|
}
|
|
catch { }
|
|
}
|
|
|
|
string lastContentType = "";
|
|
|
|
public static string GetHeadersPretty(HttpRequest request)
|
|
{
|
|
var sb = new StringBuilder();
|
|
foreach (var header in request.Headers)
|
|
{
|
|
sb.AppendLine($"{header.Key}: {header.Value}");
|
|
}
|
|
return sb.ToString();
|
|
}
|
|
|
|
[HttpPost("/api/images/v4/uploadsaved")]
|
|
public async Task<IActionResult> UploadSaved()
|
|
{
|
|
var id = AuthStuff.GetPlayerId(Request);
|
|
if (id == null)
|
|
return Unauthorized("");
|
|
|
|
var player = PlayerDB.GetCurrentPlayer(id.Value);
|
|
|
|
bool isDeveloper = player?.PlayerRoles?.Contains(PlayerRoles.Developer) ?? false;
|
|
|
|
string divertPaths = "";
|
|
|
|
var userAgent = Request.Headers.UserAgent.ToString();
|
|
if (!userAgent.Contains("BestHTTP", StringComparison.OrdinalIgnoreCase))
|
|
divertPaths = "Non-BestHTTP";
|
|
|
|
var contentType = Request.Headers.ContentType.ToString();
|
|
if (!contentType.Contains("BestHTTP", StringComparison.OrdinalIgnoreCase) && !isDeveloper)
|
|
divertPaths = "Non-image content type";
|
|
|
|
if (contentType == lastContentType)
|
|
divertPaths = "Repeated content type";
|
|
lastContentType = contentType;
|
|
|
|
if (!ServerConfig.PostingEnabled && !isDeveloper)
|
|
return StatusCode(403, new { success = false, error = "Images.UserIsAcessibilityLocked" });
|
|
|
|
if (!isDeveloper && !PlayerDB.TryIncrementDailyImageUpload(id.Value))
|
|
return StatusCode(403, new { success = false, error = "Images.DailyImagesUploadReached" });
|
|
|
|
if (PlayerDB.IsBanned(id.Value))
|
|
divertPaths = "Banned player uploaded image";
|
|
|
|
var form = await Request.ReadFormAsync();
|
|
var file = form.Files.FirstOrDefault();
|
|
if (file == null || file.Length == 0)
|
|
return BadRequest();
|
|
|
|
byte[] fileBytes;
|
|
using (var input = file.OpenReadStream())
|
|
using (var image = await SixLabors.ImageSharp.Image.LoadAsync<Rgba32>(input))
|
|
using (var output = new MemoryStream())
|
|
{
|
|
await image.SaveAsync(output, new SixLabors.ImageSharp.Formats.Png.PngEncoder());
|
|
fileBytes = output.ToArray();
|
|
}
|
|
|
|
bool tripped = string.IsNullOrWhiteSpace(divertPaths) ? false : true;
|
|
|
|
var fileName = ImageMetadataDB.SaveImageFile(fileBytes, player.PlayerId, tripped);
|
|
if (!form.TryGetValue("imgMeta", out var imgMetaValue) || string.IsNullOrWhiteSpace(imgMetaValue))
|
|
return BadRequest();
|
|
if (tripped)
|
|
{
|
|
await SendImageHileWebhook(player.PlayerId, divertPaths, fileName, GetHeadersPretty(Request));
|
|
return Ok(new { ImageName = fileName });
|
|
}
|
|
|
|
ImageMetadataDB.ImageMetadata? metadata;
|
|
try
|
|
{
|
|
metadata = System.Text.Json.JsonSerializer.Deserialize<ImageMetadataDB.ImageMetadata>(
|
|
imgMetaValue!,
|
|
new System.Text.Json.JsonSerializerOptions { PropertyNameCaseInsensitive = true });
|
|
}
|
|
catch
|
|
{
|
|
return BadRequest();
|
|
}
|
|
|
|
if (metadata == null)
|
|
return BadRequest();
|
|
|
|
var newImage = new ImageMetadataDB.FullSavedImage
|
|
{
|
|
RoomId = metadata.roomId,
|
|
PlayerId = (ulong)id.Value,
|
|
ImageName = fileName,
|
|
Description = metadata.description,
|
|
Accessibility = metadata.accessibility,
|
|
TaggedPlayerIds = metadata.playerIds ?? new List<ulong>(),
|
|
CreatedAt = DateTime.UtcNow,
|
|
Type = metadata.savedImageType
|
|
};
|
|
|
|
ImageMetadataDB.InsertImage(newImage);
|
|
|
|
string playerName = PlayerDB.GetCurrentPlayer((long)id.Value)?.Player?.DisplayName
|
|
?? $"User {id.Value}";
|
|
string playerId = $"{newImage.PlayerId}";
|
|
string roomName = RoomDB.GetRoom(metadata.roomId)?.Name
|
|
?? $"Room {metadata.roomId}";
|
|
|
|
if (metadata.accessibility == ImageMetadataDB.ImageAccessibility.Public)
|
|
{
|
|
try
|
|
{
|
|
using var http = new HttpClient();
|
|
var imageUrl = $"https://reloxa.xyz/imageserver/{fileName}";
|
|
var uploaderProfilePic = $"https://reloxa.xyz/imageserver/{PlayerDB.GetCurrentPlayer((long)newImage.PlayerId)?.Player?.ProfileImage ?? "DefaultPFP.png"}?cropSquare=true";
|
|
|
|
bool roomIsPrivate = false;
|
|
string roomDisplayName;
|
|
var room = RoomDB.GetRoom(metadata.roomId);
|
|
if (room == null || room.Accessibility != RoomDBClasses.RoomAccessibility.Public)
|
|
{
|
|
roomIsPrivate = true;
|
|
roomDisplayName = "[PRIVATE ROOM]";
|
|
}
|
|
else
|
|
{
|
|
roomDisplayName = $"^{room.Name}";
|
|
}
|
|
|
|
var payload = new
|
|
{
|
|
content = "",
|
|
tts = false,
|
|
embeds = new[]
|
|
{
|
|
new
|
|
{
|
|
author = new
|
|
{
|
|
name = $"{playerName} uploaded a photo!",
|
|
icon_url = uploaderProfilePic
|
|
},
|
|
title = $"Room - {roomDisplayName}",
|
|
image = new { url = imageUrl },
|
|
fields = Array.Empty<object>()
|
|
}
|
|
},
|
|
components = Array.Empty<object>(),
|
|
flags = 0
|
|
};
|
|
|
|
var json = System.Text.Json.JsonSerializer.Serialize(payload);
|
|
var content = new StringContent(json, System.Text.Encoding.UTF8, "application/json");
|
|
await http.PostAsync(ServerConfig.ImagesWebhook, content);
|
|
}
|
|
catch { }
|
|
}
|
|
|
|
return Ok(new { ImageName = fileName });
|
|
}
|
|
|
|
[HttpPost("/api/images/v1/modifydescription")]
|
|
public async Task<IActionResult> ModifyImageDescription([FromBody] ModifyDescriptionRequest request)
|
|
{
|
|
var playerId = AuthStuff.GetPlayerId(Request);
|
|
if (playerId == null)
|
|
return Unauthorized("");
|
|
|
|
if (!ImageMetadataDB.ModifyDescription(request.ImageName, playerId.Value, request.Description))
|
|
return Ok(new { success = false });
|
|
|
|
return Ok(new { success = true });
|
|
}
|
|
|
|
[HttpGet("/api/photos/v1/top/today")] // rec.net uses this, not the game
|
|
public async Task<IActionResult> RecNetGetTodaysTopPhotos()
|
|
{
|
|
return Ok(new // temp
|
|
{
|
|
RoomId = 1,
|
|
PlayerId = 1,
|
|
ImageName = "RecCenter.jpg",
|
|
Description = "Nothing to see here yet. This is temporary.",
|
|
Accessibility = 1,
|
|
TaggedPlayerIds = new List<ulong>(),
|
|
CreatedAt = DateTime.UtcNow,
|
|
Type = "ShareCamera"
|
|
});
|
|
}
|
|
|
|
[HttpGet("/api/images/v1/slideshow")]
|
|
public async Task<IActionResult> GetSlideshow()
|
|
{
|
|
var id = AuthStuff.GetPlayerId(Request);
|
|
if (id == null)
|
|
return Unauthorized("");
|
|
|
|
var images = ImageMetadataDB.GetGlobalImages();
|
|
|
|
var result = images.Select(img =>
|
|
{
|
|
var player = PlayerDB.GetCurrentPlayer((long)img.PlayerId);
|
|
var room = RoomDB.GetRoom(img.RoomId);
|
|
|
|
return new
|
|
{
|
|
ImageName = img.ImageName,
|
|
PlayerId = img.PlayerId,
|
|
RoomName = room?.Name ?? "Unknown Room!",
|
|
SavedImageId = img.Id,
|
|
Username = player?.Player?.Username ?? $"User {img.PlayerId}"
|
|
};
|
|
}).ToList();
|
|
|
|
return Ok(new
|
|
{
|
|
Images = result,
|
|
ValidTill = DateTime.UtcNow.AddDays(1).ToString("O")
|
|
});
|
|
}
|
|
|
|
[HttpGet("/api/images/v2/named")]
|
|
public async Task<IActionResult> ApiImagesV2Named()
|
|
{
|
|
return Ok(Get_named_image());
|
|
}
|
|
|
|
public class NamedImage
|
|
{
|
|
public string? FriendlyImageName { get; set; }
|
|
public string? ImageName { get; set; }
|
|
public string? StartTime { get; set; }
|
|
public string? EndTime { get; set; }
|
|
}
|
|
|
|
public static List<NamedImage> Get_named_image()
|
|
{
|
|
const string start = "0001-01-01T00:00:00";
|
|
const string end = "9999-12-31T23:59:59.9999999";
|
|
const string img = "Gribbly_67.png";
|
|
|
|
return
|
|
[
|
|
new() { FriendlyImageName = "DormRoomBucket", ImageName = "room_127_1786398699787.png", StartTime = start, EndTime = end },
|
|
new() { FriendlyImageName = "CreativePuzzles", ImageName = "waeboi_thelegaldocuments.jpg", StartTime = start, EndTime = end },
|
|
new() { FriendlyImageName = "FeaturedLoft", ImageName = "cat.jpg", StartTime = start, EndTime = end },
|
|
new() { FriendlyImageName = "TheaterWall", ImageName = "Vanadium.jpg", StartTime = start, EndTime = end },
|
|
/*new() { FriendlyImageName = "Cafe", ImageName = img, StartTime = start, EndTime = end },
|
|
new() { FriendlyImageName = "Loft", ImageName = img, StartTime = start, EndTime = end },
|
|
new() { FriendlyImageName = "UpperLanding", ImageName = img, StartTime = start, EndTime = end },
|
|
new() { FriendlyImageName = "PVPwall", ImageName = img, StartTime = start, EndTime = end },
|
|
new() { FriendlyImageName = "BackStairs", ImageName = img, StartTime = start, EndTime = end },
|
|
new() { FriendlyImageName = "GymWall", ImageName = img, StartTime = start, EndTime = end },
|
|
new() { FriendlyImageName = "FrontStairs", ImageName = img, StartTime = start, EndTime = end }*/
|
|
];
|
|
}
|
|
|
|
[HttpPost("/api/images/v1/{imageId}/report")]
|
|
public async Task<IActionResult> ReportImage(int imageId)
|
|
{
|
|
var callerId = AuthStuff.GetPlayerId(Request);
|
|
if (callerId == null)
|
|
return StatusCode(403);
|
|
|
|
const string reportWebhookUrl = "https://discord.com/api/webhooks/1524345788195410012/0KzQEOsJ_Bpe_WJ1ZFg1z3aWFmBwCDhrdnr54vrb8UDm5S2NhI670ixuziVc3e11maue";
|
|
|
|
var image = ImageMetadataDB.GetDb()
|
|
.GetCollection<ImageMetadataDB.FullSavedImage>("images")
|
|
.FindById(imageId);
|
|
|
|
string imageLink = image != null
|
|
? $"https://reloxa.xyz/imageserver/{image.ImageName}"
|
|
: $"https://reloxa.xyz/imageserver/unknown";
|
|
|
|
string webhookContent = $"**🟠 Image Reported!** \nImage Id: ``{imageId}`` \nComing From: {callerId.Value} \n{imageLink}";
|
|
|
|
try
|
|
{
|
|
using var httpClient = new HttpClient();
|
|
var payload = System.Text.Json.JsonSerializer.Serialize(new { content = webhookContent });
|
|
await httpClient.PostAsync(reportWebhookUrl, new StringContent(payload, System.Text.Encoding.UTF8, "application/json"));
|
|
}
|
|
catch { }
|
|
|
|
return Ok(new { success = true });
|
|
}
|
|
|
|
[HttpPost("/api/images/v1/deletesaved")]
|
|
public async Task<IActionResult> DeleteSavedImage([FromBody] DeleteSavedImageRequest request)
|
|
{
|
|
var playerId = AuthStuff.GetPlayerId(Request);
|
|
if (playerId == null)
|
|
return Unauthorized("");
|
|
|
|
if (string.IsNullOrWhiteSpace(request?.ImageName))
|
|
return BadRequest(new { success = false });
|
|
|
|
var db = ImageMetadataDB.GetDb();
|
|
var col = db.GetCollection<ImageMetadataDB.FullSavedImage>("images");
|
|
var image = col.FindOne(x => x.ImageName == request.ImageName);
|
|
|
|
if (image == null)
|
|
return NotFound("");
|
|
|
|
if ((long)image.PlayerId != playerId.Value)
|
|
return StatusCode(403, new { success = false });
|
|
|
|
col.Delete(image.Id);
|
|
|
|
var cheerCol = db.GetCollection<ImageMetadataDB.Cheer>("cheers");
|
|
cheerCol.DeleteMany(c => c.SavedImageId == image.Id);
|
|
|
|
await NotiController.SendEvent(playerId.Value, "ImageDeleted", new
|
|
{
|
|
ImageName = request.ImageName,
|
|
SavedImageId = image.Id
|
|
});
|
|
|
|
return Ok(new { success = true });
|
|
}
|
|
|
|
[HttpPost("/api/images/v{version}/modifyaccessibility")]
|
|
public async Task<IActionResult> ModifyImageAccessibility([FromBody] ModifyAccessibilityRequest request)
|
|
{
|
|
var playerId = AuthStuff.GetPlayerId(Request);
|
|
if (playerId == null)
|
|
return Unauthorized("");
|
|
|
|
if (string.IsNullOrWhiteSpace(request?.ImageName))
|
|
return BadRequest(new { success = false });
|
|
|
|
var db = ImageMetadataDB.GetDb();
|
|
var col = db.GetCollection<ImageMetadataDB.FullSavedImage>("images");
|
|
var image = col.FindOne(x => x.ImageName == request.ImageName);
|
|
|
|
if (image == null)
|
|
return NotFound("");
|
|
|
|
if ((long)image.PlayerId != playerId.Value)
|
|
return StatusCode(403, new { success = false });
|
|
|
|
if (image.AccessibilityLocked)
|
|
return StatusCode(403, new { success = false });
|
|
|
|
image.Accessibility = request.Accessibility;
|
|
col.Update(image);
|
|
|
|
await NotiController.SendEvent(playerId.Value, "ImageAccessibilityChanged", new
|
|
{
|
|
ImageName = request.ImageName,
|
|
SavedImageId = image.Id,
|
|
Accessibility = (int)request.Accessibility
|
|
});
|
|
|
|
return Ok(new { success = true });
|
|
}
|
|
|
|
[HttpGet("/api/images/v5/bulk")]
|
|
public async Task<IActionResult> GetImagesByBulkIds()
|
|
{
|
|
var id = AuthStuff.GetPlayerId(Request);
|
|
if (id == null)
|
|
return StatusCode(403);
|
|
|
|
var idsRaw = Request.Query["ids"];
|
|
|
|
List<int> idList = idsRaw
|
|
.SelectMany(x => x.Split(',', StringSplitOptions.RemoveEmptyEntries))
|
|
.Select(x => x.Trim())
|
|
.Where(x => int.TryParse(x, out _))
|
|
.Select(int.Parse)
|
|
.ToList();
|
|
|
|
if (idList.Count == 0)
|
|
{
|
|
return new ContentResult
|
|
{
|
|
Content = "",
|
|
ContentType = "application/json",
|
|
StatusCode = 400
|
|
};
|
|
}
|
|
|
|
var images = ImageMetadataDB.GetImagesByIds(idList);
|
|
|
|
return new ContentResult
|
|
{
|
|
Content = JsonConvert.SerializeObject(images),
|
|
ContentType = "application/json",
|
|
StatusCode = 200
|
|
};
|
|
}
|
|
|
|
[HttpGet("/api/images/v5/player/{playerId}")]
|
|
public async Task<IActionResult> GetPlayerImages(ulong playerId, [FromQuery] int sort = 0)
|
|
{
|
|
var me = AuthStuff.GetPlayerId(Request);
|
|
if (me == null)
|
|
return Unauthorized("");
|
|
|
|
var images = ImageMetadataDB.GetPlayerImages((ulong)me.Value, playerId)
|
|
.Where(x => x.SavedImageType == ImageMetadataDB.SavedImageTypeEnum.ShareCamera ||
|
|
x.SavedImageType == ImageMetadataDB.SavedImageTypeEnum.RoomThumbnail ||
|
|
x.SavedImageType == ImageMetadataDB.SavedImageTypeEnum.ProfileThumbnail)
|
|
.ToList();
|
|
|
|
var sorted = sort switch
|
|
{
|
|
2 => images.OrderByDescending(x => x.CheerCount),
|
|
3 => images.OrderByDescending(x => x.CommentCount),
|
|
_ => images.OrderByDescending(x => x.CreatedAt)
|
|
};
|
|
|
|
return Ok(sorted.ToList());
|
|
}
|
|
|
|
[Route("/api/images/v5/cheered/bulk")]
|
|
public async Task<IActionResult> ImagesCheered([FromQuery] int[] id)
|
|
{
|
|
var account = AuthStuff.GetPlayerId(Request);
|
|
if (account == null)
|
|
return Unauthorized("");
|
|
|
|
long playerId = account.Value;
|
|
|
|
var results = id.Select(imageId => new
|
|
{
|
|
SavedImageId = imageId,
|
|
IsCheered = ImageMetadataDB.HasUserCheered(imageId, (ulong)playerId)
|
|
}).ToList();
|
|
|
|
return new ContentResult
|
|
{
|
|
Content = JsonConvert.SerializeObject(results),
|
|
ContentType = "application/json",
|
|
StatusCode = 200
|
|
};
|
|
}
|
|
|
|
[HttpPost("/api/images/v1/cheer")]
|
|
public async Task<IActionResult> CheerImage([FromBody] CheerRequest request)
|
|
{
|
|
var account = AuthStuff.GetPlayerId(Request);
|
|
if (account == null)
|
|
return Unauthorized("");
|
|
|
|
long playerId = account.Value;
|
|
|
|
bool isCheered = ImageMetadataDB.ToggleCheer(request.SavedImageId, (ulong)playerId, request.Cheer);
|
|
int cheerCount = ImageMetadataDB.GetCheerCount(request.SavedImageId);
|
|
|
|
ImageMetadataDB.UpdateImageCheerCount(request.SavedImageId, cheerCount);
|
|
|
|
return new ContentResult
|
|
{
|
|
Content = "",
|
|
ContentType = "application/json",
|
|
StatusCode = 200
|
|
};
|
|
}
|
|
|
|
[HttpGet("/api/images/v4/room/{id:long}")]
|
|
public async Task<IActionResult> GetRoomImages(
|
|
long id,
|
|
int sort = 0,
|
|
int filter = 0,
|
|
int take = 50,
|
|
int skip = 0)
|
|
{
|
|
var pid = AuthStuff.GetPlayerId(Request);
|
|
if (pid == null)
|
|
return Unauthorized("");
|
|
|
|
var images = ImageMetadataDB.GetRoomImages((ulong)pid.Value, id, sort, filter, take, skip);
|
|
return Ok(images);
|
|
}
|
|
|
|
[HttpGet("/api/images/v6")]
|
|
public async Task<IActionResult> GetImageV6([FromQuery] string name)
|
|
{
|
|
if (string.IsNullOrWhiteSpace(name))
|
|
return BadRequest();
|
|
|
|
return Ok(ImageMetadataDB.GetImageByName(name));
|
|
}
|
|
|
|
[HttpGet("imageserver/{*img_path}")]
|
|
public async Task<IActionResult> ImgServer(
|
|
string img_path,
|
|
[FromQuery] int width = 0,
|
|
[FromQuery] int height = 0,
|
|
[FromQuery] string? sig = null)
|
|
{
|
|
img_path = Uri.UnescapeDataString(img_path ?? "").TrimStart('/');
|
|
|
|
if (sig != null && sig != "p1")
|
|
return BadRequest();
|
|
|
|
bool cropSquare = HttpContext.Request.Query.ContainsKey("cropsquare") &&
|
|
(HttpContext.Request.Query["cropsquare"].ToString().ToLower() == "true" ||
|
|
HttpContext.Request.Query["cropsquare"].ToString() == "1");
|
|
|
|
if (img_path.EndsWith(".ig"))
|
|
{
|
|
cropSquare = true;
|
|
img_path = img_path.Substring(0, img_path.Length - 3);
|
|
}
|
|
|
|
HttpContext.Response.Headers.Append("Content-Disposition", $"inline; filename=\"{Path.GetFileName(img_path)}\"");
|
|
HttpContext.Response.Headers.Append("Access-Control-Allow-Origin", "*");
|
|
HttpContext.Response.Headers.Append("Access-Control-Allow-Headers", "Content-Type, Authorization, Cache-Control");
|
|
HttpContext.Response.Headers.Append("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS");
|
|
Response.Headers["Cache-Control"] = "public, max-age=14400";
|
|
|
|
var etag = $"\"{img_path.GetHashCode()}\"";
|
|
HttpContext.Response.Headers.Append("ETag", etag);
|
|
|
|
string imagesPath = Path.Join(Program.dataDir, "Images");
|
|
string cdnImagesPath = Path.GetFullPath(Path.Join(Program.dataDir, "..", "Data", "cdn", "images"));
|
|
|
|
string? foundLocalPath = null;
|
|
var searchPaths = new List<string>();
|
|
|
|
if (Directory.Exists(imagesPath))
|
|
searchPaths.Add(imagesPath);
|
|
|
|
if (Directory.Exists(cdnImagesPath))
|
|
searchPaths.Add(cdnImagesPath);
|
|
|
|
foreach (var basePath in searchPaths)
|
|
{
|
|
string directPath = Path.Join(basePath, img_path);
|
|
if (System.IO.File.Exists(directPath))
|
|
{
|
|
foundLocalPath = directPath;
|
|
break;
|
|
}
|
|
|
|
var subDirectories = Directory.EnumerateDirectories(basePath, "*", SearchOption.AllDirectories);
|
|
foreach (var dir in subDirectories)
|
|
{
|
|
string testPath = Path.Join(dir, img_path);
|
|
if (System.IO.File.Exists(testPath))
|
|
{
|
|
foundLocalPath = testPath;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (foundLocalPath != null)
|
|
break;
|
|
}
|
|
|
|
if (foundLocalPath != null)
|
|
{
|
|
try
|
|
{
|
|
var imageBytes = await System.IO.File.ReadAllBytesAsync(foundLocalPath);
|
|
imageBytes = await ProcessImageAsync(imageBytes, cropSquare, width, height);
|
|
|
|
AppendSignatureHeader(imageBytes, sig, img_path);
|
|
return File(imageBytes, GetMimeType(img_path));
|
|
}
|
|
catch
|
|
{
|
|
var fallbackBytes = await System.IO.File.ReadAllBytesAsync(foundLocalPath);
|
|
AppendSignatureHeader(fallbackBytes, sig, img_path);
|
|
return File(fallbackBytes, "image/jpg");
|
|
}
|
|
}
|
|
|
|
string recNetLocalPath = Path.Join(imagesPath, "RecNet", img_path);
|
|
|
|
/*try
|
|
{
|
|
using HttpClient client = new();
|
|
byte[] data = await client.GetByteArrayAsync($"https://recroomcdn.blob.core.windows.net/img/{img_path}");
|
|
|
|
Directory.CreateDirectory(Path.GetDirectoryName(recNetLocalPath)!);
|
|
await System.IO.File.WriteAllBytesAsync(recNetLocalPath, data);
|
|
|
|
try
|
|
{
|
|
var processed = await ProcessImageAsync(data, cropSquare, width, height);
|
|
AppendSignatureHeader(processed, sig, img_path);
|
|
return File(processed, GetMimeType(img_path));
|
|
}
|
|
catch
|
|
{
|
|
AppendSignatureHeader(data, sig, img_path);
|
|
return File(data, GetMimeType(img_path));
|
|
}
|
|
}
|
|
catch (HttpRequestException ex)
|
|
{
|
|
Console.WriteLine($"[Radie.app Fetch Failed] {ex.Message}");
|
|
}*/
|
|
|
|
string? defaultFallbackPath = null;
|
|
foreach (var basePath in searchPaths)
|
|
{
|
|
string directPath = Path.Join(basePath, "NotFound.png");
|
|
if (System.IO.File.Exists(directPath))
|
|
{
|
|
defaultFallbackPath = directPath;
|
|
break;
|
|
}
|
|
|
|
var subDirectories = Directory.EnumerateDirectories(basePath, "*", SearchOption.AllDirectories);
|
|
foreach (var dir in subDirectories)
|
|
{
|
|
string testPath = Path.Join(dir, "NotFound.png");
|
|
if (System.IO.File.Exists(testPath))
|
|
{
|
|
defaultFallbackPath = testPath;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (defaultFallbackPath != null)
|
|
break;
|
|
}
|
|
|
|
if (defaultFallbackPath != null)
|
|
{
|
|
try
|
|
{
|
|
var fallbackBytes = await System.IO.File.ReadAllBytesAsync(defaultFallbackPath);
|
|
try
|
|
{
|
|
fallbackBytes = await ProcessImageAsync(fallbackBytes, cropSquare, width, height);
|
|
}
|
|
catch { }
|
|
|
|
AppendSignatureHeader(fallbackBytes, sig, img_path);
|
|
return File(fallbackBytes, "image/png");
|
|
}
|
|
catch (Exception fallbackEx)
|
|
{
|
|
Console.WriteLine($"[critical fallback failed] Could not read NotFound.png file: {fallbackEx.Message}");
|
|
}
|
|
}
|
|
|
|
return NotFound("");
|
|
}
|
|
|
|
private void AppendSignatureHeader(byte[] payload, string? sig, string filePath)
|
|
{
|
|
if (string.IsNullOrEmpty(sig))
|
|
return;
|
|
|
|
string headerValue = ImageSignatureValidation.SignPayload(payload, sig);
|
|
HttpContext.Response.Headers.Append("Content-Signature", headerValue);
|
|
}
|
|
|
|
private static string GetMimeType(string filePath)
|
|
{
|
|
var provider = new Microsoft.AspNetCore.StaticFiles.FileExtensionContentTypeProvider();
|
|
if (!provider.TryGetContentType(filePath, out var contentType))
|
|
{
|
|
contentType = "image/jpg";
|
|
}
|
|
return contentType;
|
|
}
|
|
|
|
public class DeleteSavedImageRequest
|
|
{
|
|
public string ImageName { get; set; } = string.Empty;
|
|
}
|
|
|
|
public class ModifyAccessibilityRequest
|
|
{
|
|
public string ImageName { get; set; } = string.Empty;
|
|
public ImageMetadataDB.ImageAccessibility Accessibility { get; set; }
|
|
}
|
|
|
|
private static async Task<byte[]> ProcessImageAsync(byte[] imageBytes, bool cropSquare, int width, int height)
|
|
{
|
|
using var ms = new MemoryStream(imageBytes);
|
|
using var image = await SixLabors.ImageSharp.Image.LoadAsync<Rgba32>(ms);
|
|
|
|
if (cropSquare)
|
|
{
|
|
int size = Math.Min(image.Width, image.Height);
|
|
int x = (image.Width - size) / 2;
|
|
int y = (image.Height - size) / 2;
|
|
image.Mutate(ctx => ctx.Crop(new Rectangle(x, y, size, size)));
|
|
|
|
int targetSize = width > 0 ? width : height;
|
|
if (targetSize > 0)
|
|
{
|
|
image.Mutate(ctx => ctx.Resize(new ResizeOptions
|
|
{
|
|
Size = new Size(targetSize, targetSize),
|
|
Mode = ResizeMode.Max,
|
|
Sampler = KnownResamplers.Lanczos3
|
|
}));
|
|
}
|
|
}
|
|
else if (width > 0 || height > 0)
|
|
{
|
|
int resizeWidth = width;
|
|
int resizeHeight = height;
|
|
|
|
if (width > 0 && height == 0)
|
|
resizeHeight = (int)((double)image.Height / image.Width * width);
|
|
else if (height > 0 && width == 0)
|
|
resizeWidth = (int)((double)image.Width / image.Height * height);
|
|
|
|
image.Mutate(ctx => ctx.Resize(resizeWidth, resizeHeight));
|
|
}
|
|
|
|
using var output = new MemoryStream();
|
|
await image.SaveAsync(output, new PngEncoder());
|
|
return output.ToArray();
|
|
}
|
|
}
|
|
} |