1
0
Fork 0
instablossom/main.ts
Sjors Provoost 00c9a3686e
Drop l and L tags
They have clashing meanings. And there's no need to have gps coordinates at different resolutions. That's what geohash is good for.

This commits drop the GPS coordinate entirely, the next commit brings it back in a different way.
2024-09-06 13:02:25 +02:00

192 lines
6 KiB
TypeScript

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'
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");
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");
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 - offset);
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;
}
// 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) {
if (i < offset) continue;
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";
}
// Populated from the first media item with coordinates
let geotags;
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.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'});
extension = ".mp4"
} else {
// Unexpected, abort
console.error("Unexpected file type for: ", media.uri);
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);
// add URL to message (plus extension)
message += tags[0][1] + extension
if (geotags == undefined && media.media_metadata && media.media_metadata.photo_metadata) {
const options = {
geohash: true, // l tag per NIP-52, adds multiple tags at decreasing resolution
gps: false, // Avoid multiple tags, insert them manually below.
city: 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
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(signed_event);
events.push(signed_event);
completed++;
if (completed == n) break;
}
alert("About to post to Nostr.");
for (const i in events) {
const event = events[i]
await relay.event(event);
console.log(event.id);
}
await relay.close()
console.log("Done!");