``` import { NSecSigner, NRelay1, NSchema as n } from '@nostrify/nostrify'; import { BlossomUploader } from '@nostrify/nostrify/uploaders'; import * as nip19 from 'nostr-tools/nip19'; // Helper function to convert a hex string to Uint8Array function hexToUint8Array(hex: string): Uint8Array { if (hex.length % 2 !== 0) { throw new Error("Hex string must have an even length"); } const array = new Uint8Array(hex.length / 2); for (let i = 0; i < hex.length; i += 2) { array[i / 2] = parseInt(hex.substr(i, 2), 16); } return array; } // Function to read uploaded files from JSON async function readUploadedFiles(uploadedFilesPath: string): Promise<Set<string>> { try { const data = await Deno.readTextFile(uploadedFilesPath); return new Set(JSON.parse(data)); } catch { return new Set(); } } // Function to write uploaded files to JSON async function writeUploadedFiles(uploadedFiles: Set<string>, uploadedFilesPath: string) { await Deno.writeTextFile(uploadedFilesPath, JSON.stringify(Array.from(uploadedFiles))); } // Function to append a URL to a text file async function appendUrlToFile(fileUrl: string, urlFilePath: string) { try { await Deno.writeTextFile(urlFilePath, fileUrl + '\n', { append: true }); console.log(`Appended URL to file: ${fileUrl}`); } catch (error) { console.error('Error appending URL to file:', error); } } // Function to sign, parse, and upload a media file async function signAndUploadMedia( filePath: string, uploadedFiles: Set<string>, urlFilePath: string, signer: NSecSigner, relays: { relay: NRelay1; url: string }[] ) { try { // Check if the file has already been uploaded if (uploadedFiles.has(filePath)) { console.log(`File ${filePath} has already been uploaded. Skipping.`); return; } // Get the public key from the signer const pubkey = await signer.getPublicKey(); // Initialize the uploader const uploader = new BlossomUploader({ servers: [ 'https://cdn.satellite.earth', 'https://nstore.nostrver.se', 'https://blossom.puhcho.me', 'https://blossom.primal.net', 'https://cdn.nostrcheck.me' ], signer: signer, }); // Read the file const fileBuffer = await Deno.readFile(filePath); const file = new File([fileBuffer], filePath.split('/').pop()!); // Upload the file and get the tags const tags = await uploader.upload(file); // Find the URL in the tags let fileUrl = 'Unknown URL'; for (const tag of tags) { if (tag[0] === "url" && tag[1]) { fileUrl = tag[1]; break; } } // Append the URL to the text file await appendUrlToFile(fileUrl, urlFilePath); // Create event data const eventData = { kind: 1, content: `${fileUrl}`, tags: tags, created_at: Math.floor(Date.now() / 1000), }; // Sign the event to get id and sig const signedEvent = await signer.signEvent(eventData); const completeEventData = { ...eventData, id: signedEvent.id, pubkey: pubkey, sig: signedEvent.sig, }; // Parse and validate the complete event data using NSchema const event = n.event().parse(completeEventData); console.log('Parsed and validated event:', event); // Send the event to each relay for (const { relay, url } of relays) { try { console.log(`Sending event to relay ${url}`); await relay.event(event); console.log(`Event sent successfully to ${url}`); } catch (error) { console.error(`Error sending event to relay ${url}:`, error); } finally { try { await relay.close(); console.log(`Relay ${url} closed`); } catch (closeError) { console.error(`Error closing relay ${url}:`, closeError); } } } // Add the file to the uploaded files set and update the JSON file uploadedFiles.add(filePath); await writeUploadedFiles(uploadedFiles, './uploaded_files.json'); console.log("Done!"); } catch (error) { console.error('Error signing and uploading media:', error); } } // Function to select a random valid file from a folder async function getRandomValidFileFromFolder(folderPath: string, uploadedFiles: Set<string>): Promise<string | null> { const validExtensions = ['jpg', 'mp4', 'webp', 'gif']; const files: string[] = []; for await (const dirEntry of Deno.readDir(folderPath)) { if (dirEntry.isFile) { const extension = dirEntry.name.split('.').pop()?.toLowerCase(); if (extension && validExtensions.includes(extension)) { files.push(dirEntry.name); } } } // Filter out files that have already been uploaded const unuploadedFiles = files.filter(file => !uploadedFiles.has(`${folderPath}/${file}`)); if (unuploadedFiles.length === 0) { console.log('All files have been uploaded. Selecting a random URL to publish.'); return null; } const randomIndex = Math.floor(Math.random() * unuploadedFiles.length); return `${folderPath}/${unuploadedFiles[randomIndex]}`; } // Function to publish a Nostr event with a random URL async function publishRandomUrlEvent(urlFilePath: string, signer: NSecSigner, relays: { relay: NRelay1; url: string }[]) { try { const urls = (await Deno.readTextFile(urlFilePath)).trim().split('\n'); if (urls.length === 0) { console.error('No URLs found in the URL file.'); return; } const randomUrl = urls[Math.floor(Math.random() * urls.length)]; // Create event data const eventData = { kind: 1, content: `${randomUrl}`, tags: [], created_at: Math.floor(Date.now() / 1000), }; // Sign the event to get id and sig const signedEvent = await signer.signEvent(eventData); const completeEventData = { ...eventData, id: signedEvent.id, pubkey: await signer.getPublicKey(), sig: signedEvent.sig, }; // Parse and validate the complete event data using NSchema const event = n.event().parse(completeEventData); console.log('Parsed and validated event:', event); for (const { relay, url } of relays) { try { console.log(`Sending event to relay ${url}`); await relay.event(event); console.log(`Event sent successfully to ${url}`); } catch (error) { console.error(`Error sending event to relay ${url}:`, error); } finally { try { await relay.close(); console.log(`Relay ${url} closed`); } catch (closeError) { console.error(`Error closing relay ${url}:`, closeError); } } } console.log("Published random URL event successfully!"); } catch (error) { console.error('Error publishing random URL event:', error); } } // Main function to execute the script async function main() { const hexSecretKey = Deno.env.get('SECRET_KEY_HEX'); if (!hexSecretKey) { console.error('Environment variable "SECRET_KEY_HEX" is not set.'); Deno.exit(1); return; } const secretKey: Uint8Array = hexToUint8Array(hexSecretKey); // Initialize the signer with your secret key const signer = new NSecSigner(secretKey); // Define the relay URLs const relayUrls = [ 'wss://nostr.mom', 'wss://nos.lol', 'wss://relay.primal.net', 'wss://e.nos.lol', 'wss://relay.damus.io', 'wss://nostr.lu.ke', 'wss://nostr.oxtr.dev', 'wss://relay.nostrcheck.me', 'wss://nostr.data.haus', 'wss://ditto.puhcho.me/relay', 'wss://offchain.pub', 'wss://strfry.iris.to' ]; // Create an array of NRelay1 instances with their URLs const relays = relayUrls.map(url => ({ relay: new NRelay1(url), url })); // Path to the JSON file that stores uploaded files const uploadedFilesPath = './home/user/test_bloom/uploaded_files.json'; // Path to the text file that stores uploaded URLs const urlFilePath = './home/user/test_bloom/uploaded_urls.txt'; // Example usage const folderPath = Deno.env.get('MEDIA_FOLDER_PATH'); if (folderPath) { try { const uploadedFiles = await readUploadedFiles(uploadedFilesPath); const randomFilePath = await getRandomValidFileFromFolder(folderPath, uploadedFiles); if (randomFilePath) { await signAndUploadMedia(randomFilePath, uploadedFiles, urlFilePath, signer, relays); } else { await publishRandomUrlEvent(urlFilePath, signer, relays); } } catch (error) { console.error('Error during execution:', error); } finally { Deno.exit(); } } else { console.error('Environment variable "MEDIA_FOLDER_PATH" is not set.'); Deno.exit(1); } } // Execute main function main(); ``` Better as it was uploading to just one blossom.