Import Instagram archive to a Bluesky account

Added custom PDS support

authored by marcomaroni.it and committed by tangled.org 8213e69c d6187875

+33 -39
+2 -1
.env.dist
··· 12 12 TEST_IMAGES_MODE=0 # 5 images in a post (only 4 should upload) 13 13 TEST_MIXED_MEDIA_MODE=0 # 5 images in a post (uploads two posts, one with 4 and a second with 1) 14 14 # Logging level 15 - LOG_LEVEL=info 15 + LOG_LEVEL=info 16 + ATPROTO_PDS=https://bsky.social
+1
README.md
··· 63 63 MIN_DATE=2020-01-01 # Only import posts after this date 64 64 MAX_DATE=2025-01-01 # Only import posts before this date 65 65 LOG_LEVEL=debug # Set logging verbosity (debug, info, warn, error) 66 + ATPROTO_PDS=https://mypds.net #Set a custom PDS for authentication, the default is https://bsky.social 66 67 ``` 67 68 68 69 ## Running the Import
+6 -14
src/bluesky/bluesky.ts
··· 1 - import { 2 - AtpAgent, 3 - RichText, 4 - BlobRef 5 - } from "@atproto/api"; 6 - 7 - import { 8 - EmbeddedMedia, 9 - PostRecordImpl 10 - } from "./types"; 11 - import { logger } from "../logger/logger"; 1 + import { AtpAgent, BlobRef, RichText } from '@atproto/api'; 12 2 13 - 3 + import { logger } from '../logger/logger'; 4 + // eslint-disable-next-line import/order 5 + import { EmbeddedMedia, PostRecordImpl } from './types'; 14 6 15 7 export class BlueskyClient { 16 8 private readonly agent: AtpAgent; 17 9 private readonly username: string; 18 10 private readonly password: string; 19 11 20 - constructor(username: string, password: string) { 21 - this.agent = new AtpAgent({ service: "https://bsky.social" }); 12 + constructor(username: string, password: string, atProtoPDS: string) { 13 + this.agent = new AtpAgent({ service: atProtoPDS }); 22 14 this.username = username; 23 15 this.password = password; 24 16 }
+12 -3
src/config.ts
··· 1 - import path from 'path'; 2 - 1 + // eslint-disable-next-line import/order 3 2 import * as dotenv from 'dotenv'; 3 + // eslint-disable-next-line import/order 4 + import path from 'path'; 4 5 5 6 dotenv.config(); 6 7 ··· 19 20 private readonly blueskyUsername: string; 20 21 private readonly blueskyPassword: string; 21 22 private readonly archiveFolder: string; 23 + private readonly atProtoPDS: string 22 24 23 25 constructor(config: { 24 26 testVideoMode: boolean; ··· 31 33 blueskyUsername: string; 32 34 blueskyPassword: string; 33 35 archiveFolder: string; 36 + atProtoPDS: string 34 37 }) { 35 38 this.testVideoMode = config.testVideoMode; 36 39 this.testImageMode = config.testImageMode; ··· 42 45 this.blueskyUsername = config.blueskyUsername; 43 46 this.blueskyPassword = config.blueskyPassword; 44 47 this.archiveFolder = config.archiveFolder; 48 + this.atProtoPDS = config.atProtoPDS 45 49 } 46 50 47 51 /** ··· 58 62 maxDate: process.env.MAX_DATE ? new Date(process.env.MAX_DATE) : undefined, 59 63 blueskyUsername: process.env.BLUESKY_USERNAME ?? '', 60 64 blueskyPassword: process.env.BLUESKY_PASSWORD ?? '', 61 - archiveFolder: process.env.ARCHIVE_FOLDER ?? '' 65 + archiveFolder: process.env.ARCHIVE_FOLDER ?? '', 66 + atProtoPDS: process.env.ATPROTO_PDS ?? 'https://bsky.social' 62 67 }); 63 68 } 64 69 ··· 102 107 */ 103 108 getBlueskyPassword(): string { 104 109 return this.blueskyPassword; 110 + } 111 + 112 + getAtProtoPDS(): string { 113 + return this.atProtoPDS 105 114 } 106 115 107 116 /**
+12 -21
src/instagram-to-bluesky.ts
··· 1 - import path from "path"; 1 + import path from 'path'; 2 2 3 - import { BlobRef } from "@atproto/api"; 3 + import { BlobRef } from '@atproto/api'; 4 4 5 - import { BlueskyClient } from "./bluesky/bluesky"; 5 + import { BlueskyClient } from './bluesky/bluesky'; 6 6 import { 7 - EmbeddedMedia, 8 - ImageEmbed, 9 - ImageEmbedImpl, 10 - ImagesEmbedImpl, 11 - VideoEmbedImpl, 12 - } from "./bluesky/index"; 13 - import { AppConfig } from "./config"; 14 - import { logger } from "./logger/logger"; 7 + EmbeddedMedia, ImageEmbed, ImageEmbedImpl, ImagesEmbedImpl, VideoEmbedImpl 8 + } from './bluesky/index'; 9 + import { AppConfig } from './config'; 10 + import { logger } from './logger/logger'; 15 11 import { 16 - ImageMediaProcessResultImpl, 17 - MediaProcessResult, 18 - VideoMediaProcessResultImpl, 19 - decodeUTF8, 20 - InstagramMediaProcessor, 21 - InstagramExportedPost, 22 - readJsonFile, 23 - sortPostsByCreationTime, 24 - } from "./media"; 12 + decodeUTF8, ImageMediaProcessResultImpl, InstagramExportedPost, InstagramMediaProcessor, 13 + MediaProcessResult, readJsonFile, sortPostsByCreationTime, VideoMediaProcessResultImpl 14 + } from './media'; 25 15 26 16 const API_RATE_LIMIT_DELAY = 3000; // https://docs.bsky.app/docs/advanced-guides/rate-limits 27 17 ··· 145 135 logger.info("--- SIMULATE mode is disabled, posts will be imported ---"); 146 136 bluesky = new BlueskyClient( 147 137 config.getBlueskyUsername(), 148 - config.getBlueskyPassword() 138 + config.getBlueskyPassword(), 139 + config.getAtProtoPDS() 149 140 ); 150 141 await bluesky.login(); 151 142 } else {