feat: nip96
This commit is contained in:
parent
38277c3252
commit
5942d92923
@ -46,6 +46,7 @@ Snort supports the following NIP's:
|
|||||||
- [x] NIP-78: App specific data
|
- [x] NIP-78: App specific data
|
||||||
- [ ] NIP-89: App handlers
|
- [ ] NIP-89: App handlers
|
||||||
- [x] NIP-94: File Metadata
|
- [x] NIP-94: File Metadata
|
||||||
|
- [x] NIP-96: HTTP File Storage Integration (Draft)
|
||||||
- [x] NIP-98: HTTP Auth
|
- [x] NIP-98: HTTP Auth
|
||||||
|
|
||||||
### Running
|
### Running
|
||||||
|
@ -13,11 +13,6 @@ export const Day = Hour * 24;
|
|||||||
*/
|
*/
|
||||||
export const ApiHost = "https://api.snort.social";
|
export const ApiHost = "https://api.snort.social";
|
||||||
|
|
||||||
/**
|
|
||||||
* Void.cat file upload service url
|
|
||||||
*/
|
|
||||||
export const VoidCatHost = "https://void.cat";
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Kierans pubkey
|
* Kierans pubkey
|
||||||
*/
|
*/
|
||||||
|
@ -45,7 +45,7 @@ export interface UserPreferences {
|
|||||||
/**
|
/**
|
||||||
* File uploading service to upload attachments to
|
* File uploading service to upload attachments to
|
||||||
*/
|
*/
|
||||||
fileUploader: "void.cat" | "nostr.build" | "nostrimg.com";
|
fileUploader: "void.cat" | "nostr.build" | "nostrimg.com" | "void.cat-NIP96";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Use imgproxy to optimize images
|
* Use imgproxy to optimize images
|
||||||
|
@ -471,6 +471,7 @@ const PreferencesPage = () => {
|
|||||||
<option value="void.cat">
|
<option value="void.cat">
|
||||||
void.cat <FormattedMessage {...messages.Default} />
|
void.cat <FormattedMessage {...messages.Default} />
|
||||||
</option>
|
</option>
|
||||||
|
<option value="void.cat-NIP96">void.cat (NIP-96)</option>
|
||||||
<option value="nostr.build">nostr.build</option>
|
<option value="nostr.build">nostr.build</option>
|
||||||
<option value="nostrimg.com">nostrimg.com</option>
|
<option value="nostrimg.com">nostrimg.com</option>
|
||||||
</select>
|
</select>
|
||||||
|
73
packages/app/src/Upload/Nip96.ts
Normal file
73
packages/app/src/Upload/Nip96.ts
Normal file
@ -0,0 +1,73 @@
|
|||||||
|
import { base64 } from "@scure/base";
|
||||||
|
import { throwIfOffline } from "@snort/shared";
|
||||||
|
import { EventPublisher, EventKind } from "@snort/system";
|
||||||
|
import { UploadResult, Uploader } from "Upload";
|
||||||
|
|
||||||
|
export class Nip96Uploader implements Uploader {
|
||||||
|
constructor(
|
||||||
|
readonly url: string,
|
||||||
|
readonly publisher: EventPublisher,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
get progress() {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
async upload(file: File | Blob, filename: string): Promise<UploadResult> {
|
||||||
|
throwIfOffline();
|
||||||
|
const auth = async (url: string, method: string) => {
|
||||||
|
const auth = await this.publisher.generic(eb => {
|
||||||
|
return eb.kind(EventKind.HttpAuthentication).tag(["u", url]).tag(["method", method]);
|
||||||
|
});
|
||||||
|
return `Nostr ${base64.encode(new TextEncoder().encode(JSON.stringify(auth)))}`;
|
||||||
|
};
|
||||||
|
|
||||||
|
const fd = new FormData();
|
||||||
|
fd.append("size", file.size.toString());
|
||||||
|
fd.append("alt", filename);
|
||||||
|
fd.append("media_type", file.type);
|
||||||
|
fd.append("file", file);
|
||||||
|
|
||||||
|
const rsp = await fetch(this.url, {
|
||||||
|
body: fd,
|
||||||
|
method: "POST",
|
||||||
|
headers: {
|
||||||
|
accept: "application/json",
|
||||||
|
authorization: await auth(this.url, "POST"),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
if (rsp.ok) {
|
||||||
|
throwIfOffline();
|
||||||
|
const data = (await rsp.json()) as Nip96Result;
|
||||||
|
if (data.status === "success") {
|
||||||
|
const dim = data.nip94_event.tags
|
||||||
|
.find(a => a[0] === "dim")
|
||||||
|
?.at(1)
|
||||||
|
?.split("x");
|
||||||
|
return {
|
||||||
|
url: data.nip94_event.tags.find(a => a[0] === "url")?.at(1),
|
||||||
|
metadata: {
|
||||||
|
width: dim?.at(0) ? Number(dim[0]) : undefined,
|
||||||
|
height: dim?.at(1) ? Number(dim[1]) : undefined,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
error: data.message,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
error: "Upload failed",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Nip96Result {
|
||||||
|
status: string;
|
||||||
|
message: string;
|
||||||
|
processing_url?: string;
|
||||||
|
nip94_event: {
|
||||||
|
tags: Array<Array<string>>;
|
||||||
|
content: string;
|
||||||
|
};
|
||||||
|
}
|
@ -1,7 +1,7 @@
|
|||||||
import { EventKind, EventPublisher } from "@snort/system";
|
import { EventKind, EventPublisher } from "@snort/system";
|
||||||
import { UploadState, VoidApi } from "@void-cat/api";
|
import { UploadState, VoidApi } from "@void-cat/api";
|
||||||
|
|
||||||
import { FileExtensionRegex, VoidCatHost } from "Const";
|
import { FileExtensionRegex } from "Const";
|
||||||
import { UploadResult } from "Upload";
|
import { UploadResult } from "Upload";
|
||||||
import { base64 } from "@scure/base";
|
import { base64 } from "@scure/base";
|
||||||
import { throwIfOffline } from "@snort/shared";
|
import { throwIfOffline } from "@snort/shared";
|
||||||
@ -26,7 +26,7 @@ export default async function VoidCatUpload(
|
|||||||
return `Nostr ${base64.encode(new TextEncoder().encode(JSON.stringify(auth)))}`;
|
return `Nostr ${base64.encode(new TextEncoder().encode(JSON.stringify(auth)))}`;
|
||||||
}
|
}
|
||||||
: undefined;
|
: undefined;
|
||||||
const api = new VoidApi(VoidCatHost, auth);
|
const api = new VoidApi("https://void.cat", auth);
|
||||||
const uploader = api.getUploader(
|
const uploader = api.getUploader(
|
||||||
file,
|
file,
|
||||||
sx => {
|
sx => {
|
||||||
@ -58,7 +58,7 @@ export default async function VoidCatUpload(
|
|||||||
if (rsp.file?.metadata?.mimeType === "image/webp") {
|
if (rsp.file?.metadata?.mimeType === "image/webp") {
|
||||||
ext = ["", "webp"];
|
ext = ["", "webp"];
|
||||||
}
|
}
|
||||||
const resultUrl = rsp.file?.metadata?.url ?? `${VoidCatHost}/d/${rsp.file?.id}${ext ? `.${ext[1]}` : ""}`;
|
const resultUrl = rsp.file?.metadata?.url ?? `https://void.cat/d/${rsp.file?.id}${ext ? `.${ext[1]}` : ""}`;
|
||||||
|
|
||||||
const ret = {
|
const ret = {
|
||||||
url: resultUrl,
|
url: resultUrl,
|
||||||
|
@ -7,8 +7,9 @@ import NostrBuild from "Upload/NostrBuild";
|
|||||||
import VoidCat from "Upload/VoidCat";
|
import VoidCat from "Upload/VoidCat";
|
||||||
import NostrImg from "Upload/NostrImg";
|
import NostrImg from "Upload/NostrImg";
|
||||||
import { KieranPubKey } from "Const";
|
import { KieranPubKey } from "Const";
|
||||||
import { bech32ToHex } from "SnortUtils";
|
import { bech32ToHex, unwrap } from "SnortUtils";
|
||||||
import useEventPublisher from "Hooks/useEventPublisher";
|
import useEventPublisher from "Hooks/useEventPublisher";
|
||||||
|
import { Nip96Uploader } from "./Nip96";
|
||||||
|
|
||||||
export interface UploadResult {
|
export interface UploadResult {
|
||||||
url?: string;
|
url?: string;
|
||||||
@ -74,6 +75,9 @@ export default function useFileUpload(): Uploader {
|
|||||||
progress: [],
|
progress: [],
|
||||||
} as Uploader;
|
} as Uploader;
|
||||||
}
|
}
|
||||||
|
case "void.cat-NIP96": {
|
||||||
|
return new Nip96Uploader("https://void.cat/nostr", unwrap(publisher));
|
||||||
|
}
|
||||||
case "nostrimg.com": {
|
case "nostrimg.com": {
|
||||||
return {
|
return {
|
||||||
upload: NostrImg,
|
upload: NostrImg,
|
||||||
|
Loading…
x
Reference in New Issue
Block a user