```
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.