agent-messenger 2.27.2 → 2.29.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/README.md +11 -1
- package/.claude-plugin/marketplace.json +14 -1
- package/.claude-plugin/plugin.json +4 -2
- package/AGENTS.md +4 -0
- package/README.md +58 -10
- package/dist/package.json +10 -2
- package/dist/src/cli.d.ts.map +1 -1
- package/dist/src/cli.js +3 -0
- package/dist/src/cli.js.map +1 -1
- package/dist/src/platforms/discord/client.d.ts +6 -2
- package/dist/src/platforms/discord/client.d.ts.map +1 -1
- package/dist/src/platforms/discord/client.js +10 -3
- package/dist/src/platforms/discord/client.js.map +1 -1
- package/dist/src/platforms/discordbot/cli.js +1 -1
- package/dist/src/platforms/imessage/cli.d.ts +5 -0
- package/dist/src/platforms/imessage/cli.d.ts.map +1 -0
- package/dist/src/platforms/imessage/cli.js +31 -0
- package/dist/src/platforms/imessage/cli.js.map +1 -0
- package/dist/src/platforms/imessage/client.d.ts +43 -0
- package/dist/src/platforms/imessage/client.d.ts.map +1 -0
- package/dist/src/platforms/imessage/client.js +190 -0
- package/dist/src/platforms/imessage/client.js.map +1 -0
- package/dist/src/platforms/imessage/commands/auth.d.ts +31 -0
- package/dist/src/platforms/imessage/commands/auth.d.ts.map +1 -0
- package/dist/src/platforms/imessage/commands/auth.js +100 -0
- package/dist/src/platforms/imessage/commands/auth.js.map +1 -0
- package/dist/src/platforms/imessage/commands/chat.d.ts +3 -0
- package/dist/src/platforms/imessage/commands/chat.d.ts.map +1 -0
- package/dist/src/platforms/imessage/commands/chat.js +42 -0
- package/dist/src/platforms/imessage/commands/chat.js.map +1 -0
- package/dist/src/platforms/imessage/commands/doctor.d.ts +25 -0
- package/dist/src/platforms/imessage/commands/doctor.d.ts.map +1 -0
- package/dist/src/platforms/imessage/commands/doctor.js +101 -0
- package/dist/src/platforms/imessage/commands/doctor.js.map +1 -0
- package/dist/src/platforms/imessage/commands/index.d.ts +7 -0
- package/dist/src/platforms/imessage/commands/index.d.ts.map +1 -0
- package/dist/src/platforms/imessage/commands/index.js +7 -0
- package/dist/src/platforms/imessage/commands/index.js.map +1 -0
- package/dist/src/platforms/imessage/commands/message.d.ts +5 -0
- package/dist/src/platforms/imessage/commands/message.d.ts.map +1 -0
- package/dist/src/platforms/imessage/commands/message.js +127 -0
- package/dist/src/platforms/imessage/commands/message.js.map +1 -0
- package/dist/src/platforms/imessage/commands/setup.d.ts +20 -0
- package/dist/src/platforms/imessage/commands/setup.d.ts.map +1 -0
- package/dist/src/platforms/imessage/commands/setup.js +57 -0
- package/dist/src/platforms/imessage/commands/setup.js.map +1 -0
- package/dist/src/platforms/imessage/commands/shared.d.ts +10 -0
- package/dist/src/platforms/imessage/commands/shared.d.ts.map +1 -0
- package/dist/src/platforms/imessage/commands/shared.js +53 -0
- package/dist/src/platforms/imessage/commands/shared.js.map +1 -0
- package/dist/src/platforms/imessage/commands/whoami.d.ts +3 -0
- package/dist/src/platforms/imessage/commands/whoami.d.ts.map +1 -0
- package/dist/src/platforms/imessage/commands/whoami.js +20 -0
- package/dist/src/platforms/imessage/commands/whoami.js.map +1 -0
- package/dist/src/platforms/imessage/credential-manager.d.ts +22 -0
- package/dist/src/platforms/imessage/credential-manager.d.ts.map +1 -0
- package/dist/src/platforms/imessage/credential-manager.js +111 -0
- package/dist/src/platforms/imessage/credential-manager.js.map +1 -0
- package/dist/src/platforms/imessage/ensure-auth.d.ts +2 -0
- package/dist/src/platforms/imessage/ensure-auth.d.ts.map +1 -0
- package/dist/src/platforms/imessage/ensure-auth.js +13 -0
- package/dist/src/platforms/imessage/ensure-auth.js.map +1 -0
- package/dist/src/platforms/imessage/errors.d.ts +3 -0
- package/dist/src/platforms/imessage/errors.d.ts.map +1 -0
- package/dist/src/platforms/imessage/errors.js +25 -0
- package/dist/src/platforms/imessage/errors.js.map +1 -0
- package/dist/src/platforms/imessage/index.d.ts +7 -0
- package/dist/src/platforms/imessage/index.d.ts.map +1 -0
- package/dist/src/platforms/imessage/index.js +5 -0
- package/dist/src/platforms/imessage/index.js.map +1 -0
- package/dist/src/platforms/imessage/rpc.d.ts +20 -0
- package/dist/src/platforms/imessage/rpc.d.ts.map +1 -0
- package/dist/src/platforms/imessage/rpc.js +133 -0
- package/dist/src/platforms/imessage/rpc.js.map +1 -0
- package/dist/src/platforms/imessage/types.d.ts +59 -0
- package/dist/src/platforms/imessage/types.d.ts.map +1 -0
- package/dist/src/platforms/imessage/types.js +29 -0
- package/dist/src/platforms/imessage/types.js.map +1 -0
- package/dist/src/platforms/instagram/cli.js +1 -1
- package/dist/src/platforms/instagram/client.d.ts +29 -2
- package/dist/src/platforms/instagram/client.d.ts.map +1 -1
- package/dist/src/platforms/instagram/client.js +360 -53
- package/dist/src/platforms/instagram/client.js.map +1 -1
- package/dist/src/platforms/instagram/commands/auth.d.ts +1 -0
- package/dist/src/platforms/instagram/commands/auth.d.ts.map +1 -1
- package/dist/src/platforms/instagram/commands/auth.js +123 -11
- package/dist/src/platforms/instagram/commands/auth.js.map +1 -1
- package/dist/src/platforms/instagram/credential-manager.d.ts +4 -1
- package/dist/src/platforms/instagram/credential-manager.d.ts.map +1 -1
- package/dist/src/platforms/instagram/credential-manager.js +17 -1
- package/dist/src/platforms/instagram/credential-manager.js.map +1 -1
- package/dist/src/platforms/instagram/hybrid-listener.d.ts +48 -0
- package/dist/src/platforms/instagram/hybrid-listener.d.ts.map +1 -0
- package/dist/src/platforms/instagram/hybrid-listener.js +154 -0
- package/dist/src/platforms/instagram/hybrid-listener.js.map +1 -0
- package/dist/src/platforms/instagram/index.d.ts +2 -0
- package/dist/src/platforms/instagram/index.d.ts.map +1 -1
- package/dist/src/platforms/instagram/index.js +2 -0
- package/dist/src/platforms/instagram/index.js.map +1 -1
- package/dist/src/platforms/instagram/mqtt/connection.d.ts +4 -0
- package/dist/src/platforms/instagram/mqtt/connection.d.ts.map +1 -0
- package/dist/src/platforms/instagram/mqtt/connection.js +66 -0
- package/dist/src/platforms/instagram/mqtt/connection.js.map +1 -0
- package/dist/src/platforms/instagram/mqtt/thrift.d.ts +22 -0
- package/dist/src/platforms/instagram/mqtt/thrift.d.ts.map +1 -0
- package/dist/src/platforms/instagram/mqtt/thrift.js +143 -0
- package/dist/src/platforms/instagram/mqtt/thrift.js.map +1 -0
- package/dist/src/platforms/instagram/mqtt/transport.d.ts +42 -0
- package/dist/src/platforms/instagram/mqtt/transport.d.ts.map +1 -0
- package/dist/src/platforms/instagram/mqtt/transport.js +246 -0
- package/dist/src/platforms/instagram/mqtt/transport.js.map +1 -0
- package/dist/src/platforms/instagram/realtime-listener.d.ts +33 -0
- package/dist/src/platforms/instagram/realtime-listener.d.ts.map +1 -0
- package/dist/src/platforms/instagram/realtime-listener.js +151 -0
- package/dist/src/platforms/instagram/realtime-listener.js.map +1 -0
- package/dist/src/platforms/instagram/types.d.ts +10 -8
- package/dist/src/platforms/instagram/types.d.ts.map +1 -1
- package/dist/src/platforms/instagram/types.js.map +1 -1
- package/dist/src/platforms/kakaotalk/cli.js +1 -1
- package/dist/src/platforms/line/cli.js +1 -1
- package/dist/src/platforms/teams/app-config.d.ts +36 -0
- package/dist/src/platforms/teams/app-config.d.ts.map +1 -0
- package/dist/src/platforms/teams/app-config.js +69 -0
- package/dist/src/platforms/teams/app-config.js.map +1 -0
- package/dist/src/platforms/teams/client.d.ts +19 -2
- package/dist/src/platforms/teams/client.d.ts.map +1 -1
- package/dist/src/platforms/teams/client.js +295 -7
- package/dist/src/platforms/teams/client.js.map +1 -1
- package/dist/src/platforms/teams/commands/auth.d.ts +10 -0
- package/dist/src/platforms/teams/commands/auth.d.ts.map +1 -1
- package/dist/src/platforms/teams/commands/auth.js +137 -3
- package/dist/src/platforms/teams/commands/auth.js.map +1 -1
- package/dist/src/platforms/teams/commands/file.d.ts +3 -0
- package/dist/src/platforms/teams/commands/file.d.ts.map +1 -1
- package/dist/src/platforms/teams/commands/file.js +59 -2
- package/dist/src/platforms/teams/commands/file.js.map +1 -1
- package/dist/src/platforms/teams/commands/message.d.ts +11 -0
- package/dist/src/platforms/teams/commands/message.d.ts.map +1 -1
- package/dist/src/platforms/teams/commands/message.js +121 -2
- package/dist/src/platforms/teams/commands/message.js.map +1 -1
- package/dist/src/platforms/teams/credential-manager.d.ts +23 -1
- package/dist/src/platforms/teams/credential-manager.d.ts.map +1 -1
- package/dist/src/platforms/teams/credential-manager.js +46 -2
- package/dist/src/platforms/teams/credential-manager.js.map +1 -1
- package/dist/src/platforms/teams/device-code.d.ts +48 -0
- package/dist/src/platforms/teams/device-code.d.ts.map +1 -0
- package/dist/src/platforms/teams/device-code.js +199 -0
- package/dist/src/platforms/teams/device-code.js.map +1 -0
- package/dist/src/platforms/teams/device-login.d.ts +37 -0
- package/dist/src/platforms/teams/device-login.d.ts.map +1 -0
- package/dist/src/platforms/teams/device-login.js +129 -0
- package/dist/src/platforms/teams/device-login.js.map +1 -0
- package/dist/src/platforms/teams/ensure-auth.d.ts.map +1 -1
- package/dist/src/platforms/teams/ensure-auth.js +12 -0
- package/dist/src/platforms/teams/ensure-auth.js.map +1 -1
- package/dist/src/platforms/teams/index.d.ts +7 -3
- package/dist/src/platforms/teams/index.d.ts.map +1 -1
- package/dist/src/platforms/teams/index.js +5 -2
- package/dist/src/platforms/teams/index.js.map +1 -1
- package/dist/src/platforms/teams/listener.d.ts +35 -0
- package/dist/src/platforms/teams/listener.d.ts.map +1 -0
- package/dist/src/platforms/teams/listener.js +236 -0
- package/dist/src/platforms/teams/listener.js.map +1 -0
- package/dist/src/platforms/teams/realm-discovery.d.ts +3 -0
- package/dist/src/platforms/teams/realm-discovery.d.ts.map +1 -0
- package/dist/src/platforms/teams/realm-discovery.js +33 -0
- package/dist/src/platforms/teams/realm-discovery.js.map +1 -0
- package/dist/src/platforms/teams/token-extractor.d.ts +3 -0
- package/dist/src/platforms/teams/token-extractor.d.ts.map +1 -1
- package/dist/src/platforms/teams/token-extractor.js +60 -0
- package/dist/src/platforms/teams/token-extractor.js.map +1 -1
- package/dist/src/platforms/teams/token-provider.d.ts +25 -0
- package/dist/src/platforms/teams/token-provider.d.ts.map +1 -0
- package/dist/src/platforms/teams/token-provider.js +190 -0
- package/dist/src/platforms/teams/token-provider.js.map +1 -0
- package/dist/src/platforms/teams/trouter.d.ts +31 -0
- package/dist/src/platforms/teams/trouter.d.ts.map +1 -0
- package/dist/src/platforms/teams/trouter.js +181 -0
- package/dist/src/platforms/teams/trouter.js.map +1 -0
- package/dist/src/platforms/teams/types.d.ts +94 -0
- package/dist/src/platforms/teams/types.d.ts.map +1 -1
- package/dist/src/platforms/teams/types.js +30 -0
- package/dist/src/platforms/teams/types.js.map +1 -1
- package/dist/src/platforms/webex/cli.js +1 -1
- package/dist/src/platforms/webex/types.d.ts +1 -1
- package/dist/src/platforms/webexbot/cli.js +1 -1
- package/dist/src/platforms/wechatbot/cli.js +1 -1
- package/dist/src/platforms/whatsapp/cli.js +1 -1
- package/dist/src/platforms/whatsappbot/cli.js +1 -1
- package/dist/src/tui/adapters/imessage-adapter.d.ts +21 -0
- package/dist/src/tui/adapters/imessage-adapter.d.ts.map +1 -0
- package/dist/src/tui/adapters/imessage-adapter.js +110 -0
- package/dist/src/tui/adapters/imessage-adapter.js.map +1 -0
- package/dist/src/tui/adapters/instagram-adapter.d.ts.map +1 -1
- package/dist/src/tui/adapters/instagram-adapter.js +16 -0
- package/dist/src/tui/adapters/instagram-adapter.js.map +1 -1
- package/dist/src/tui/adapters/teams-adapter.d.ts +3 -0
- package/dist/src/tui/adapters/teams-adapter.d.ts.map +1 -1
- package/dist/src/tui/adapters/teams-adapter.js +21 -0
- package/dist/src/tui/adapters/teams-adapter.js.map +1 -1
- package/dist/src/tui/app.d.ts.map +1 -1
- package/dist/src/tui/app.js +12 -3
- package/dist/src/tui/app.js.map +1 -1
- package/docs/content/docs/agent-skills.mdx +1 -1
- package/docs/content/docs/cli/imessage.mdx +99 -0
- package/docs/content/docs/cli/instagram.mdx +2 -1
- package/docs/content/docs/cli/meta.json +1 -0
- package/docs/content/docs/cli/teams.mdx +22 -0
- package/docs/content/docs/index.mdx +9 -6
- package/docs/content/docs/quick-start.mdx +2 -0
- package/docs/content/docs/sdk/instagram.mdx +90 -5
- package/docs/content/docs/tui.mdx +4 -3
- package/package.json +10 -2
- package/scripts/postbuild.ts +12 -15
- package/skills/agent-channeltalk/SKILL.md +1 -1
- package/skills/agent-channeltalkbot/SKILL.md +1 -1
- package/skills/agent-discord/SKILL.md +1 -1
- package/skills/agent-discordbot/SKILL.md +1 -1
- package/skills/agent-imessage/SKILL.md +133 -0
- package/skills/agent-imessage/references/authentication.md +66 -0
- package/skills/agent-imessage/references/permissions.md +31 -0
- package/skills/agent-imessage/references/setup.md +49 -0
- package/skills/agent-instagram/SKILL.md +72 -2
- package/skills/agent-instagram/references/authentication.md +11 -0
- package/skills/agent-instagram/references/common-patterns.md +36 -0
- package/skills/agent-instagram/templates/monitor-chat.sh +5 -0
- package/skills/agent-kakaotalk/SKILL.md +1 -1
- package/skills/agent-line/SKILL.md +1 -1
- package/skills/agent-slack/SKILL.md +1 -1
- package/skills/agent-slackbot/SKILL.md +1 -1
- package/skills/agent-teams/SKILL.md +59 -6
- package/skills/agent-teams/references/authentication.md +31 -1
- package/skills/agent-telegram/SKILL.md +1 -1
- package/skills/agent-telegrambot/SKILL.md +1 -1
- package/skills/agent-webex/SKILL.md +1 -1
- package/skills/agent-webexbot/SKILL.md +1 -1
- package/skills/agent-wechatbot/SKILL.md +1 -1
- package/skills/agent-whatsapp/SKILL.md +1 -1
- package/skills/agent-whatsappbot/SKILL.md +1 -1
- package/src/cli.ts +4 -0
- package/src/platforms/discord/client.test.ts +86 -0
- package/src/platforms/discord/client.ts +10 -3
- package/src/platforms/imessage/cli.ts +39 -0
- package/src/platforms/imessage/client.test.ts +137 -0
- package/src/platforms/imessage/client.ts +253 -0
- package/src/platforms/imessage/commands/auth.ts +142 -0
- package/src/platforms/imessage/commands/chat.ts +51 -0
- package/src/platforms/imessage/commands/doctor.test.ts +80 -0
- package/src/platforms/imessage/commands/doctor.ts +139 -0
- package/src/platforms/imessage/commands/index.ts +6 -0
- package/src/platforms/imessage/commands/message.test.ts +20 -0
- package/src/platforms/imessage/commands/message.ts +151 -0
- package/src/platforms/imessage/commands/setup.test.ts +52 -0
- package/src/platforms/imessage/commands/setup.ts +76 -0
- package/src/platforms/imessage/commands/shared.test.ts +19 -0
- package/src/platforms/imessage/commands/shared.ts +69 -0
- package/src/platforms/imessage/commands/whoami.ts +22 -0
- package/src/platforms/imessage/credential-manager.test.ts +122 -0
- package/src/platforms/imessage/credential-manager.ts +123 -0
- package/src/platforms/imessage/ensure-auth.ts +18 -0
- package/src/platforms/imessage/errors.ts +27 -0
- package/src/platforms/imessage/index.test.ts +13 -0
- package/src/platforms/imessage/index.ts +16 -0
- package/src/platforms/imessage/rpc.ts +157 -0
- package/src/platforms/imessage/test-stub-imsg.mjs +147 -0
- package/src/platforms/imessage/types.test.ts +47 -0
- package/src/platforms/imessage/types.ts +95 -0
- package/src/platforms/instagram/client.test.ts +478 -33
- package/src/platforms/instagram/client.ts +472 -68
- package/src/platforms/instagram/commands/auth.test.ts +177 -1
- package/src/platforms/instagram/commands/auth.ts +182 -11
- package/src/platforms/instagram/credential-manager.test.ts +30 -0
- package/src/platforms/instagram/credential-manager.ts +23 -1
- package/src/platforms/instagram/hybrid-listener.test.ts +135 -0
- package/src/platforms/instagram/hybrid-listener.ts +196 -0
- package/src/platforms/instagram/index.ts +10 -0
- package/src/platforms/instagram/mqtt/connection.ts +72 -0
- package/src/platforms/instagram/mqtt/thrift.test.ts +91 -0
- package/src/platforms/instagram/mqtt/thrift.ts +159 -0
- package/src/platforms/instagram/mqtt/transport.ts +285 -0
- package/src/platforms/instagram/realtime-listener.test.ts +34 -0
- package/src/platforms/instagram/realtime-listener.ts +201 -0
- package/src/platforms/instagram/types.ts +11 -8
- package/src/platforms/teams/app-config.ts +92 -0
- package/src/platforms/teams/client.test.ts +335 -1
- package/src/platforms/teams/client.ts +342 -7
- package/src/platforms/teams/commands/auth.test.ts +93 -1
- package/src/platforms/teams/commands/auth.ts +182 -3
- package/src/platforms/teams/commands/file.test.ts +92 -1
- package/src/platforms/teams/commands/file.ts +76 -1
- package/src/platforms/teams/commands/message.test.ts +95 -1
- package/src/platforms/teams/commands/message.ts +145 -3
- package/src/platforms/teams/credential-manager.test.ts +20 -0
- package/src/platforms/teams/credential-manager.ts +68 -3
- package/src/platforms/teams/device-code.test.ts +311 -0
- package/src/platforms/teams/device-code.ts +277 -0
- package/src/platforms/teams/device-login.test.ts +288 -0
- package/src/platforms/teams/device-login.ts +206 -0
- package/src/platforms/teams/ensure-auth.ts +11 -0
- package/src/platforms/teams/index.test.ts +10 -0
- package/src/platforms/teams/index.ts +16 -1
- package/src/platforms/teams/listener.ts +295 -0
- package/src/platforms/teams/realm-discovery.test.ts +73 -0
- package/src/platforms/teams/realm-discovery.ts +42 -0
- package/src/platforms/teams/token-extractor.ts +69 -0
- package/src/platforms/teams/token-provider.test.ts +227 -0
- package/src/platforms/teams/token-provider.ts +284 -0
- package/src/platforms/teams/trouter.test.ts +114 -0
- package/src/platforms/teams/trouter.ts +228 -0
- package/src/platforms/teams/types.test.ts +17 -0
- package/src/platforms/teams/types.ts +86 -0
- package/src/platforms/webexbot/cli.ts +0 -0
- package/src/tui/adapters/imessage-adapter.ts +131 -0
- package/src/tui/adapters/instagram-adapter.ts +13 -0
- package/src/tui/adapters/teams-adapter.ts +23 -0
- package/src/tui/app.ts +16 -3
|
@@ -5,17 +5,81 @@ import { InstagramCredentialManager } from './credential-manager.js';
|
|
|
5
5
|
import { InstagramError, extractMediaUrl, extractMessageText, getMessageType, } from './types.js';
|
|
6
6
|
const IG_BASE_URL = 'https://i.instagram.com/api/v1';
|
|
7
7
|
const IG_APP_ID = '567067343352427';
|
|
8
|
-
const IG_VERSION = '
|
|
9
|
-
const IG_VERSION_CODE = '
|
|
10
|
-
|
|
11
|
-
const
|
|
8
|
+
const IG_VERSION = '428.0.0.47.67';
|
|
9
|
+
const IG_VERSION_CODE = '719358530';
|
|
10
|
+
// Bloks client versioning id; required header for Bloks endpoints (2FA / CAA flows).
|
|
11
|
+
const IG_BLOKS_VERSION_ID = '5f56efad68e1edec7801f630b5c122704ec5378adbee6609a448f105f34a9c73';
|
|
12
|
+
const IG_CAPABILITIES = '3brTv10=';
|
|
13
|
+
const IG_LOCALE = 'en_US';
|
|
14
|
+
// Instagram silently holds the socket open (no response) for flagged IPs / fingerprints instead
|
|
15
|
+
// of returning an error, so an unbounded fetch hangs forever. Cap every request and surface a
|
|
16
|
+
// clear, actionable error instead of a frozen process.
|
|
17
|
+
const IG_REQUEST_TIMEOUT_MS = 30_000;
|
|
18
|
+
const ANDROID_VERSION = '14';
|
|
19
|
+
const ANDROID_RELEASE = '34';
|
|
12
20
|
const DEVICE_DPI = '480dpi';
|
|
13
|
-
const DEVICE_RESOLUTION = '
|
|
14
|
-
const DEVICE_MANUFACTURER = '
|
|
15
|
-
const DEVICE_MODEL = '
|
|
16
|
-
const
|
|
21
|
+
const DEVICE_RESOLUTION = '1344x2992';
|
|
22
|
+
const DEVICE_MANUFACTURER = 'Google/google';
|
|
23
|
+
const DEVICE_MODEL = 'Pixel 8 Pro';
|
|
24
|
+
const DEVICE_NAME = 'husky';
|
|
25
|
+
const DEVICE_CHIPSET = 'husky';
|
|
17
26
|
export function generateDeviceString() {
|
|
18
|
-
return `${
|
|
27
|
+
return `${ANDROID_RELEASE}/${ANDROID_VERSION}; ${DEVICE_DPI}; ${DEVICE_RESOLUTION}; ${DEVICE_MANUFACTURER}; ${DEVICE_MODEL}; ${DEVICE_NAME}; ${DEVICE_CHIPSET}; en_US; ${IG_VERSION_CODE}`;
|
|
28
|
+
}
|
|
29
|
+
// Instagram no longer verifies the body HMAC; the current mobile API expects a literal
|
|
30
|
+
// "SIGNATURE." prefix (per subzeroid/instagrapi, subzeroid/aiograpi). Sending a real HMAC +
|
|
31
|
+
// ig_sig_key_version is rejected as malformed on /accounts/login/, surfacing as a misleading
|
|
32
|
+
// "account not found" error. URLSearchParams url-encodes the value exactly once.
|
|
33
|
+
function signBody(payload) {
|
|
34
|
+
return { signed_body: `SIGNATURE.${JSON.stringify(payload)}` };
|
|
35
|
+
}
|
|
36
|
+
// jazoest = "2" + sum of ASCII byte values of the input; an anti-bot checksum expected by login.
|
|
37
|
+
function createJazoest(input) {
|
|
38
|
+
let sum = 0;
|
|
39
|
+
for (let i = 0; i < input.length; i++) {
|
|
40
|
+
sum += input.charCodeAt(i);
|
|
41
|
+
}
|
|
42
|
+
return `2${sum}`;
|
|
43
|
+
}
|
|
44
|
+
function generateToken() {
|
|
45
|
+
return randomBytes(16).toString('hex');
|
|
46
|
+
}
|
|
47
|
+
function isTimeoutAbort(error) {
|
|
48
|
+
// AbortSignal.timeout aborts the connect AND the body stream, surfacing as TimeoutError on the
|
|
49
|
+
// fetch and AbortError when a stalled body read is aborted mid-stream; both mean "IG stalled".
|
|
50
|
+
return error instanceof DOMException && (error.name === 'TimeoutError' || error.name === 'AbortError');
|
|
51
|
+
}
|
|
52
|
+
function stalledError(path) {
|
|
53
|
+
return new InstagramError(`Instagram did not respond within ${IG_REQUEST_TIMEOUT_MS / 1000}s (${path}). The connection stalled, ` +
|
|
54
|
+
'which usually means the IP is flagged (VPN/datacenter/proxy or prior automated attempts) or the ' +
|
|
55
|
+
'device fingerprint was rejected. Try "agent-instagram auth extract" from a browser logged into ' +
|
|
56
|
+
'instagram.com instead of password login.', 'request_stalled');
|
|
57
|
+
}
|
|
58
|
+
// One AbortSignal.timeout covers the whole request lifecycle: connect, headers, AND the body read.
|
|
59
|
+
// Reading the body through readText() keeps a post-header stall mapped to request_stalled instead
|
|
60
|
+
// of leaking out as a generic parse failure.
|
|
61
|
+
async function fetchWithTimeout(url, init, path) {
|
|
62
|
+
const signal = AbortSignal.timeout(IG_REQUEST_TIMEOUT_MS);
|
|
63
|
+
let response;
|
|
64
|
+
try {
|
|
65
|
+
response = await fetch(url, { ...init, signal });
|
|
66
|
+
}
|
|
67
|
+
catch (error) {
|
|
68
|
+
if (isTimeoutAbort(error))
|
|
69
|
+
throw stalledError(path);
|
|
70
|
+
throw error;
|
|
71
|
+
}
|
|
72
|
+
const readText = async () => {
|
|
73
|
+
try {
|
|
74
|
+
return await response.text();
|
|
75
|
+
}
|
|
76
|
+
catch (error) {
|
|
77
|
+
if (isTimeoutAbort(error))
|
|
78
|
+
throw stalledError(path);
|
|
79
|
+
throw error;
|
|
80
|
+
}
|
|
81
|
+
};
|
|
82
|
+
return { response, readText };
|
|
19
83
|
}
|
|
20
84
|
function buildUserAgent(deviceString) {
|
|
21
85
|
return `Instagram ${IG_VERSION} Android (${deviceString})`;
|
|
@@ -23,6 +87,29 @@ function buildUserAgent(deviceString) {
|
|
|
23
87
|
export function generateAndroidDeviceId() {
|
|
24
88
|
return `android-${randomBytes(8).toString('hex')}`;
|
|
25
89
|
}
|
|
90
|
+
export function generateDevice() {
|
|
91
|
+
return {
|
|
92
|
+
phone_id: randomUUID(),
|
|
93
|
+
uuid: randomUUID(),
|
|
94
|
+
android_device_id: generateAndroidDeviceId(),
|
|
95
|
+
advertising_id: randomUUID(),
|
|
96
|
+
client_session_id: randomUUID(),
|
|
97
|
+
device_string: generateDeviceString(),
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
export function parseOneClickLoginLink(input) {
|
|
101
|
+
const trimmed = input.trim();
|
|
102
|
+
let query = trimmed.includes('?') ? trimmed.slice(trimmed.indexOf('?') + 1) : trimmed;
|
|
103
|
+
const hashIdx = query.indexOf('#');
|
|
104
|
+
if (hashIdx !== -1)
|
|
105
|
+
query = query.slice(0, hashIdx);
|
|
106
|
+
const params = new URLSearchParams(query);
|
|
107
|
+
const uid = params.get('uid');
|
|
108
|
+
const token = params.get('token');
|
|
109
|
+
if (uid && token)
|
|
110
|
+
return { uid, token };
|
|
111
|
+
return null;
|
|
112
|
+
}
|
|
26
113
|
// Instagram DM timestamps are in microseconds
|
|
27
114
|
function microsecondsToISO(us) {
|
|
28
115
|
return new Date(us / 1000).toISOString();
|
|
@@ -33,12 +120,28 @@ export class InstagramClient {
|
|
|
33
120
|
sessionPath = null;
|
|
34
121
|
userId = null;
|
|
35
122
|
cookies = new Map();
|
|
123
|
+
countryCode = '1';
|
|
36
124
|
constructor(credentialManager) {
|
|
37
125
|
this.credentialManager = credentialManager ?? new InstagramCredentialManager();
|
|
38
126
|
}
|
|
127
|
+
setCountryCode(code) {
|
|
128
|
+
this.countryCode = code;
|
|
129
|
+
}
|
|
39
130
|
async login(credentials, accountId) {
|
|
40
131
|
if (credentials) {
|
|
41
|
-
await this.authenticate(credentials.username, credentials.password);
|
|
132
|
+
const result = await this.authenticate(credentials.username, credentials.password);
|
|
133
|
+
if (!result.userId) {
|
|
134
|
+
if (result.requiresTwoFactor) {
|
|
135
|
+
throw new InstagramError('Two-factor authentication required. Use the CLI (auth login/verify) to complete 2FA.', 'two_factor_required');
|
|
136
|
+
}
|
|
137
|
+
if (result.challengeRequired) {
|
|
138
|
+
throw new InstagramError('Instagram requires a security challenge. Use the CLI (auth login/challenge) to resolve it.', 'challenge_required');
|
|
139
|
+
}
|
|
140
|
+
if (result.oneClickEmailAvailable) {
|
|
141
|
+
throw new InstagramError('Password login was rejected; this account can log in by email. Use the CLI "auth login-email" to complete it.', 'one_click_email_available');
|
|
142
|
+
}
|
|
143
|
+
throw new InstagramError('Login did not complete.', 'login_incomplete');
|
|
144
|
+
}
|
|
42
145
|
return this;
|
|
43
146
|
}
|
|
44
147
|
const account = await this.credentialManager.getAccount(accountId);
|
|
@@ -61,40 +164,27 @@ export class InstagramClient {
|
|
|
61
164
|
this.debugLog = fn;
|
|
62
165
|
}
|
|
63
166
|
async authenticate(username, password) {
|
|
64
|
-
const
|
|
65
|
-
const device = {
|
|
66
|
-
phone_id: randomUUID(),
|
|
67
|
-
uuid: randomUUID(),
|
|
68
|
-
android_device_id: generateAndroidDeviceId(),
|
|
69
|
-
advertising_id: randomUUID(),
|
|
70
|
-
client_session_id: randomUUID(),
|
|
71
|
-
device_string: deviceString,
|
|
72
|
-
};
|
|
167
|
+
const device = await this.resolveDevice();
|
|
73
168
|
this.session = { cookies: '', device };
|
|
74
169
|
const encryptionKey = await this.preLoginFlow();
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
if (encryptionKey) {
|
|
78
|
-
this.debugLog?.(`encrypting password with key_id=${encryptionKey.keyId}`);
|
|
79
|
-
encPassword = this.encryptPassword(password, encryptionKey);
|
|
80
|
-
}
|
|
81
|
-
else {
|
|
82
|
-
this.debugLog?.('no encryption key, using plaintext password format');
|
|
83
|
-
encPassword = this.plaintextPassword(password);
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
|
-
catch (err) {
|
|
87
|
-
this.debugLog?.(`encryption failed: ${err}, falling back to plaintext`);
|
|
88
|
-
encPassword = this.plaintextPassword(password);
|
|
170
|
+
if (!encryptionKey) {
|
|
171
|
+
throw new InstagramError('Instagram did not return a password encryption key. Login cannot proceed safely.', 'encryption_key_missing');
|
|
89
172
|
}
|
|
173
|
+
this.debugLog?.(`encrypting password with key_id=${encryptionKey.keyId}`);
|
|
174
|
+
const encPassword = this.encryptPassword(password, encryptionKey);
|
|
90
175
|
const { status, data } = await this.request('POST', '/accounts/login/', {
|
|
91
176
|
username,
|
|
92
177
|
enc_password: encPassword,
|
|
93
178
|
guid: device.uuid,
|
|
94
179
|
phone_id: device.phone_id,
|
|
180
|
+
_csrftoken: this.cookies.get('csrftoken') ?? generateToken(),
|
|
95
181
|
device_id: device.android_device_id,
|
|
182
|
+
adid: device.advertising_id,
|
|
183
|
+
google_tokens: '[]',
|
|
96
184
|
login_attempt_count: '0',
|
|
97
|
-
|
|
185
|
+
country_codes: JSON.stringify([{ country_code: this.countryCode, source: ['default'] }]),
|
|
186
|
+
jazoest: createJazoest(device.phone_id),
|
|
187
|
+
}, { signed: true });
|
|
98
188
|
this.debugLog?.(`login response status=${status} body=${JSON.stringify(data)}`);
|
|
99
189
|
if (data['two_factor_required'] === true) {
|
|
100
190
|
const twoFactorInfo = data['two_factor_info'];
|
|
@@ -110,6 +200,20 @@ export class InstagramClient {
|
|
|
110
200
|
await this.saveSession();
|
|
111
201
|
return { userId: '', challengeRequired: true, challengePath: challengeApiPath };
|
|
112
202
|
}
|
|
203
|
+
if (this.isFacebookLinkedLogin(data)) {
|
|
204
|
+
throw new InstagramError('This account appears to sign in with Facebook and has no usable Instagram password for CLI login. ' +
|
|
205
|
+
'Most reliable fix: log in to instagram.com in your browser, then run "agent-instagram auth extract". ' +
|
|
206
|
+
'Alternatively, give the account its own password. Most reliably, unlink Facebook first: in the Instagram app go to ' +
|
|
207
|
+
'Menu > Settings and privacy > Accounts Center > Manage accounts > (your Facebook profile) Manage > ' +
|
|
208
|
+
'Move out of this Account Center > Move account > Continue; removing it prompts you to create an Instagram password. ' +
|
|
209
|
+
'If Facebook is already unlinked, set one directly under ' +
|
|
210
|
+
'Menu > Settings and privacy > Accounts Center > Password and security > Change password. ' +
|
|
211
|
+
'Setting a password may enable "auth login", though Instagram can still reject automated logins from a new device.', 'facebook_linked');
|
|
212
|
+
}
|
|
213
|
+
if (this.hasLoginButton(data, 'send_one_click_login_email')) {
|
|
214
|
+
this.session.cookies = this.serializeCookies();
|
|
215
|
+
return { userId: '', oneClickEmailAvailable: true };
|
|
216
|
+
}
|
|
113
217
|
if (status !== 200 || data['status'] !== 'ok') {
|
|
114
218
|
const message = data['message'] ?? 'Login failed';
|
|
115
219
|
throw new InstagramError(message, errorType ?? 'login_failed');
|
|
@@ -118,14 +222,25 @@ export class InstagramClient {
|
|
|
118
222
|
return { userId: this.userId ?? '' };
|
|
119
223
|
}
|
|
120
224
|
async twoFactorLogin(username, code, twoFactorIdentifier) {
|
|
225
|
+
const device = this.session?.device;
|
|
121
226
|
const { status, data } = await this.request('POST', '/accounts/two_factor_login/', {
|
|
122
227
|
username,
|
|
123
228
|
verification_code: code,
|
|
124
229
|
two_factor_identifier: twoFactorIdentifier,
|
|
125
230
|
trust_this_device: '1',
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
231
|
+
verification_method: '3',
|
|
232
|
+
_csrftoken: this.cookies.get('csrftoken') ?? generateToken(),
|
|
233
|
+
guid: device?.uuid ?? '',
|
|
234
|
+
phone_id: device?.phone_id ?? '',
|
|
235
|
+
device_id: device?.android_device_id ?? '',
|
|
236
|
+
}, { signed: true });
|
|
237
|
+
if (this.isBloksTwoFactorFallback(status, data)) {
|
|
238
|
+
this.debugLog?.('legacy two_factor_login rejected, retrying via Bloks flow');
|
|
239
|
+
const bloksContext = typeof data['two_step_verification_context'] === 'string'
|
|
240
|
+
? data['two_step_verification_context']
|
|
241
|
+
: twoFactorIdentifier;
|
|
242
|
+
return this.bloksTwoFactorLogin(code, bloksContext);
|
|
243
|
+
}
|
|
129
244
|
if (status !== 200 || data['status'] !== 'ok') {
|
|
130
245
|
const message = data['message'] ?? 'Two-factor authentication failed';
|
|
131
246
|
throw new InstagramError(message, 'two_factor_failed');
|
|
@@ -133,6 +248,127 @@ export class InstagramClient {
|
|
|
133
248
|
await this.finalizeLogin(data);
|
|
134
249
|
return { userId: this.userId ?? '' };
|
|
135
250
|
}
|
|
251
|
+
// The login dialog's "send_one_click_login_email" button triggers /accounts/one_click_login/
|
|
252
|
+
// with auto_send=true (NOT /accounts/send_recovery_flow_email/, which is the generic password
|
|
253
|
+
// reset). It needs the account's numeric user id, resolved via a pre-login /users/lookup/.
|
|
254
|
+
async sendOneClickLoginEmail(username) {
|
|
255
|
+
const device = await this.ensureDeviceSession();
|
|
256
|
+
const uid = await this.lookupUserId(username);
|
|
257
|
+
this.debugLog?.(`one_click_login auto_send for uid=${uid}`);
|
|
258
|
+
const { status, data } = await this.request('POST', '/accounts/one_click_login/', {
|
|
259
|
+
uid,
|
|
260
|
+
source: 'one_click_login_email',
|
|
261
|
+
auto_send: 'true',
|
|
262
|
+
guid: device.uuid,
|
|
263
|
+
device_id: device.android_device_id,
|
|
264
|
+
adid: device.advertising_id,
|
|
265
|
+
});
|
|
266
|
+
this.debugLog?.(`one_click_login response status=${status} body=${JSON.stringify(data)}`);
|
|
267
|
+
if (status !== 200 || data['status'] === 'fail') {
|
|
268
|
+
const message = data['message'] ?? 'Failed to send login email';
|
|
269
|
+
throw new InstagramError(message, 'one_click_email_failed');
|
|
270
|
+
}
|
|
271
|
+
const contactPoint = data['obfuscated_email'] ?? data['email'] ?? data['contact_point'] ?? '';
|
|
272
|
+
return { sent: true, contactPoint };
|
|
273
|
+
}
|
|
274
|
+
async lookupUserId(username) {
|
|
275
|
+
const device = await this.ensureDeviceSession();
|
|
276
|
+
this.debugLog?.(`users/lookup for ${username}`);
|
|
277
|
+
const { status, data } = await this.request('POST', '/users/lookup/', {
|
|
278
|
+
q: username,
|
|
279
|
+
directly_sign_in: 'true',
|
|
280
|
+
_csrftoken: this.cookies.get('csrftoken') ?? generateToken(),
|
|
281
|
+
guid: device.uuid,
|
|
282
|
+
device_id: device.android_device_id,
|
|
283
|
+
country_codes: JSON.stringify([{ country_code: this.countryCode, source: ['default'] }]),
|
|
284
|
+
}, { signed: true });
|
|
285
|
+
this.debugLog?.(`users/lookup response status=${status} body=${JSON.stringify(data)}`);
|
|
286
|
+
const userId = (data['user_id'] ?? data['uid'] ?? data['pk']);
|
|
287
|
+
if (userId == null || String(userId).length === 0) {
|
|
288
|
+
const message = data['message'] ?? `Could not find an Instagram account for "${username}".`;
|
|
289
|
+
throw new InstagramError(message, 'user_lookup_failed');
|
|
290
|
+
}
|
|
291
|
+
return String(userId);
|
|
292
|
+
}
|
|
293
|
+
async oneClickLogin(uid, token, source = 'one_click_login_email') {
|
|
294
|
+
const device = await this.ensureDeviceSession();
|
|
295
|
+
const { status, data } = await this.request('POST', '/accounts/one_click_login/', {
|
|
296
|
+
uid,
|
|
297
|
+
token,
|
|
298
|
+
source,
|
|
299
|
+
device_id: device.android_device_id,
|
|
300
|
+
guid: device.uuid,
|
|
301
|
+
adid: device.advertising_id,
|
|
302
|
+
});
|
|
303
|
+
if (status !== 200 || (data['status'] != null && data['status'] !== 'ok')) {
|
|
304
|
+
const message = data['message'] ?? 'One-click login failed';
|
|
305
|
+
throw new InstagramError(message, 'one_click_login_failed');
|
|
306
|
+
}
|
|
307
|
+
if (!data['logged_in_user']) {
|
|
308
|
+
throw new InstagramError('One-click login did not return a session', 'one_click_login_failed');
|
|
309
|
+
}
|
|
310
|
+
await this.finalizeLogin(data);
|
|
311
|
+
return { userId: this.userId ?? '' };
|
|
312
|
+
}
|
|
313
|
+
async ensureDeviceSession() {
|
|
314
|
+
if (!this.session) {
|
|
315
|
+
this.session = { cookies: '', device: await this.resolveDevice() };
|
|
316
|
+
}
|
|
317
|
+
return this.session.device;
|
|
318
|
+
}
|
|
319
|
+
isFacebookLinkedLogin(data) {
|
|
320
|
+
return this.hasLoginButton(data, 'login_with_facebook');
|
|
321
|
+
}
|
|
322
|
+
hasLoginButton(data, action) {
|
|
323
|
+
const buttons = data['buttons'];
|
|
324
|
+
if (!Array.isArray(buttons))
|
|
325
|
+
return false;
|
|
326
|
+
return buttons.some((button) => button?.['action'] === action);
|
|
327
|
+
}
|
|
328
|
+
isBloksTwoFactorFallback(status, data) {
|
|
329
|
+
if (status === 200 && data['status'] === 'ok')
|
|
330
|
+
return false;
|
|
331
|
+
const message = (data['message'] ?? '').trim().toLowerCase();
|
|
332
|
+
return message === 'invalid parameters' || data['two_step_verification_context'] != null;
|
|
333
|
+
}
|
|
334
|
+
async bloksTwoFactorLogin(code, twoFactorIdentifier) {
|
|
335
|
+
const device = this.session?.device;
|
|
336
|
+
const params = {
|
|
337
|
+
client_input_params: {
|
|
338
|
+
code,
|
|
339
|
+
should_trust_device: 1,
|
|
340
|
+
family_device_id: device?.phone_id ?? '',
|
|
341
|
+
device_id: device?.android_device_id ?? '',
|
|
342
|
+
machine_id: this.session?.mid ?? '',
|
|
343
|
+
},
|
|
344
|
+
server_params: {
|
|
345
|
+
challenge: 'totp',
|
|
346
|
+
two_step_verification_context: twoFactorIdentifier,
|
|
347
|
+
flow_source: 'two_factor_login',
|
|
348
|
+
},
|
|
349
|
+
};
|
|
350
|
+
const { status, data } = await this.request('POST', '/bloks/async_action/com.bloks.www.two_step_verification.verify_code.async/', { params: JSON.stringify(params), bk_client_context: JSON.stringify({ bloks_version: IG_BLOKS_VERSION_ID }) });
|
|
351
|
+
if (status !== 200 || (data['status'] != null && data['status'] !== 'ok')) {
|
|
352
|
+
const message = data['message'] ?? 'Two-factor authentication failed';
|
|
353
|
+
throw new InstagramError(message, 'two_factor_failed');
|
|
354
|
+
}
|
|
355
|
+
const loggedInUser = data['logged_in_user'];
|
|
356
|
+
if (loggedInUser) {
|
|
357
|
+
await this.finalizeLogin(data);
|
|
358
|
+
return { userId: this.userId ?? '' };
|
|
359
|
+
}
|
|
360
|
+
if (this.session?.authorization) {
|
|
361
|
+
const userId = this.session.user_id ?? this.cookies.get('ds_user_id');
|
|
362
|
+
if (!userId) {
|
|
363
|
+
throw new InstagramError('Two-factor authentication failed', 'two_factor_failed');
|
|
364
|
+
}
|
|
365
|
+
this.userId = userId;
|
|
366
|
+
this.session.user_id = userId;
|
|
367
|
+
await this.saveSession();
|
|
368
|
+
return { userId };
|
|
369
|
+
}
|
|
370
|
+
throw new InstagramError('Two-factor authentication failed', 'two_factor_failed');
|
|
371
|
+
}
|
|
136
372
|
async challengeSendCode(apiPath, method = 'email') {
|
|
137
373
|
this.ensureSession();
|
|
138
374
|
const { data: getData } = await this.request('GET', `${apiPath}?guid=${this.session.device.uuid}&device_id=${this.session.device.android_device_id}`);
|
|
@@ -219,6 +455,19 @@ export class InstagramClient {
|
|
|
219
455
|
const threads = (inbox?.['threads'] ?? []);
|
|
220
456
|
return threads.map((t) => this.mapThread(t));
|
|
221
457
|
}
|
|
458
|
+
async fetchIrisBootstrap() {
|
|
459
|
+
this.ensureSession();
|
|
460
|
+
const { data } = await this.request('GET', '/direct_v2/inbox/?limit=1');
|
|
461
|
+
if (data['status'] !== 'ok') {
|
|
462
|
+
throw new InstagramError('Failed to fetch iris bootstrap', 'inbox_error');
|
|
463
|
+
}
|
|
464
|
+
const seqId = data['seq_id'];
|
|
465
|
+
const snapshotAtMs = data['snapshot_at_ms'];
|
|
466
|
+
if (typeof seqId !== 'number' || typeof snapshotAtMs !== 'number') {
|
|
467
|
+
throw new InstagramError('Iris bootstrap missing seq_id or snapshot_at_ms', 'iris_bootstrap_missing');
|
|
468
|
+
}
|
|
469
|
+
return { seqId, snapshotAtMs };
|
|
470
|
+
}
|
|
222
471
|
async searchChats(query, limit = 20) {
|
|
223
472
|
const allChats = await this.listChats(Math.max(limit, 50));
|
|
224
473
|
const lower = query.toLowerCase();
|
|
@@ -310,6 +559,10 @@ export class InstagramClient {
|
|
|
310
559
|
getUserId() {
|
|
311
560
|
return this.userId;
|
|
312
561
|
}
|
|
562
|
+
getSessionState() {
|
|
563
|
+
this.ensureSession();
|
|
564
|
+
return this.session;
|
|
565
|
+
}
|
|
313
566
|
async getProfile() {
|
|
314
567
|
if (!this.userId) {
|
|
315
568
|
throw new InstagramError('Not authenticated. Call login() first.', 'not_authenticated');
|
|
@@ -325,15 +578,19 @@ export class InstagramClient {
|
|
|
325
578
|
async preLoginFlow() {
|
|
326
579
|
const url = `${IG_BASE_URL}/qe/sync/`;
|
|
327
580
|
const headers = this.buildHeaders();
|
|
328
|
-
const response = await
|
|
581
|
+
const { response } = await fetchWithTimeout(url, {
|
|
329
582
|
method: 'POST',
|
|
330
583
|
headers,
|
|
331
|
-
body: new URLSearchParams({
|
|
584
|
+
body: new URLSearchParams(signBody({
|
|
332
585
|
id: this.session.device.uuid,
|
|
333
586
|
experiments: 'ig_android_fci,ig_android_device_detection_info_upload,ig_android_device_verification_fb_signup',
|
|
334
|
-
}).toString(),
|
|
335
|
-
});
|
|
587
|
+
})).toString(),
|
|
588
|
+
}, '/qe/sync/');
|
|
336
589
|
this.extractResponseCookies(response.headers);
|
|
590
|
+
const wwwClaim = response.headers.get('x-ig-set-www-claim');
|
|
591
|
+
if (wwwClaim && this.session) {
|
|
592
|
+
this.session.www_claim = wwwClaim;
|
|
593
|
+
}
|
|
337
594
|
const pubKeyHeader = response.headers.get('ig-set-password-encryption-pub-key');
|
|
338
595
|
const keyIdHeader = response.headers.get('ig-set-password-encryption-key-id');
|
|
339
596
|
if (pubKeyHeader && keyIdHeader) {
|
|
@@ -353,8 +610,14 @@ export class InstagramClient {
|
|
|
353
610
|
const tag = cipher.getAuthTag();
|
|
354
611
|
const pubKeyPem = Buffer.from(key.publicKey, 'base64').toString('utf-8');
|
|
355
612
|
const rsaEncrypted = publicEncrypt({ key: pubKeyPem, padding: constants.RSA_PKCS1_PADDING }, aesKey);
|
|
613
|
+
if (!/^(?:0|[1-9]\d*)$/.test(key.keyId)) {
|
|
614
|
+
throw new InstagramError(`Invalid password encryption key id: ${key.keyId}`, 'encryption_key_invalid');
|
|
615
|
+
}
|
|
616
|
+
const keyIdNum = Number(key.keyId);
|
|
617
|
+
if (keyIdNum > 255) {
|
|
618
|
+
throw new InstagramError(`Invalid password encryption key id: ${key.keyId}`, 'encryption_key_invalid');
|
|
619
|
+
}
|
|
356
620
|
// Wire format: version(1) | keyId(1) | iv(12) | rsaLen(2 LE) | rsaEncrypted | tag(16) | aesEncrypted
|
|
357
|
-
const keyIdNum = Number.parseInt(key.keyId, 10);
|
|
358
621
|
const buf = Buffer.alloc(1 + 1 + 12 + 2 + rsaEncrypted.length + 16 + encrypted.length);
|
|
359
622
|
let offset = 0;
|
|
360
623
|
buf.writeUInt8(1, offset);
|
|
@@ -372,50 +635,84 @@ export class InstagramClient {
|
|
|
372
635
|
encrypted.copy(buf, offset);
|
|
373
636
|
return `#PWD_INSTAGRAM:4:${timestamp}:${buf.toString('base64')}`;
|
|
374
637
|
}
|
|
375
|
-
|
|
376
|
-
const timestamp = Math.floor(Date.now() / 1000).toString();
|
|
377
|
-
return `#PWD_INSTAGRAM:0:${timestamp}:${password}`;
|
|
378
|
-
}
|
|
379
|
-
async request(method, path, body) {
|
|
638
|
+
async request(method, path, body, options) {
|
|
380
639
|
const url = `${IG_BASE_URL}${path}`;
|
|
381
640
|
const headers = this.buildHeaders();
|
|
382
|
-
const
|
|
641
|
+
const requestInit = { method, headers };
|
|
383
642
|
if (body) {
|
|
384
|
-
|
|
643
|
+
const payload = options?.signed ? signBody(body) : body;
|
|
644
|
+
requestInit.body = new URLSearchParams(payload).toString();
|
|
385
645
|
}
|
|
386
|
-
const response = await
|
|
646
|
+
const { response, readText } = await fetchWithTimeout(url, requestInit, path);
|
|
387
647
|
this.extractResponseCookies(response.headers);
|
|
388
648
|
const authHeader = response.headers.get('ig-set-authorization');
|
|
389
649
|
if (authHeader && this.session) {
|
|
390
650
|
this.session.authorization = authHeader;
|
|
391
651
|
}
|
|
652
|
+
const wwwClaim = response.headers.get('x-ig-set-www-claim');
|
|
653
|
+
if (wwwClaim && this.session) {
|
|
654
|
+
this.session.www_claim = wwwClaim;
|
|
655
|
+
}
|
|
656
|
+
const rawBody = await readText();
|
|
392
657
|
if (response.status === 429) {
|
|
393
|
-
|
|
658
|
+
this.debugLog?.(`429 from ${path} body=${rawBody}`);
|
|
659
|
+
const igMessage = this.extractJsonMessage(rawBody);
|
|
660
|
+
throw new InstagramError(igMessage
|
|
661
|
+
? `Rate limited by Instagram: ${igMessage} Wait before trying again.`
|
|
662
|
+
: 'Rate limited by Instagram. Try again later.', 'rate_limited');
|
|
394
663
|
}
|
|
395
664
|
let data;
|
|
396
665
|
try {
|
|
397
|
-
data =
|
|
666
|
+
data = JSON.parse(rawBody);
|
|
398
667
|
}
|
|
399
668
|
catch {
|
|
400
669
|
throw new InstagramError(`Failed to parse response from ${path}`, response.status);
|
|
401
670
|
}
|
|
402
671
|
return { status: response.status, data };
|
|
403
672
|
}
|
|
673
|
+
extractJsonMessage(body) {
|
|
674
|
+
try {
|
|
675
|
+
const parsed = JSON.parse(body);
|
|
676
|
+
return parsed['message'] ?? '';
|
|
677
|
+
}
|
|
678
|
+
catch {
|
|
679
|
+
return '';
|
|
680
|
+
}
|
|
681
|
+
}
|
|
404
682
|
buildHeaders() {
|
|
405
683
|
const deviceString = this.session?.device.device_string ?? generateDeviceString();
|
|
684
|
+
// Instagram's edge silently drops connections whose fingerprint headers don't match a real
|
|
685
|
+
// Android client. This is a partial alignment toward the current app profile
|
|
686
|
+
// (per subzeroid/instagrapi) covering the highest-signal headers, not a full fingerprint match
|
|
687
|
+
// (e.g. X-Tigon-Is-Retry, X-Zero-*, X-IG-Nav-Chain, X-IG-SALT-IDS are intentionally omitted).
|
|
406
688
|
const headers = {
|
|
407
689
|
'User-Agent': buildUserAgent(deviceString),
|
|
690
|
+
'X-IG-App-Locale': IG_LOCALE,
|
|
691
|
+
'X-IG-Device-Locale': IG_LOCALE,
|
|
692
|
+
'X-IG-Mapped-Locale': IG_LOCALE,
|
|
693
|
+
'X-Pigeon-Session-Id': `UFS-${randomUUID()}-0`,
|
|
694
|
+
'X-Pigeon-Rawclienttime': (Date.now() / 1000).toFixed(3),
|
|
695
|
+
'X-IG-Bandwidth-Speed-KBPS': '-1.000',
|
|
696
|
+
'X-IG-Bandwidth-TotalBytes-B': '0',
|
|
697
|
+
'X-IG-Bandwidth-TotalTime-MS': '0',
|
|
698
|
+
'X-IG-App-Startup-Country': 'US',
|
|
408
699
|
'X-IG-App-ID': IG_APP_ID,
|
|
409
|
-
'X-IG-Capabilities':
|
|
700
|
+
'X-IG-Capabilities': IG_CAPABILITIES,
|
|
410
701
|
'X-IG-Connection-Type': 'WIFI',
|
|
411
702
|
'X-IG-Timezone-Offset': String(new Date().getTimezoneOffset() * -60),
|
|
703
|
+
'X-Bloks-Version-Id': IG_BLOKS_VERSION_ID,
|
|
704
|
+
'X-FB-HTTP-Engine': 'Tigon/MNS/TCP',
|
|
705
|
+
Priority: 'u=3',
|
|
706
|
+
'IG-INTENDED-USER-ID': this.userId ?? '0',
|
|
412
707
|
'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8',
|
|
413
708
|
Accept: '*/*',
|
|
414
709
|
'Accept-Language': 'en-US,en;q=0.9',
|
|
415
710
|
};
|
|
711
|
+
headers['X-IG-WWW-Claim'] = this.session?.www_claim ?? '0';
|
|
416
712
|
if (this.session) {
|
|
417
713
|
headers['X-IG-Device-ID'] = this.session.device.uuid;
|
|
418
714
|
headers['X-IG-Android-ID'] = this.session.device.android_device_id;
|
|
715
|
+
headers['X-IG-Family-Device-ID'] = this.session.device.phone_id;
|
|
419
716
|
if (this.session.authorization) {
|
|
420
717
|
headers['Authorization'] = this.session.authorization;
|
|
421
718
|
}
|
|
@@ -482,6 +779,16 @@ export class InstagramClient {
|
|
|
482
779
|
}
|
|
483
780
|
await this.saveSession();
|
|
484
781
|
}
|
|
782
|
+
// Reuse one persisted device per machine so repeat logins present a consistent fingerprint.
|
|
783
|
+
// Regenerating device ids each attempt looks like a brand-new phone and hurts login trust.
|
|
784
|
+
async resolveDevice() {
|
|
785
|
+
const existing = await this.credentialManager.loadDevice();
|
|
786
|
+
if (existing)
|
|
787
|
+
return existing;
|
|
788
|
+
const device = generateDevice();
|
|
789
|
+
await this.credentialManager.saveDevice(device);
|
|
790
|
+
return device;
|
|
791
|
+
}
|
|
485
792
|
async saveSession() {
|
|
486
793
|
if (!this.session || !this.sessionPath)
|
|
487
794
|
return;
|