From c019dcb3fb211dc2969ae60e2a43053fb277f999 Mon Sep 17 00:00:00 2001 From: Kieran Date: Fri, 22 Dec 2023 13:27:07 +0000 Subject: [PATCH] Inject tags to index.html --- .dockerignore | 4 +- VoidCat/Controllers/IndexController.cs | 106 +++++- VoidCat/Pages/Index.cshtml | 85 ----- VoidCat/VoidCat.csproj | 1 + VoidCat/spa/src/api/src/api.ts | 316 +++++++++--------- VoidCat/spa/src/app/src/Pages/FilePreview.tsx | 2 +- VoidCat/spa/src/app/vite.config.ts | 3 +- VoidCat/wwwroot/asset-manifest.json | 10 - 8 files changed, 255 insertions(+), 272 deletions(-) delete mode 100644 VoidCat/Pages/Index.cshtml delete mode 100644 VoidCat/wwwroot/asset-manifest.json diff --git a/.dockerignore b/.dockerignore index 0544f28..b2c7512 100644 --- a/.dockerignore +++ b/.dockerignore @@ -23,4 +23,6 @@ LICENSE README.md **/appsettings.*.json -**/data \ No newline at end of file +**/data +**/build +**/dist \ No newline at end of file diff --git a/VoidCat/Controllers/IndexController.cs b/VoidCat/Controllers/IndexController.cs index e2e6431..7bada84 100644 --- a/VoidCat/Controllers/IndexController.cs +++ b/VoidCat/Controllers/IndexController.cs @@ -1,6 +1,6 @@ -using System.Text.RegularExpressions; +using AngleSharp; +using AngleSharp.Dom; using Microsoft.AspNetCore.Mvc; -using Newtonsoft.Json; using VoidCat.Model; using VoidCat.Services.Abstractions; @@ -10,11 +10,13 @@ public class IndexController : Controller { private readonly IWebHostEnvironment _webHost; private readonly IFileMetadataStore _fileMetadata; + private readonly VoidSettings _settings; - public IndexController(IFileMetadataStore fileMetadata, IWebHostEnvironment webHost) + public IndexController(IFileMetadataStore fileMetadata, IWebHostEnvironment webHost, VoidSettings settings) { _fileMetadata = fileMetadata; _webHost = webHost; + _settings = settings; } /// @@ -28,22 +30,57 @@ public class IndexController : Controller { id.TryFromBase58Guid(out var gid); - var manifestPath = Path.Combine(_webHost.WebRootPath, "asset-manifest.json"); - if (!System.IO.File.Exists(manifestPath)) return StatusCode(500); + var ubDownload = new UriBuilder(_settings.SiteUrl) + { + Path = $"/d/{gid.ToBase58()}" + }; - // old format hash, return 404 - if (id.Length == 40 && Regex.IsMatch(id, @"[0-9a-z]{40}")) + var ubView = new UriBuilder(_settings.SiteUrl) { - Response.StatusCode = 404; + Path = $"/{gid.ToBase58()}" + }; + + var indexPath = Path.Combine(_webHost.WebRootPath, "index.html"); + var indexContent = await System.IO.File.ReadAllTextAsync(indexPath); + + var meta = (await _fileMetadata.Get(gid))?.ToMeta(false); + var tags = new List>() + { + new("site_name", "void.cat"), + new("title", meta?.Name ?? ""), + new("description", meta?.Description ?? ""), + new("url", ubView.Uri.ToString()), + }; + + var mime = meta?.MimeType; + if (mime?.StartsWith("image/") ?? false) + { + tags.Add(new("type", "image")); + tags.Add(new("image", ubDownload.Uri.ToString())); + tags.Add(new("image:type", mime)); } - - var jsonManifest = await System.IO.File.ReadAllTextAsync(manifestPath); - return View("~/Pages/Index.cshtml", new IndexModel + else if (mime?.StartsWith("video/") ?? false) { - Id = gid, - Meta = (await _fileMetadata.Get(gid))?.ToMeta(false), - Manifest = JsonConvert.DeserializeObject(jsonManifest)! - }); + tags.Add(new("type", "video.other")); + tags.Add(new("image", "")); + tags.Add(new("video", ubDownload.Uri.ToString())); + tags.Add(new("video:url", ubDownload.Uri.ToString())); + tags.Add(new("video:secure_url", ubDownload.Uri.ToString())); + tags.Add(new("video:type", mime)); + } + else if (mime?.StartsWith("audio/") ?? false) + { + tags.Add(new("type", "audio.other")); + tags.Add(new("audio", ubDownload.Uri.ToString())); + tags.Add(new("audio:type", mime)); + } + else + { + tags.Add(new("type", "website")); + } + + var injectedHtml = await InjectTags(indexContent, tags); + return Content(injectedHtml?.ToHtml() ?? indexContent, "text/html"); } public class IndexModel @@ -59,4 +96,41 @@ public class IndexController : Controller public Dictionary Files { get; init; } public List Entrypoints { get; init; } } -} \ No newline at end of file + + private async Task InjectTags(string html, List> tags) + { + var config = Configuration.Default; + var context = BrowsingContext.New(config); + var doc = await context.OpenAsync(c => c.Content(html)); + + foreach (var tag in tags) + { + var ogTag = doc.CreateElement("meta"); + ogTag.SetAttribute("property", $"og:{tag.Key}"); + ogTag.SetAttribute("content", tag.Value); + doc.Head?.AppendChild(ogTag); + switch (tag.Key.ToLower()) + { + case "title": + { + var titleTag = doc.Head?.QuerySelector("title"); + if (titleTag != default) + { + titleTag.TextContent = tag.Value; + } + + break; + } + case "description": + { + var descriptionTag = doc.Head?.QuerySelector("meta[name='description']"); + descriptionTag?.SetAttribute("content", tag.Value); + + break; + } + } + } + + return doc; + } +} diff --git a/VoidCat/Pages/Index.cshtml b/VoidCat/Pages/Index.cshtml deleted file mode 100644 index a7422c0..0000000 --- a/VoidCat/Pages/Index.cshtml +++ /dev/null @@ -1,85 +0,0 @@ -@using VoidCat.Model -@model VoidCat.Controllers.IndexController.IndexModel -@inject VoidSettings Settings - - - - - - - - - - - @if (Model.Meta != default) - { - var ubDownload = new UriBuilder(Settings.SiteUrl) - { - Path = $"/d/{Model.Id.ToBase58()}" - }; - var ubView = new UriBuilder(Settings.SiteUrl) - { - Path = $"/{Model.Id.ToBase58()}" - }; - - void.cat - @Model.Meta.Name - - - - - - - var mime = Model.Meta.MimeType; - if (!string.IsNullOrEmpty(mime)) - { - if (mime.StartsWith("image/")) - { - - - - } - else if (mime.StartsWith("video/")) - { - - - - - - - } - else if (mime.StartsWith("audio/")) - { - - - - } - } - } - else - { - void.cat - - - } - - @foreach (var ep in Model.Manifest.Entrypoints) - { - switch (System.IO.Path.GetExtension(ep)) - { - case ".css": - { - - break; - } - case ".js": - { - - break; - } - } - } - - -
- - \ No newline at end of file diff --git a/VoidCat/VoidCat.csproj b/VoidCat/VoidCat.csproj index d8784ab..8accc52 100644 --- a/VoidCat/VoidCat.csproj +++ b/VoidCat/VoidCat.csproj @@ -13,6 +13,7 @@ + diff --git a/VoidCat/spa/src/api/src/api.ts b/VoidCat/spa/src/api/src/api.ts index 23f5252..3691bf2 100644 --- a/VoidCat/spa/src/api/src/api.ts +++ b/VoidCat/spa/src/api/src/api.ts @@ -1,192 +1,192 @@ import { - AdminProfile, - AdminUserListResult, - ApiError, - ApiKey, - LoginSession, - PagedRequest, - PagedResponse, - PaymentOrder, - Profile, - SetPaymentConfigRequest, - SiteInfoResponse, - VoidFileResponse, + AdminProfile, + AdminUserListResult, + ApiError, + ApiKey, + LoginSession, + PagedRequest, + PagedResponse, + PaymentOrder, + Profile, + SetPaymentConfigRequest, + SiteInfoResponse, + VoidFileResponse, } from "./index"; import { - ProgressHandler, - ProxyChallengeHandler, - StateChangeHandler, - VoidUploader, + ProgressHandler, + ProxyChallengeHandler, + StateChangeHandler, + VoidUploader, } from "./upload"; -import { StreamUploader } from "./stream-uploader"; -import { XHRUploader } from "./xhr-uploader"; +import {StreamUploader} from "./stream-uploader"; +import {XHRUploader} from "./xhr-uploader"; export type AuthHandler = (url: string, method: string) => Promise; export class VoidApi { - readonly #uri: string; - readonly #auth?: AuthHandler; + readonly #uri: string; + readonly #auth?: AuthHandler; - constructor(uri: string, auth?: AuthHandler) { - this.#uri = uri; - this.#auth = auth; - } - - async #req(method: string, url: string, body?: object): Promise { - const absoluteUrl = `${this.#uri}${url}`; - const headers: HeadersInit = { - Accept: "application/json", - }; - if (this.#auth) { - headers["Authorization"] = await this.#auth(absoluteUrl, method); - } - if (body) { - headers["Content-Type"] = "application/json"; + constructor(uri?: string, auth?: AuthHandler) { + this.#uri = uri ?? ""; + this.#auth = auth; } - const res = await fetch(absoluteUrl, { - method, - headers, - mode: "cors", - body: body ? JSON.stringify(body) : undefined, - }); - const text = await res.text(); - if (res.ok) { - return text ? (JSON.parse(text) as T) : ({} as T); - } else { - throw new ApiError(res.status, text); + async #req(method: string, url: string, body?: object): Promise { + const absoluteUrl = `${this.#uri}${url}`; + const headers: HeadersInit = { + Accept: "application/json", + }; + if (this.#auth) { + headers["Authorization"] = await this.#auth(absoluteUrl, method); + } + if (body) { + headers["Content-Type"] = "application/json"; + } + + const res = await fetch(absoluteUrl, { + method, + headers, + mode: "cors", + body: body ? JSON.stringify(body) : undefined, + }); + const text = await res.text(); + if (res.ok) { + return text ? (JSON.parse(text) as T) : ({} as T); + } else { + throw new ApiError(res.status, text); + } } - } - /** - * Get uploader for uploading files - */ - getUploader( - file: File | Blob, - stateChange?: StateChangeHandler, - progress?: ProgressHandler, - proxyChallenge?: ProxyChallengeHandler, - chunkSize?: number, - ): VoidUploader { - if (StreamUploader.canUse()) { - return new StreamUploader( - this.#uri, - file, - stateChange, - progress, - proxyChallenge, - this.#auth, - chunkSize, - ); - } else { - return new XHRUploader( - this.#uri, - file, - stateChange, - progress, - proxyChallenge, - this.#auth, - chunkSize, - ); + /** + * Get uploader for uploading files + */ + getUploader( + file: File | Blob, + stateChange?: StateChangeHandler, + progress?: ProgressHandler, + proxyChallenge?: ProxyChallengeHandler, + chunkSize?: number, + ): VoidUploader { + if (StreamUploader.canUse()) { + return new StreamUploader( + this.#uri, + file, + stateChange, + progress, + proxyChallenge, + this.#auth, + chunkSize, + ); + } else { + return new XHRUploader( + this.#uri, + file, + stateChange, + progress, + proxyChallenge, + this.#auth, + chunkSize, + ); + } } - } - /** - * General site information - */ - info() { - return this.#req("GET", "/info"); - } + /** + * General site information + */ + info() { + return this.#req("GET", "/info"); + } - fileInfo(id: string) { - return this.#req("GET", `/upload/${id}`); - } + fileInfo(id: string) { + return this.#req("GET", `/upload/${id}`); + } - setPaymentConfig(id: string, cfg: SetPaymentConfigRequest) { - return this.#req("POST", `/upload/${id}/payment`, cfg); - } + setPaymentConfig(id: string, cfg: SetPaymentConfigRequest) { + return this.#req("POST", `/upload/${id}/payment`, cfg); + } - createOrder(id: string) { - return this.#req("GET", `/upload/${id}/payment`); - } + createOrder(id: string) { + return this.#req("GET", `/upload/${id}/payment`); + } - getOrder(file: string, order: string) { - return this.#req("GET", `/upload/${file}/payment/${order}`); - } + getOrder(file: string, order: string) { + return this.#req("GET", `/upload/${file}/payment/${order}`); + } - login(username: string, password: string, captcha?: string) { - return this.#req("POST", `/auth/login`, { - username, - password, - captcha, - }); - } + login(username: string, password: string, captcha?: string) { + return this.#req("POST", `/auth/login`, { + username, + password, + captcha, + }); + } - register(username: string, password: string, captcha?: string) { - return this.#req("POST", `/auth/register`, { - username, - password, - captcha, - }); - } + register(username: string, password: string, captcha?: string) { + return this.#req("POST", `/auth/register`, { + username, + password, + captcha, + }); + } - getUser(id: string) { - return this.#req("GET", `/user/${id}`); - } + getUser(id: string) { + return this.#req("GET", `/user/${id}`); + } - updateUser(u: Profile) { - return this.#req("POST", `/user/${u.id}`, u); - } + updateUser(u: Profile) { + return this.#req("POST", `/user/${u.id}`, u); + } - listUserFiles(uid: string, pageReq: PagedRequest) { - return this.#req>( - "POST", - `/user/${uid}/files`, - pageReq, - ); - } + listUserFiles(uid: string, pageReq: PagedRequest) { + return this.#req>( + "POST", + `/user/${uid}/files`, + pageReq, + ); + } - submitVerifyCode(uid: string, code: string) { - return this.#req("POST", `/user/${uid}/verify`, { code }); - } + submitVerifyCode(uid: string, code: string) { + return this.#req("POST", `/user/${uid}/verify`, {code}); + } - sendNewCode(uid: string) { - return this.#req("GET", `/user/${uid}/verify`); - } + sendNewCode(uid: string) { + return this.#req("GET", `/user/${uid}/verify`); + } - updateFileMetadata(id: string, meta: any) { - return this.#req("POST", `/upload/${id}/meta`, meta); - } + updateFileMetadata(id: string, meta: any) { + return this.#req("POST", `/upload/${id}/meta`, meta); + } - listApiKeys() { - return this.#req>("GET", `/auth/api-key`); - } + listApiKeys() { + return this.#req>("GET", `/auth/api-key`); + } - createApiKey(req: any) { - return this.#req("POST", `/auth/api-key`, req); - } + createApiKey(req: any) { + return this.#req("POST", `/auth/api-key`, req); + } - adminListFiles(pageReq: PagedRequest) { - return this.#req>( - "POST", - "/admin/file", - pageReq, - ); - } + adminListFiles(pageReq: PagedRequest) { + return this.#req>( + "POST", + "/admin/file", + pageReq, + ); + } - adminDeleteFile(id: string) { - return this.#req("DELETE", `/admin/file/${id}`); - } + adminDeleteFile(id: string) { + return this.#req("DELETE", `/admin/file/${id}`); + } - adminUserList(pageReq: PagedRequest) { - return this.#req>( - "POST", - `/admin/users`, - pageReq, - ); - } + adminUserList(pageReq: PagedRequest) { + return this.#req>( + "POST", + `/admin/users`, + pageReq, + ); + } - adminUpdateUser(u: AdminProfile) { - return this.#req("POST", `/admin/update-user`, u); - } + adminUpdateUser(u: AdminProfile) { + return this.#req("POST", `/admin/update-user`, u); + } } diff --git a/VoidCat/spa/src/app/src/Pages/FilePreview.tsx b/VoidCat/spa/src/app/src/Pages/FilePreview.tsx index d16b5dc..4d2642d 100644 --- a/VoidCat/spa/src/app/src/Pages/FilePreview.tsx +++ b/VoidCat/spa/src/app/src/Pages/FilePreview.tsx @@ -248,7 +248,7 @@ export function FilePreview() { useEffect(() => { if (info) { - const fileLink = info.metadata?.url ?? `${import.meta.env.VITE_API_HOST}/d/${info.id}`; + const fileLink = info.metadata?.url ?? `${import.meta.env.VITE_API_HOST ?? ""}/d/${info.id}`; setFileSize(info.metadata?.size ?? 0); const order = window.localStorage.getItem(`payment-${info.id}`); diff --git a/VoidCat/spa/src/app/vite.config.ts b/VoidCat/spa/src/app/vite.config.ts index c19cdc1..d2eb1cd 100644 --- a/VoidCat/spa/src/app/vite.config.ts +++ b/VoidCat/spa/src/app/vite.config.ts @@ -20,8 +20,9 @@ export default defineConfig({ ], assetsInclude: [], build: { - outDir: "build", + outDir: "build" }, + base: "/", clearScreen: false, resolve: { alias: { diff --git a/VoidCat/wwwroot/asset-manifest.json b/VoidCat/wwwroot/asset-manifest.json deleted file mode 100644 index 7bc2426..0000000 --- a/VoidCat/wwwroot/asset-manifest.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "files": { - "main.js": "/static/js/bundle.js", - "index.html": "/index.html", - "bundle.js.map": "/static/js/bundle.js.map", - }, - "entrypoints": [ - "static/js/bundle.js" - ] -} \ No newline at end of file