From 385b4479ae3cee74ac5da1af63c29767266f7c33 Mon Sep 17 00:00:00 2001 From: Kieran Date: Mon, 23 Jan 2023 17:36:49 +0000 Subject: [PATCH] feat: nostr.build --- src/Element/NoteCreator.tsx | 21 ++++++++------------- src/Feed/FileUpload.ts | 30 ++++++++++++++++++++++++++++++ src/Feed/NostrBuildUpload.ts | 26 ++++++++++++++++++++++++++ src/Feed/VoidUpload.ts | 20 ++++++++++++++++---- src/Pages/settings/Preferences.tsx | 12 ++++++++++++ src/Pages/settings/Profile.tsx | 16 ++++++++-------- src/State/Login.ts | 10 ++++++++-- 7 files changed, 108 insertions(+), 27 deletions(-) create mode 100644 src/Feed/FileUpload.ts create mode 100644 src/Feed/NostrBuildUpload.ts diff --git a/src/Element/NoteCreator.tsx b/src/Element/NoteCreator.tsx index c9e4e3e1..719645be 100644 --- a/src/Element/NoteCreator.tsx +++ b/src/Element/NoteCreator.tsx @@ -6,10 +6,9 @@ import "./NoteCreator.css"; import useEventPublisher from "Feed/EventPublisher"; import { openFile } from "Util"; -import VoidUpload from "Feed/VoidUpload"; -import { FileExtensionRegex, VoidCatHost } from "Const"; import Textarea from "Element/Textarea"; -import Event, { default as NEvent } from "Nostr/Event"; +import { default as NEvent } from "Nostr/Event"; +import useFileUpload from "Feed/FileUpload"; export interface NoteCreatorProps { replyTo?: NEvent, @@ -23,6 +22,7 @@ export function NoteCreator(props: NoteCreatorProps) { const [note, setNote] = useState(); const [error, setError] = useState(); const [active, setActive] = useState(false); + const uploader = useFileUpload(); async function sendNote() { if (note) { @@ -41,16 +41,11 @@ export function NoteCreator(props: NoteCreatorProps) { try { let file = await openFile(); if (file) { - let rx = await VoidUpload(file, file.name); - if (rx?.ok && rx?.file) { - let ext = file.name.match(FileExtensionRegex); - - // extension tricks note parser to embed the content - let url = rx.file.meta?.url ?? `${VoidCatHost}/d/${rx.file.id}${ext ? `.${ext[1]}` : ""}`; - - setNote(n => `${n}\n${url}`); - } else if (rx?.errorMessage) { - setError(rx.errorMessage); + let rx = await uploader.upload(file, file.name); + if (rx.url) { + setNote(n => `${n}\n${rx.url}`); + } else if (rx?.error) { + setError(rx.error); } } } catch (error: any) { diff --git a/src/Feed/FileUpload.ts b/src/Feed/FileUpload.ts new file mode 100644 index 00000000..1610c855 --- /dev/null +++ b/src/Feed/FileUpload.ts @@ -0,0 +1,30 @@ +import { useSelector } from "react-redux"; +import { RootState } from "State/Store"; +import NostrBuildUpload from "./NostrBuildUpload"; +import VoidUpload from "./VoidUpload"; + +export interface UploadResult { + url?: string, + error?: string +} + +export interface Uploader { + upload: (f: File | Blob, filename: string) => Promise +} + +export default function useFileUpload(): Uploader { + const fileUploader = useSelector((s: RootState) => s.login.preferences.fileUploader); + + switch (fileUploader) { + case "nostr.build": { + return { + upload: NostrBuildUpload + } as Uploader; + } + default: { + return { + upload: VoidUpload + } as Uploader; + } + } +} \ No newline at end of file diff --git a/src/Feed/NostrBuildUpload.ts b/src/Feed/NostrBuildUpload.ts new file mode 100644 index 00000000..7f8105ca --- /dev/null +++ b/src/Feed/NostrBuildUpload.ts @@ -0,0 +1,26 @@ +import { UploadResult } from "./FileUpload"; + +export default async function NostrBuildUpload(file: File | Blob): Promise { + let fd = new FormData(); + fd.append("fileToUpload", file); + fd.append("submit", "Upload Image"); + + let rsp = await fetch("https://nostr.build/api/upload/", { + body: fd, + method: "POST", + headers: { + "content-type": "multipart/form-data", + "accept": "application/json" + } + }); + if(rsp.ok) { + let data = await rsp.json(); + console.debug(data); + return { + url: data.url + } + } + return { + error: "Upload failed" + } +} \ No newline at end of file diff --git a/src/Feed/VoidUpload.ts b/src/Feed/VoidUpload.ts index 53c4aeb0..794d2bd6 100644 --- a/src/Feed/VoidUpload.ts +++ b/src/Feed/VoidUpload.ts @@ -1,11 +1,12 @@ import * as secp from "@noble/secp256k1"; -import { VoidCatHost } from "Const"; +import { FileExtensionRegex, VoidCatHost } from "Const"; +import { UploadResult } from "./FileUpload"; /** * Upload file to void.cat * https://void.cat/swagger/index.html */ -export default async function VoidUpload(file: File | Blob, filename: string) { +export default async function VoidUpload(file: File | Blob, filename: string): Promise { const buf = await file.arrayBuffer(); const digest = await crypto.subtle.digest("SHA-256", buf); @@ -25,9 +26,20 @@ export default async function VoidUpload(file: File | Blob, filename: string) { if (req.ok) { let rsp: VoidUploadResponse = await req.json(); - return rsp; + if (rsp.ok) { + let ext = filename.match(FileExtensionRegex); + return { + url: rsp.file?.meta?.url ?? `${VoidCatHost}/d/${rsp.file?.id}${ext ? `.${ext[1]}` : ""}` + } + } else { + return { + error: rsp.errorMessage + } + } } - return null; + return { + error: "Upload failed" + }; } export type VoidUploadResponse = { diff --git a/src/Pages/settings/Preferences.tsx b/src/Pages/settings/Preferences.tsx index 3304868c..ee1f50f2 100644 --- a/src/Pages/settings/Preferences.tsx +++ b/src/Pages/settings/Preferences.tsx @@ -59,6 +59,18 @@ const PreferencesPage = () => { dispatch(setPreferences({ ...perf, autoShowLatest: e.target.checked }))} /> +
+
+
File upload service
+ Pick which upload service you want to upload attachments to +
+
+ +
+
Debug Menus
diff --git a/src/Pages/settings/Profile.tsx b/src/Pages/settings/Profile.tsx index 76f42acc..09ffd469 100644 --- a/src/Pages/settings/Profile.tsx +++ b/src/Pages/settings/Profile.tsx @@ -9,13 +9,12 @@ import { faShop } from "@fortawesome/free-solid-svg-icons"; import useEventPublisher from "Feed/EventPublisher"; import { useUserProfile } from "Feed/ProfileFeed"; -import VoidUpload from "Feed/VoidUpload"; import { logout } from "State/Login"; import { hexToBech32, openFile } from "Util"; import Copy from "Element/Copy"; import { RootState } from "State/Store"; import { HexKey } from "Nostr"; -import { VoidCatHost } from "Const"; +import useFileUpload from "Feed/FileUpload"; export default function ProfileSettings() { const navigate = useNavigate(); @@ -24,6 +23,7 @@ export default function ProfileSettings() { const dispatch = useDispatch(); const user = useUserProfile(id!); const publisher = useEventPublisher(); + const uploader = useFileUpload(); const [name, setName] = useState(); const [displayName, setDisplayName] = useState(); @@ -77,25 +77,25 @@ export default function ProfileSettings() { let file = await openFile(); if (file) { console.log(file); - let rsp = await VoidUpload(file, file.name); - if (!rsp?.ok) { - throw "Upload failed, please try again later"; + let rsp = await uploader.upload(file, file.name); + if (!rsp?.error) { + throw new Error("Upload failed, please try again later"); } - return rsp.file; + return rsp.url; } } async function setNewAvatar() { const rsp = await uploadFile(); if (rsp) { - setPicture(rsp.meta?.url ?? `${VoidCatHost}/d/${rsp.id}`); + setPicture(rsp); } } async function setNewBanner() { const rsp = await uploadFile(); if (rsp) { - setBanner(rsp.meta?.url ?? `${VoidCatHost}/d/${rsp.id}`); + setBanner(rsp); } } diff --git a/src/State/Login.ts b/src/State/Login.ts index f1e9683f..66950a3b 100644 --- a/src/State/Login.ts +++ b/src/State/Login.ts @@ -40,7 +40,12 @@ export interface UserPreferences { /** * Show debugging menus to help diagnose issues */ - showDebugMenus: boolean + showDebugMenus: boolean, + + /** + * File uploading service to upload attachments to + */ + fileUploader: "void.cat" | "nostr.build" } export interface LoginStore { @@ -117,7 +122,8 @@ const InitState = { theme: "system", confirmReposts: false, showDebugMenus: false, - autoShowLatest: false + autoShowLatest: false, + fileUploader: "void.cat" } } as LoginStore;