import { assert } from "https://deno.land/std@0.224.0/assert/mod.ts"; import { NSchema, NSecSigner, NRelay1 } from '@nostrify/nostrify'; import { BlossomUploader } from '@nostrify/nostrify/uploaders'; import * as nip19 from 'nostr-tools/nip19' console.log("\nUse at your own risk!\n"); console.log("Considering trying it out with a throwaway nsec."); console.log("All data will be public. Anything you upload is hard to delete.\n"); const path = Deno.env.get("INSTAGRAM_BACKUP_PATH") || prompt("Enter Instagram backup (absolute) path (unzip first): "); const posts = JSON.parse(await Deno.readTextFile(path + "/content/posts_1.json")); console.log("Number of posts:", posts.length); // TODO: sanity check all posts for (const i in posts) { const post = posts[i] } console.log("You can try one picture at a time, duplicates are skipped the next run."); const n = Number(prompt("How many do you want to upload?", posts.length)); if (n == 0) { Deno.exit(0); } assert(n > 0); assert(n <= posts.length); const nsec_str = Deno.env.get("NSEC") || prompt("\nPlease enter your nsec:"); // Sanity check format NSchema.bech32('nsec').parse(nsec_str); // Actually parse nsec const { type, data } = nip19.decode(nsec_str); assert(type === 'nsec'); const signer = new NSecSigner(data); const pubkey_hex = await signer.getPublicKey(); const npub = nip19.npubEncode(pubkey_hex); console.log("\nYour npub: ", npub); console.log("In hex format: ", pubkey_hex); // TODO: get relays from profile and/or allow multiple const relay_str = Deno.env.get("RELAY") || prompt("Pick a relay:"); const relay = new NRelay1(relay_str); console.log("\nAs a sanity check, here's your last message (if any):"); for await (const msg of relay.req([{ kinds: [1], limit: 1, authors: [pubkey_hex] }])) { if (msg[0] === 'EVENT') console.log(msg[2].content); if (msg[0] === 'EOSE') break; } await relay.close() // TODO: suggest public (free or paid) Blossom servers, allow multiple const blossom_url = Deno.env.get("BLOSSOM") || prompt("Enter Blossom server URL:", "https://blossom.primal.net/"); const uploader = new BlossomUploader({ servers: [String(blossom_url)], signer: signer, }); alert("About to upload all images. Not posting to Nostr yet."); let completed = 0; let events = []; for (const i in posts) { const post = posts[i] console.log(post); // Not all posts have a creation timestamp. If it's absent we'll use // the first media timestamp. let created_at = post.creation_timestamp; // Message is the post title followed by each image on a new line. // If an image has a title, that's added too. let message = ""; // Some posts don't have a "title" field, others have an empty string. if (post.title != undefined && post.title != "") { message = post.title + "\n"; } let first = true; for (const j in post.media) { const media = post.media[j]; if (first) { first = false; } else { message += "\n"; } if (created_at == undefined) { created_at = media.creation_timestamp; } if (media.title != undefined && media.title != "") { message += media.title + "\n"; } const data = await Deno.readFile(path + "/" + media.uri); let blob; let extension; // Check file extension if (media.uri.slice(-5) == ".webp") { blob = new Blob([data], {type: 'image/webp'}); extension = ".webp" } else if (!media.uri.includes(".")) { // Assume it's an mp4 movie blob = new Blob([data], {type: 'video/mp4'}); extension = ".mp4" } else { // Unexpected, abort console.error("Unexpected file type for: ", media.uri); console.log(post); Deno.exit(1); } const tags = await uploader.upload(blob); if (extension == ".mp4") { console.log(tags); } // add URL to message (plus extension) message += tags[0][1] + extension // TODO: get city? } // Generate Nostr event const event = await signer.signEvent({ kind: 1, content: message, tags: [], created_at: created_at}); console.log(event); events.push(event); completed++; if (completed == n) break; } alert("About to post to Nostr."); for (const event in events) { await relay.event(event); } console.log("Done!");