hridoy-ultimate 9.0.0 โ 9.0.2
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 +346 -69
- package/{docs/UPSTREAM_API_DOCS.md โ DOCS.md} +3 -3
- package/README.md +536 -161
- package/func/checkUpdate.js +1 -0
- package/index.d.ts +13 -74
- package/index.js +0 -6
- package/module/config.js +2 -5
- package/module/login.js +1 -2
- package/module/loginHelper.js +9 -41
- package/module/options.js +6 -6
- package/package.json +44 -39
- package/src/api/action/logout.js +0 -19
- package/src/api/messaging/sendMessage.js +1 -1
- package/src/api/messaging/sessionGuard.js +8 -0
- package/src/api/messaging/setMessageReaction.js +2 -7
- package/src/api/socket/core/connectMqtt.js +72 -99
- package/src/api/socket/core/emitAuth.js +17 -9
- package/src/api/socket/e2ee/index.js +15 -33
- package/src/api/socket/e2ee/native/NOTICE.md +2 -2
- package/src/api/socket/e2ee/native/lib/index.mjs +1 -2
- package/src/api/socket/e2ee/native/nativeMediaBridge.js +4 -4
- package/src/api/socket/listenE2EE.js +1 -3
- package/src/api/socket/listenMqtt.js +60 -67
- package/src/utils/antiSuspension.js +3 -3
- package/src/utils/fingerprint.js +35 -16
- package/src/utils/headers.js +1 -1
- package/src/utils/versionCheck.js +47 -0
- package/LICENSE +0 -23
- package/MERGE_REPORT.md +0 -54
- package/THIRD_PARTY_NOTICES.md +0 -36
- package/docs/TEST_RESULTS.md +0 -68
- package/scripts/run-tests.js +0 -19
- package/scripts/syntax-check.js +0 -22
- package/src/api/socket/core/lifecycle.js +0 -117
- package/src/api/socket/e2ee/proto/proto-writer.ts +0 -76
- package/src/utils/typingIndicator.js +0 -202
- package/test/helpers/stub-modules.js +0 -122
- package/test/integration/logout.test.js +0 -69
- package/test/integration/mqtt-lifecycle.test.js +0 -195
- package/test/integration/package.test.js +0 -116
- package/test/integration/typing-wiring.test.js +0 -71
- package/test/unit/fingerprint.test.js +0 -19
- package/test/unit/humanizer.test.js +0 -32
- package/test/unit/lifecycle.test.js +0 -92
- package/test/unit/parsers.test.js +0 -43
- package/test/unit/typing.test.js +0 -136
package/README.md
CHANGED
|
@@ -1,179 +1,554 @@
|
|
|
1
|
-
|
|
1
|
+
<div align="center">
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
# ๐ฌ hridoy-ultimate
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
analysed, see `MERGE_REPORT.md`; itself derived from fca-unofficial) with a hardened
|
|
8
|
-
MQTT lifecycle: bounded exponential backoff, a reconnect budget, clean shutdown and no
|
|
9
|
-
timer left behind after `stopListening()` / `logout()`.
|
|
5
|
+
**Unofficial Facebook Messenger Bot API for Node.js**
|
|
6
|
+
NEXCA MQTT ยท Signal Protocol E2EE (mautrix-go powered) ยท sessionGuard ยท 90+ API Methods ยท Zero TypeScript
|
|
10
7
|
|
|
11
|
-
|
|
12
|
-
|
|
8
|
+
[](https://www.npmjs.com/package/hridoy-ultimate)
|
|
9
|
+
[](./LICENSE-MIT)
|
|
10
|
+
[](https://nodejs.org)
|
|
13
11
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
12
|
+
[Features](#-features) โข [Installation](#-installation) โข [Quick Start](#-quick-start) โข [E2EE](#-e2ee--encrypted-conversations) โข [sessionGuard](#-sessionguard) โข [sendBroadcast](#-sendbroadcast) โข [API Reference](#-api-reference)
|
|
13
|
+
|
|
14
|
+
</div>
|
|
15
|
+
|
|
16
|
+
---
|
|
17
|
+
|
|
18
|
+
## โก Why hridoy-ultimate?
|
|
19
|
+
|
|
20
|
+
- โ
**NEXCA MQTT** โ stable connection core, autoReconnect, jitter
|
|
21
|
+
- โ
**Signal Protocol E2EE** โ Facebook real encrypted conversations support. Reliable media/reaction/unsend delivery is powered by a bundled **mautrix-go / mautrix-meta** native engine (ยฉ Tulir Asokan, MPL-2.0) โ NEXCA's own hand-written E2EE encoder produced protocol-valid messages that Facebook accepted but Messenger clients failed to render for attachments/reactions/unsend, so this fork routes those through the native engine instead. Text messaging still uses the original engine (already reliable). The native engine is opt-in (`FCA_E2EE_NATIVE=1`) โ see [Native media engine](#native-media-engine).
|
|
22
|
+
- โ
**sessionGuard** โ appstate corruption and silent logout protection, auto-backup
|
|
23
|
+
- โ
**sendBroadcast** โ rate-limited multi-thread broadcast
|
|
24
|
+
- โ
**Fixed MQTT subscribe race condition** โ no more "Connection refused: No subscription existed"
|
|
25
|
+
- โ
**`isActiveClient()` guard** โ stale MQTT client events no longer processed
|
|
26
|
+
- โ
**`connectTimeout` extended** โ no premature logout on slow networks
|
|
27
|
+
- โ
**autoReconnect** โ auto-reconnect on connection drop
|
|
28
|
+
- โ
**GoatBot compatible** โ all API signatures unchanged (`threadID` optional etc.)
|
|
29
|
+
- โ
**90+ API methods** โ sendMessage, editMessage, setMessageReaction, getThreadInfo and more
|
|
30
|
+
|
|
31
|
+
---
|
|
23
32
|
|
|
24
|
-
|
|
33
|
+
## โจ Features
|
|
34
|
+
|
|
35
|
+
- โ
Full Messenger API โ messages, reactions, attachments, stickers, polls, pins
|
|
36
|
+
- โ
NEXCA MQTT โ stable connection, autoReconnect, jitter, isActiveClient guard
|
|
37
|
+
- โ
E2EE โ Signal Protocol encrypted threads, auto-connects after login (listenE2EE, e2ee.*)
|
|
38
|
+
- โ
E2EE media engine โ native mautrix-go engine (opt-in via `FCA_E2EE_NATIVE=1`) for reliable image/video/audio/document sending, reactions, and unsend in encrypted threads (falls back to the built-in JS engine automatically)
|
|
39
|
+
- โ
E2EE incoming media โ attachments on encrypted messages (including replies) are auto-decrypted and served locally, so existing commands work unmodified
|
|
40
|
+
- โ
sessionGuard โ appstate auto-save, corruption guard, .bak backup
|
|
41
|
+
- โ
Command anti-spam humanizer โ human-like reply delay, per-thread cooldown, GoatBot no-prefix/onChat/onReply auto-detection
|
|
42
|
+
- โ
sendBroadcast โ parallel/sequential multi-thread sending with rate limit
|
|
43
|
+
- โ
MessengerBot โ Discord.js/Telegraf style (.command, .hears, .launch)
|
|
44
|
+
- โ
createFcaClient โ namespaced facade (client.messages, client.threads etc.)
|
|
45
|
+
- โ
GoatBot / Mirai compatible โ drop-in replacement
|
|
46
|
+
|
|
47
|
+
---
|
|
48
|
+
|
|
49
|
+
## ๐ฆ Installation
|
|
25
50
|
|
|
26
|
-
## Install
|
|
27
|
-
Requires Node.js >= 18.
|
|
28
51
|
```bash
|
|
29
|
-
npm install hridoy-ultimate
|
|
30
|
-
npm install ./hridoy-ultimate-9.0.0.tgz
|
|
52
|
+
npm install hridoy-ultimate
|
|
31
53
|
```
|
|
32
|
-
`sqlite3` compiles/downloads a native module; E2EE-related packages are optional.
|
|
33
54
|
|
|
34
|
-
|
|
35
|
-
```js
|
|
36
|
-
const login = require("hridoy-ultimate"); // also: require(...).login, .default
|
|
37
|
-
const fs = require("fs");
|
|
55
|
+
Node.js >= 20 required.
|
|
38
56
|
|
|
39
|
-
|
|
57
|
+
The E2EE media engine ships a precompiled native binary (`src/api/socket/e2ee/native/build/`) for **Linux (.so)** and **Windows (.dll)**, loaded via the `koffi` FFI. `npm install` pulls in `koffi` and `yumi-json-bigint` automatically โ no extra setup needed. Note the native engine only runs when `FCA_E2EE_NATIVE=1` is set (see [Native media engine](#native-media-engine)); on platforms other than Linux/Windows x64 (e.g. macOS, ARM) the binary fails to load and E2EE media/reactions/unsend automatically fall back to the built-in JS engine (text messaging is unaffected either way).
|
|
40
58
|
|
|
41
|
-
|
|
42
|
-
if (err) return console.error("login failed:", err.error || err.message || err);
|
|
59
|
+
The E2EE socket's User-Agent defaults to desktop Windows Chrome 139 to match the rest of the client; override with `FCA_E2EE_UA` if you run with a different UA.
|
|
43
60
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
61
|
+
---
|
|
62
|
+
|
|
63
|
+
## ๐ Quick Start
|
|
64
|
+
|
|
65
|
+
### Classic (GoatBot compatible)
|
|
66
|
+
|
|
67
|
+
```javascript
|
|
68
|
+
const login = require("hridoy-ultimate");
|
|
69
|
+
|
|
70
|
+
login({ appState: require("./account.json") }, { listenEvents: true }, (err, api) => {
|
|
71
|
+
if (err) throw err;
|
|
72
|
+
|
|
73
|
+
api.sessionGuard("./account.json", {
|
|
74
|
+
interval: 3 * 60 * 1000,
|
|
75
|
+
debounce: 30 * 1000
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
api.listenMqtt((err, event) => {
|
|
79
|
+
if (err) throw err;
|
|
80
|
+
if (event.type === "message") api.sendMessage(event.body, event.threadID);
|
|
81
|
+
});
|
|
82
|
+
});
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
### With E2EE (regular + encrypted threads)
|
|
86
|
+
|
|
87
|
+
`connectE2EE()` runs **automatically** right after login โ you never call it yourself. Calling it again in your own code opens a second, racing connection on the same bridge. Just check `api.e2ee.isConnected()` if you need to know the status.
|
|
88
|
+
|
|
89
|
+
```javascript
|
|
90
|
+
login({ appState: require("./account.json") }, { listenEvents: true }, (err, api) => {
|
|
91
|
+
if (err) throw err;
|
|
92
|
+
|
|
93
|
+
api.sessionGuard("./account.json");
|
|
94
|
+
// E2EE is already connecting in the background here โ do not call connectE2EE().
|
|
95
|
+
|
|
96
|
+
api.listenE2EE((err, event) => {
|
|
97
|
+
if (err) throw err;
|
|
98
|
+
if (event.type === "message") {
|
|
99
|
+
if (event.isE2EE) {
|
|
100
|
+
api.e2ee.sendMessage(event.threadID, "Got your encrypted message!");
|
|
101
|
+
} else {
|
|
102
|
+
api.sendMessage("Got it!", event.threadID);
|
|
103
|
+
}
|
|
52
104
|
}
|
|
53
|
-
}
|
|
105
|
+
});
|
|
106
|
+
});
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
### GoatBot login.js
|
|
110
|
+
|
|
111
|
+
```javascript
|
|
112
|
+
const login = require("hridoy-ultimate");
|
|
113
|
+
|
|
114
|
+
login({ appState }, options, (err, api) => {
|
|
115
|
+
if (err) return;
|
|
54
116
|
|
|
55
|
-
process.
|
|
56
|
-
|
|
117
|
+
api.sessionGuard(path.join(process.cwd(), "account.txt"), {
|
|
118
|
+
interval: 3 * 60 * 1000,
|
|
119
|
+
debounce: 30 * 1000
|
|
57
120
|
});
|
|
121
|
+
|
|
122
|
+
// Nothing else needed for E2EE โ it auto-connects inside hridoy-ultimate
|
|
123
|
+
// right after login. Do NOT call api.connectE2EE() here.
|
|
124
|
+
|
|
125
|
+
api.listenMqtt(callback);
|
|
58
126
|
});
|
|
59
127
|
```
|
|
60
|
-
|
|
61
|
-
`
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
`
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
128
|
+
|
|
129
|
+
**This is the only setup needed โ nothing else in `login.js` (or anywhere in GoatBot) has to change for E2EE.** Once login succeeds, `api.e2ee` is already connecting in the background, and every existing GoatBot call โ `api.sendMessage(...)` (with or without `attachment`), `api.setMessageReaction(...)`, `api.unsendMessage(...)` โ automatically detects when a thread is an encrypted DM and routes through the E2EE engine internally. Commands don't need to know or care whether a thread is encrypted; they call the same functions either way.
|
|
130
|
+
|
|
131
|
+
#### Adding this to your own login.js
|
|
132
|
+
|
|
133
|
+
Your bot's actual `login.js` will look different from the snippet above (dashboard setup, database sync, custom logging, etc.) โ you're not replacing the file, just adding/checking two things inside your existing login callback:
|
|
134
|
+
|
|
135
|
+
| Step | What to do |
|
|
136
|
+
|---|---|
|
|
137
|
+
| 1. sessionGuard | Add `api.sessionGuard(accountPath, { interval, debounce })` once, right after login succeeds, if it's not already there. |
|
|
138
|
+
| 2. E2EE | Nothing to add โ it's automatic. If your `login.js` already has an explicit `await api.connectE2EE()` call, **delete it** โ it's redundant with the automatic connection and the two can race. |
|
|
139
|
+
|
|
140
|
+
To check whether E2EE is already wired into your bot, search for `e2ee.isConnected` in your `login.js` โ if it's there, setup is already done.
|
|
141
|
+
|
|
142
|
+
---
|
|
143
|
+
|
|
144
|
+
## ๐ E2EE โ Encrypted Conversations
|
|
145
|
+
|
|
146
|
+
Uses Facebook's real Signal Protocol infrastructure โ same as the official Messenger app.
|
|
147
|
+
|
|
148
|
+
### Setup
|
|
149
|
+
|
|
150
|
+
E2EE connects automatically after login (creating `.nexca/e2ee_device.json` on first run) โ there's nothing to call manually. Just check status once connected:
|
|
151
|
+
|
|
152
|
+
```javascript
|
|
153
|
+
console.log(api.e2ee.isConnected()); // true (may need a moment right after login)
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
### Listen (regular + E2EE combined)
|
|
157
|
+
|
|
158
|
+
```javascript
|
|
159
|
+
api.listenE2EE((err, event) => {
|
|
160
|
+
if (event.type === "message") {
|
|
161
|
+
if (event.isE2EE) {
|
|
162
|
+
api.e2ee.sendMessage(event.threadID, "Encrypted reply!");
|
|
163
|
+
} else {
|
|
164
|
+
api.sendMessage("Normal reply!", event.threadID);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
});
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
### E2EE Methods
|
|
171
|
+
|
|
172
|
+
```javascript
|
|
173
|
+
await api.e2ee.sendMessage(threadID, "Hello!");
|
|
174
|
+
await api.e2ee.sendMessage(threadID, { body: "Photo!", attachment: fs.createReadStream("photo.jpg") });
|
|
175
|
+
await api.e2ee.sendReaction(threadID, messageID, "โค๏ธ");
|
|
176
|
+
await api.e2ee.sendTyping(threadID, true);
|
|
177
|
+
await api.e2ee.unsendMessage(messageID, threadID);
|
|
178
|
+
await api.e2ee.editMessage(threadID, messageID, "Updated!");
|
|
179
|
+
api.e2ee.isConnected();
|
|
180
|
+
await api.e2ee.disconnect();
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
### Native media engine
|
|
184
|
+
|
|
185
|
+
Sending image/video/audio/document attachments, reactions, and unsend in E2EE threads can be routed through a precompiled **mautrix-go / mautrix-meta** binary (`src/api/socket/e2ee/native/`) via the `koffi` FFI, instead of this project's own hand-written Signal Protocol encoder. This project's own encoder builds protocol-valid messages that Facebook's servers accept, but real Messenger clients silently fail to render โ the native engine avoids that entirely.
|
|
186
|
+
|
|
187
|
+
**โ ๏ธ The native engine is DISABLED by default.** Both the vendored JS engine and the native engine register with Facebook as the *same* E2EE device (same device id), and Facebook's server keeps only one live E2EE socket per device โ so when both run at once they kick each other off in a loop (a fresh handshake every few seconds, which looks like automated behavior to Facebook). The vendored JS engine is therefore given exclusive use of the socket unless you opt in:
|
|
188
|
+
|
|
189
|
+
- Set `FCA_E2EE_NATIVE=1` in the environment to enable the native engine. With it on you get E2EE group text receive/send, native media sends, native reactions/unsend/edit/markRead โ but you also accept the device-kick socket churn described above (the JS engine reconnects with exponential backoff, so it stays usable, just noisy).
|
|
190
|
+
- With it off (default): E2EE DM text + DM media work through the vendored engine. E2EE **group** send/receive, and E2EE markRead, are unavailable; reactions/unsend/edit in E2EE DMs fall back to the vendored engine.
|
|
191
|
+
|
|
192
|
+
- Text messages always use hridoy-ultimate's own E2EE engine (already reliable, no native dependency).
|
|
193
|
+
- If the native engine fails to load or a send fails (unsupported platform, binary mismatch, etc.), hridoy-ultimate automatically falls back to its own engine and logs `[native-media] ... falling back to legacy vendor engine`.
|
|
194
|
+
- See `src/api/socket/e2ee/native/NOTICE.md` for the required MPL-2.0 attribution to the upstream mautrix-go/mautrix-meta project (ยฉ Tulir Asokan and contributors) โ do not remove it if you redistribute this fork.
|
|
195
|
+
|
|
196
|
+
### Receiving E2EE media (attachments & replies)
|
|
197
|
+
|
|
198
|
+
Incoming encrypted image/video/audio/document messages are automatically decrypted and served over a short-lived local URL (`http://127.0.0.1:<port>/<token>`, 15 min TTL), and populated into `event.attachments` / `event.messageReply.attachments` โ same shape as normal (non-E2EE) attachments. Reply-based commands (e.g. "reply to an image with `/imgur`") work the same in E2EE threads as in normal ones, no command code changes needed.
|
|
199
|
+
|
|
200
|
+
```javascript
|
|
201
|
+
if (event.messageReply && event.messageReply.attachments.length) {
|
|
202
|
+
const url = event.messageReply.attachments[0].url; // works for E2EE too
|
|
203
|
+
}
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
If media resolution fails, check the console for `[media-resolve]` / `[media-decode]` errors โ this usually means the CDN download host (`FB_E2EE_MEDIA_DOWNLOAD_HOST` env var, default `rupload.facebook.com`) needs adjusting for your account/region.
|
|
207
|
+
|
|
208
|
+
---
|
|
209
|
+
|
|
210
|
+
## ๐ก๏ธ Command Anti-Spam (Humanizer)
|
|
211
|
+
|
|
212
|
+
Delays and rate-limits command replies so the bot reacts like a person
|
|
213
|
+
typing, not a script firing instantly โ and drops commands sent faster
|
|
214
|
+
than a human could plausibly send them (spam bursts).
|
|
215
|
+
|
|
216
|
+
Installed automatically on login (`enabled` by default). No setup needed:
|
|
217
|
+
|
|
218
|
+
```javascript
|
|
219
|
+
login({ appState }, {}, (err, api) => {
|
|
220
|
+
api.listenMqtt((err, event) => { /* ... */ });
|
|
221
|
+
});
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
Configure it via the `humanize` login option, or `api.setOptions({ humanize })`:
|
|
225
|
+
|
|
226
|
+
```javascript
|
|
227
|
+
login({ appState }, {
|
|
228
|
+
humanize: {
|
|
229
|
+
enabled: true,
|
|
230
|
+
reactDelayMs: [2000, 3000], // random delay before a command is let through
|
|
231
|
+
cooldownMs: 5500, // min gap between two accepted commands (per thread)
|
|
232
|
+
resetMs: 10000, // idle gap before cooldown state resets
|
|
233
|
+
maxPerMinute: 20, // global accepted-commands cap
|
|
234
|
+
commands: ["/", "!", "."], // prefixes that count as "this is a command"
|
|
235
|
+
perThread: true // cooldown tracked per thread vs. globally
|
|
236
|
+
}
|
|
237
|
+
}, cb);
|
|
238
|
+
```
|
|
239
|
+
|
|
240
|
+
`api.setOptions({ humanize: { enabled: false } })` turns it off entirely.
|
|
241
|
+
|
|
242
|
+
### What counts as a "command"
|
|
243
|
+
|
|
244
|
+
By default, only messages starting with one of the `commands` prefixes are
|
|
245
|
+
treated as commands โ everything else (plain chat) passes through
|
|
246
|
+
untouched, with no delay and no typing indicator.
|
|
247
|
+
|
|
248
|
+
If `global.GoatBot` is present (i.e. running under GoatBot-Pro), the
|
|
249
|
+
humanizer **automatically** also treats these as commands, with zero
|
|
250
|
+
config needed:
|
|
251
|
+
|
|
252
|
+
- **No-prefix admin commands** โ `config.noPrefix.enable: true` +
|
|
253
|
+
sender in `config.adminBot`, first word matches a real command/alias.
|
|
254
|
+
- **`onChat` triggers** โ a message whose full text exactly matches a
|
|
255
|
+
command name registered in `GoatBot.onChat` (e.g. typing `prefix` with
|
|
256
|
+
no `/`, which GoatBot's `prefix.js` reacts to).
|
|
257
|
+
- **`onReply` flows** โ a reply to a message tracked in `GoatBot.onReply`
|
|
258
|
+
(e.g. the numeric "1", "2", "3" replies used by GoatBot's `setting.js`
|
|
259
|
+
menu system).
|
|
260
|
+
|
|
261
|
+
This means the whole multi-step `setting` command flow โ not just the
|
|
262
|
+
initial `/setting` โ gets the same delay/cooldown/typing treatment as a
|
|
263
|
+
normal command.
|
|
264
|
+
|
|
265
|
+
For anything outside these built-ins, give it a custom matcher:
|
|
266
|
+
|
|
267
|
+
```javascript
|
|
268
|
+
const h = api.getHumanizer();
|
|
269
|
+
h.setMatcher((event) => {
|
|
270
|
+
// your own logic โ return true to treat this event as a command
|
|
271
|
+
return event.body === "some no-prefix trigger";
|
|
272
|
+
});
|
|
273
|
+
```
|
|
274
|
+
|
|
275
|
+
`matcher` can also be passed directly in the `humanize` config
|
|
276
|
+
(`humanize: { matcher: fn }`). A message counts as a command if it matches
|
|
277
|
+
the prefix list **or** a built-in GoatBot check **or** the matcher.
|
|
278
|
+
|
|
279
|
+
### Typing indicator
|
|
280
|
+
|
|
281
|
+
When a command is accepted, `api.sendTypingIndicator(threadID, true)` is
|
|
282
|
+
called immediately, held for the exact accepted delay, then turned off
|
|
283
|
+
right before the event is forwarded โ so the typing indicator and the
|
|
284
|
+
reply delay are the same window, not two stacked waits. Disabled
|
|
285
|
+
automatically if `global.GoatBot.config.enableTypingIndicator` is `false`.
|
|
286
|
+
|
|
287
|
+
### Inspecting it
|
|
288
|
+
|
|
289
|
+
```javascript
|
|
290
|
+
const h = api.getHumanizer();
|
|
291
|
+
h.stats(); // { enabled, global: {accepted, dropped, ...}, threads: [...] }
|
|
292
|
+
h.reset(); // clear all cooldown state
|
|
293
|
+
```
|
|
294
|
+
|
|
295
|
+
> Note: this operates on **incoming** command volume โ separate from
|
|
296
|
+
> outgoing send pacing (see `sendBroadcast` below, and any queueing you
|
|
297
|
+
> add yourself around `api.sendMessage`).
|
|
298
|
+
|
|
299
|
+
---
|
|
300
|
+
|
|
301
|
+
## ๐ก๏ธ sessionGuard
|
|
302
|
+
|
|
303
|
+
Protects your appstate from corruption and silent logouts.
|
|
304
|
+
|
|
305
|
+
```javascript
|
|
306
|
+
api.sessionGuard("./account.json");
|
|
307
|
+
|
|
308
|
+
// Custom timing
|
|
309
|
+
api.sessionGuard("./account.json", {
|
|
310
|
+
interval: 3 * 60 * 1000,
|
|
311
|
+
debounce: 30 * 1000
|
|
312
|
+
});
|
|
313
|
+
```
|
|
314
|
+
|
|
315
|
+
What it does:
|
|
316
|
+
- Auto-saves appstate every N minutes
|
|
317
|
+
- Saves after every successful sendMessage (debounced)
|
|
318
|
+
- Corruption guard โ never overwrites a larger appstate with a smaller one
|
|
319
|
+
- Auto-backup โ writes `.bak` before every overwrite
|
|
320
|
+
|
|
321
|
+
```javascript
|
|
322
|
+
api.saveSession(); // force save now
|
|
323
|
+
api.restoreSessionBackup(); // restore from .bak
|
|
324
|
+
api.stopSessionGuard(); // stop the timer
|
|
325
|
+
```
|
|
326
|
+
|
|
327
|
+
---
|
|
328
|
+
|
|
329
|
+
## ๐ก sendBroadcast
|
|
330
|
+
|
|
331
|
+
Rate-limited multi-thread broadcast.
|
|
332
|
+
|
|
333
|
+
```javascript
|
|
334
|
+
const result = await api.sendBroadcast(
|
|
335
|
+
"Hello everyone!",
|
|
336
|
+
["THREAD_1", "THREAD_2", "THREAD_3"],
|
|
337
|
+
{
|
|
338
|
+
delay: 2000,
|
|
339
|
+
parallel: 2,
|
|
340
|
+
onEach: (err, info, id) => {
|
|
341
|
+
console.log(err ? "Failed: " + id : "Sent: " + id);
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
);
|
|
345
|
+
console.log(result.sent.length + "/" + result.total + " delivered");
|
|
346
|
+
```
|
|
347
|
+
|
|
348
|
+
---
|
|
349
|
+
|
|
350
|
+
## ๐ค MessengerBot
|
|
351
|
+
|
|
352
|
+
Discord.js/Telegraf style high-level bot class.
|
|
353
|
+
|
|
354
|
+
```javascript
|
|
355
|
+
const { createMessengerBot } = require("hridoy-ultimate");
|
|
356
|
+
|
|
357
|
+
const bot = await createMessengerBot(
|
|
358
|
+
{ appState: require("./account.json") },
|
|
359
|
+
{ commandPrefix: "/", stopOnSignals: true }
|
|
360
|
+
);
|
|
361
|
+
|
|
362
|
+
bot.command("ping", async ctx => await ctx.replyAsync("pong ๐"));
|
|
363
|
+
bot.hears(/hello/i, async ctx => await ctx.replyAsync("Hi! ๐"));
|
|
364
|
+
bot.on("messageCreate", event => console.log(event.body));
|
|
365
|
+
|
|
366
|
+
await bot.launch({ stopOnSignals: true });
|
|
367
|
+
```
|
|
368
|
+
|
|
369
|
+
---
|
|
370
|
+
|
|
371
|
+
## ๐ฏ createFcaClient
|
|
372
|
+
|
|
373
|
+
Namespaced facade grouping all API methods by domain.
|
|
374
|
+
|
|
375
|
+
```javascript
|
|
376
|
+
const { createFcaClient } = require("hridoy-ultimate");
|
|
377
|
+
const client = createFcaClient(api);
|
|
378
|
+
|
|
379
|
+
await client.messages.send("Hello!", threadID);
|
|
380
|
+
await client.messages.react("โค๏ธ", messageID, threadID);
|
|
381
|
+
await client.threads.getInfo(threadID);
|
|
382
|
+
await client.users.getInfo(userID);
|
|
383
|
+
await client.account.refreshDtsg();
|
|
384
|
+
```
|
|
385
|
+
|
|
386
|
+
---
|
|
387
|
+
|
|
388
|
+
## ๐ API Reference
|
|
389
|
+
|
|
390
|
+
### Sending Messages
|
|
391
|
+
|
|
392
|
+
```javascript
|
|
393
|
+
api.sendMessage("Hello!", threadID);
|
|
394
|
+
api.sendMessage({ body: "Photo!", attachment: fs.createReadStream("photo.jpg") }, threadID);
|
|
395
|
+
api.sendMessage({ body: "Hey @John", mentions: [{ id: "uid", tag: "@John", fromIndex: 4 }] }, threadID);
|
|
396
|
+
api.sendMessage({ sticker: "369239263222822" }, threadID);
|
|
397
|
+
api.sendMessage({ location: { latitude: 23.8, longitude: 90.4, current: true } }, threadID);
|
|
398
|
+
api.sendBroadcast("msg", ["tid1", "tid2"], { delay: 2000 });
|
|
399
|
+
api.sendGif("https://media.giphy.com/xyz.gif", threadID);
|
|
400
|
+
api.sendLocation(23.8, 90.4, threadID);
|
|
401
|
+
api.sendImage("./photo.jpg", threadID, "caption");
|
|
402
|
+
api.sendVideo("./video.mp4", threadID);
|
|
403
|
+
api.sendAudio("./voice.ogg", threadID);
|
|
404
|
+
api.sendFile("./doc.pdf", threadID);
|
|
405
|
+
api.shareLink("https://github.com", threadID, "Check this!");
|
|
406
|
+
api.shareContact("Meet my friend!", userID, threadID);
|
|
407
|
+
```
|
|
408
|
+
|
|
409
|
+
### Message Actions
|
|
410
|
+
|
|
411
|
+
```javascript
|
|
412
|
+
api.editMessage("Updated text", messageID);
|
|
413
|
+
api.unsendMessage(messageID);
|
|
414
|
+
api.deleteMessage([messageID]);
|
|
415
|
+
api.setMessageReaction("๐", messageID, threadID); // threadID optional
|
|
416
|
+
api.setMessageReaction("", messageID); // remove reaction
|
|
417
|
+
api.getMessage(threadID, messageID);
|
|
418
|
+
api.forwardAttachment(attachmentID, [userID]);
|
|
419
|
+
api.uploadAttachment([fs.createReadStream("photo.jpg")]);
|
|
420
|
+
```
|
|
421
|
+
|
|
422
|
+
### Read Receipts & Typing
|
|
423
|
+
|
|
424
|
+
```javascript
|
|
425
|
+
api.markAsRead(threadID);
|
|
426
|
+
api.markAsReadAll();
|
|
427
|
+
api.markAsDelivered(threadID, messageID);
|
|
428
|
+
api.markAsSeen();
|
|
429
|
+
api.sendTypingIndicator(threadID, true);
|
|
430
|
+
```
|
|
431
|
+
|
|
432
|
+
### Thread Management
|
|
433
|
+
|
|
434
|
+
```javascript
|
|
435
|
+
api.getThreadInfo(threadID);
|
|
436
|
+
api.getThreadList(10, null, ["INBOX"]);
|
|
437
|
+
api.getThreadHistory(threadID, 20);
|
|
438
|
+
api.createGroup("Hey!", ["uid1", "uid2"]);
|
|
439
|
+
api.deleteThread(threadID);
|
|
440
|
+
api.muteThread(threadID, 3600);
|
|
441
|
+
api.changeArchivedStatus(threadID, true);
|
|
442
|
+
api.handleMessageRequest(threadID, true);
|
|
443
|
+
api.searchForThread("query");
|
|
444
|
+
```
|
|
445
|
+
|
|
446
|
+
### Thread Customization
|
|
447
|
+
|
|
448
|
+
```javascript
|
|
449
|
+
api.setTitle("New Name", threadID);
|
|
450
|
+
api.changeThreadColor("#0084FF", threadID);
|
|
451
|
+
api.changeThreadEmoji("๐ฅ", threadID);
|
|
452
|
+
api.changeNickname("The Boss", threadID, userID);
|
|
453
|
+
api.changeGroupImage(fs.createReadStream("group.jpg"), threadID);
|
|
454
|
+
api.changeAdminStatus(threadID, userID, true);
|
|
455
|
+
api.addUserToGroup(userID, threadID);
|
|
456
|
+
api.removeUserFromGroup(userID, threadID);
|
|
457
|
+
api.createPoll("Question?", threadID, { "Yes": false, "No": false });
|
|
458
|
+
api.pinMessage(messageID, threadID);
|
|
459
|
+
api.unpinMessage(messageID, threadID);
|
|
460
|
+
```
|
|
461
|
+
|
|
462
|
+
### User Info
|
|
463
|
+
|
|
464
|
+
```javascript
|
|
465
|
+
api.getUserInfo(userID);
|
|
466
|
+
api.getUserID("John Doe", callback);
|
|
467
|
+
api.getUID("https://facebook.com/zuck");
|
|
468
|
+
api.getFriendsList();
|
|
469
|
+
api.getAvatarUser(userID);
|
|
470
|
+
api.getProfileInfo(userID);
|
|
471
|
+
api.getPublicData(userID);
|
|
472
|
+
api.sendFriendRequest(userID);
|
|
473
|
+
api.handleFriendRequest(userID, true);
|
|
474
|
+
api.changeBlockedStatus(userID, true);
|
|
475
|
+
api.followUser(userID);
|
|
476
|
+
api.unfollowUser(userID);
|
|
477
|
+
api.unfriend(userID);
|
|
478
|
+
```
|
|
479
|
+
|
|
480
|
+
### Social
|
|
481
|
+
|
|
482
|
+
```javascript
|
|
483
|
+
api.reactToPost(postID, "love");
|
|
484
|
+
api.reactToComment(commentID, "haha");
|
|
485
|
+
api.postComment(postID, "Great post!");
|
|
486
|
+
api.sharePost(postID, "Check this!");
|
|
178
487
|
```
|
|
179
|
-
|
|
488
|
+
|
|
489
|
+
### Account & Config
|
|
490
|
+
|
|
491
|
+
```javascript
|
|
492
|
+
api.getCurrentUserID();
|
|
493
|
+
api.getAppState();
|
|
494
|
+
api.setOptions({ listenTyping: true });
|
|
495
|
+
api.logout();
|
|
496
|
+
api.refreshFb_dtsg();
|
|
497
|
+
api.addExternalModule("myFunc", (defaultFuncs, api, ctx) => {
|
|
498
|
+
return function(text, threadID) {
|
|
499
|
+
return api.sendMessage("[BOT] " + text, threadID);
|
|
500
|
+
};
|
|
501
|
+
});
|
|
502
|
+
```
|
|
503
|
+
|
|
504
|
+
### HTTP Utilities
|
|
505
|
+
|
|
506
|
+
```javascript
|
|
507
|
+
api.httpGet(url, params, callback);
|
|
508
|
+
api.httpPost(url, form, callback);
|
|
509
|
+
api.httpPostFormData(url, form, callback);
|
|
510
|
+
api.uploadImageToImgbb(imageUrl);
|
|
511
|
+
```
|
|
512
|
+
|
|
513
|
+
---
|
|
514
|
+
|
|
515
|
+
## ๐ Login Options
|
|
516
|
+
|
|
517
|
+
| Option | Type | Default | Description |
|
|
518
|
+
|--------|------|---------|-------------|
|
|
519
|
+
| `selfListen` | boolean | false | Receive your own sent messages |
|
|
520
|
+
| `listenEvents` | boolean | true | Receive thread/group events |
|
|
521
|
+
| `listenTyping` | boolean | false | Receive typing indicator events |
|
|
522
|
+
| `updatePresence` | boolean | false | Receive online/offline presence events |
|
|
523
|
+
| `autoMarkDelivery` | boolean | false | Auto-mark incoming messages as delivered |
|
|
524
|
+
| `autoMarkRead` | boolean | false | Auto-mark threads as read |
|
|
525
|
+
| `autoReconnect` | boolean | true | Auto-reconnect MQTT on disconnect |
|
|
526
|
+
| `online` | boolean | false | Appear as online to others |
|
|
527
|
+
| `emitReady` | boolean | false | Emit ready event when MQTT connected |
|
|
528
|
+
| `proxy` | string | โ | HTTP proxy URL |
|
|
529
|
+
| `userAgent` | string | Safari UA | Override HTTP User-Agent |
|
|
530
|
+
|
|
531
|
+
---
|
|
532
|
+
|
|
533
|
+
## ๐ License
|
|
534
|
+
|
|
535
|
+
MIT License
|
|
536
|
+
|
|
537
|
+
**hridoy-ultimate** v9.0.1 by [HR ID OY](https://github.com/hridoyhossen48360)
|
|
538
|
+
|
|
539
|
+
> The E2EE native media engine (`src/api/socket/e2ee/native/`) bundles a
|
|
540
|
+
> precompiled binary built from **mautrix-go** / **mautrix-meta**, ยฉ Tulir
|
|
541
|
+
> Asokan and contributors, licensed under **MPL-2.0** (not MIT). See
|
|
542
|
+
> `src/api/socket/e2ee/native/NOTICE.md` for full attribution โ this notice
|
|
543
|
+
> must be preserved in any redistribution of this fork.
|
|
544
|
+
NEXCA MQTT core by [Deku](https://github.com/dekuzxc) โ MIT License
|
|
545
|
+
|
|
546
|
+
> Unauthorized copying or redistribution without credit is prohibited.
|
|
547
|
+
|
|
548
|
+
---
|
|
549
|
+
|
|
550
|
+
<div align="center">
|
|
551
|
+
|
|
552
|
+
Made with โค๏ธ by HR ID OY
|
|
553
|
+
|
|
554
|
+
</div>
|