Files
lumina/utils/utils.ts
Silberengel 525a0850b1 Upload from url PART 2 (#139)
* add URL option to image upload

* Full NIP-68 and NIP-71 implementation

* changed deprecated note ids to nevent ids, with backward-compatability

* interim state reels implementation

* Fixed uploading from URL and added a Cancel button to the upload modal. Couldn't get rid of the errors.

* Added ability to upload kinds 20, 21, 22, along with source tags (e, a, or u). Includes validation check.

* added thumbnail support

* included kind 21 and kind 22 in the feeds and searches

* Implement inboxes/outboxes

* implemented thumbnails in the profile feed

* enhanced reels feed with #reels

* interim implementation of pins

* added pins

* fixed the pins

* tidied up the reels

* fixed the uploader

* Fixed build

* update reels feed with the one from Lumina main

* fixed the reels interactions

* Added audio controls

* Interim reelfeed state

* feed working again

* full fead

---------

Co-authored-by: Silberengel <silberengel7@proton.com>
2025-09-01 23:05:54 +02:00

165 lines
5.6 KiB
TypeScript

import { Event as NostrEvent, finalizeEvent} from "nostr-tools";
import { hexToBytes } from "@noble/hashes/utils"
import { signEventWithBunker } from "./bunkerUtils";
// Check if the event has nsfw or sexy tags
export function hasNsfwContent(tags: string[][]): boolean {
return tags.some(tag =>
(tag[0] === 't' && (tag[1]?.toLowerCase() === 'nsfw' || tag[1]?.toLowerCase() === 'sexy')) ||
(tag[0] === 'content-warning')
);
}
export function getImageUrl(tags: string[][]): string {
const imetaTags = tags.filter(tag => tag[0] === 'imeta');
// First, check for 'image' fields (thumbnails for videos)
for (const imetaTag of imetaTags) {
const imageItem = imetaTag.find(item => item.startsWith('image '));
if (imageItem) {
return imageItem.split(' ')[1];
}
}
// Then, check for 'url' fields that point to images
for (const imetaTag of imetaTags) {
const urlItem = imetaTag.find(item => item.startsWith('url '));
const mimeItem = imetaTag.find(item => item.startsWith('m '));
if (urlItem) {
const url = urlItem.split(' ')[1];
// If this imeta tag has an image mime type, use it
if (mimeItem && mimeItem.startsWith('m image/')) {
return url;
}
// If the URL looks like an image, use it
if (url.match(/\.(jpg|jpeg|png|webp|gif|apng|avif)$/i)) {
return url;
}
}
}
// Fallback: return the first URL found
const firstImetaTag = imetaTags[0];
if (firstImetaTag) {
const urlItem = firstImetaTag.find(item => item.startsWith('url '));
if (urlItem) {
return urlItem.split(' ')[1];
}
}
return '';
}
export function getThumbnailUrl(tags: string[][]): string {
const imetaTag = tags.find(tag => tag[0] === 'imeta');
if (imetaTag) {
const imageItem = imetaTag.find(item => item.startsWith('image '));
if (imageItem) {
return imageItem.split(' ')[1];
}
}
return '';
}
export function extractDimensions(event: NostrEvent): { width: number; height: number } {
const imetaTag = event.tags.find(tag => tag[0] === 'imeta');
if (imetaTag) {
const dimInfo = imetaTag.find(item => item.startsWith('dim '));
if (dimInfo) {
const [width, height] = dimInfo.split(' ')[1].split('x').map(Number);
return { width, height };
}
}
return { width: 500, height: 300 }; // Default dimensions if not found
}
export async function signEvent(loginType: string | null, event: NostrEvent): Promise<NostrEvent | null> {
console.log("signEvent called with loginType:", loginType)
console.log("Event to sign:", { kind: event.kind, content: event.content?.substring(0, 100) + "..." })
// Sign event
let eventSigned: NostrEvent = { ...event, sig: '' };
if (loginType === 'extension') {
try {
console.log("Signing with extension...")
eventSigned = await window.nostr.signEvent(event);
console.log("Extension signing successful:", eventSigned.id)
} catch (error) {
console.error("Extension signing failed:", error)
throw error
}
} else if (loginType === 'amber') {
// TODO: Sign event with amber
alert('Signing with Amber is not implemented yet, sorry!');
return null;
} else if (loginType === 'bunker') {
// Sign with bunker (NIP-46)
try {
console.log("Signing with bunker...")
const signedWithBunker = await signEventWithBunker(event);
if (signedWithBunker) {
console.log("Bunker signing successful:", signedWithBunker.id)
return signedWithBunker;
} else {
console.error("Bunker signing returned null")
alert('Failed to sign with bunker. Please check your connection and try again.');
return null;
}
} catch (error) {
console.error("Bunker signing failed:", error)
alert(`Failed to sign with bunker: ${error instanceof Error ? error.message : 'Unknown error'}`);
return null;
}
} else if (loginType === 'raw_nsec') {
if (typeof window !== 'undefined') {
try {
console.log("Signing with raw nsec...")
let nsecStr = null;
nsecStr = window.localStorage.getItem('nsec');
if (nsecStr != null) {
eventSigned = finalizeEvent(event, hexToBytes(nsecStr));
console.log("Raw nsec signing successful:", eventSigned.id)
} else {
console.error("No nsec found in localStorage")
throw new Error("No private key found")
}
} catch (error) {
console.error("Raw nsec signing failed:", error)
throw error
}
}
} else {
console.error("Unknown login type:", loginType)
throw new Error(`Unknown login type: ${loginType}`)
}
console.log("Final signed event:", eventSigned);
return eventSigned;
}
// Create proxied image URL
export const getProxiedImageUrl = (url: string, width: number, height: number) => {
if (!url.startsWith("http")) return url;
try {
// Encode the URL to be used in the proxy
const encodedUrl = encodeURIComponent(url);
const imgproxyEnv = process.env.NEXT_PUBLIC_IMGPROXY_URL;
const imgproxyUrl = new URL(imgproxyEnv || "https://imgproxy.example.com");
return `${imgproxyUrl}_/resize:fit:${width}:${height}/plain/${encodedUrl}`;
} catch (error) {
console.error("Error creating proxied image URL:", error);
return url;
}
}
// Blacklist annoying pubkeys
export const blacklistPubkeys = new Set([
"0403c86a1bb4cfbc34c8a493fbd1f0d158d42dd06d03eaa3720882a066d3a378",
"7444faae22d4d4939c815819dca3c4822c209758bf86afc66365db5f79f67ddb",
"3ffac3a6c859eaaa8cdddb2c7002a6e10b33efeb92d025b14ead6f8a2d656657",
"5943c88f3c60cd9edb125a668e2911ad419fc04e94549ed96a721901dd958372",
]);