@eryxenx/insta-chat 2.0.1 → 2.0.4
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 +112 -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 +224 -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 +284 -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
package/CHANGELOG.md
CHANGED
|
@@ -4,20 +4,124 @@ All notable changes to `@eryxenx/insta-chat` are documented here.
|
|
|
4
4
|
The format follows [Keep a Changelog](https://keepachangelog.com/) and uses
|
|
5
5
|
[Semantic Versioning](https://semver.org/).
|
|
6
6
|
|
|
7
|
-
## [2.0.
|
|
7
|
+
## [2.0.4] - 2026-10-10
|
|
8
|
+
|
|
9
|
+
### Fixed
|
|
10
|
+
|
|
11
|
+
- **Captioned media lagged behind its text.** A message carrying both text and
|
|
12
|
+
an attachment uploads the media first, then commits the caption and the media
|
|
13
|
+
back-to-back, so the text and the image land together instead of the image
|
|
14
|
+
trailing by the whole upload time. Instagram's DM media mutation has no caption
|
|
15
|
+
field, so caption + attachment is still two messages; this only removes the
|
|
16
|
+
perceived gap after the caption.
|
|
8
17
|
|
|
9
18
|
### Added
|
|
10
19
|
|
|
11
|
-
- `
|
|
12
|
-
|
|
13
|
-
|
|
20
|
+
- `antiSuspension.waitForTurn(threadID, { immediate })`: an immediate turn that
|
|
21
|
+
still counts against the volume caps but skips the inter-message gap, so a
|
|
22
|
+
captioned attachment can follow its caption at once.
|
|
23
|
+
|
|
24
|
+
## [1.0.12] - 2026-10-10
|
|
25
|
+
|
|
26
|
+
### Fixed
|
|
27
|
+
|
|
28
|
+
- **Bot logged out on a network blip.** `init()` treated every failed probe
|
|
29
|
+
(timeout, 5xx, 429) as an expired cookie. It now distinguishes a dead session
|
|
30
|
+
(`IG_SESSION_INVALID`), a security check (`IG_CHALLENGE`), a suspended account
|
|
31
|
+
(`IG_SUSPENDED`) and a temporary failure (`IG_TRANSIENT`, with `retryAfterMs`),
|
|
32
|
+
and retries transient failures before giving up.
|
|
33
|
+
- **Poller stopped on a single redirect.** `listen()` shut down on the first
|
|
34
|
+
3xx. It now requires two consecutive auth failures plus a fresh session check
|
|
35
|
+
before reporting the session dead, stops at once only on a challenge or a
|
|
36
|
+
suspension, and keeps polling through transient errors with backoff.
|
|
37
|
+
- **Rotated cookies were never reused.** The saved `appstate.json` is now
|
|
38
|
+
merged back in on the next start when it belongs to the same `sessionid`, and
|
|
39
|
+
snapshots are written atomically so a crash cannot leave a truncated file.
|
|
40
|
+
- **Poll cadence looked scripted.** Poll interval floor raised to 15s (default
|
|
41
|
+
20s) with +/-20% jitter, and polling slows down while a cooldown is active.
|
|
42
|
+
- **Cooldown did nothing.** The old cooldown added 2-5s per send and the
|
|
43
|
+
hourly/daily caps only added a short delay. Sends are now serialized with a
|
|
44
|
+
global gap, a per-thread gap and jitter; caps and cooldown enforce a real
|
|
45
|
+
gap, strikes decay, and repeated cooldowns escalate (1h up to 6h).
|
|
46
|
+
- Suspicion signals are now detected on every request (challenge, checkpoint,
|
|
47
|
+
`feedback_required`, suspended), not only on sends, and one failing request
|
|
48
|
+
counts as one strike instead of two.
|
|
49
|
+
- The legacy broadcast fallback is no longer attempted while rate-limited,
|
|
50
|
+
challenged or cooling down.
|
|
51
|
+
- Responses carrying `x-ig-set-www-claim` are tracked and echoed back like the
|
|
52
|
+
web client does, instead of always sending `0`.
|
|
53
|
+
- Redirects are no longer followed on API calls, so a dead session fails on the
|
|
54
|
+
first response instead of after five wasted requests.
|
|
55
|
+
|
|
56
|
+
### Added
|
|
57
|
+
|
|
58
|
+
- `INSTA_DATA_DIR` environment variable: directory for `device.json`,
|
|
59
|
+
`session-params.json` and `appstate.json` (point it at a persistent volume on
|
|
60
|
+
Render/Railway so the identity survives restarts).
|
|
61
|
+
- `appStateFile` option on `InstaAPI`.
|
|
62
|
+
|
|
63
|
+
## [1.0.11] - 2026-10-10
|
|
14
64
|
|
|
15
65
|
### Fixed
|
|
16
66
|
|
|
17
|
-
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
67
|
+
- **Attachment caption order.** A message carrying both text and an attachment
|
|
68
|
+
(e.g. `sendMessage({ body, attachment })`) sent the media first and the text
|
|
69
|
+
second, so the image rendered above its caption. The text is now sent before
|
|
70
|
+
the media upload, so the recipient sees the caption on top and the
|
|
71
|
+
photo/attachment underneath — matching how ported GoatBot-Pro commands format
|
|
72
|
+
a header line followed by an image.
|
|
73
|
+
|
|
74
|
+
## [1.0.10] - 2026-10-10
|
|
75
|
+
|
|
76
|
+
### Fixed
|
|
77
|
+
|
|
78
|
+
- **Silent message drop.** Instagram answers a rejected Relay mutation with HTTP
|
|
79
|
+
200 plus a top-level `errors` array. `postGraphQL` returned that payload as if
|
|
80
|
+
it were a success, so `sendMessage` reported a `messageID` for a message
|
|
81
|
+
Instagram never stored. Write operations (text send, media commit, caption,
|
|
82
|
+
reaction, edit) now pass `{ throwOnError: true }`, which throws an
|
|
83
|
+
`InstagramGraphQLError` carrying the relay `errors`. Read/query paths keep the
|
|
84
|
+
old lenient behaviour so `getThreadHistory` still maps a rejected query to `[]`.
|
|
85
|
+
- `sessionGuard` now snapshots the cookie jar once at install time instead of
|
|
86
|
+
waiting for the first 5-minute interval, so a crash right after login no longer
|
|
87
|
+
leaves no good `appstate.json` on disk.
|
|
88
|
+
- `_resolveMentionsOffline` falls back to a live `getUserInfoByUsername` (and
|
|
89
|
+
indexes the result) when a handle was never seen in the inbox, instead of
|
|
90
|
+
dropping the mention.
|
|
91
|
+
- `addUserToGroup` / `removeUserFromGroup` degrade to a `false` stub when the
|
|
92
|
+
native client does not expose the pair, matching the other group-management
|
|
93
|
+
stubs.
|
|
94
|
+
- Reaction send/remove now note a failed mutation to the anti-suspension pacer,
|
|
95
|
+
and reaction removal is paced like every other outbound send. Previously a
|
|
96
|
+
checkpoint surfaced by a reaction was never counted as a strike, and removals
|
|
97
|
+
bypassed the pacer entirely.
|
|
98
|
+
- Adds `test/safetyWiring.test.js`, which asserts the pacer is actually invoked
|
|
99
|
+
(paced before the network call, noted on failure) by the text and reaction
|
|
100
|
+
send paths, rather than merely being exported.
|
|
101
|
+
|
|
102
|
+
## [1.0.9] - 2026-10-09
|
|
103
|
+
|
|
104
|
+
### Added
|
|
105
|
+
|
|
106
|
+
- **Anti-suspension send pacer** (`src/utils/antiSuspension.js`), ported from
|
|
107
|
+
`@eryxenx/fca`. Every outbound DM (text, media, reactions) is paced with a
|
|
108
|
+
per-thread gap plus jitter, capped per hour/day, and throttled for 45 min when
|
|
109
|
+
a response or error mentions a checkpoint/spam/rate-limit signal. A freshly
|
|
110
|
+
logged-in session starts under a stricter warmup ceiling (40/hr → 350/hr).
|
|
111
|
+
This is the main reason an account survives weeks instead of days.
|
|
112
|
+
- **Session guard** (`src/utils/sessionGuard.js`), ported from `@eryxenx/fca`.
|
|
113
|
+
Snapshots the live cookie jar to `appstate.json` every 5 min, keeps the
|
|
114
|
+
previous copy as `.bak`, and refuses to overwrite a good file with a shorter
|
|
115
|
+
one (truncation guard). Installed automatically on every `InstaClient`;
|
|
116
|
+
disable with `login(cookie, { sessionGuard: false })`.
|
|
117
|
+
|
|
118
|
+
### Changed
|
|
119
|
+
|
|
120
|
+
- `listen()` no longer piles polls on top of an HTTP 429. It tears down the
|
|
121
|
+
current poll, re-arms with exponential backoff (30s → 10min), and resets on a
|
|
122
|
+
clean poll. A dead session (redirect to the login page) now stops the listener
|
|
123
|
+
instead of spinning forever. The poll interval is floored at 10s.
|
|
124
|
+
- Exported `antiSuspension` and `sessionGuard` from the package root.
|
|
21
125
|
|
|
22
126
|
## [1.1.0] - 2026-10-08
|
|
23
127
|
|
package/index.js
CHANGED
|
@@ -4,4 +4,108 @@
|
|
|
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
|
+
"use strict";
|
|
9
|
+
|
|
10
|
+
const InstaAPI = require("./src/api");
|
|
11
|
+
const constants = require("./src/constants");
|
|
12
|
+
const { normalizeCookie } = require("./src/cookies");
|
|
13
|
+
const { harvestSessionParams, findChromium } = require("./src/session");
|
|
14
|
+
const InstaClient = require("./src/app/InstaClient");
|
|
15
|
+
const { InstaContext } = require("./src/app/InstaContext");
|
|
16
|
+
const { createFcaClient } = require("./src/app/createFcaClient");
|
|
17
|
+
const EventNormalizer = require("./src/events/normalizeEvent");
|
|
18
|
+
const loadApiMethods = require("./src/app/loadApiMethods");
|
|
19
|
+
const moduleLogin = require("./module/login");
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Log in to Instagram with a browser cookie / sessionid and return a ready
|
|
23
|
+
* client. Mirrors the classic FCA `login(credentials, callback)` shape so bot
|
|
24
|
+
* projects can swap it in the same way they swap an FCA package.
|
|
25
|
+
*
|
|
26
|
+
* By default it resolves to an {@link InstaClient} — the FCA-shaped surface
|
|
27
|
+
* (sendMessage, setMessageReaction, getThreadInfo, ...) — which is what
|
|
28
|
+
* GoatBot-Pro commands are written against. Pass `{ native: true }` to get the
|
|
29
|
+
* bare {@link InstaAPI} instead.
|
|
30
|
+
*
|
|
31
|
+
* @param {string|object} cookie sessionid string, "k=v; k=v" cookie string,
|
|
32
|
+
* a cookie object, or a browser cookie array.
|
|
33
|
+
* @param {object} [options] { native, sessionParams, sessionParamsFile,
|
|
34
|
+
* deviceFile, pollInterval }
|
|
35
|
+
* @param {function} [callback] (err, api) classic node-callback style.
|
|
36
|
+
* @returns {Promise<InstaClient|InstaAPI>}
|
|
37
|
+
*/
|
|
38
|
+
function login(cookie, options, callback) {
|
|
39
|
+
if (typeof options === "function") {
|
|
40
|
+
callback = options;
|
|
41
|
+
options = {};
|
|
42
|
+
}
|
|
43
|
+
options = options || {};
|
|
44
|
+
|
|
45
|
+
const promise = (async () => {
|
|
46
|
+
const native = new InstaAPI(cookie, options);
|
|
47
|
+
await native.init();
|
|
48
|
+
return options.native ? native : new InstaClient(native, options);
|
|
49
|
+
})();
|
|
50
|
+
|
|
51
|
+
if (typeof callback === "function") {
|
|
52
|
+
promise.then((api) => callback(null, api), (err) => callback(err, null));
|
|
53
|
+
return undefined;
|
|
54
|
+
}
|
|
55
|
+
return promise;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Alias kept for parity with FCA-style packages that expose `createClient`.
|
|
60
|
+
*/
|
|
61
|
+
function createClient(cookie, options) {
|
|
62
|
+
return login(cookie, options);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Wraps an already-initialised native InstaAPI in the FCA-shaped client.
|
|
67
|
+
*/
|
|
68
|
+
function wrap(native, options) {
|
|
69
|
+
return new InstaClient(native, options);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Wraps an already-initialised native client in a per-thread context.
|
|
74
|
+
*/
|
|
75
|
+
function createContext(nativeOrClient, threadID) {
|
|
76
|
+
const client = nativeOrClient instanceof InstaClient ? nativeOrClient : wrap(nativeOrClient);
|
|
77
|
+
return new InstaContext(client, threadID);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
login.InstaAPI = InstaAPI;
|
|
81
|
+
login.InstaClient = InstaClient;
|
|
82
|
+
login.InstaContext = InstaContext;
|
|
83
|
+
login.createClient = createClient;
|
|
84
|
+
login.createFcaClient = createFcaClient;
|
|
85
|
+
login.wrap = wrap;
|
|
86
|
+
login.createContext = createContext;
|
|
87
|
+
login.CookieUtils = { parse: normalizeCookie };
|
|
88
|
+
login.harvestSessionParams = harvestSessionParams;
|
|
89
|
+
login.findChromium = findChromium;
|
|
90
|
+
login.moduleLogin = moduleLogin;
|
|
91
|
+
|
|
92
|
+
module.exports = login;
|
|
93
|
+
|
|
94
|
+
module.exports.login = login;
|
|
95
|
+
module.exports.default = login;
|
|
96
|
+
module.exports.createClient = createClient;
|
|
97
|
+
module.exports.InstaAPI = InstaAPI;
|
|
98
|
+
module.exports.InstaClient = InstaClient;
|
|
99
|
+
module.exports.InstaContext = InstaContext;
|
|
100
|
+
module.exports.createFcaClient = createFcaClient;
|
|
101
|
+
module.exports.EventNormalizer = EventNormalizer;
|
|
102
|
+
module.exports.loadApiMethods = loadApiMethods;
|
|
103
|
+
module.exports.wrap = wrap;
|
|
104
|
+
module.exports.createContext = createContext;
|
|
105
|
+
module.exports.CookieUtils = { parse: normalizeCookie };
|
|
106
|
+
module.exports.harvestSessionParams = harvestSessionParams;
|
|
107
|
+
module.exports.findChromium = findChromium;
|
|
108
|
+
module.exports.constants = constants;
|
|
109
|
+
module.exports.moduleLogin = moduleLogin;
|
|
110
|
+
module.exports.antiSuspension = require("./src/utils/antiSuspension");
|
|
111
|
+
module.exports.sessionGuard = require("./src/utils/sessionGuard");
|
package/module/login.js
CHANGED
|
@@ -4,4 +4,54 @@
|
|
|
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
|
+
// Classic FCA-style entry point: login(credentials, [options], [callback]).
|
|
9
|
+
// Returns a ready native client (InstaAPI) with its FCA-shaped surface attached.
|
|
10
|
+
|
|
11
|
+
const InstaAPI = require("../src/api");
|
|
12
|
+
const { normalizeCookie } = require("../src/cookies");
|
|
13
|
+
|
|
14
|
+
// Accepts a sessionid string, a "k=v; k=v" cookie string, a cookie object, a
|
|
15
|
+
// browser cookie array, or { appState | Cookie | cookie | sessionid }.
|
|
16
|
+
function extractCookie(credentials) {
|
|
17
|
+
if (typeof credentials === "string") return credentials;
|
|
18
|
+
if (Array.isArray(credentials)) return JSON.stringify(credentials);
|
|
19
|
+
if (credentials && typeof credentials === "object") {
|
|
20
|
+
const candidate =
|
|
21
|
+
credentials.appState ||
|
|
22
|
+
credentials.Cookie ||
|
|
23
|
+
credentials.cookie ||
|
|
24
|
+
credentials.sessionid ||
|
|
25
|
+
credentials.sessionId;
|
|
26
|
+
if (Array.isArray(candidate)) return JSON.stringify(candidate);
|
|
27
|
+
if (typeof candidate === "string") return candidate;
|
|
28
|
+
// A raw cookie map { sessionid: "...", csrftoken: "..." }.
|
|
29
|
+
if (credentials.sessionid) return normalizeCookie(credentials);
|
|
30
|
+
}
|
|
31
|
+
return credentials;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function login(credentials, options, callback) {
|
|
35
|
+
if (typeof options === "function") {
|
|
36
|
+
callback = options;
|
|
37
|
+
options = {};
|
|
38
|
+
}
|
|
39
|
+
options = options || {};
|
|
40
|
+
|
|
41
|
+
const cookie = extractCookie(credentials);
|
|
42
|
+
|
|
43
|
+
const promise = (async () => {
|
|
44
|
+
if (!cookie) throw new Error("login: no Instagram session cookie supplied");
|
|
45
|
+
const api = new InstaAPI(cookie, options);
|
|
46
|
+
await api.init();
|
|
47
|
+
return api;
|
|
48
|
+
})();
|
|
49
|
+
|
|
50
|
+
if (typeof callback === "function") {
|
|
51
|
+
promise.then((api) => callback(null, api), (err) => callback(err, null));
|
|
52
|
+
return undefined;
|
|
53
|
+
}
|
|
54
|
+
return promise;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
module.exports = login;
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@eryxenx/insta-chat",
|
|
3
|
-
"version": "2.0.
|
|
4
|
-
"description": "Unofficial Instagram Chat API for Node.js | Build Instagram DM bots on personal accounts
|
|
3
|
+
"version": "2.0.4",
|
|
4
|
+
"description": " Unofficial Instagram Chat API for Node.js | Build Instagram DM bots on personal accounts \u2014 send messages, media, voice notes, reactions, edit & unsend, listen for incoming chats. No browser at runtime",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"types": "index.d.ts",
|
|
7
7
|
"exports": {
|
|
@@ -22,6 +22,10 @@
|
|
|
22
22
|
"NOTICE",
|
|
23
23
|
"CHANGELOG.md"
|
|
24
24
|
],
|
|
25
|
+
"scripts": {
|
|
26
|
+
"test": "node test/smoke.js && node test/antiSuspension.test.js && node test/replyContent.test.js && node test/edit.test.js && node test/sendReply.test.js && node test/client.test.js && node test/relayError.test.js && node test/safetyWiring.test.js && node test/mediaOrder.test.js && node test/sessionSafety.test.js",
|
|
27
|
+
"build": "node scripts/build.js"
|
|
28
|
+
},
|
|
25
29
|
"keywords": [
|
|
26
30
|
"instagram",
|
|
27
31
|
"insta",
|
|
@@ -54,5 +58,8 @@
|
|
|
54
58
|
"publishConfig": {
|
|
55
59
|
"access": "public",
|
|
56
60
|
"registry": "https://registry.npmjs.org/"
|
|
61
|
+
},
|
|
62
|
+
"devDependencies": {
|
|
63
|
+
"javascript-obfuscator": "^4.1.0"
|
|
57
64
|
}
|
|
58
65
|
}
|
|
@@ -4,4 +4,22 @@
|
|
|
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
|
+
// fca.getAppState()/getCookies() - the current cookie jar as
|
|
9
|
+
// [{ key, value, domain, path }].
|
|
10
|
+
function readState(client) {
|
|
11
|
+
const jar = client.api.http?.jar;
|
|
12
|
+
if (!jar) return [];
|
|
13
|
+
return jar.getCookiesSync("https://www.instagram.com").map((c) => ({
|
|
14
|
+
key: c.key,
|
|
15
|
+
value: c.value,
|
|
16
|
+
domain: c.domain,
|
|
17
|
+
path: c.path
|
|
18
|
+
}));
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
module.exports = function (client) {
|
|
22
|
+
return function getAppState() {
|
|
23
|
+
return readState(client);
|
|
24
|
+
};
|
|
25
|
+
};
|
|
@@ -4,4 +4,10 @@
|
|
|
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
|
+
// fca.getCookies() — alias of getAppState().
|
|
9
|
+
module.exports = function (client) {
|
|
10
|
+
return function getCookies() {
|
|
11
|
+
return client.getAppState();
|
|
12
|
+
};
|
|
13
|
+
};
|
|
@@ -4,4 +4,10 @@
|
|
|
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
|
+
// fca.getCurrentUserID()
|
|
9
|
+
module.exports = function (client) {
|
|
10
|
+
return function getCurrentUserID() {
|
|
11
|
+
return client.userID;
|
|
12
|
+
};
|
|
13
|
+
};
|
|
@@ -4,4 +4,11 @@
|
|
|
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
|
+
// fca.setOptions() - accepted for compatibility; Instagram DMs have no
|
|
9
|
+
// equivalent knobs.
|
|
10
|
+
module.exports = function () {
|
|
11
|
+
return function setOptions() {
|
|
12
|
+
return true;
|
|
13
|
+
};
|
|
14
|
+
};
|
|
@@ -4,4 +4,22 @@
|
|
|
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
|
+
// fca.editMessage(text, messageID) — edit the body of a message we sent.
|
|
9
|
+
// Instagram only allows this within 15 minutes of sending.
|
|
10
|
+
module.exports = function (client) {
|
|
11
|
+
return async function editMessage(text, messageID, callback) {
|
|
12
|
+
const cb = typeof callback === "function" ? callback : null;
|
|
13
|
+
try {
|
|
14
|
+
const tid = client._resolveThread(messageID);
|
|
15
|
+
const res = await client.api.editMessage(tid, String(messageID), String(text ?? ""));
|
|
16
|
+
if (cb) cb(null, res);
|
|
17
|
+
return res;
|
|
18
|
+
} catch (error) {
|
|
19
|
+
// Never reject: some callers don't await and a rejection would crash the
|
|
20
|
+
// process. Return false so they fall back to sending a new message.
|
|
21
|
+
if (cb) cb(error, null);
|
|
22
|
+
return false;
|
|
23
|
+
}
|
|
24
|
+
};
|
|
25
|
+
};
|
|
@@ -4,4 +4,10 @@
|
|
|
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
|
+
// fca.markAsRead(threadID, itemID) -> Instagram's item-seen call.
|
|
9
|
+
module.exports = function (client) {
|
|
10
|
+
return async function markAsRead(threadID, itemID) {
|
|
11
|
+
return client.api.markThreadSeen(String(threadID), itemID).catch(() => false);
|
|
12
|
+
};
|
|
13
|
+
};
|
|
@@ -4,4 +4,11 @@
|
|
|
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
|
+
// Instagram profile pictures are already public HTTPS URLs, so resolving one is
|
|
9
|
+
// (close to) identity.
|
|
10
|
+
module.exports = function () {
|
|
11
|
+
return function resolvePhotoUrl(url) {
|
|
12
|
+
return url;
|
|
13
|
+
};
|
|
14
|
+
};
|
|
@@ -4,4 +4,48 @@
|
|
|
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
|
+
// fca.sendMessage(form, threadID, callback, replyToMessage) on Instagram.
|
|
9
|
+
// `form` is a string or { body, attachment | attachments }; `replyToMessage` is
|
|
10
|
+
// the target message id (or an event / messageReply object).
|
|
11
|
+
const { extractMessageId } = require("../../utils/format");
|
|
12
|
+
|
|
13
|
+
module.exports = function (client) {
|
|
14
|
+
return async function sendMessage(form, threadID, callback, replyToMessage) {
|
|
15
|
+
// fca also accepts sendMessage(form, threadID, messageID) where the 3rd arg
|
|
16
|
+
// is a string (the message being replied to).
|
|
17
|
+
if (typeof callback === "string" && replyToMessage === undefined) {
|
|
18
|
+
replyToMessage = callback;
|
|
19
|
+
callback = undefined;
|
|
20
|
+
}
|
|
21
|
+
let replyTo = null;
|
|
22
|
+
if (replyToMessage != null) {
|
|
23
|
+
replyTo =
|
|
24
|
+
typeof replyToMessage === "object"
|
|
25
|
+
? replyToMessage.messageID || replyToMessage.id || null
|
|
26
|
+
: String(replyToMessage);
|
|
27
|
+
}
|
|
28
|
+
const cb = typeof callback === "function" ? callback : null;
|
|
29
|
+
|
|
30
|
+
try {
|
|
31
|
+
const { body, attachment, attachments } = client._normalizeForm(form);
|
|
32
|
+
const media = attachment || (attachments && attachments[0]);
|
|
33
|
+
let result;
|
|
34
|
+
|
|
35
|
+
if (media) {
|
|
36
|
+
result = await client._sendAttachment(String(threadID), media, body);
|
|
37
|
+
} else {
|
|
38
|
+
result = await client.api.sendMessage(client.convert(body), String(threadID), replyTo);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const messageID = extractMessageId(result);
|
|
42
|
+
if (messageID) client._msgThread.set(messageID, String(threadID));
|
|
43
|
+
const info = { messageID, threadID: String(threadID), timestamp: Date.now() };
|
|
44
|
+
if (cb) cb(null, info);
|
|
45
|
+
return info;
|
|
46
|
+
} catch (error) {
|
|
47
|
+
if (cb) cb(error, null);
|
|
48
|
+
throw error;
|
|
49
|
+
}
|
|
50
|
+
};
|
|
51
|
+
};
|