diff --git a/src/equicordplugins/fileUpload/index.tsx b/src/equicordplugins/fileUpload/index.tsx index cf12f7d5a..1c91c126d 100644 --- a/src/equicordplugins/fileUpload/index.tsx +++ b/src/equicordplugins/fileUpload/index.tsx @@ -5,97 +5,15 @@ */ import { findGroupChildrenByChildId, NavContextMenuPatchCallback } from "@api/ContextMenu"; -import { definePluginSettings } from "@api/Settings"; import { EquicordDevs } from "@utils/constants"; -import definePlugin, { OptionType } from "@utils/types"; +import definePlugin from "@utils/types"; import { Menu } from "@webpack/common"; -import { SettingsComponent } from "./settings"; +import { settings } from "./settings"; import { serviceLabels, ServiceType } from "./types"; import { getMediaUrl } from "./utils/getMediaUrl"; import { uploadFile } from "./utils/upload"; -export const settings = definePluginSettings({ - serviceType: { - type: OptionType.SELECT, - description: "", - options: [ - { label: "Zipline", value: ServiceType.ZIPLINE, default: true }, - { label: "E-Z Host", value: ServiceType.EZHOST }, - { label: "Nest", value: ServiceType.NEST }, - { label: "Catbox.moe", value: ServiceType.CATBOX }, - { label: "0x0.st", value: ServiceType.ZEROX0 }, - { label: "Litterbox", value: ServiceType.LITTERBOX } - ], - hidden: true - }, - serviceUrl: { - type: OptionType.STRING, - description: "", - default: "", - hidden: true - }, - ezHostKey: { - type: OptionType.STRING, - description: "E-Z Host API key", - default: "", - hidden: true - }, - ziplineToken: { - type: OptionType.STRING, - description: "", - default: "", - hidden: true - }, - nestToken: { - type: OptionType.STRING, - description: "", - default: "", - hidden: true - }, - litterboxExpiry: { - type: OptionType.SELECT, - description: "", - options: [ - { label: "1 hour", value: "1h" }, - { label: "12 hours", value: "12h" }, - { label: "24 hours", value: "24h", default: true }, - { label: "72 hours", value: "72h" } - ], - default: "24h", - hidden: true - }, - folderId: { - type: OptionType.STRING, - description: "", - default: "", - hidden: true - }, - stripQueryParams: { - type: OptionType.BOOLEAN, - description: "", - default: false, - hidden: true - }, - apngToGif: { - type: OptionType.BOOLEAN, - description: "", - default: false, - hidden: true - }, - autoCopy: { - type: OptionType.BOOLEAN, - description: "", - default: true, - hidden: true - }, - settingsComponent: { - type: OptionType.COMPONENT, - description: "Settings", - component: SettingsComponent - } -}); - const messageContextMenuPatch: NavContextMenuPatchCallback = (children, props) => { if (!props) return; diff --git a/src/equicordplugins/fileUpload/native.ts b/src/equicordplugins/fileUpload/native.ts index 61286df07..487be09e7 100644 --- a/src/equicordplugins/fileUpload/native.ts +++ b/src/equicordplugins/fileUpload/native.ts @@ -82,6 +82,60 @@ export async function uploadToEzHost( } } +export async function uploadTo0x0( + _: IpcMainInvokeEvent, + fileBuffer: ArrayBuffer, + filename: string +): Promise { + try { + const formData = new FormData(); + formData.append("file", new Blob([fileBuffer]), filename); + + const response = await fetch("https://0x0.st", { + method: "POST", + body: formData + }); + + if (!response.ok) { + const errorText = await response.text(); + return { success: false, error: `Upload failed: ${response.status} ${errorText}` }; + } + + const text = (await response.text()).trim(); + if (!text) { + return { success: false, error: "No URL returned from upload" }; + } + + return { success: true, url: text }; + } catch (e) { + return { success: false, error: e instanceof Error ? e.message : "Unknown error" }; + } +} + +export async function uploadToS3( + _: IpcMainInvokeEvent, + fileBuffer: ArrayBuffer, + uploadUrl: string, + headers: Record +): Promise { + try { + const response = await fetch(uploadUrl, { + method: "PUT", + headers, + body: new Blob([fileBuffer]) + }); + + if (!response.ok) { + const errorText = await response.text(); + return { success: false, error: `Upload failed: ${response.status} ${errorText}` }; + } + + return { success: true, url: uploadUrl }; + } catch (e) { + return { success: false, error: e instanceof Error ? e.message : "Unknown error" }; + } +} + export async function fetchFile( _: IpcMainInvokeEvent, diff --git a/src/equicordplugins/fileUpload/settings.tsx b/src/equicordplugins/fileUpload/settings.tsx index b019d041e..7da397c98 100644 --- a/src/equicordplugins/fileUpload/settings.tsx +++ b/src/equicordplugins/fileUpload/settings.tsx @@ -4,31 +4,184 @@ * SPDX-License-Identifier: GPL-3.0-or-later */ +import { definePluginSettings } from "@api/Settings"; import { SettingsSection } from "@components/settings/tabs/plugins/components/Common"; import { Switch } from "@components/Switch"; import { useForceUpdater } from "@utils/react"; +import { OptionType } from "@utils/types"; import { Select, TextInput } from "@webpack/common"; -import { settings } from "./index"; import { ServiceType } from "./types"; +const serviceOptions = [ + { label: "Zipline", value: ServiceType.ZIPLINE, default: true }, + { label: "E-Z Host", value: ServiceType.EZHOST }, + { label: "Nest", value: ServiceType.NEST }, + { label: "S3-Compatible", value: ServiceType.S3 }, + { label: "Catbox.moe", value: ServiceType.CATBOX }, + ...(IS_DISCORD_DESKTOP ? [{ label: "0x0.st", value: ServiceType.ZEROX0 }] : []), + { label: "Litterbox", value: ServiceType.LITTERBOX } +]; + +const litterboxOptions = [ + { label: "1 hour", value: "1h" }, + { label: "12 hours", value: "12h" }, + { label: "24 hours", value: "24h", default: true }, + { label: "72 hours", value: "72h" } +]; + +export const settings = definePluginSettings({ + serviceType: { + type: OptionType.SELECT, + description: "Selected uploader service", + options: serviceOptions, + hidden: true + }, + serviceUrl: { + type: OptionType.STRING, + description: "Zipline service URL", + default: "", + hidden: true + }, + ziplineToken: { + type: OptionType.STRING, + description: "Zipline auth token", + default: "", + hidden: true + }, + folderId: { + type: OptionType.STRING, + description: "Optional Zipline folder ID", + default: "", + hidden: true + }, + ezHostKey: { + type: OptionType.STRING, + description: "E-Z Host API key", + default: "", + hidden: true + }, + nestToken: { + type: OptionType.STRING, + description: "Nest API token", + default: "", + hidden: true + }, + s3Endpoint: { + type: OptionType.STRING, + description: "S3-compatible endpoint URL", + default: "", + hidden: true + }, + s3Bucket: { + type: OptionType.STRING, + description: "S3 bucket name", + default: "", + hidden: true + }, + s3Region: { + type: OptionType.STRING, + description: "S3 region (use auto for R2)", + default: "auto", + hidden: true + }, + s3AccessKeyId: { + type: OptionType.STRING, + description: "S3 access key ID", + default: "", + hidden: true + }, + s3SecretAccessKey: { + type: OptionType.STRING, + description: "S3 secret access key", + default: "", + hidden: true + }, + s3SessionToken: { + type: OptionType.STRING, + description: "Optional S3 session token", + default: "", + hidden: true + }, + s3PublicUrl: { + type: OptionType.STRING, + description: "Optional public base URL", + default: "", + hidden: true + }, + s3Prefix: { + type: OptionType.STRING, + description: "Optional S3 object key prefix", + default: "", + hidden: true + }, + s3ForcePathStyle: { + type: OptionType.BOOLEAN, + description: "Use path-style S3 URLs", + default: true, + hidden: true + }, + litterboxExpiry: { + type: OptionType.SELECT, + description: "Litterbox retention window", + options: litterboxOptions, + default: "24h", + hidden: true + }, + stripQueryParams: { + type: OptionType.BOOLEAN, + description: "Strip query params from uploaded URLs", + default: false, + hidden: true + }, + apngToGif: { + type: OptionType.BOOLEAN, + description: "Convert APNG uploads to GIF", + default: false, + hidden: true + }, + autoCopy: { + type: OptionType.BOOLEAN, + description: "Auto copy upload URL", + default: true, + hidden: true + }, + settingsComponent: { + type: OptionType.COMPONENT, + description: "Settings", + component: SettingsComponent + } +}); + +function SettingTextInput(props: { + description: string; + name: string; + onChange: (value: string) => void; + placeholder: string; + value: string; +}) { + const { description, name, onChange, placeholder, value } = props; + + return ( + + + + ); +} + export function SettingsComponent() { const update = useForceUpdater(); const { store } = settings; const isNest = store.serviceType === ServiceType.NEST; const isEzHost = store.serviceType === ServiceType.EZHOST; + const isS3 = store.serviceType === ServiceType.S3; const isZipline = store.serviceType === ServiceType.ZIPLINE; const isLitterbox = store.serviceType === ServiceType.LITTERBOX; - const serviceOptions = [ - { label: "Zipline", value: ServiceType.ZIPLINE }, - { label: "E-Z Host", value: ServiceType.EZHOST }, - { label: "Nest", value: ServiceType.NEST }, - { label: "Catbox.moe", value: ServiceType.CATBOX }, - { label: "0x0.st", value: ServiceType.ZEROX0 }, - { label: "Litterbox", value: ServiceType.LITTERBOX } - ]; - return ( <> @@ -45,64 +198,122 @@ export function SettingsComponent() { {isZipline && ( - - + store.serviceUrl = v} placeholder="https://your-zipline-instance.com" /> - - )} - - {isZipline && ( - - store.ziplineToken = v} placeholder="Your Zipline API token" /> - - )} - - {isEzHost && ( - - (store as { ezHostKey?: string }).ezHostKey = v} - placeholder="Your E-Z Host API key" - /> - - )} - - {isNest && ( - - store.nestToken = v} - placeholder="Your Nest API token" - /> - - )} - - {isZipline && ( - - store.folderId = v} placeholder="Leave empty for no folder" /> - + + )} + + {isEzHost && ( + store.ezHostKey = v} + placeholder="Your E-Z Host API key" + /> + )} + + {isNest && ( + store.nestToken = v} + placeholder="Your Nest API token" + /> + )} + + {isS3 && ( + <> + store.s3Endpoint = v} + placeholder="https://your-endpoint.example.com" + /> + store.s3Bucket = v} + placeholder="my-bucket" + /> + store.s3Region = v} + placeholder="auto" + /> + store.s3AccessKeyId = v} + placeholder="Your access key ID" + /> + store.s3SecretAccessKey = v} + placeholder="Your secret access key" + /> + store.s3SessionToken = v} + placeholder="Optional session token" + /> + store.s3PublicUrl = v} + placeholder="https://cdn.example.com" + /> + store.s3Prefix = v} + placeholder="uploads/discord" + /> + + store.s3ForcePathStyle = v} + /> + + )} {isLitterbox && (