1
0
Fork 0
instablossom/main.ts

193 lines
5.9 KiB
TypeScript
Raw Normal View History

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';
import * as nip19 from 'nostr-tools/nip19'
2024-08-31 21:09:58 +00:00
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");
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:");
// 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:");
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] }])) {
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!");