diff --git a/nip94.ts b/nip94.ts new file mode 100644 index 0000000..94bee75 --- /dev/null +++ b/nip94.ts @@ -0,0 +1,201 @@ +import { Event, EventTemplate } from './core' +import { FileMetadata as FileMetadataKind } from './kinds.ts' + +/** + * Type definition for File Metadata as specified in NIP-94. + * This type is used to represent the metadata associated with a file sharing event (kind: 1063). + */ +export type FileMetadataObject = { + /** + * A description or caption for the file content. + */ + content: string + + /** + * The URL to download the file. + */ + url: string + + /** + * The MIME type of the file, in lowercase. + */ + m: string + + /** + * The SHA-256 hex-encoded string of the file. + */ + x: string + + /** + * The SHA-256 hex-encoded string of the original file, before any transformations done by the upload server. + */ + ox: string + + /** + * Optional: The size of the file in bytes. + */ + size?: string + + /** + * Optional: The dimensions of the file in pixels, in the format "x". + */ + dim?: string + + /** + * Optional: The URI to the magnet file. + */ + magnet?: string + + /** + * Optional: The torrent infohash. + */ + i?: string + + /** + * Optional: The blurhash string to show while the file is being loaded by the client. + */ + blurhash?: string + + /** + * Optional: The URL of the thumbnail image with the same aspect ratio as the original file. + */ + thumb?: string + + /** + * Optional: The URL of a preview image with the same dimensions as the original file. + */ + image?: string + + /** + * Optional: A text excerpt or summary of the file's content. + */ + summary?: string + + /** + * Optional: A description for accessibility, providing context or a brief description of the file. + */ + alt?: string +} + +/** + * Generates an event template based on a file metadata object. + * + * @param fileMetadata - The file metadata object. + * @returns The event template. + */ +export function generateEventTemplate(fileMetadata: FileMetadataObject): EventTemplate { + const eventTemplate: EventTemplate = { + content: fileMetadata.content, + created_at: Math.floor(Date.now() / 1000), + kind: FileMetadataKind, + tags: [ + ['url', fileMetadata.url], + ['m', fileMetadata.m], + ['x', fileMetadata.x], + ['ox', fileMetadata.ox], + ], + } + + if (fileMetadata.size) eventTemplate.tags.push(['size', fileMetadata.size]) + if (fileMetadata.dim) eventTemplate.tags.push(['dim', fileMetadata.dim]) + if (fileMetadata.i) eventTemplate.tags.push(['i', fileMetadata.i]) + if (fileMetadata.blurhash) eventTemplate.tags.push(['blurhash', fileMetadata.blurhash]) + if (fileMetadata.thumb) eventTemplate.tags.push(['thumb', fileMetadata.thumb]) + if (fileMetadata.image) eventTemplate.tags.push(['image', fileMetadata.image]) + if (fileMetadata.summary) eventTemplate.tags.push(['summary', fileMetadata.summary]) + if (fileMetadata.alt) eventTemplate.tags.push(['alt', fileMetadata.alt]) + + return eventTemplate +} + +/** + * Validates an event to ensure it is a valid file metadata event. + * @param event - The event to validate. + * @returns True if the event is valid, false otherwise. + */ +export function validateEvent(event: Event): boolean { + if (event.kind !== FileMetadataKind) return false + + if (!event.content) return false + + const requiredTags = ['url', 'm', 'x', 'ox'] as const + for (const tag of requiredTags) { + if (!event.tags.find(([t]) => t == tag)) return false + } + + // validate optional size tag + const sizeTag = event.tags.find(([t]) => t == 'size') + if (sizeTag && isNaN(Number(sizeTag[1]))) return false + + // validate optional dim tag + const dimTag = event.tags.find(([t]) => t == 'dim') + if (dimTag && !dimTag[1].match(/^\d+x\d+$/)) return false + + return true +} + +/** + * Parses an event and returns a file metadata object. + * @param event - The event to parse. + * @returns The file metadata object. + * @throws Error if the event is invalid. + */ +export function parseEvent(event: Event): FileMetadataObject { + if (!validateEvent(event)) { + throw new Error('Invalid event') + } + + const fileMetadata: FileMetadataObject = { + content: event.content, + url: '', + m: '', + x: '', + ox: '', + } + + for (const [tag, value] of event.tags) { + switch (tag) { + case 'url': + fileMetadata.url = value + break + case 'm': + fileMetadata.m = value + break + case 'x': + fileMetadata.x = value + break + case 'ox': + fileMetadata.ox = value + break + case 'size': + fileMetadata.size = value + break + case 'dim': + fileMetadata.dim = value + break + case 'magnet': + fileMetadata.magnet = value + break + case 'i': + fileMetadata.i = value + break + case 'blurhash': + fileMetadata.blurhash = value + break + case 'thumb': + fileMetadata.thumb = value + break + case 'image': + fileMetadata.image = value + break + case 'summary': + fileMetadata.summary = value + break + case 'alt': + fileMetadata.alt = value + break + } + } + + return fileMetadata +}