nodejs-insta-private-api-mqt 1.3.70

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.
Files changed (240) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +3677 -0
  3. package/dist/constants/constants.js +342 -0
  4. package/dist/constants/index.js +58 -0
  5. package/dist/core/client.js +419 -0
  6. package/dist/core/nav-chain.js +282 -0
  7. package/dist/core/repository.js +7 -0
  8. package/dist/core/request.js +390 -0
  9. package/dist/core/state.js +1473 -0
  10. package/dist/core/utils.js +786 -0
  11. package/dist/downloadMedia.js +381 -0
  12. package/dist/errors/index.d.ts +16 -0
  13. package/dist/errors/index.js +38 -0
  14. package/dist/errors/index.js.map +1 -0
  15. package/dist/extend.js +167 -0
  16. package/dist/fbns/fbns.client.d.ts +32 -0
  17. package/dist/fbns/fbns.client.events.d.ts +41 -0
  18. package/dist/fbns/fbns.client.events.js +3 -0
  19. package/dist/fbns/fbns.client.events.js.map +1 -0
  20. package/dist/fbns/fbns.client.js +252 -0
  21. package/dist/fbns/fbns.client.js.map +1 -0
  22. package/dist/fbns/fbns.device-auth.d.ts +17 -0
  23. package/dist/fbns/fbns.device-auth.js +54 -0
  24. package/dist/fbns/fbns.device-auth.js.map +1 -0
  25. package/dist/fbns/fbns.types.d.ts +83 -0
  26. package/dist/fbns/fbns.types.js +3 -0
  27. package/dist/fbns/fbns.types.js.map +1 -0
  28. package/dist/fbns/fbns.utilities.d.ts +2 -0
  29. package/dist/fbns/fbns.utilities.js +79 -0
  30. package/dist/fbns/fbns.utilities.js.map +1 -0
  31. package/dist/fbns/index.d.ts +4 -0
  32. package/dist/fbns/index.js +21 -0
  33. package/dist/fbns/index.js.map +1 -0
  34. package/dist/index.js +139 -0
  35. package/dist/mqtt-shim.d.ts +96 -0
  36. package/dist/mqtt-shim.js +15 -0
  37. package/dist/mqttot/index.d.ts +4 -0
  38. package/dist/mqttot/index.js +21 -0
  39. package/dist/mqttot/index.js.map +1 -0
  40. package/dist/mqttot/mqttot.client.d.ts +39 -0
  41. package/dist/mqttot/mqttot.client.js +318 -0
  42. package/dist/mqttot/mqttot.client.js.map +1 -0
  43. package/dist/mqttot/mqttot.connect.request.packet.d.ts +7 -0
  44. package/dist/mqttot/mqttot.connect.request.packet.js +9 -0
  45. package/dist/mqttot/mqttot.connect.request.packet.js.map +1 -0
  46. package/dist/mqttot/mqttot.connect.response.packet.d.ts +7 -0
  47. package/dist/mqttot/mqttot.connect.response.packet.js +24 -0
  48. package/dist/mqttot/mqttot.connect.response.packet.js.map +1 -0
  49. package/dist/mqttot/mqttot.connection.d.ts +57 -0
  50. package/dist/mqttot/mqttot.connection.js +79 -0
  51. package/dist/mqttot/mqttot.connection.js.map +1 -0
  52. package/dist/package.json +59 -0
  53. package/dist/realtime/commands/commands.d.ts +15 -0
  54. package/dist/realtime/commands/commands.js +71 -0
  55. package/dist/realtime/commands/commands.js.map +1 -0
  56. package/dist/realtime/commands/direct.commands.d.ts +75 -0
  57. package/dist/realtime/commands/direct.commands.js +417 -0
  58. package/dist/realtime/commands/direct.commands.js.map +1 -0
  59. package/dist/realtime/commands/enhanced.direct.commands.js +1731 -0
  60. package/dist/realtime/commands/enhanced.direct.commands.js.bak +967 -0
  61. package/dist/realtime/commands/index.d.ts +2 -0
  62. package/dist/realtime/commands/index.js +20 -0
  63. package/dist/realtime/commands/index.js.map +1 -0
  64. package/dist/realtime/delta-sync.manager.js +293 -0
  65. package/dist/realtime/features/dm-sender.js +88 -0
  66. package/dist/realtime/features/error-handler.js +185 -0
  67. package/dist/realtime/features/gap-handler.js +61 -0
  68. package/dist/realtime/features/persistent-logger.js +186 -0
  69. package/dist/realtime/features/presence.manager.js +66 -0
  70. package/dist/realtime/features/session-health-monitor.js +345 -0
  71. package/dist/realtime/index.js +30 -0
  72. package/dist/realtime/messages/app-presence.event.d.ts +9 -0
  73. package/dist/realtime/messages/app-presence.event.js +3 -0
  74. package/dist/realtime/messages/app-presence.event.js.map +1 -0
  75. package/dist/realtime/messages/index.d.ts +3 -0
  76. package/dist/realtime/messages/index.js +20 -0
  77. package/dist/realtime/messages/index.js.map +1 -0
  78. package/dist/realtime/messages/message-sync.message.d.ts +222 -0
  79. package/dist/realtime/messages/message-sync.message.js +43 -0
  80. package/dist/realtime/messages/message-sync.message.js.map +1 -0
  81. package/dist/realtime/messages/realtime-sub.direct.data.d.ts +11 -0
  82. package/dist/realtime/messages/realtime-sub.direct.data.js +3 -0
  83. package/dist/realtime/messages/realtime-sub.direct.data.js.map +1 -0
  84. package/dist/realtime/messages/thread-update.message.d.ts +68 -0
  85. package/dist/realtime/messages/thread-update.message.js +3 -0
  86. package/dist/realtime/messages/thread-update.message.js.map +1 -0
  87. package/dist/realtime/mixins/index.d.ts +3 -0
  88. package/dist/realtime/mixins/index.js +20 -0
  89. package/dist/realtime/mixins/index.js.map +1 -0
  90. package/dist/realtime/mixins/message-sync.mixin.d.ts +8 -0
  91. package/dist/realtime/mixins/message-sync.mixin.js +596 -0
  92. package/dist/realtime/mixins/message-sync.mixin.js.map +1 -0
  93. package/dist/realtime/mixins/mixin.d.ts +19 -0
  94. package/dist/realtime/mixins/mixin.js +41 -0
  95. package/dist/realtime/mixins/mixin.js.map +1 -0
  96. package/dist/realtime/mixins/presence-typing.mixin.js +33 -0
  97. package/dist/realtime/mixins/realtime-sub.mixin.d.ts +8 -0
  98. package/dist/realtime/mixins/realtime-sub.mixin.js +181 -0
  99. package/dist/realtime/mixins/realtime-sub.mixin.js.map +1 -0
  100. package/dist/realtime/parsers/graphql-parser.js +43 -0
  101. package/dist/realtime/parsers/graphql.parser.d.ts +15 -0
  102. package/dist/realtime/parsers/graphql.parser.js +22 -0
  103. package/dist/realtime/parsers/graphql.parser.js.map +1 -0
  104. package/dist/realtime/parsers/index.d.ts +6 -0
  105. package/dist/realtime/parsers/index.js +23 -0
  106. package/dist/realtime/parsers/index.js.map +1 -0
  107. package/dist/realtime/parsers/iris-parser.js +43 -0
  108. package/dist/realtime/parsers/iris.parser.d.ts +17 -0
  109. package/dist/realtime/parsers/iris.parser.js +10 -0
  110. package/dist/realtime/parsers/iris.parser.js.map +1 -0
  111. package/dist/realtime/parsers/json-parser.js +43 -0
  112. package/dist/realtime/parsers/json.parser.d.ts +6 -0
  113. package/dist/realtime/parsers/json.parser.js +10 -0
  114. package/dist/realtime/parsers/json.parser.js.map +1 -0
  115. package/dist/realtime/parsers/parser.d.ts +9 -0
  116. package/dist/realtime/parsers/parser.js +3 -0
  117. package/dist/realtime/parsers/parser.js.map +1 -0
  118. package/dist/realtime/parsers/region-hint-parser.js +43 -0
  119. package/dist/realtime/parsers/region-hint.parser.d.ts +12 -0
  120. package/dist/realtime/parsers/region-hint.parser.js +15 -0
  121. package/dist/realtime/parsers/region-hint.parser.js.map +1 -0
  122. package/dist/realtime/parsers/skywalker-parser.js +43 -0
  123. package/dist/realtime/parsers/skywalker.parser.d.ts +12 -0
  124. package/dist/realtime/parsers/skywalker.parser.js +15 -0
  125. package/dist/realtime/parsers/skywalker.parser.js.map +1 -0
  126. package/dist/realtime/parsers-advanced.js +158 -0
  127. package/dist/realtime/proto/common.proto +38 -0
  128. package/dist/realtime/proto/direct.proto +65 -0
  129. package/dist/realtime/proto/ig-messages.proto +83 -0
  130. package/dist/realtime/proto/iris.proto +188 -0
  131. package/dist/realtime/proto-parser.js +195 -0
  132. package/dist/realtime/protocols/iris.handshake.js +74 -0
  133. package/dist/realtime/protocols/proto-definitions.js +80 -0
  134. package/dist/realtime/protocols/skywalker.protocol.js +91 -0
  135. package/dist/realtime/realtime.client.events.js +3 -0
  136. package/dist/realtime/realtime.client.js +1915 -0
  137. package/dist/realtime/realtime.service.js +462 -0
  138. package/dist/realtime/reconnect.manager.js +88 -0
  139. package/dist/realtime/session.manager.js +121 -0
  140. package/dist/realtime/subscriptions/graphql.subscription.d.ts +47 -0
  141. package/dist/realtime/subscriptions/graphql.subscription.js +99 -0
  142. package/dist/realtime/subscriptions/graphql.subscription.js.map +1 -0
  143. package/dist/realtime/subscriptions/index.d.ts +2 -0
  144. package/dist/realtime/subscriptions/index.js +19 -0
  145. package/dist/realtime/subscriptions/index.js.map +1 -0
  146. package/dist/realtime/subscriptions/skywalker.subscription.d.ts +4 -0
  147. package/dist/realtime/subscriptions/skywalker.subscription.js +13 -0
  148. package/dist/realtime/subscriptions/skywalker.subscription.js.map +1 -0
  149. package/dist/realtime/topic-map.js +71 -0
  150. package/dist/realtime/topic.js +80 -0
  151. package/dist/repositories/account.repository.js +575 -0
  152. package/dist/repositories/bloks.repository.js +70 -0
  153. package/dist/repositories/captcha.repository.js +44 -0
  154. package/dist/repositories/challenge.repository.js +120 -0
  155. package/dist/repositories/clip.repository.js +165 -0
  156. package/dist/repositories/close-friends.repository.js +46 -0
  157. package/dist/repositories/collection.repository.js +68 -0
  158. package/dist/repositories/direct-thread.repository.js +446 -0
  159. package/dist/repositories/direct.repository.js +232 -0
  160. package/dist/repositories/explore.repository.js +70 -0
  161. package/dist/repositories/fbsearch.repository.js +140 -0
  162. package/dist/repositories/feed.repository.js +245 -0
  163. package/dist/repositories/friendship.repository.js +296 -0
  164. package/dist/repositories/fundraiser.repository.js +49 -0
  165. package/dist/repositories/hashtag.repository.js +99 -0
  166. package/dist/repositories/highlights.repository.js +121 -0
  167. package/dist/repositories/insights.repository.js +82 -0
  168. package/dist/repositories/location.repository.js +84 -0
  169. package/dist/repositories/media.repository.js +395 -0
  170. package/dist/repositories/multiple-accounts.repository.js +41 -0
  171. package/dist/repositories/news.repository.js +35 -0
  172. package/dist/repositories/note.repository.js +57 -0
  173. package/dist/repositories/notification.repository.js +79 -0
  174. package/dist/repositories/share.repository.js +35 -0
  175. package/dist/repositories/signup.repository.js +218 -0
  176. package/dist/repositories/story.repository.js +290 -0
  177. package/dist/repositories/timeline.repository.js +60 -0
  178. package/dist/repositories/totp.repository.js +139 -0
  179. package/dist/repositories/track.repository.js +53 -0
  180. package/dist/repositories/upload.repository.js +204 -0
  181. package/dist/repositories/user.repository.js +360 -0
  182. package/dist/sendmedia/index.js +27 -0
  183. package/dist/sendmedia/sendFile.js +72 -0
  184. package/dist/sendmedia/sendPhoto.js +142 -0
  185. package/dist/sendmedia/sendRavenPhoto.js +153 -0
  186. package/dist/sendmedia/sendRavenVideo.js +158 -0
  187. package/dist/sendmedia/uploadPhoto.js +107 -0
  188. package/dist/sendmedia/uploadfFile.js +130 -0
  189. package/dist/services/live.service.js +139 -0
  190. package/dist/services/search.service.js +115 -0
  191. package/dist/shared/index.js +96 -0
  192. package/dist/shared/shared.js +86 -0
  193. package/dist/thrift/index.d.ts +3 -0
  194. package/dist/thrift/index.js +20 -0
  195. package/dist/thrift/index.js.map +1 -0
  196. package/dist/thrift/thrift.d.ts +59 -0
  197. package/dist/thrift/thrift.js +101 -0
  198. package/dist/thrift/thrift.js.map +1 -0
  199. package/dist/thrift/thrift.reading.d.ts +41 -0
  200. package/dist/thrift/thrift.reading.js +327 -0
  201. package/dist/thrift/thrift.reading.js.map +1 -0
  202. package/dist/thrift/thrift.writing.d.ts +44 -0
  203. package/dist/thrift/thrift.writing.js +342 -0
  204. package/dist/thrift/thrift.writing.js.map +1 -0
  205. package/dist/types/index.js +285 -0
  206. package/dist/useMultiFileAuthState.js +1768 -0
  207. package/dist/utils/helper-1.js +1 -0
  208. package/dist/utils/helper-10.js +1 -0
  209. package/dist/utils/helper-11.js +1 -0
  210. package/dist/utils/helper-12.js +1 -0
  211. package/dist/utils/helper-13.js +1 -0
  212. package/dist/utils/helper-14.js +1 -0
  213. package/dist/utils/helper-15.js +1 -0
  214. package/dist/utils/helper-16.js +1 -0
  215. package/dist/utils/helper-17.js +1 -0
  216. package/dist/utils/helper-18.js +1 -0
  217. package/dist/utils/helper-19.js +1 -0
  218. package/dist/utils/helper-2.js +1 -0
  219. package/dist/utils/helper-20.js +1 -0
  220. package/dist/utils/helper-21.js +1 -0
  221. package/dist/utils/helper-22.js +1 -0
  222. package/dist/utils/helper-23.js +1 -0
  223. package/dist/utils/helper-24.js +1 -0
  224. package/dist/utils/helper-25.js +1 -0
  225. package/dist/utils/helper-26.js +1 -0
  226. package/dist/utils/helper-27.js +1 -0
  227. package/dist/utils/helper-28.js +1 -0
  228. package/dist/utils/helper-29.js +1 -0
  229. package/dist/utils/helper-3.js +1 -0
  230. package/dist/utils/helper-30.js +1 -0
  231. package/dist/utils/helper-4.js +1 -0
  232. package/dist/utils/helper-5.js +1 -0
  233. package/dist/utils/helper-6.js +1 -0
  234. package/dist/utils/helper-7.js +1 -0
  235. package/dist/utils/helper-8.js +1 -0
  236. package/dist/utils/helper-9.js +1 -0
  237. package/dist/utils/index.js +280 -0
  238. package/dist/utils/insta-mqtt-helper.js +128 -0
  239. package/examples/listen-to-messages.js +86 -0
  240. package/package.json +82 -0
