SOURCE / PINNED RELEASE
Made of little things.
Pickle Rustlers
- Release
- 039e51f55e34…
- Author-recorded commit
- 40608586a8a1…
- License
- LICENSE
- Author’s source reference
- nostr://npub12lm3rxsva8kfempyjkxupt09e02n3fezurkyadyqfejdc5e0r2kscqty6r/wss%3A%2F%2Fgit.napplet.soy%2F/n-bad44e7ab35
Archive hash verified: f38e99afa0992052…. The source-to-build association is the author’s claim; it has not been independently rebuilt.
// Upload the card through NAP-UPLOAD, then publish a kind 1 note through NAP-OUTBOX.
import { outbox, upload, type UploadResult } from '@napplet/sdk';
import type { ShareResult } from './types.js';
type Uploaded = UploadResult & { url: string };
// A failed publish can be retried without uploading the same card again.
const uploads = new WeakMap<Blob, Uploaded>();
const reason = (error: unknown) => (error instanceof Error ? error.message : String(error ?? 'unknown error'));
/** Resolve once the upload reaches a terminal state. */
async function uploadCard(png: Blob, onStatus: (text: string) => void): Promise<UploadResult> {
let id: string | null = null;
const early: UploadResult[] = [];
let settle: ((r: UploadResult) => void) | null = null;
const terminal = (s: UploadResult) => s.status === 'complete' || s.status === 'failed' || s.status === 'cancelled';
// Listen before uploading so a fast completion is not missed.
const listener = upload.onStatus((status) => {
if (id === null) early.push(status);
else if (status.uploadId === id && terminal(status)) settle?.(status);
});
try {
onStatus('Waiting for you to approve the upload…');
const initial = await upload.upload({
data: png,
mimeType: 'image/png',
filename: 'pickle-rustlers-scorecard.png',
caption: 'Pickle Rustlers score card',
noTransform: true,
});
if (terminal(initial)) return initial;
id = initial.uploadId;
const seen = early.find((s) => s.uploadId === id && terminal(s));
if (seen) return seen;
onStatus('Uploading your score card…');
return await new Promise<UploadResult>((resolve) => (settle = resolve));
} finally {
listener.close();
}
}
/** NIP-92 imeta from the upload's NIP-94 tags, falling back to the result fields. */
function imeta(result: Uploaded, width: number, height: number): string[] {
const fields = new Map<string, string[]>();
const add = (key: string, value: string | number | undefined) => {
if (value === undefined || value === '') return;
fields.set(key, [...(fields.get(key) ?? []), String(value)]);
};
for (const tag of result.nip94 ?? []) {
const [key, value] = tag;
if (key && value && key !== 'url') add(key, value);
}
if (!fields.has('m')) add('m', result.mimeType ?? 'image/png');
if (!fields.has('x')) add('x', result.sha256);
if (!fields.has('size')) add('size', result.size);
if (!fields.has('dim')) add('dim', result.dimensions ? `${result.dimensions.width}x${result.dimensions.height}` : `${width}x${height}`);
if (!fields.has('fallback')) for (const url of result.fallbackUrls ?? []) add('fallback', url);
const entries = [`url ${result.url}`];
for (const [key, values] of fields) for (const value of values) entries.push(`${key} ${value}`);
return ['imeta', ...entries];
}
export async function postScoreCard(
png: Blob,
text: string,
size: { width: number; height: number },
onStatus: (text: string) => void,
): Promise<ShareResult> {
let uploaded = uploads.get(png);
if (!uploaded) {
let result: UploadResult;
try {
result = await uploadCard(png, onStatus);
} catch (error) {
return { ok: false, done: false, message: `Upload failed: ${reason(error)}` };
}
if (result.status !== 'complete' || !result.url) {
const what = result.status === 'cancelled' ? 'Upload cancelled' : 'Upload failed';
return { ok: false, done: false, message: result.error ? `${what}: ${result.error}` : `${what}.` };
}
uploaded = result as Uploaded;
uploads.set(png, uploaded);
}
onStatus('Waiting for you to approve the note…');
try {
const result = await outbox.publish({
kind: 1,
content: `${text.trim()}\n\n${uploaded.url}`,
tags: [imeta(uploaded, size.width, size.height), ['t', 'picklerustlers']],
created_at: Math.floor(Date.now() / 1000),
});
if (result.ok) return { ok: true, done: true, message: 'Posted! Your score card is on your feed.' };
if (result.eventId) {
// Some relays may already hold the signed note; a second post would duplicate it.
return { ok: false, done: true, message: `Posted to some relays only${result.error ? ` (${result.error})` : ''}.` };
}
return { ok: false, done: false, message: `Couldn't post: ${result.error ?? 'the host declined'}. Card: ${uploaded.url}` };
} catch (error) {
return { ok: false, done: false, message: `Couldn't post: ${reason(error)}. Card: ${uploaded.url}` };
}
}
