2024-08-31 16:55:52 +00:00
|
|
|
import { assert } from "https://deno.land/std@0.224.0/assert/mod.ts";
|
|
|
|
|
|
|
|
import {
|
|
|
|
NSchema,
|
|
|
|
NSecSigner,
|
|
|
|
NRelay1
|
|
|
|
} from '@nostrify/nostrify';
|
|
|
|
|
2024-08-31 19:25:48 +00:00
|
|
|
import { BlossomUploader } from '@nostrify/nostrify/uploaders';
|
|
|
|
|
2024-08-31 16:55:52 +00:00
|
|
|
import * as nip19 from 'nostr-tools/nip19'
|
|
|
|
|
2024-08-31 21:09:58 +00:00
|
|
|
import ngeotags from 'nostr-geotags';
|
|
|
|
|
2024-08-31 16:55:52 +00:00
|
|
|
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");
|
|
|
|
|
2024-08-31 19:25:48 +00:00
|
|
|
const path = Deno.env.get("INSTAGRAM_BACKUP_PATH") || prompt("Enter Instagram backup (absolute) path (unzip first): ");
|
2024-08-31 17:29:32 +00:00
|
|
|
const posts = JSON.parse(await Deno.readTextFile(path + "/content/posts_1.json"));
|
|
|
|
|
|
|
|
console.log("Number of posts:", posts.length);
|
|
|
|
|
2024-08-31 19:25:48 +00:00
|
|
|
// TODO: sanity check all posts
|
|
|
|
for (const i in posts) {
|
|
|
|
const post = posts[i]
|
|
|
|
}
|
|
|
|
|
2024-08-31 21:09:58 +00:00
|
|
|
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));
|
2024-08-31 17:29:32 +00:00
|
|
|
if (n == 0) { Deno.exit(0); }
|
|
|
|
assert(n > 0);
|
2024-08-31 21:09:58 +00:00
|
|
|
assert(n <= posts.length - offset);
|
2024-08-31 17:29:32 +00:00
|
|
|
|
2024-08-31 19:25:48 +00:00
|
|
|
const nsec_str = Deno.env.get("NSEC") || prompt("\nPlease enter your nsec:");
|
2024-08-31 16:55:52 +00:00
|
|
|
|
|
|
|
// 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
|
2024-08-31 19:25:48 +00:00
|
|
|
const relay_str = Deno.env.get("RELAY") || prompt("Pick a relay:");
|
2024-08-31 16:55:52 +00:00
|
|
|
const relay = new NRelay1(relay_str);
|
|
|
|
|
|
|
|
console.log("\nAs a sanity check, here's your last message (if any):");
|
|
|
|
|
2024-08-31 21:09:58 +00:00
|
|
|
for await (const msg of relay.req([{ kinds: [1], limit: 960, authors: [pubkey_hex] }])) {
|
2024-08-31 16:55:52 +00:00
|
|
|
if (msg[0] === 'EVENT') console.log(msg[2].content);
|
|
|
|
if (msg[0] === 'EOSE') break;
|
|
|
|
}
|
|
|
|
|
2024-08-31 19:25:48 +00:00
|
|
|
// 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) {
|
2024-08-31 21:09:58 +00:00
|
|
|
if (i < offset) continue;
|
2024-08-31 19:25:48 +00:00
|
|
|
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";
|
|
|
|
}
|
|
|
|
|
2024-08-31 21:09:58 +00:00
|
|
|
// Populated from the first media item with coordinates
|
|
|
|
let geotags;
|
|
|
|
|
2024-08-31 19:25:48 +00:00
|
|
|
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"
|
2024-08-31 21:09:58 +00:00
|
|
|
} 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"
|
2024-08-31 19:25:48 +00:00
|
|
|
} 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);
|
|
|
|
}
|
2024-08-31 21:09:58 +00:00
|
|
|
// TODO: if upload fails, log something useful
|
|
|
|
// * e.g. file size (might be over the upload limit)
|
2024-08-31 19:25:48 +00:00
|
|
|
const tags = await uploader.upload(blob);
|
|
|
|
|
|
|
|
// add URL to message (plus extension)
|
|
|
|
message += tags[0][1] + extension
|
|
|
|
|
2024-08-31 21:09:58 +00:00
|
|
|
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);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2024-08-31 19:25:48 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Generate Nostr event
|
2024-08-31 21:09:58 +00:00
|
|
|
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);
|
2024-08-31 19:25:48 +00:00
|
|
|
console.log(event);
|
|
|
|
events.push(event);
|
|
|
|
|
|
|
|
completed++;
|
|
|
|
if (completed == n) break;
|
|
|
|
}
|
|
|
|
|
|
|
|
alert("About to post to Nostr.");
|
|
|
|
|
|
|
|
for (const event in events) {
|
2024-08-31 21:09:58 +00:00
|
|
|
console.log(event);
|
|
|
|
console.log(await relay.event(event));
|
2024-08-31 19:25:48 +00:00
|
|
|
}
|
|
|
|
|
2024-08-31 21:09:58 +00:00
|
|
|
await relay.close()
|
|
|
|
|
2024-08-31 19:25:48 +00:00
|
|
|
console.log("Done!");
|