diff --git a/deno.json b/deno.json index 5973d7c..0ddc779 100644 --- a/deno.json +++ b/deno.json @@ -1,6 +1,7 @@ { "imports": { "@nostrify/nostrify": "jsr:@nostrify/nostrify@^0.30.1", + "nostr-geotags": "npm:nostr-geotags@^0.7.1", "nostr-tools": "npm:nostr-tools@^2.7.2" } } diff --git a/deno.lock b/deno.lock index 3bc1a55..e81cd47 100644 --- a/deno.lock +++ b/deno.lock @@ -12,6 +12,7 @@ "npm:@scure/bip32@^1.4.0": "npm:@scure/bip32@1.4.0", "npm:@scure/bip39@^1.3.0": "npm:@scure/bip39@1.3.0", "npm:lru-cache@^10.2.0": "npm:lru-cache@10.4.3", + "npm:nostr-geotags@^0.7.1": "npm:nostr-geotags@0.7.1", "npm:nostr-tools@^2.7.0": "npm:nostr-tools@2.7.2", "npm:nostr-tools@^2.7.2": "npm:nostr-tools@2.7.2", "npm:websocket-ts@^2.1.5": "npm:websocket-ts@2.1.5", @@ -122,10 +123,25 @@ "@scure/base": "@scure/base@1.1.7" } }, + "iso-3166@4.3.0": { + "integrity": "sha512-H4kM/sVbxTjSl9xnQCYOrNWdpN0R8Uz26j1BuXI9E6U+kw5wmd3HyPgr/v4+NCuvV/NcvwTfZxd5XZ4lPKvBNA==", + "dependencies": {} + }, "lru-cache@10.4.3": { "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", "dependencies": {} }, + "ngeohash@0.6.3": { + "integrity": "sha512-kltF0cOxgx1AbmVzKxYZaoB0aj7mOxZeHaerEtQV0YaqnkXNq26WWqMmJ6lTqShYxVRWZ/mwvvTrNeOwdslWiw==", + "dependencies": {} + }, + "nostr-geotags@0.7.1": { + "integrity": "sha512-3xnmDUqTP7MzWLmaJvTgWYwlex3yYGmKi6qAEOHRrh+gZs26YEqECe5yalRH0i4rBZXNXDfkiZs/gvRts6eoAQ==", + "dependencies": { + "iso-3166": "iso-3166@4.3.0", + "ngeohash": "ngeohash@0.6.3" + } + }, "nostr-tools@2.7.2": { "integrity": "sha512-Bq3Ug0SZFtgtL1+0wCnAe8AJtI7yx/00/a2nUug9SkhfOwlKS92Tef12iCK9FdwXw+oFZWMtRnSwcLayQso+xA==", "dependencies": { @@ -192,6 +208,7 @@ "workspace": { "dependencies": [ "jsr:@nostrify/nostrify@^0.30.1", + "npm:nostr-geotags@^0.7.1", "npm:nostr-tools@^2.7.2" ] } diff --git a/main.ts b/main.ts index 67d4e3a..2df0e0e 100644 --- a/main.ts +++ b/main.ts @@ -10,6 +10,8 @@ import { BlossomUploader } from '@nostrify/nostrify/uploaders'; import * as nip19 from 'nostr-tools/nip19' +import ngeotags from 'nostr-geotags'; + 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"); @@ -24,14 +26,12 @@ 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)); - +console.log("You can try one picture at a time"); +const offset = Number(prompt("Skip how many posts?", 0)); +const n = Number(prompt("How many do you want to upload?", posts.length - offset)); if (n == 0) { Deno.exit(0); } - assert(n > 0); -assert(n <= posts.length); +assert(n <= posts.length - offset); const nsec_str = Deno.env.get("NSEC") || prompt("\nPlease enter your nsec:"); @@ -54,13 +54,11 @@ 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] }])) { +for await (const msg of relay.req([{ kinds: [1], limit: 960, 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/"); @@ -75,6 +73,7 @@ let completed = 0; let events = []; for (const i in posts) { + if (i < offset) continue; const post = posts[i] console.log(post); @@ -91,6 +90,9 @@ for (const i in posts) { message = post.title + "\n"; } + // Populated from the first media item with coordinates + let geotags; + let first = true; for (const j in post.media) { const media = post.media[j]; @@ -112,6 +114,12 @@ for (const i in posts) { if (media.uri.slice(-5) == ".webp") { blob = new Blob([data], {type: 'image/webp'}); extension = ".webp" + } else if (media.uri.slice(-4) == ".jpg") { + blob = new Blob([data], {type: 'image/jpeg'}); + extension = ".jpg" + } else if (media.uri.slice(-4) == ".mp4") { + blob = new Blob([data], {type: 'video/mp4'}); + extension = ".mp4" } else if (!media.uri.includes(".")) { // Assume it's an mp4 movie blob = new Blob([data], {type: 'video/mp4'}); @@ -122,21 +130,49 @@ for (const i in posts) { console.log(post); Deno.exit(1); } + // TODO: if upload fails, log something useful + // * e.g. file size (might be over the upload limit) 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? + if (geotags == undefined && media.media_metadata && media.media_metadata.photo_metadata) { + const options = { + geohash: true, + gps: true, + city: false, + iso31662: false, + iso31662: false, + iso31663: false + }; + let exif_data = media.media_metadata.photo_metadata.exif_data; + if (exif_data) { + for (const k in exif_data) { + const exif_item = exif_data[k]; + if (exif_item.latitude != undefined && exif_item.latitude != undefined) { + geotags = ngeotags({ + lat: exif_item.latitude, + lon: exif_item.longitude + }, options); + } + } + } + } } // Generate Nostr event - const event = await signer.signEvent({ kind: 1, content: message, tags: [], created_at: created_at}); + let event = { + kind: 1, + content: message, + tags: [], + created_at: created_at + } + if (geotags != undefined) { + event.tags = [...event.tags, ...geotags]; + } + const signed_event = await signer.signEvent(event); console.log(event); events.push(event); @@ -147,7 +183,10 @@ for (const i in posts) { alert("About to post to Nostr."); for (const event in events) { - await relay.event(event); + console.log(event); + console.log(await relay.event(event)); } +await relay.close() + console.log("Done!");