FileUpload: add support for encrypting.host (#1139)

* FileUpload: add support for encrypting.host

* so you can throw me to the wolves
tomorrow i will come back leader of the whole pack
beat me black and blueee
every word will shape me
every scar will build my throoone
This commit is contained in:
Key 2026-06-02 15:23:28 +01:00 • committed by GitHub
parent 03b6c19441
commit 7aebbe9707
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 217 additions and 0 deletions

View file

@ -24,6 +24,7 @@ const serviceOptions = [
{ label: "Zipline", value: ServiceType.ZIPLINE, default: true }, { label: "Zipline", value: ServiceType.ZIPLINE, default: true },
{ label: "E-Z Host", value: ServiceType.EZHOST }, { label: "E-Z Host", value: ServiceType.EZHOST },
{ label: "Nest", value: ServiceType.NEST }, { label: "Nest", value: ServiceType.NEST },
{ label: "Encrypting.host", value: ServiceType.ENCRYPTINGHOST },
{ label: "S3-Compatible", value: ServiceType.S3 }, { label: "S3-Compatible", value: ServiceType.S3 },
{ label: "Catbox.moe", value: ServiceType.CATBOX }, { label: "Catbox.moe", value: ServiceType.CATBOX },
...(IS_DISCORD_DESKTOP ? [{ label: "0x0.st", value: ServiceType.ZEROX0 }] : []), ...(IS_DISCORD_DESKTOP ? [{ label: "0x0.st", value: ServiceType.ZEROX0 }] : []),
@ -50,6 +51,13 @@ const embedProxyOptions = [
{ label: "discord.nfp.is", value: "nfp" } { label: "discord.nfp.is", value: "nfp" }
]; ];
const encryptingHostUrlStyleOptions = [
{ label: "Query", value: "query", default: true },
{ label: "Param", value: "param" },
{ label: "Fake Link", value: "fakelink" },
{ label: "Embed", value: "embed" }
];
export const settings = definePluginSettings({ export const settings = definePluginSettings({
serviceType: { serviceType: {
type: OptionType.SELECT, type: OptionType.SELECT,
@ -87,6 +95,43 @@ export const settings = definePluginSettings({
default: "", default: "",
hidden: true hidden: true
}, },
encryptingHostKey: {
type: OptionType.STRING,
description: "Encrypting.host API key",
default: "",
hidden: true
},
encryptingHostUrlStyle: {
type: OptionType.SELECT,
description: "Encrypting.host URL style",
options: encryptingHostUrlStyleOptions,
default: "query",
hidden: true
},
encryptingHostDomains: {
type: OptionType.STRING,
description: "Encrypting.host domains JSON list",
default: "[\"offensive\"]",
hidden: true
},
encryptingHostTitle: {
type: OptionType.STRING,
description: "Optional Encrypting.host embed title",
default: "",
hidden: true
},
encryptingHostColor: {
type: OptionType.STRING,
description: "Optional Encrypting.host embed color",
default: "",
hidden: true
},
encryptingHostFakelink: {
type: OptionType.STRING,
description: "Optional Encrypting.host fake link",
default: "",
hidden: true
},
s3Endpoint: { s3Endpoint: {
type: OptionType.STRING, type: OptionType.STRING,
description: "S3-compatible endpoint URL", description: "S3-compatible endpoint URL",
@ -421,6 +466,7 @@ export function SettingsComponent() {
const sharexFileInputRef = React.useRef<HTMLInputElement>(null); const sharexFileInputRef = React.useRef<HTMLInputElement>(null);
const isNest = store.serviceType === ServiceType.NEST; const isNest = store.serviceType === ServiceType.NEST;
const isEzHost = store.serviceType === ServiceType.EZHOST; const isEzHost = store.serviceType === ServiceType.EZHOST;
const isEncryptingHost = store.serviceType === ServiceType.ENCRYPTINGHOST;
const isS3 = store.serviceType === ServiceType.S3; const isS3 = store.serviceType === ServiceType.S3;
const isZipline = store.serviceType === ServiceType.ZIPLINE; const isZipline = store.serviceType === ServiceType.ZIPLINE;
const isCatbox = store.serviceType === ServiceType.CATBOX; const isCatbox = store.serviceType === ServiceType.CATBOX;
@ -527,6 +573,59 @@ export function SettingsComponent() {
</SettingGroup> </SettingGroup>
)} )}
{isEncryptingHost && (
<SettingGroup name="Encrypting.host" description="Connection details for Encrypting.host uploads.">
<SettingTextInput
name="Encrypting.host API Key"
description="Your Encrypting.host API key"
value={(store as { encryptingHostKey?: string; }).encryptingHostKey || ""}
onChange={v => (store as { encryptingHostKey?: string; }).encryptingHostKey = v}
placeholder="Your Encrypting.host API key"
/>
<SettingsSection name="URL Style" description="How Encrypting.host should format returned links.">
<Select
options={encryptingHostUrlStyleOptions}
isSelected={v => v === (store as { encryptingHostUrlStyle?: string; }).encryptingHostUrlStyle}
select={v => {
(store as { encryptingHostUrlStyle?: string; }).encryptingHostUrlStyle = v;
update();
}}
serialize={v => v}
placeholder="Select URL style"
/>
</SettingsSection>
<SettingsSection name="Domains JSON" description={"JSON array of domains to use, for example [\"offensive\"]."}>
<TextArea
value={(store as { encryptingHostDomains?: string; }).encryptingHostDomains || ""}
rows={3}
placeholder='["offensive"]'
onChange={v => (store as { encryptingHostDomains?: string; }).encryptingHostDomains = v}
/>
</SettingsSection>
<SettingTextInput
name="Embed Title"
description="Optional title for embed style responses."
value={(store as { encryptingHostTitle?: string; }).encryptingHostTitle || ""}
onChange={v => (store as { encryptingHostTitle?: string; }).encryptingHostTitle = v}
placeholder="Optional title"
/>
<SettingTextInput
name="Embed Color"
description="Optional color for embed style responses."
value={(store as { encryptingHostColor?: string; }).encryptingHostColor || ""}
onChange={v => (store as { encryptingHostColor?: string; }).encryptingHostColor = v}
placeholder="Optional color"
/>
<SettingTextInput
name="Fake Link"
description="Optional fake link value for fakelink style responses."
value={(store as { encryptingHostFakelink?: string; }).encryptingHostFakelink || ""}
onChange={v => (store as { encryptingHostFakelink?: string; }).encryptingHostFakelink = v}
placeholder="Optional fake link"
/>
</SettingGroup>
)}
{isS3 && ( {isS3 && (
<SettingGroup name="S3-Compatible Storage" description="Connection details and object naming for your bucket."> <SettingGroup name="S3-Compatible Storage" description="Connection details and object naming for your bucket.">
<SettingTextInput <SettingTextInput

View file

@ -8,6 +8,7 @@ export enum ServiceType {
ZIPLINE = "zipline", ZIPLINE = "zipline",
NEST = "nest", NEST = "nest",
EZHOST = "ezhost", EZHOST = "ezhost",
ENCRYPTINGHOST = "encryptinghost",
S3 = "s3", S3 = "s3",
CATBOX = "catbox", CATBOX = "catbox",
ZEROX0 = "0x0", ZEROX0 = "0x0",
@ -26,6 +27,7 @@ export const serviceLabels: Record<ServiceType, string> = {
[ServiceType.ZIPLINE]: "Zipline", [ServiceType.ZIPLINE]: "Zipline",
[ServiceType.NEST]: "Nest", [ServiceType.NEST]: "Nest",
[ServiceType.EZHOST]: "E-Z Host", [ServiceType.EZHOST]: "E-Z Host",
[ServiceType.ENCRYPTINGHOST]: "Encrypting.host",
[ServiceType.S3]: "S3-Compatible", [ServiceType.S3]: "S3-Compatible",
[ServiceType.CATBOX]: "Catbox", [ServiceType.CATBOX]: "Catbox",
[ServiceType.ZEROX0]: "0x0.st", [ServiceType.ZEROX0]: "0x0.st",
@ -44,6 +46,7 @@ export const fallbackServiceOrder: ServiceType[] = [
ServiceType.ZIPLINE, ServiceType.ZIPLINE,
ServiceType.EZHOST, ServiceType.EZHOST,
ServiceType.NEST, ServiceType.NEST,
ServiceType.ENCRYPTINGHOST,
ServiceType.S3, ServiceType.S3,
ServiceType.CATBOX, ServiceType.CATBOX,
ServiceType.ZEROX0, ServiceType.ZEROX0,

View file

@ -448,6 +448,117 @@ async function uploadToNest(fileBlob: Blob, filename: string): Promise<string> {
throw new Error("No URL returned from upload"); throw new Error("No URL returned from upload");
} }
type EncryptingHostUrlStyle = "query" | "param" | "fakelink" | "embed";
function parseEncryptingHostDomains(raw: string): string[] {
let parsed: unknown;
try {
parsed = JSON.parse(raw);
} catch {
throw new Error("Encrypting.host domains must be a JSON array of non-empty strings");
}
if (!Array.isArray(parsed) || parsed.some(domain => typeof domain !== "string" || !domain.trim())) {
throw new Error("Encrypting.host domains must be a JSON array of non-empty strings");
}
return parsed.map(domain => domain.trim());
}
function getEncryptingHostConfig(): {
key: string;
urlStyle: EncryptingHostUrlStyle;
domains: string[];
title: string;
color: string;
fakelink: string;
} {
const {
encryptingHostKey,
encryptingHostUrlStyle,
encryptingHostDomains,
encryptingHostTitle,
encryptingHostColor,
encryptingHostFakelink
} = settings.store as {
encryptingHostKey?: string;
encryptingHostUrlStyle?: string;
encryptingHostDomains?: string;
encryptingHostTitle?: string;
encryptingHostColor?: string;
encryptingHostFakelink?: string;
};
const key = encryptingHostKey?.trim() || "";
if (!key) {
throw new Error("Encrypting.host API key is required");
}
const style = encryptingHostUrlStyle || "query";
if (style !== "query" && style !== "param" && style !== "fakelink" && style !== "embed") {
throw new Error("Invalid Encrypting.host URL style");
}
const domainsRaw = encryptingHostDomains?.trim() || "[\"offensive\"]";
return {
key,
urlStyle: style,
domains: parseEncryptingHostDomains(domainsRaw),
title: encryptingHostTitle?.trim() || "",
color: encryptingHostColor?.trim() || "",
fakelink: encryptingHostFakelink?.trim() || ""
};
}
async function uploadToEncryptingHost(fileBlob: Blob, filename: string): Promise<string> {
const config = getEncryptingHostConfig();
const formData = new FormData();
formData.append("password", makeRandomHex(16));
formData.append("userKey", config.key);
formData.append("urlStyle", config.urlStyle);
formData.append("domains", JSON.stringify(config.domains));
if (config.title) {
formData.append("title", config.title);
}
if (config.color) {
formData.append("color", config.color);
}
if (config.fakelink) {
formData.append("fakelink", config.fakelink);
}
formData.append("file", fileBlob, filename);
const response = await uploadRequestWithTimeout("https://encrypting.host/upload", {
method: "POST",
body: formData
});
const text = await response.text();
let data: { url?: string; error?: string; } | null = null;
try {
data = text ? JSON.parse(text) as { url?: string; error?: string; } : null;
} catch {
data = null;
}
if (!response.ok) {
throw new Error(`Upload failed: ${response.status} ${text}`);
}
const parsedUrl = data?.url?.trim() || "";
const fallbackUrl = text.trim().startsWith("http") ? text.trim() : "";
const url = parsedUrl || fallbackUrl;
if (!url) {
throw new Error("No URL returned from upload");
}
return url;
}
export function isConfigured(): boolean { export function isConfigured(): boolean {
const { const {
serviceType, serviceType,
@ -465,6 +576,8 @@ export function isConfigured(): boolean {
return Boolean(nestToken); return Boolean(nestToken);
case ServiceType.EZHOST: case ServiceType.EZHOST:
return Boolean((settings.store as { ezHostKey?: string; }).ezHostKey); return Boolean((settings.store as { ezHostKey?: string; }).ezHostKey);
case ServiceType.ENCRYPTINGHOST:
return Boolean((settings.store as { encryptingHostKey?: string; }).encryptingHostKey);
case ServiceType.S3: case ServiceType.S3:
return isS3Configured(); return isS3Configured();
case ServiceType.CATBOX: case ServiceType.CATBOX:
@ -867,6 +980,8 @@ async function uploadToService(serviceType: ServiceType, fileBlob: Blob, filenam
return uploadToNest(fileBlob, filename); return uploadToNest(fileBlob, filename);
case ServiceType.EZHOST: case ServiceType.EZHOST:
return uploadToEzHost(fileBlob, filename); return uploadToEzHost(fileBlob, filename);
case ServiceType.ENCRYPTINGHOST:
return uploadToEncryptingHost(fileBlob, filename);
case ServiceType.S3: case ServiceType.S3:
return uploadToS3(fileBlob, filename, Native, uploadRequestWithTimeout); return uploadToS3(fileBlob, filename, Native, uploadRequestWithTimeout);
case ServiceType.CATBOX: case ServiceType.CATBOX: