Monorepo for Aesthetic.Computer aesthetic.computer
at main 180 lines 5.7 kB view raw
1#!/usr/bin/env node 2 3// Interactive Instagram login script. 4// Performs the same login flow as instagram-cli (preLoginSync, challenge handling, 5// session persistence) and saves the resulting session to MongoDB for use by 6// the insta-api serverless function. 7// 8// Usage: 9// source ../aesthetic-computer-vault/.env (or set MONGODB_CONNECTION_STRING) 10// node scripts/insta-login.mjs 11// 12// If INSTA_USER / INSTA_PASS are not set, it prompts interactively. 13 14import { MongoClient } from "mongodb"; 15import { 16 IgApiClient, 17 IgCheckpointError, 18 IgLoginTwoFactorRequiredError, 19 IgLoginBadPasswordError, 20} from "instagram-private-api"; 21import { createInterface } from "readline"; 22 23const rl = createInterface({ input: process.stdin, output: process.stdout }); 24const ask = (q) => new Promise((res) => rl.question(q, res)); 25 26const MONGODB_CONNECTION_STRING = process.env.MONGODB_CONNECTION_STRING; 27const MONGODB_NAME = process.env.MONGODB_NAME || "aesthetic"; 28 29if (!MONGODB_CONNECTION_STRING) { 30 console.error("MONGODB_CONNECTION_STRING not set."); 31 console.error("Run: source aesthetic-computer-vault/.env"); 32 process.exit(1); 33} 34 35async function main() { 36 // Get credentials 37 let username = process.env.INSTA_USER; 38 let password = process.env.INSTA_PASS; 39 40 if (!username) username = await ask("Instagram username: "); 41 if (!password) password = await ask("Instagram password: "); 42 43 console.log(`\nLogging in as @${username}...`); 44 45 const ig = new IgApiClient(); 46 ig.state.generateDevice(username); 47 48 // Pre-login flow (instagram-cli pattern — reduces challenge risk) 49 console.log(" Running preLoginSync..."); 50 try { 51 await ig.launcher.preLoginSync(); 52 console.log(" preLoginSync OK"); 53 } catch (e) { 54 console.warn(" preLoginSync failed (continuing):", e.message); 55 } 56 57 // Attempt login 58 let loggedIn = false; 59 try { 60 await ig.account.login(username, password); 61 loggedIn = true; 62 console.log(" Login successful!"); 63 } catch (err) { 64 if (err instanceof IgCheckpointError) { 65 console.log("\n Instagram checkpoint triggered."); 66 console.log(" Requesting verification code..."); 67 68 try { 69 await ig.challenge.auto(true); 70 console.log(" Verification code sent (check email/SMS)."); 71 72 const code = await ask("\n Enter verification code: "); 73 await ig.challenge.sendSecurityCode(code.trim()); 74 loggedIn = true; 75 console.log(" Challenge completed!"); 76 } catch (challengeErr) { 77 console.error(" Challenge failed:", challengeErr.message); 78 rl.close(); 79 process.exit(1); 80 } 81 } else if (err instanceof IgLoginTwoFactorRequiredError) { 82 console.log("\n Two-factor authentication required."); 83 const twoFactorInfo = err.response.body.two_factor_info; 84 const totp = twoFactorInfo.totp_two_factor_on; 85 const method = totp ? "authenticator app" : "SMS"; 86 console.log(` Method: ${method}`); 87 88 const code = await ask(`\n Enter 2FA code from ${method}: `); 89 try { 90 await ig.account.twoFactorLogin({ 91 username, 92 verificationCode: code.trim(), 93 twoFactorIdentifier: twoFactorInfo.two_factor_identifier, 94 verificationMethod: totp ? "0" : "1", 95 }); 96 loggedIn = true; 97 console.log(" 2FA login successful!"); 98 } catch (tfaErr) { 99 console.error(" 2FA failed:", tfaErr.message); 100 rl.close(); 101 process.exit(1); 102 } 103 } else if (err instanceof IgLoginBadPasswordError) { 104 console.error(" Bad password. Check credentials."); 105 rl.close(); 106 process.exit(1); 107 } else { 108 console.error(" Login error:", err.message); 109 rl.close(); 110 process.exit(1); 111 } 112 } 113 114 if (!loggedIn) { 115 console.error("Login failed."); 116 rl.close(); 117 process.exit(1); 118 } 119 120 // Post-login flow (instagram-cli pattern) 121 console.log(" Running postLoginFlow..."); 122 try { 123 await ig.feed.reelsTray("cold_start").request(); 124 await ig.feed.timeline("cold_start_fetch").request(); 125 console.log(" postLoginFlow OK"); 126 } catch (e) { 127 console.warn(" postLoginFlow failed (continuing):", e.message); 128 } 129 130 // Verify login 131 const currentUser = await ig.account.currentUser(); 132 console.log(`\n Logged in as: @${currentUser.username} (pk: ${currentUser.pk})`); 133 134 // Serialize and save session to MongoDB 135 console.log("\n Saving session to MongoDB..."); 136 const serialized = await ig.state.serialize(); 137 delete serialized.constants; 138 delete serialized.supportedCapabilities; 139 140 const client = new MongoClient(MONGODB_CONNECTION_STRING); 141 try { 142 await client.connect(); 143 const db = client.db(MONGODB_NAME); 144 145 await db.collection("insta-sessions").updateOne( 146 { _id: username }, 147 { 148 $set: { 149 _id: username, 150 state: serialized, 151 updatedAt: new Date(), 152 loginMethod: "insta-login.mjs", 153 verifiedUser: currentUser.username, 154 }, 155 }, 156 { upsert: true }, 157 ); 158 console.log(" Session saved to MongoDB (insta-sessions collection)"); 159 160 // Verify by reading it back 161 const doc = await db 162 .collection("insta-sessions") 163 .findOne({ _id: username }); 164 console.log(` Session document size: ~${JSON.stringify(doc.state).length} bytes`); 165 console.log(` Updated at: ${doc.updatedAt}`); 166 } finally { 167 await client.close(); 168 } 169 170 console.log("\n Done! The insta-api function will now reuse this session."); 171 console.log(" Try: /api/insta?action=profile&username=whistlegraph\n"); 172 173 rl.close(); 174} 175 176main().catch((e) => { 177 console.error("Fatal:", e.message); 178 rl.close(); 179 process.exit(1); 180});