Monorepo for Aesthetic.Computer
aesthetic.computer
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});