@@ -0,0 +1,446 @@
1
+ const Repository = require('../core/repository');
2
+ const Chance = require('chance');
3
+
4
+ class DirectThreadRepository extends Repository {
5
+ constructor(client) {
6
+ super(client);
7
+ this.maxRetries = 3; // default max retries for requests
8
+ }
9
+
10
+ /**
11
+ * Generic request wrapper with retry and debug logging
12
+ * @param {Function} requestFn - async function performing request
13
+ * @param {number} retries - current retry count
14
+ */
15
+ async requestWithRetry(requestFn, retries = 0) {
16
+ try {
17
+ if (process.env.DEBUG) console.log(`[DEBUG] Attempt #${retries + 1}`);
18
+ const result = await requestFn();
19
+ return result;
20
+ } catch (error) {
21
+ const shouldRetry =
22
+ (error.data?.error_type === 'server_error' ||
23
+ error.data?.error_type === 'rate_limited' ||
24
+ error.name === 'IgActionSpamError' ||
25
+ error.status === 503 ||
26
+ error.status === 429) &&
27
+ retries < this.maxRetries;
28
+
29
+ if (shouldRetry) {
30
+ const delay = 1000 * (retries + 1);
31
+ if (process.env.DEBUG) console.log(`[DEBUG] Retrying after ${delay}ms due to ${error.data?.error_type || error.message || error.name || error.status}`);
32
+ await new Promise(resolve => setTimeout(resolve, delay));
33
+ return this.requestWithRetry(requestFn, retries + 1);
34
+ }
35
+
36
+ throw error;
37
+ }
38
+ }
39
+
40
+ /**
41
+ * Send a text message to a group thread
42
+ * @param {Object} options - { threadId, message }
43
+ */
44
+ async sendToGroup(options) {
45
+ const { threadId, message } = options;
46
+ if (!threadId || !message) throw new Error('threadId and message are required');
47
+
48
+ return this.broadcast({
49
+ threadIds: [threadId],
50
+ item: 'text',
51
+ form: { text: message },
52
+ });
53
+ }
54
+
55
+ /**
56
+ * Fetch a specific thread by its ID
57
+ * @param {string} threadId
58
+ */
59
+ async getThread(threadId) {
60
+ return this.requestWithRetry(async () => {
61
+ const response = await this.client.request.send({
62
+ method: 'GET',
63
+ url: `/api/v1/direct_v2/threads/${threadId}/`,
64
+ });
65
+ return response.body || response.data || response;
66
+ });
67
+ }
68
+
69
+ /**
70
+ * Fetch threads by participants
71
+ * @param {Array} recipientUsers
72
+ */
73
+ async getByParticipants(recipientUsers) {
74
+ return this.requestWithRetry(async () => {
75
+ const response = await this.client.request.send({
76
+ method: 'GET',
77
+ url: '/api/v1/direct_v2/threads/get_by_participants/',
78
+ qs: { recipient_users: JSON.stringify(recipientUsers) },
79
+ });
80
+ return response.body || response.data || response;
81
+ });
82
+ }
83
+
84
+ /**
85
+ * Broadcast a message to multiple threads or users
86
+ */
87
+ async broadcast(options) {
88
+ const mutationToken = new Chance().guid();
89
+ const recipients = options.threadIds || options.userIds;
90
+ const recipientsType = options.threadIds ? 'thread_ids' : 'recipient_users';
91
+ const recipientsIds = Array.isArray(recipients) ? recipients : [recipients];
92
+ const recipientsValue = recipientsType === 'thread_ids'
93
+ ? JSON.stringify(recipientsIds)
94
+ : JSON.stringify([recipientsIds]);
95
+
96
+ const form = {
97
+ action: 'send_item',
98
+ [recipientsType]: recipientsValue,
99
+ client_context: mutationToken,
100
+ _csrftoken: this.client.state.cookieCsrfToken,
101
+ device_id: this.client.state.deviceId,
102
+ mutation_token: mutationToken,
103
+ _uuid: this.client.state.uuid,
104
+ ...options.form,
105
+ };
106
+
107
+ return this.requestWithRetry(async () => {
108
+ const payloadForm = options.signed && this.client.request && typeof this.client.request.sign === 'function'
109
+ ? this.client.request.sign(form)
110
+ : form;
111
+
112
+ const response = await this.client.request.send({
113
+ url: `/api/v1/direct_v2/threads/broadcast/${options.item}/`,
114
+ method: 'POST',
115
+ form: payloadForm,
116
+ qs: options.qs,
117
+ });
118
+ return response.body || response.data || response;
119
+ });
120
+ }
121
+
122
+ /**
123
+ * Broadcast a photo to one or more threads (uses REST configure_photo)
124
+ * Options:
125
+ * - uploadId (required) : upload_id returned from rupload
126
+ * - threadIds or threadId (required) : target thread id(s)
127
+ * - caption (optional) : caption/text to attach
128
+ * - signed (optional, default true) : whether to sign the form (if client.request.sign available)
129
+ */
130
+ async broadcastPhoto(options) {
131
+ // normalize inputs
132
+ const uploadId = options.uploadId || options.upload_id || options.uploadIdStr;
133
+ const threadIds = options.threadIds || (options.threadId ? [options.threadId] : []);
134
+ const caption = options.caption || options.text || '';
135
+ const signed = (options.signed === undefined) ? true : !!options.signed; // default to true for media
136
+ const mutationToken = new Chance().guid();
137
+ const clientContext = mutationToken;
138
+
139
+ if (!uploadId) throw new Error('broadcastPhoto: uploadId is required');
140
+ if (!threadIds || !Array.isArray(threadIds) || threadIds.length === 0) throw new Error('broadcastPhoto: at least one threadId is required');
141
+
142
+ const form = {
143
+ action: 'send_item',
144
+ upload_id: uploadId.toString(),
145
+ thread_ids: JSON.stringify(threadIds),
146
+ client_context: clientContext,
147
+ _csrftoken: this.client.state.cookieCsrfToken,
148
+ mutation_token: mutationToken,
149
+ offline_threading_id: clientContext,
150
+ device_id: this.client.state.deviceId,
151
+ _uuid: this.client.state.uuid,
152
+ };
153
+
154
+ if (caption) {
155
+ // Instagram often expects 'text' for the message body
156
+ form.text = caption;
157
+ }
158
+
159
+ // perform request with retry wrapper
160
+ return this.requestWithRetry(async () => {
161
+ const payloadForm = (signed && this.client.request && typeof this.client.request.sign === 'function')
162
+ ? this.client.request.sign(form)
163
+ : form;
164
+
165
+ const response = await this.client.request.send({
166
+ url: `/api/v1/direct_v2/threads/broadcast/configure_photo/`,
167
+ method: 'POST',
168
+ form: payloadForm,
169
+ qs: {
170
+ use_unified_inbox: true,
171
+ },
172
+ });
173
+
174
+ // normalize: some wrappers return { body } or axios response
175
+ const body = response && (response.body || response.data || response);
176
+
177
+ if (!body) {
178
+ const err = new Error('broadcastPhoto: empty response');
179
+ throw err;
180
+ }
181
+
182
+ // parse if string
183
+ let parsed = null;
184
+ if (typeof body === 'string') {
185
+ try {
186
+ parsed = JSON.parse(body);
187
+ } catch (e) {
188
+ parsed = null;
189
+ }
190
+ } else if (typeof body === 'object') {
191
+ parsed = body;
192
+ }
193
+
194
+ // Typical success: parsed.status === 'ok' OR parsed.media/parsed.result/payload present
195
+ const ok = parsed && (parsed.status === 'ok' || parsed.media || parsed.result || parsed.payload || parsed.items || parsed.thread);
196
+ if (ok) return parsed;
197
+
198
+ // If we reach here, treat as error to trigger retry logic
199
+ const error = new Error('broadcastPhoto: Request failed');
200
+ error.response = response;
201
+ if (parsed) error.data = parsed;
202
+ throw error;
203
+ });
204
+ }
205
+
206
+ /**
207
+ * Broadcast a raven (view-once/ephemeral) attachment to one or more threads via REST.
208
+ * IMPORTANT: Media must be uploaded to rupload.facebook.com/messenger_image/ first!
209
+ * Uses endpoint: /api/v1/direct_v2/threads/broadcast/raven_attachment/
210
+ *
211
+ * Options:
212
+ * - uploadId (required): upload_id from messenger_image rupload
213
+ * - attachmentFbid (optional): media_id from messenger_image upload response
214
+ * - threadIds or threadId (required): target thread id(s)
215
+ * - viewMode (optional, default 'replayable'): 'once' or 'replayable'
216
+ * - mediaType (optional, default '1'): '1' = photo, '2' = video
217
+ */
218
+ async broadcastRaven(options) {
219
+ const uploadId = options.uploadId || options.upload_id;
220
+ const threadIds = options.threadIds || (options.threadId ? [options.threadId] : []);
221
+ const viewMode = options.viewMode || 'replayable';
222
+ const mediaType = String(options.mediaType || '1');
223
+ const attachmentFbid = options.attachmentFbid || options.attachment_fbid || null;
224
+
225
+ if (!uploadId) throw new Error('broadcastRaven: uploadId is required');
226
+ if (!threadIds || !Array.isArray(threadIds) || threadIds.length === 0) throw new Error('broadcastRaven: at least one threadId is required');
227
+
228
+ const { v4: uuidv4 } = require('uuid');
229
+ const clientContext = BigInt(Math.floor(Math.random() * 2**62)).toString();
230
+ const now = Math.floor(Date.now() / 1000);
231
+
232
+ const form = {
233
+ allow_multi_configures: '1',
234
+ recipient_users: '[]',
235
+ view_mode: viewMode,
236
+ is_shh_mode: '0',
237
+ camera_entry_point: '3',
238
+ thread_ids: JSON.stringify(threadIds.map(String)),
239
+ reshare_mode: 'allow_reshare',
240
+ original_media_type: mediaType,
241
+ send_attribution: 'direct_thread_camera',
242
+ client_context: clientContext,
243
+ camera_session_id: uuidv4(),
244
+ include_e2ee_mentioned_user_list: '1',
245
+ hide_from_profile_grid: 'false',
246
+ scene_capture_type: '',
247
+ timezone_offset: String(new Date().getTimezoneOffset() * -60),
248
+ client_shared_at: String(now),
249
+ configure_mode: '2',
250
+ source_type: '4',
251
+ camera_position: 'unknown',
252
+ _uid: String(this.client.state.cookieUserId || this.client.state.igUserId),
253
+ device_id: this.client.state.deviceId,
254
+ composition_id: uuidv4(),
255
+ mutation_token: clientContext,
256
+ _uuid: this.client.state.uuid,
257
+ creation_tool_info: '[]',
258
+ creation_surface: 'camera',
259
+ capture_type: 'normal',
260
+ audience: 'default',
261
+ upload_id: uploadId.toString(),
262
+ client_timestamp: String(now),
263
+ sampled: 'true',
264
+ media_transformation_info: JSON.stringify({
265
+ width: '720', height: '1280',
266
+ x_transform: '0', y_transform: '0',
267
+ zoom: '1.0', rotation: '0.0', background_coverage: '0.0',
268
+ }),
269
+ edits: JSON.stringify({ filter_type: 0, filter_strength: 0.5, crop_original_size: [720.0, 1280.0] }),
270
+ extra: JSON.stringify({ source_width: 720, source_height: 1280 }),
271
+ device: JSON.stringify({
272
+ manufacturer: this.client.state.devicePayload?.manufacturer || 'samsung',
273
+ model: this.client.state.devicePayload?.model || 'SM-S938B',
274
+ android_version: this.client.state.devicePayload?.android_version || 35,
275
+ android_release: this.client.state.devicePayload?.android_release || '15',
276
+ }),
277
+ };
278
+
279
+ if (attachmentFbid) {
280
+ form.attachment_fbid = String(attachmentFbid);
281
+ }
282
+
283
+ return this.requestWithRetry(async () => {
284
+ const payloadForm = (this.client.request && typeof this.client.request.sign === 'function')
285
+ ? this.client.request.sign(form)
286
+ : form;
287
+
288
+ const response = await this.client.request.send({
289
+ url: `/api/v1/direct_v2/threads/broadcast/raven_attachment/`,
290
+ method: 'POST',
291
+ form: payloadForm,
292
+ qs: {
293
+ use_unified_inbox: true,
294
+ },
295
+ });
296
+
297
+ const body = response && (response.body || response.data || response);
298
+ if (!body) {
299
+ throw new Error('broadcastRaven: empty response');
300
+ }
301
+
302
+ let parsed = null;
303
+ if (typeof body === 'string') {
304
+ try { parsed = JSON.parse(body); } catch (e) { parsed = null; }
305
+ } else if (typeof body === 'object') {
306
+ parsed = body;
307
+ }
308
+
309
+ const ok = parsed && (parsed.status === 'ok' || parsed.media || parsed.result || parsed.payload || parsed.items || parsed.thread);
310
+ if (ok) return parsed;
311
+
312
+ const error = new Error('broadcastRaven: Request failed');
313
+ error.response = response;
314
+ if (parsed) error.data = parsed;
315
+ throw error;
316
+ });
317
+ }
318
+
319
+ /**
320
+ * Mark a specific item in a thread as seen
321
+ */
322
+ async markItemSeen(threadId, threadItemId) {
323
+ return this.requestWithRetry(async () => {
324
+ const response = await this.client.request.send({
325
+ url: `/api/v1/direct_v2/threads/${threadId}/items/${threadItemId}/seen/`,
326
+ method: 'POST',
327
+ form: {
328
+ _uuid: this.client.state.uuid,
329
+ use_unified_inbox: true,
330
+ action: 'mark_seen',
331
+ thread_id: threadId,
332
+ item_id: threadItemId,
333
+ },
334
+ });
335
+ return response.body || response.data || response;
336
+ });
337
+ }
338
+
339
+ /**
340
+ * Delete an item from a thread
341
+ */
342
+ async deleteItem(threadId, itemId) {
343
+ return this.requestWithRetry(async () => {
344
+ const response = await this.client.request.send({
345
+ url: `/api/v1/direct_v2/threads/${threadId}/items/${itemId}/delete/`,
346
+ method: 'POST',
347
+ });
348
+ return response.body || response.data || response;
349
+ });
350
+ }
351
+
352
+ /**
353
+ * Approve a pending thread
354
+ */
355
+ async approve(threadId) {
356
+ return this.requestWithRetry(async () => {
357
+ const response = await this.client.request.send({
358
+ url: `/api/v1/direct_v2/threads/${threadId}/approve/`,
359
+ method: 'POST',
360
+ });
361
+ return response.body || response.data || response;
362
+ });
363
+ }
364
+
365
+ /**
366
+ * Decline a pending thread
367
+ */
368
+ async decline(threadId) {
369
+ return this.requestWithRetry(async () => {
370
+ const response = await this.client.request.send({
371
+ url: `/api/v1/direct_v2/threads/${threadId}/decline/`,
372
+ method: 'POST',
373
+ });
374
+ return response.body || response.data || response;
375
+ });
376
+ }
377
+
378
+ /**
379
+ * Mute a thread
380
+ */
381
+ async mute(threadId) {
382
+ return this.requestWithRetry(async () => {
383
+ const response = await this.client.request.send({
384
+ url: `/api/v1/direct_v2/threads/${threadId}/mute/`,
385
+ method: 'POST',
386
+ });
387
+ return response.body || response.data || response;
388
+ });
389
+ }
390
+
391
+ /**
392
+ * Unmute a thread
393
+ */
394
+ async unmute(threadId) {
395
+ return this.requestWithRetry(async () => {
396
+ const response = await this.client.request.send({
397
+ url: `/api/v1/direct_v2/threads/${threadId}/unmute/`,
398
+ method: 'POST',
399
+ });
400
+ return response.body || response.data || response;
401
+ });
402
+ }
403
+
404
+ /**
405
+ * Add users to a thread
406
+ */
407
+ async addUser(threadId, userIds) {
408
+ if (!Array.isArray(userIds)) throw new Error('userIds must be an array');
409
+ return this.requestWithRetry(async () => {
410
+ const response = await this.client.request.send({
411
+ url: `/api/v1/direct_v2/threads/${threadId}/add_user/`,
412
+ method: 'POST',
413
+ });
414
+ return response.body || response.data || response;
415
+ });
416
+ }
417
+
418
+ /**
419
+ * Leave a thread
420
+ */
421
+ async leave(threadId) {
422
+ return this.requestWithRetry(async () => {
423
+ const response = await this.client.request.send({
424
+ url: `/api/v1/direct_v2/threads/${threadId}/leave/`,
425
+ method: 'POST',
426
+ });
427
+ return response.body || response.data || response;
428
+ });
429
+ }
430
+
431
+ /**
432
+ * Update thread title
433
+ */
434
+ async updateTitle(threadId, title) {
435
+ return this.requestWithRetry(async () => {
436
+ const response = await this.client.request.send({
437
+ url: `/api/v1/direct_v2/threads/${threadId}/update_title/`,
438
+ method: 'POST',
439
+ });
440
+ return response.body || response.data || response;
441
+ });
442
+ }
443
+ }
444
+
445
+ module.exports = DirectThreadRepository;
446
+
@@ -0,0 +1,232 @@
1
+ const Repository = require('../core/repository');
2
+ const fs = require('fs');
3
+
4
+ class DirectRepository extends Repository {
5
+ constructor(client) {
6
+ super(client);
7
+ this.maxRetries = 3;
8
+ }
9
+
10
+ async requestWithRetry(requestFn, retries = 0) {
11
+ try {
12
+ const result = await requestFn();
13
+ return result;
14
+ } catch (error) {
15
+ const shouldRetry =
16
+ (error.data?.error_type === 'server_error' ||
17
+ error.data?.error_type === 'rate_limited') &&
18
+ retries < this.maxRetries;
19
+ if (shouldRetry) {
20
+ const delay = 1000 * (retries + 1);
21
+ await new Promise(resolve => setTimeout(resolve, delay));
22
+ return this.requestWithRetry(requestFn, retries + 1);
23
+ }
24
+ throw error;
25
+ }
26
+ }
27
+
28
+ async send(options) {
29
+ const { to, message } = options;
30
+ if (!to || !message) throw new Error('Recipient (to) and message are required');
31
+
32
+ return this.requestWithRetry(async () => {
33
+ const user = await this.client.user.infoByUsername(to);
34
+ const thread = await this.client.directThread.getByParticipants([user.pk]);
35
+ return this.client.directThread.broadcast({
36
+ threadIds: [thread.thread_id],
37
+ item: 'text',
38
+ form: { text: message },
39
+ });
40
+ });
41
+ }
42
+
43
+ async sendToUserId(userId, message) {
44
+ return this.requestWithRetry(async () => {
45
+ return this.client.directThread.broadcast({
46
+ userIds: [userId],
47
+ item: 'text',
48
+ form: { text: message },
49
+ });
50
+ });
51
+ }
52
+
53
+ async sendImage(options) {
54
+ const { to, imagePath } = options;
55
+ if (!to || !imagePath) throw new Error('Recipient (to) and imagePath are required');
56
+
57
+ return this.requestWithRetry(async () => {
58
+ const imageBuffer = fs.readFileSync(imagePath);
59
+ const uploadResult = await this.client.upload.photo({ file: imageBuffer, uploadId: Date.now() });
60
+ const user = await this.client.user.infoByUsername(to);
61
+ const thread = await this.client.directThread.getByParticipants([user.pk]);
62
+ return this.client.directThread.broadcast({
63
+ threadIds: [thread.thread_id],
64
+ item: 'configure_photo',
65
+ form: { upload_id: uploadResult.upload_id, allow_full_aspect_ratio: true },
66
+ });
67
+ });
68
+ }
69
+
70
+ async sendVideo(options) {
71
+ const { to, videoPath } = options;
72
+ if (!to || !videoPath) throw new Error('Recipient (to) and videoPath are required');
73
+
74
+ return this.requestWithRetry(async () => {
75
+ const videoBuffer = fs.readFileSync(videoPath);
76
+ const uploadResult = await this.client.upload.video({ video: videoBuffer, uploadId: Date.now() });
77
+ const user = await this.client.user.infoByUsername(to);
78
+ const thread = await this.client.directThread.getByParticipants([user.pk]);
79
+ return this.client.directThread.broadcast({
80
+ threadIds: [thread.thread_id],
81
+ item: 'configure_video',
82
+ form: { upload_id: uploadResult.upload_id, video_result: 'deprecated' },
83
+ });
84
+ });
85
+ }
86
+
87
+ async sendLink(options) {
88
+ const { to, text, urls } = options;
89
+ return this.requestWithRetry(async () => {
90
+ const user = await this.client.user.infoByUsername(to);
91
+ const thread = await this.client.directThread.getByParticipants([user.pk]);
92
+ return this.client.directThread.broadcast({
93
+ threadIds: [thread.thread_id],
94
+ item: 'link',
95
+ form: {
96
+ link_text: text || '',
97
+ link_urls: JSON.stringify(urls || []),
98
+ },
99
+ });
100
+ });
101
+ }
102
+
103
+ async sendMediaShare(options) {
104
+ const { to, mediaId } = options;
105
+ return this.requestWithRetry(async () => {
106
+ const user = await this.client.user.infoByUsername(to);
107
+ const thread = await this.client.directThread.getByParticipants([user.pk]);
108
+ return this.client.directThread.broadcast({
109
+ threadIds: [thread.thread_id],
110
+ item: 'media_share',
111
+ form: { media_id: mediaId },
112
+ });
113
+ });
114
+ }
115
+
116
+ async sendProfile(options) {
117
+ const { to, profileUserId } = options;
118
+ return this.requestWithRetry(async () => {
119
+ const user = await this.client.user.infoByUsername(to);
120
+ const thread = await this.client.directThread.getByParticipants([user.pk]);
121
+ return this.client.directThread.broadcast({
122
+ threadIds: [thread.thread_id],
123
+ item: 'profile',
124
+ form: { profile_user_id: profileUserId },
125
+ });
126
+ });
127
+ }
128
+
129
+ async sendHashtag(options) {
130
+ const { to, hashtag, text } = options;
131
+ return this.requestWithRetry(async () => {
132
+ const user = await this.client.user.infoByUsername(to);
133
+ const thread = await this.client.directThread.getByParticipants([user.pk]);
134
+ return this.client.directThread.broadcast({
135
+ threadIds: [thread.thread_id],
136
+ item: 'hashtag',
137
+ form: {
138
+ hashtag,
139
+ text: text || '',
140
+ },
141
+ });
142
+ });
143
+ }
144
+
145
+ async sendLocation(options) {
146
+ const { to, locationId, text } = options;
147
+ return this.requestWithRetry(async () => {
148
+ const user = await this.client.user.infoByUsername(to);
149
+ const thread = await this.client.directThread.getByParticipants([user.pk]);
150
+ return this.client.directThread.broadcast({
151
+ threadIds: [thread.thread_id],
152
+ item: 'location',
153
+ form: {
154
+ venue_id: locationId,
155
+ text: text || '',
156
+ },
157
+ });
158
+ });
159
+ }
160
+
161
+ async getInbox(cursor = null, limit = 20) {
162
+ return this.requestWithRetry(async () => {
163
+ const qs = { persistentBadging: true, limit };
164
+ if (cursor) qs.cursor = cursor;
165
+ const response = await this.client.request.send({ method: 'GET', url: '/api/v1/direct_v2/inbox/', qs });
166
+ return response.body;
167
+ });
168
+ }
169
+
170
+ async getPendingInbox(cursor = null) {
171
+ return this.requestWithRetry(async () => {
172
+ const qs = cursor ? { cursor } : {};
173
+ const response = await this.client.request.send({ method: 'GET', url: '/api/v1/direct_v2/pending_inbox/', qs });
174
+ return response.body;
175
+ });
176
+ }
177
+
178
+ async createGroupThread(recipientUsers, threadTitle) {
179
+ if (!Array.isArray(recipientUsers) || !threadTitle) throw new Error('recipientUsers must be array and threadTitle required');
180
+
181
+ return this.requestWithRetry(async () => {
182
+ const response = await this.client.request.send({
183
+ method: 'POST',
184
+ url: '/api/v1/direct_v2/create_group_thread/',
185
+ form: this.client.request.sign({
186
+ _uuid: this.client.state.uuid,
187
+ _uid: this.client.state.cookieUserId,
188
+ recipient_users: JSON.stringify(recipientUsers),
189
+ thread_title: threadTitle,
190
+ }),
191
+ });
192
+ return response.body;
193
+ });
194
+ }
195
+
196
+ async rankedRecipients(mode = 'raven', query = '') {
197
+ return this.requestWithRetry(async () => {
198
+ const response = await this.client.request.send({
199
+ method: 'GET',
200
+ url: '/api/v1/direct_v2/ranked_recipients/',
201
+ qs: { mode, query, show_threads: true },
202
+ });
203
+ return response.body;
204
+ });
205
+ }
206
+
207
+ async getPresence() {
208
+ return this.requestWithRetry(async () => {
209
+ const response = await this.client.request.send({ method: 'GET', url: '/api/v1/direct_v2/get_presence/' });
210
+ return response.body;
211
+ });
212
+ }
213
+
214
+ async markAsSeen(threadId, itemId) {
215
+ return this.client.directThread.markItemSeen(threadId, itemId);
216
+ }
217
+
218
+ async hideThread(threadId) {
219
+ return this.requestWithRetry(async () => {
220
+ const response = await this.client.request.send({
221
+ method: 'POST',
222
+ url: `/api/v1/direct_v2/threads/${threadId}/hide/`,
223
+ form: this.client.request.sign({
224
+ _uuid: this.client.state.uuid,
225
+ }),
226
+ });
227
+ return response.body;
228
+ });
229
+ }
230
+ }
231
+
232
+ module.exports = DirectRepository;