@eryxenx/insta-chat 2.0.0 → 2.0.3
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/CHANGELOG.md +95 -8
- package/index.js +105 -1
- package/module/login.js +51 -1
- package/package.json +9 -2
- package/src/api/action/getAppState.js +19 -1
- package/src/api/action/getCookies.js +7 -1
- package/src/api/action/getCurrentUserID.js +7 -1
- package/src/api/action/setOptions.js +8 -1
- package/src/api/messaging/editMessage.js +19 -1
- package/src/api/messaging/markAsRead.js +7 -1
- package/src/api/messaging/resolvePhotoUrl.js +8 -1
- package/src/api/messaging/sendMessage.js +45 -1
- package/src/api/messaging/sendMessageSync.js +8 -1
- package/src/api/messaging/sendTypingIndicator.js +7 -1
- package/src/api/messaging/setMessageReaction.js +19 -1
- package/src/api/messaging/shareContact.js +19 -1
- package/src/api/messaging/unsendMessage.js +16 -1
- package/src/api/realtime/getInbox.js +7 -1
- package/src/api/realtime/listen.js +7 -1
- package/src/api/realtime/stopListening.js +7 -1
- package/src/api/threads/getThreadHistory.js +16 -1
- package/src/api/threads/getThreadInfo.js +59 -1
- package/src/api/threads/getThreadList.js +22 -1
- package/src/api/users/getUID.js +12 -1
- package/src/api/users/getUserInfo.js +37 -1
- package/src/api.js +253 -1
- package/src/app/InstaClient.js +352 -1
- package/src/app/InstaContext.js +46 -1
- package/src/app/createFcaClient.js +59 -1
- package/src/app/loadApiMethods.js +30 -1
- package/src/constants.js +53 -1
- package/src/cookies.js +41 -1
- package/src/deviceIdentity.js +31 -1
- package/src/events/normalizeEvent.js +142 -1
- package/src/http.js +381 -1
- package/src/messageContent.js +73 -1
- package/src/methods/inbox.js +464 -1
- package/src/methods/media.js +218 -1
- package/src/methods/message.js +115 -1
- package/src/methods/reactions.js +52 -1
- package/src/methods/thread.js +96 -1
- package/src/methods/typing.js +35 -1
- package/src/methods/user.js +33 -1
- package/src/session.js +94 -1
- package/src/threadDetail.js +36 -1
- package/src/utils/antiSuspension.js +269 -0
- package/src/utils/dataDir.js +11 -0
- package/src/utils/errors.js +106 -0
- package/src/utils/format.js +38 -1
- package/src/utils/messageStyle.js +57 -1
- package/src/utils/sessionGuard.js +93 -0
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
const TRANSIENT_CODES = new Set([
|
|
2
|
+
"ECONNRESET",
|
|
3
|
+
"ETIMEDOUT",
|
|
4
|
+
"ECONNABORTED",
|
|
5
|
+
"ENOTFOUND",
|
|
6
|
+
"EAI_AGAIN",
|
|
7
|
+
"ECONNREFUSED",
|
|
8
|
+
"EPIPE",
|
|
9
|
+
"ENETUNREACH",
|
|
10
|
+
"EHOSTUNREACH"
|
|
11
|
+
]);
|
|
12
|
+
|
|
13
|
+
const RATE_RE = /too many requests|too_many_requests|rate.?limit|please wait a few minutes/i;
|
|
14
|
+
const SUSPENDED_RE = /account (has been )?(suspended|disabled|banned|locked)|account_locked|has been (temporarily )?(suspended|disabled)/i;
|
|
15
|
+
const CHALLENGE_RE = /challenge_required|checkpoint_required|checkpoint|challenge/i;
|
|
16
|
+
const AUTH_RE = /login_required|require_login|not_logged_in|please log in/i;
|
|
17
|
+
const BLOCK_RE = /feedback_required|action_blocked|action blocked|automated behavior|automated_behavior|temporarily blocked|bot detected|bot_detected|unusual activity|suspicious activity|spam/i;
|
|
18
|
+
const TRANSIENT_RE = /timeout|timed out|socket hang up|network error|getaddrinfo/i;
|
|
19
|
+
|
|
20
|
+
const CODE_BY_KIND = {
|
|
21
|
+
auth: "IG_SESSION_INVALID",
|
|
22
|
+
challenge: "IG_CHALLENGE",
|
|
23
|
+
suspended: "IG_SUSPENDED",
|
|
24
|
+
blocked: "IG_BLOCKED",
|
|
25
|
+
rate_limited: "IG_RATE_LIMITED"
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
function statusOf(err) {
|
|
29
|
+
if (!err || typeof err !== "object") return null;
|
|
30
|
+
if (err.response && err.response.status) return err.response.status;
|
|
31
|
+
return err.status || null;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function locationOf(err) {
|
|
35
|
+
const headers = err && err.response && err.response.headers;
|
|
36
|
+
if (!headers) return "";
|
|
37
|
+
return String(headers.location || headers.Location || "");
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function textOf(err) {
|
|
41
|
+
if (!err) return "";
|
|
42
|
+
if (typeof err === "string") return err;
|
|
43
|
+
const parts = [err.message, err.code];
|
|
44
|
+
const data = err.response && err.response.data;
|
|
45
|
+
if (data !== undefined && data !== null) {
|
|
46
|
+
try {
|
|
47
|
+
parts.push((typeof data === "string" ? data : JSON.stringify(data)).slice(0, 2000));
|
|
48
|
+
} catch (_) {}
|
|
49
|
+
}
|
|
50
|
+
if (err.errors) {
|
|
51
|
+
try {
|
|
52
|
+
parts.push(JSON.stringify(err.errors).slice(0, 1000));
|
|
53
|
+
} catch (_) {}
|
|
54
|
+
}
|
|
55
|
+
return parts.filter(Boolean).join(" ");
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function classify(err) {
|
|
59
|
+
const status = statusOf(err);
|
|
60
|
+
const text = textOf(err);
|
|
61
|
+
const location = locationOf(err);
|
|
62
|
+
|
|
63
|
+
if (status === 429 || RATE_RE.test(text)) return { kind: "rate_limited", status };
|
|
64
|
+
if (SUSPENDED_RE.test(text)) return { kind: "suspended", status };
|
|
65
|
+
if (/challenge|checkpoint/i.test(location) || CHALLENGE_RE.test(text)) return { kind: "challenge", status };
|
|
66
|
+
if ((status >= 300 && status < 400) || /maximum number of redirects/i.test(text)) return { kind: "auth", status };
|
|
67
|
+
if (status === 401 || AUTH_RE.test(text)) return { kind: "auth", status };
|
|
68
|
+
if (BLOCK_RE.test(text)) return { kind: "blocked", status };
|
|
69
|
+
const code = err && typeof err === "object" ? err.code : null;
|
|
70
|
+
if ((status && status >= 500) || TRANSIENT_CODES.has(code) || TRANSIENT_RE.test(text)) {
|
|
71
|
+
return { kind: "transient", status };
|
|
72
|
+
}
|
|
73
|
+
return { kind: "unknown", status };
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function inspectBody(data) {
|
|
77
|
+
if (!data || typeof data !== "object" || Array.isArray(data)) return null;
|
|
78
|
+
const failed =
|
|
79
|
+
data.status === "fail" ||
|
|
80
|
+
data.require_login === true ||
|
|
81
|
+
Boolean(data.checkpoint_url) ||
|
|
82
|
+
Boolean(data.challenge);
|
|
83
|
+
if (!failed) return null;
|
|
84
|
+
const message = String(data.message || data.error_type || data.error_title || "");
|
|
85
|
+
const info = classify({ message, response: { status: 200, data, headers: {} } });
|
|
86
|
+
if (info.kind === "unknown" || info.kind === "transient") return null;
|
|
87
|
+
return { ...info, message };
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function toIgError(err, info) {
|
|
91
|
+
const error = err instanceof Error ? err : new Error(String(err));
|
|
92
|
+
const kind = (info && info.kind) || "unknown";
|
|
93
|
+
error.igKind = kind;
|
|
94
|
+
if (info && info.status) error.igStatus = info.status;
|
|
95
|
+
if (CODE_BY_KIND[kind]) error.code = CODE_BY_KIND[kind];
|
|
96
|
+
return error;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function retryAfterMs(err) {
|
|
100
|
+
const headers = err && err.response && err.response.headers;
|
|
101
|
+
const raw = headers && (headers["retry-after"] || headers["Retry-After"]);
|
|
102
|
+
const seconds = Number(raw);
|
|
103
|
+
return Number.isFinite(seconds) && seconds > 0 ? Math.min(seconds * 1000, 30 * 60 * 1000) : 0;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
module.exports = { classify, inspectBody, toIgError, retryAfterMs };
|
package/src/utils/format.js
CHANGED
|
@@ -4,4 +4,41 @@
|
|
|
4
4
|
* Licensed under the Apache License, Version 2.0. See LICENSE and NOTICE.
|
|
5
5
|
* Original author credit must be retained in all copies and derivatives.
|
|
6
6
|
*/
|
|
7
|
-
|
|
7
|
+
|
|
8
|
+
// Small shared helpers used by the FCA-shaped adapter methods.
|
|
9
|
+
|
|
10
|
+
const path = require("path");
|
|
11
|
+
|
|
12
|
+
const AUDIO_EXT = new Set([".ogg", ".opus", ".mp3", ".m4a", ".aac", ".wav"]);
|
|
13
|
+
const VIDEO_EXT = new Set([".mp4", ".mov", ".mkv", ".webm", ".gif"]);
|
|
14
|
+
|
|
15
|
+
function extOf(name) {
|
|
16
|
+
return path.extname(String(name || "").split("?")[0]).toLowerCase();
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
// Instagram's Relay mutations wrap the payload in a single dynamically-named
|
|
20
|
+
// key (e.g. xig_direct_text_send_with_slide_messaging_response), so walk the
|
|
21
|
+
// response depth-first for the first message/item id we recognise.
|
|
22
|
+
const ID_KEYS = ["message_id", "item_id", "messageId", "itemId", "id"];
|
|
23
|
+
function extractMessageId(result) {
|
|
24
|
+
if (!result) return null;
|
|
25
|
+
if (typeof result === "string") return result;
|
|
26
|
+
|
|
27
|
+
const seen = new Set();
|
|
28
|
+
const walk = (node) => {
|
|
29
|
+
if (!node || typeof node !== "object" || seen.has(node)) return null;
|
|
30
|
+
seen.add(node);
|
|
31
|
+
for (const key of ID_KEYS) {
|
|
32
|
+
const v = node[key];
|
|
33
|
+
if (typeof v === "string" && v) return v;
|
|
34
|
+
}
|
|
35
|
+
for (const key of Object.keys(node)) {
|
|
36
|
+
const found = walk(node[key]);
|
|
37
|
+
if (found) return found;
|
|
38
|
+
}
|
|
39
|
+
return null;
|
|
40
|
+
};
|
|
41
|
+
return walk(result);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
module.exports = { AUDIO_EXT, VIDEO_EXT, extOf, extractMessageId };
|
|
@@ -1 +1,57 @@
|
|
|
1
|
-
|
|
1
|
+
// Instagram DMs render plain text only — no markdown, no bold. GoatBot-Pro
|
|
2
|
+
// commands rely on *bold* / _italic_ / ~strike~, so translate those to Unicode
|
|
3
|
+
// look-alikes before sending.
|
|
4
|
+
|
|
5
|
+
const BOLD = {
|
|
6
|
+
a: "𝗮", b: "𝗯", c: "𝗰", d: "𝗱", e: "𝗲", f: "𝗳", g: "𝗴", h: "𝗵", i: "𝗶",
|
|
7
|
+
j: "𝗷", k: "𝗸", l: "𝗹", m: "𝗺", n: "𝗻", o: "𝗼", p: "𝗽", q: "𝗾", r: "𝗿",
|
|
8
|
+
s: "𝘀", t: "𝘁", u: "𝘂", v: "𝘃", w: "𝘄", x: "𝘅", y: "𝘆", z: "𝘇",
|
|
9
|
+
A: "𝗔", B: "𝗕", C: "𝗖", D: "𝗗", E: "𝗘", F: "𝗙", G: "𝗚", H: "𝗛", I: "𝗜",
|
|
10
|
+
J: "𝗝", K: "𝗞", L: "𝗟", M: "𝗠", N: "𝗡", O: "𝗢", P: "𝗣", Q: "𝗤", R: "𝗥",
|
|
11
|
+
S: "𝗦", T: "𝗧", U: "𝗨", V: "𝗩", W: "𝗪", X: "𝗫", Y: "𝗬", Z: "𝗭",
|
|
12
|
+
0: "𝟬", 1: "𝟭", 2: "𝟮", 3: "𝟯", 4: "𝟰", 5: "𝟱", 6: "𝟲", 7: "𝟳", 8: "𝟴", 9: "𝟵"
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
const ITALIC = {
|
|
16
|
+
a: "𝑎", b: "𝑏", c: "𝑐", d: "𝑑", e: "𝑒", f: "𝑓", g: "𝑔", h: "ℎ", i: "𝑖",
|
|
17
|
+
j: "𝑗", k: "𝑘", l: "𝑙", m: "𝑚", n: "𝑛", o: "𝑜", p: "𝑝", q: "𝑞", r: "𝑟",
|
|
18
|
+
s: "𝑠", t: "𝑡", u: "𝑢", v: "𝑣", w: "𝑤", x: "𝑥", y: "𝑦", z: "𝑧",
|
|
19
|
+
A: "𝐴", B: "𝐵", C: "𝐶", D: "𝐷", E: "𝐸", F: "𝐹", G: "𝐺", H: "𝐻", I: "𝐼",
|
|
20
|
+
J: "𝐽", K: "𝐾", L: "𝐿", M: "𝑀", N: "𝑁", O: "𝑂", P: "𝑃", Q: "𝑄", R: "𝑅",
|
|
21
|
+
S: "𝑆", T: "𝑇", U: "𝑈", V: "𝑉", W: "𝑊", X: "𝑋", Y: "𝑌", Z: "𝑍"
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
const MONO = {
|
|
25
|
+
a: "𝚊", b: "𝚋", c: "𝚌", d: "𝚍", e: "𝚎", f: "𝚏", g: "𝚐", h: "𝚑", i: "𝚒",
|
|
26
|
+
j: "𝚓", k: "𝚔", l: "𝚕", m: "𝚖", n: "𝚗", o: "𝚘", p: "𝚙", q: "𝚚", r: "𝚛",
|
|
27
|
+
s: "𝚜", t: "𝚝", u: "𝚞", v: "𝚟", w: "𝚠", x: "𝚡", y: "𝚢", z: "𝚣",
|
|
28
|
+
A: "𝙰", B: "𝙱", C: "𝙲", D: "𝙳", E: "𝙴", F: "𝙵", G: "𝙶", H: "𝙷", I: "𝙸",
|
|
29
|
+
J: "𝙹", K: "𝙺", L: "𝙻", M: "𝙼", N: "𝙽", O: "𝙾", P: "𝙿", Q: "𝚀", R: "𝚁",
|
|
30
|
+
S: "𝚂", T: "𝚃", U: "𝚄", V: "𝚅", W: "𝚆", X: "𝚇", Y: "𝚈", Z: "𝚉"
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
function mapChars(str, table) {
|
|
34
|
+
return String(str).replace(/[A-Za-z0-9]/g, (ch) => table[ch] || ch);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const bold = (s) => mapChars(s, BOLD);
|
|
38
|
+
const italic = (s) => mapChars(s, ITALIC);
|
|
39
|
+
const monospace = (s) => mapChars(s, MONO);
|
|
40
|
+
const strike = (s) => String(s).replace(/[A-Za-z0-9]/g, (ch) => `${ch}\u0336`);
|
|
41
|
+
|
|
42
|
+
// Applies *bold*, _italic_, ~strike~ and ```mono``` wrappers.
|
|
43
|
+
function convert(text) {
|
|
44
|
+
if (typeof text !== "string") return text;
|
|
45
|
+
let out = text;
|
|
46
|
+
out = out.replace(/\*\*\*(.+?)\*\*\*/gs, (_, m) => bold(italic(m)));
|
|
47
|
+
out = out.replace(/\*\*(.+?)\*\*/gs, (_, m) => bold(m));
|
|
48
|
+
out = out.replace(/__(.+?)__/gs, (_, m) => italic(m));
|
|
49
|
+
out = out.replace(/\*(.+?)\*/gs, (_, m) => bold(m));
|
|
50
|
+
out = out.replace(/(^|[^A-Za-z0-9])_([^_]+)_(?![A-Za-z0-9])/gs, (_, p, m) => p + italic(m));
|
|
51
|
+
out = out.replace(/~~(.+?)~~/gs, (_, m) => strike(m));
|
|
52
|
+
out = out.replace(/(^|[^~])~([^~]+)~(?![~])/gs, (_, p, m) => p + strike(m));
|
|
53
|
+
out = out.replace(/```(.+?)```/gs, (_, m) => monospace(m));
|
|
54
|
+
return out;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
module.exports = { convert, bold, italic, strike, monospace };
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
/*!
|
|
2
|
+
* insta-chat - Unofficial Instagram Chat API
|
|
3
|
+
* Copyright (c) 2026 EryXenX (https://github.com/EryXenX)
|
|
4
|
+
* Licensed under the Apache License, Version 2.0. See LICENSE and NOTICE.
|
|
5
|
+
* Original author credit must be retained in all copies and derivatives.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
"use strict";
|
|
9
|
+
|
|
10
|
+
const fs = require("fs");
|
|
11
|
+
const { dataPath } = require("./dataDir");
|
|
12
|
+
|
|
13
|
+
const SAVE_INTERVAL_MS = 5 * 60 * 1000;
|
|
14
|
+
const MIN_COOKIES = 3;
|
|
15
|
+
const KEEP_FROM_SEED = new Set(["sessionid", "ds_user_id"]);
|
|
16
|
+
|
|
17
|
+
function readCookieFile(filePath) {
|
|
18
|
+
try {
|
|
19
|
+
const parsed = JSON.parse(fs.readFileSync(filePath, "utf8"));
|
|
20
|
+
return Array.isArray(parsed) ? parsed : null;
|
|
21
|
+
} catch (_) {
|
|
22
|
+
return null;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function loadSaved(filePath, seed) {
|
|
27
|
+
const target = filePath || dataPath("appstate.json");
|
|
28
|
+
const entries = readCookieFile(target) || readCookieFile(`${target}.bak`);
|
|
29
|
+
if (!entries || entries.length < MIN_COOKIES) return null;
|
|
30
|
+
const map = {};
|
|
31
|
+
for (const c of entries) {
|
|
32
|
+
const key = c && (c.key || c.name);
|
|
33
|
+
if (key && c.value) map[key] = c.value;
|
|
34
|
+
}
|
|
35
|
+
if (!seed || !seed.sessionid || map.sessionid !== seed.sessionid) return null;
|
|
36
|
+
for (const key of KEEP_FROM_SEED) delete map[key];
|
|
37
|
+
return map;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function writeAtomic(filePath, content) {
|
|
41
|
+
const tmp = `${filePath}.tmp`;
|
|
42
|
+
fs.writeFileSync(tmp, content, "utf8");
|
|
43
|
+
fs.renameSync(tmp, filePath);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function install(client, appStatePath) {
|
|
47
|
+
if (!client || typeof client.getAppState !== "function") return null;
|
|
48
|
+
const target = appStatePath || dataPath("appstate.json");
|
|
49
|
+
|
|
50
|
+
function getState() {
|
|
51
|
+
try {
|
|
52
|
+
return client.getAppState();
|
|
53
|
+
} catch (_) {
|
|
54
|
+
return null;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function save(filePath = target) {
|
|
59
|
+
const state = getState();
|
|
60
|
+
if (!Array.isArray(state) || state.length < MIN_COOKIES) return false;
|
|
61
|
+
try {
|
|
62
|
+
if (fs.existsSync(filePath)) {
|
|
63
|
+
try {
|
|
64
|
+
const existing = JSON.parse(fs.readFileSync(filePath, "utf8"));
|
|
65
|
+
if (Array.isArray(existing) && state.length < existing.length * 0.8) return false;
|
|
66
|
+
writeAtomic(`${filePath}.bak`, JSON.stringify(existing, null, 2));
|
|
67
|
+
} catch (_) {}
|
|
68
|
+
}
|
|
69
|
+
writeAtomic(filePath, JSON.stringify(state, null, 2));
|
|
70
|
+
return true;
|
|
71
|
+
} catch (_) {
|
|
72
|
+
return false;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const timer = setInterval(() => save(), SAVE_INTERVAL_MS);
|
|
77
|
+
if (timer.unref) timer.unref();
|
|
78
|
+
|
|
79
|
+
const onExit = () => save();
|
|
80
|
+
process.once("exit", onExit);
|
|
81
|
+
|
|
82
|
+
client.saveSession = (p) => save(p || target);
|
|
83
|
+
client.stopSessionGuard = () => {
|
|
84
|
+
clearInterval(timer);
|
|
85
|
+
process.removeListener("exit", onExit);
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
save();
|
|
89
|
+
|
|
90
|
+
return { save: client.saveSession, stop: client.stopSessionGuard, path: target };
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
module.exports = { install, loadSaved, SAVE_INTERVAL_MS, MIN_COOKIES };
|