mirror of
https://git.v0l.io/Kieran/void.cat.git
synced 2025-03-17 18:12:21 +01:00
Inject tags to index.html
This commit is contained in:
parent
b9a9d7bd26
commit
c019dcb3fb
@ -23,4 +23,6 @@
|
||||
LICENSE
|
||||
README.md
|
||||
**/appsettings.*.json
|
||||
**/data
|
||||
**/data
|
||||
**/build
|
||||
**/dist
|
@ -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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -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<KeyValuePair<string, string>>()
|
||||
{
|
||||
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<AssetManifest>(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<string, string> Files { get; init; }
|
||||
public List<string> Entrypoints { get; init; }
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<IDocument?> InjectTags(string html, List<KeyValuePair<string, string>> 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;
|
||||
}
|
||||
}
|
||||
|
@ -1,85 +0,0 @@
|
||||
@using VoidCat.Model
|
||||
@model VoidCat.Controllers.IndexController.IndexModel
|
||||
@inject VoidSettings Settings
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8"/>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1"/>
|
||||
<meta name="theme-color" content="#000000"/>
|
||||
<link rel="icon" href="/favicon.ico"/>
|
||||
<link rel="apple-touch-icon" href="/logo.png"/>
|
||||
<link rel="manifest" href="/manifest.json"/>
|
||||
|
||||
@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()}"
|
||||
};
|
||||
|
||||
<title>void.cat - @Model.Meta.Name</title>
|
||||
<meta name="description" content="@Model.Meta.Description"/>
|
||||
<meta property="og:site_name" content="void.cat"/>
|
||||
<meta property="og:title" content="@Model.Meta.Name"/>
|
||||
<meta property="og:description" content="@Model.Meta.Description"/>
|
||||
<meta property="og:url" content="@ubView"/>
|
||||
|
||||
var mime = Model.Meta.MimeType;
|
||||
if (!string.IsNullOrEmpty(mime))
|
||||
{
|
||||
if (mime.StartsWith("image/"))
|
||||
{
|
||||
<meta property="og:type" content="image"/>
|
||||
<meta property="og:image" content="@ubDownload"/>
|
||||
<meta property="og:image:type" content="@mime"/>
|
||||
}
|
||||
else if (mime.StartsWith("video/"))
|
||||
{
|
||||
<meta property="og:type" content="video.other"/>
|
||||
<meta property="og:image" content=""/>
|
||||
<meta property="og:video" content="@ubDownload"/>
|
||||
<meta property="og:video:url" content="@ubDownload"/>
|
||||
<meta property="og:video:secure_url" content="@ubDownload"/>
|
||||
<meta property="og:video:type" content="@mime"/>
|
||||
}
|
||||
else if (mime.StartsWith("audio/"))
|
||||
{
|
||||
<meta property="og:type" content="audio.other"/>
|
||||
<meta property="og:audio" content="@ubDownload"/>
|
||||
<meta property="og:audio:type" content="@mime"/>
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
<title>void.cat</title>
|
||||
<meta property="og:type" content="website"/>
|
||||
<meta name="description" content="void.cat - free, simple file sharing."/>
|
||||
}
|
||||
|
||||
@foreach (var ep in Model.Manifest.Entrypoints)
|
||||
{
|
||||
switch (System.IO.Path.GetExtension(ep))
|
||||
{
|
||||
case ".css":
|
||||
{
|
||||
<link rel="stylesheet" href="@ep"/>
|
||||
break;
|
||||
}
|
||||
case ".js":
|
||||
{
|
||||
<script defer src="@ep"></script>
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
</body>
|
||||
</html>
|
@ -13,6 +13,7 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="AngleSharp" Version="1.0.7" />
|
||||
<PackageReference Include="AWSSDK.S3" Version="3.7.103.41" />
|
||||
<PackageReference Include="BencodeNET" Version="5.0.0" />
|
||||
<PackageReference Include="BTCPayServer.Lightning.Common" Version="1.3.21" />
|
||||
|
@ -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<string>;
|
||||
|
||||
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<T>(method: string, url: string, body?: object): Promise<T> {
|
||||
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<T>(method: string, url: string, body?: object): Promise<T> {
|
||||
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<SiteInfoResponse>("GET", "/info");
|
||||
}
|
||||
/**
|
||||
* General site information
|
||||
*/
|
||||
info() {
|
||||
return this.#req<SiteInfoResponse>("GET", "/info");
|
||||
}
|
||||
|
||||
fileInfo(id: string) {
|
||||
return this.#req<VoidFileResponse>("GET", `/upload/${id}`);
|
||||
}
|
||||
fileInfo(id: string) {
|
||||
return this.#req<VoidFileResponse>("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<PaymentOrder>("GET", `/upload/${id}/payment`);
|
||||
}
|
||||
createOrder(id: string) {
|
||||
return this.#req<PaymentOrder>("GET", `/upload/${id}/payment`);
|
||||
}
|
||||
|
||||
getOrder(file: string, order: string) {
|
||||
return this.#req<PaymentOrder>("GET", `/upload/${file}/payment/${order}`);
|
||||
}
|
||||
getOrder(file: string, order: string) {
|
||||
return this.#req<PaymentOrder>("GET", `/upload/${file}/payment/${order}`);
|
||||
}
|
||||
|
||||
login(username: string, password: string, captcha?: string) {
|
||||
return this.#req<LoginSession>("POST", `/auth/login`, {
|
||||
username,
|
||||
password,
|
||||
captcha,
|
||||
});
|
||||
}
|
||||
login(username: string, password: string, captcha?: string) {
|
||||
return this.#req<LoginSession>("POST", `/auth/login`, {
|
||||
username,
|
||||
password,
|
||||
captcha,
|
||||
});
|
||||
}
|
||||
|
||||
register(username: string, password: string, captcha?: string) {
|
||||
return this.#req<LoginSession>("POST", `/auth/register`, {
|
||||
username,
|
||||
password,
|
||||
captcha,
|
||||
});
|
||||
}
|
||||
register(username: string, password: string, captcha?: string) {
|
||||
return this.#req<LoginSession>("POST", `/auth/register`, {
|
||||
username,
|
||||
password,
|
||||
captcha,
|
||||
});
|
||||
}
|
||||
|
||||
getUser(id: string) {
|
||||
return this.#req<Profile>("GET", `/user/${id}`);
|
||||
}
|
||||
getUser(id: string) {
|
||||
return this.#req<Profile>("GET", `/user/${id}`);
|
||||
}
|
||||
|
||||
updateUser(u: Profile) {
|
||||
return this.#req<void>("POST", `/user/${u.id}`, u);
|
||||
}
|
||||
updateUser(u: Profile) {
|
||||
return this.#req<void>("POST", `/user/${u.id}`, u);
|
||||
}
|
||||
|
||||
listUserFiles(uid: string, pageReq: PagedRequest) {
|
||||
return this.#req<PagedResponse<VoidFileResponse>>(
|
||||
"POST",
|
||||
`/user/${uid}/files`,
|
||||
pageReq,
|
||||
);
|
||||
}
|
||||
listUserFiles(uid: string, pageReq: PagedRequest) {
|
||||
return this.#req<PagedResponse<VoidFileResponse>>(
|
||||
"POST",
|
||||
`/user/${uid}/files`,
|
||||
pageReq,
|
||||
);
|
||||
}
|
||||
|
||||
submitVerifyCode(uid: string, code: string) {
|
||||
return this.#req<void>("POST", `/user/${uid}/verify`, { code });
|
||||
}
|
||||
submitVerifyCode(uid: string, code: string) {
|
||||
return this.#req<void>("POST", `/user/${uid}/verify`, {code});
|
||||
}
|
||||
|
||||
sendNewCode(uid: string) {
|
||||
return this.#req<void>("GET", `/user/${uid}/verify`);
|
||||
}
|
||||
sendNewCode(uid: string) {
|
||||
return this.#req<void>("GET", `/user/${uid}/verify`);
|
||||
}
|
||||
|
||||
updateFileMetadata(id: string, meta: any) {
|
||||
return this.#req<void>("POST", `/upload/${id}/meta`, meta);
|
||||
}
|
||||
updateFileMetadata(id: string, meta: any) {
|
||||
return this.#req<void>("POST", `/upload/${id}/meta`, meta);
|
||||
}
|
||||
|
||||
listApiKeys() {
|
||||
return this.#req<Array<ApiKey>>("GET", `/auth/api-key`);
|
||||
}
|
||||
listApiKeys() {
|
||||
return this.#req<Array<ApiKey>>("GET", `/auth/api-key`);
|
||||
}
|
||||
|
||||
createApiKey(req: any) {
|
||||
return this.#req<ApiKey>("POST", `/auth/api-key`, req);
|
||||
}
|
||||
createApiKey(req: any) {
|
||||
return this.#req<ApiKey>("POST", `/auth/api-key`, req);
|
||||
}
|
||||
|
||||
adminListFiles(pageReq: PagedRequest) {
|
||||
return this.#req<PagedResponse<VoidFileResponse>>(
|
||||
"POST",
|
||||
"/admin/file",
|
||||
pageReq,
|
||||
);
|
||||
}
|
||||
adminListFiles(pageReq: PagedRequest) {
|
||||
return this.#req<PagedResponse<VoidFileResponse>>(
|
||||
"POST",
|
||||
"/admin/file",
|
||||
pageReq,
|
||||
);
|
||||
}
|
||||
|
||||
adminDeleteFile(id: string) {
|
||||
return this.#req<void>("DELETE", `/admin/file/${id}`);
|
||||
}
|
||||
adminDeleteFile(id: string) {
|
||||
return this.#req<void>("DELETE", `/admin/file/${id}`);
|
||||
}
|
||||
|
||||
adminUserList(pageReq: PagedRequest) {
|
||||
return this.#req<PagedResponse<AdminUserListResult>>(
|
||||
"POST",
|
||||
`/admin/users`,
|
||||
pageReq,
|
||||
);
|
||||
}
|
||||
adminUserList(pageReq: PagedRequest) {
|
||||
return this.#req<PagedResponse<AdminUserListResult>>(
|
||||
"POST",
|
||||
`/admin/users`,
|
||||
pageReq,
|
||||
);
|
||||
}
|
||||
|
||||
adminUpdateUser(u: AdminProfile) {
|
||||
return this.#req<void>("POST", `/admin/update-user`, u);
|
||||
}
|
||||
adminUpdateUser(u: AdminProfile) {
|
||||
return this.#req<void>("POST", `/admin/update-user`, u);
|
||||
}
|
||||
}
|
||||
|
@ -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}`);
|
||||
|
@ -20,8 +20,9 @@ export default defineConfig({
|
||||
],
|
||||
assetsInclude: [],
|
||||
build: {
|
||||
outDir: "build",
|
||||
outDir: "build"
|
||||
},
|
||||
base: "/",
|
||||
clearScreen: false,
|
||||
resolve: {
|
||||
alias: {
|
||||
|
@ -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"
|
||||
]
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user