bb-fca 2.0.18 → 2.0.19
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/dist/deltas/apis/posting/createReel.js +649 -0
- package/dist/deltas/apis/posting/createReel.js.map +1 -0
- package/dist/index.d.ts +42 -0
- package/dist/types/deltas/apis/posting/createReel.d.ts +1 -0
- package/dist/utils/axios.js +24 -3
- package/dist/utils/axios.js.map +1 -1
- package/package.json +1 -1
- package/src/deltas/apis/posting/createReel.ts +744 -0
- package/src/types/index.d.ts +42 -0
- package/src/utils/axios.ts +26 -3
|
@@ -0,0 +1,649 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.default = default_1;
|
|
37
|
+
const crypto = __importStar(require("crypto"));
|
|
38
|
+
const fs = __importStar(require("fs"));
|
|
39
|
+
const path = __importStar(require("path"));
|
|
40
|
+
const utils = require("../../../utils");
|
|
41
|
+
/**
|
|
42
|
+
* @namespace api.createReel
|
|
43
|
+
* @description Upload and publish a Facebook Reel with full copyright check flow.
|
|
44
|
+
* Flow: Start Session → rupload binary → Receive confirm → Copyright Check → Publish
|
|
45
|
+
* @license Ex-it
|
|
46
|
+
*/
|
|
47
|
+
/**
|
|
48
|
+
* Generate a UUID v4 string for composer_session_id / waterfall_id.
|
|
49
|
+
*/
|
|
50
|
+
function generateUUID() {
|
|
51
|
+
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (c) => {
|
|
52
|
+
const r = (Math.random() * 16) | 0;
|
|
53
|
+
const v = c === 'x' ? r : (r & 0x3) | 0x8;
|
|
54
|
+
return v.toString(16);
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Sleep helper for polling.
|
|
59
|
+
*/
|
|
60
|
+
function sleep(ms) {
|
|
61
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
62
|
+
}
|
|
63
|
+
function default_1(defaultFuncs, api, ctx) {
|
|
64
|
+
/**
|
|
65
|
+
* Upload and publish a video as a Facebook Reel.
|
|
66
|
+
*
|
|
67
|
+
* @param {object} options - The reel options.
|
|
68
|
+
* @param {string} options.videoPath - File path to the video file.
|
|
69
|
+
* @param {string} [options.message=""] - Caption text for the reel.
|
|
70
|
+
* @param {string} [options.privacy="EVERYONE"] - Privacy: "EVERYONE", "FRIENDS", or "SELF".
|
|
71
|
+
* @param {boolean} [options.skipCopyrightCheck=false] - Skip copyright check polling (faster but riskier).
|
|
72
|
+
* @param {number} [options.copyrightCheckTimeout=60000] - Max time to wait for copyright check (ms).
|
|
73
|
+
* @param {number} [options.copyrightCheckInterval=2000] - Polling interval for copyright check (ms).
|
|
74
|
+
* @param {Function} [callback] - Optional callback function.
|
|
75
|
+
* @returns {Promise<object>} Object containing postID, storyID and metadata.
|
|
76
|
+
*/
|
|
77
|
+
async function createReel(options, callback) {
|
|
78
|
+
let resolveFunc = function () { };
|
|
79
|
+
let rejectFunc = function () { };
|
|
80
|
+
const returnPromise = new Promise(function (resolve, reject) {
|
|
81
|
+
resolveFunc = resolve;
|
|
82
|
+
rejectFunc = reject;
|
|
83
|
+
});
|
|
84
|
+
callback =
|
|
85
|
+
callback ||
|
|
86
|
+
function (err, data) {
|
|
87
|
+
if (err)
|
|
88
|
+
return rejectFunc(err);
|
|
89
|
+
resolveFunc(data);
|
|
90
|
+
};
|
|
91
|
+
try {
|
|
92
|
+
// ===== Validate Input =====
|
|
93
|
+
if (!options || typeof options !== 'object') {
|
|
94
|
+
throw new Error('Options must be an object with at least { videoPath }.');
|
|
95
|
+
}
|
|
96
|
+
const videoPath = options.videoPath;
|
|
97
|
+
const message = options.message || '';
|
|
98
|
+
const privacy = options.privacy || 'EVERYONE';
|
|
99
|
+
const skipCopyrightCheck = options.skipCopyrightCheck || false;
|
|
100
|
+
const copyrightCheckTimeout = options.copyrightCheckTimeout || 60000;
|
|
101
|
+
const copyrightCheckInterval = options.copyrightCheckInterval || 2000;
|
|
102
|
+
if (!videoPath || typeof videoPath !== 'string') {
|
|
103
|
+
throw new Error('videoPath is required and must be a string.');
|
|
104
|
+
}
|
|
105
|
+
if (!fs.existsSync(videoPath)) {
|
|
106
|
+
throw new Error(`Video file not found: ${videoPath}`);
|
|
107
|
+
}
|
|
108
|
+
const validPrivacy = ['EVERYONE', 'FRIENDS', 'SELF', 'ALL_FRIENDS'];
|
|
109
|
+
if (!validPrivacy.includes(privacy)) {
|
|
110
|
+
throw new Error(`Invalid privacy setting. Use one of: ${validPrivacy.join(', ')}`);
|
|
111
|
+
}
|
|
112
|
+
const fileBuffer = fs.readFileSync(videoPath);
|
|
113
|
+
const fileSize = fileBuffer.length;
|
|
114
|
+
const fileExtension = path
|
|
115
|
+
.extname(videoPath)
|
|
116
|
+
.replace('.', '')
|
|
117
|
+
.toLowerCase();
|
|
118
|
+
// Validate extension
|
|
119
|
+
const allowedExtensions = [
|
|
120
|
+
'mov',
|
|
121
|
+
'mp4',
|
|
122
|
+
'avi',
|
|
123
|
+
'mkv',
|
|
124
|
+
'webm',
|
|
125
|
+
'wmv',
|
|
126
|
+
'flv',
|
|
127
|
+
'mpg',
|
|
128
|
+
'mpeg',
|
|
129
|
+
'3gp',
|
|
130
|
+
'3g2',
|
|
131
|
+
'm4v',
|
|
132
|
+
];
|
|
133
|
+
if (!allowedExtensions.includes(fileExtension)) {
|
|
134
|
+
throw new Error(`Unsupported video format: .${fileExtension}. Supported: ${allowedExtensions.join(', ')}`);
|
|
135
|
+
}
|
|
136
|
+
const fileHash = crypto
|
|
137
|
+
.createHash('md5')
|
|
138
|
+
.update(fileBuffer)
|
|
139
|
+
.digest('hex');
|
|
140
|
+
const composerSessionId = generateUUID();
|
|
141
|
+
// CORS headers for cross-subdomain requests
|
|
142
|
+
const corsHeaders = {
|
|
143
|
+
Origin: 'https://www.facebook.com',
|
|
144
|
+
Referer: 'https://www.facebook.com/',
|
|
145
|
+
'Sec-Fetch-Dest': 'empty',
|
|
146
|
+
'Sec-Fetch-Mode': 'cors',
|
|
147
|
+
'Sec-Fetch-Site': 'same-site',
|
|
148
|
+
};
|
|
149
|
+
// ====================================================================
|
|
150
|
+
// STEP 1: Initialize Upload Session (POST vupload-edge.facebook.com)
|
|
151
|
+
// Exact match with browser network capture
|
|
152
|
+
// ====================================================================
|
|
153
|
+
const startUrl = 'https://vupload-edge.facebook.com/ajax/video/upload/requests/start/?av=' +
|
|
154
|
+
ctx.userID +
|
|
155
|
+
'&__a=1';
|
|
156
|
+
// Resolve lsd once — browser always sends a real token here
|
|
157
|
+
const lsd = ctx.lsd || ctx.fb_dtsg;
|
|
158
|
+
const startForm = {
|
|
159
|
+
file_size: String(fileSize),
|
|
160
|
+
file_extension: fileExtension,
|
|
161
|
+
target_id: ctx.userID,
|
|
162
|
+
source: 'reel_composer',
|
|
163
|
+
composer_dialog_version: '',
|
|
164
|
+
waterfall_id: composerSessionId,
|
|
165
|
+
composer_session_id: composerSessionId,
|
|
166
|
+
composer_entry_point_ref: 'comet_pp_plus_reel_composer_feed_sprout',
|
|
167
|
+
composer_work_shared_draft_mode: '',
|
|
168
|
+
has_file_been_replaced: 'false',
|
|
169
|
+
supports_chunking: 'true',
|
|
170
|
+
supports_file_api: 'true',
|
|
171
|
+
partition_start_offset: '0',
|
|
172
|
+
partition_end_offset: String(fileSize),
|
|
173
|
+
creator_product: '2',
|
|
174
|
+
spherical: 'false',
|
|
175
|
+
video_publisher_action_source: '',
|
|
176
|
+
// Auth params — must match browser session exactly
|
|
177
|
+
__aaid: '0',
|
|
178
|
+
__user: ctx.userID,
|
|
179
|
+
__a: '1',
|
|
180
|
+
__req: '1',
|
|
181
|
+
__hs: ctx.hs || '20577.HYP:comet_pkg.2.1...0',
|
|
182
|
+
dpr: '1',
|
|
183
|
+
__ccg: 'EXCELLENT',
|
|
184
|
+
__rev: ctx.revision || '',
|
|
185
|
+
__s: ctx.__s || '',
|
|
186
|
+
__hsi: ctx.__hsi || '',
|
|
187
|
+
__dyn: ctx.__dyn || '',
|
|
188
|
+
__csr: ctx.__csr || '',
|
|
189
|
+
__hsdp: ctx.__hsdp || '',
|
|
190
|
+
__hblp: ctx.__hblp || '',
|
|
191
|
+
__sjsp: ctx.__sjsp || '',
|
|
192
|
+
__comet_req: '15',
|
|
193
|
+
fb_dtsg: ctx.fb_dtsg,
|
|
194
|
+
jazoest: ctx.jazoest,
|
|
195
|
+
lsd,
|
|
196
|
+
__spin_r: ctx.revision || '',
|
|
197
|
+
__spin_b: 'trunk',
|
|
198
|
+
__spin_t: String(Math.floor(Date.now() / 1000)),
|
|
199
|
+
__crn: ctx.__crn || 'comet.fbweb.CometHomeRoute',
|
|
200
|
+
};
|
|
201
|
+
// Debug: log auth params to identify missing values
|
|
202
|
+
utils.log('createReel', `Start session params: fb_dtsg=${ctx.fb_dtsg ? 'OK(' + ctx.fb_dtsg.substring(0, 10) + '...)' : 'MISSING'}, ` +
|
|
203
|
+
`jazoest=${ctx.jazoest || 'MISSING'}, __rev=${ctx.revision || 'MISSING'}, ` +
|
|
204
|
+
`__hs=${ctx.hs || 'MISSING'}, lsd=${ctx.lsd || 'MISSING'}, ` +
|
|
205
|
+
`userID=${ctx.userID || 'MISSING'}`);
|
|
206
|
+
const startRaw = await utils
|
|
207
|
+
.post(startUrl, ctx.jar, startForm, ctx.globalOptions, ctx, {
|
|
208
|
+
X_fb_video_waterfall_id: composerSessionId,
|
|
209
|
+
...corsHeaders,
|
|
210
|
+
'Content-Type': 'application/x-www-form-urlencoded',
|
|
211
|
+
});
|
|
212
|
+
// Debug: log raw response before parsing
|
|
213
|
+
utils.log('createReel', `Start raw response status=${startRaw?.statusCode}, body=${JSON.stringify(startRaw?.body)?.substring(0, 500)}`);
|
|
214
|
+
const startRes = await utils.parseAndCheckLogin(ctx, defaultFuncs)(startRaw);
|
|
215
|
+
const videoId = startRes?.payload?.video_id || startRes?.video_id;
|
|
216
|
+
const uploadSessionId = startRes?.payload?.upload_session_id || startRes?.upload_session_id;
|
|
217
|
+
const startOffset = startRes?.payload?.start_offset ?? startRes?.start_offset ?? 0;
|
|
218
|
+
const endOffset = startRes?.payload?.end_offset ?? startRes?.end_offset ?? fileSize;
|
|
219
|
+
if (!videoId) {
|
|
220
|
+
throw new Error('Failed to get video_id from upload session. Response: ' +
|
|
221
|
+
JSON.stringify(startRes));
|
|
222
|
+
}
|
|
223
|
+
utils.log('createReel', `Upload session started. video_id=${videoId}, session=${uploadSessionId}`);
|
|
224
|
+
// ====================================================================
|
|
225
|
+
// STEP 2: Probe rupload server (GET)
|
|
226
|
+
// ====================================================================
|
|
227
|
+
const ruploadPath = `/fb_video/${fileHash}-${startOffset}-${endOffset}`;
|
|
228
|
+
const ruploadAuthParams = new URLSearchParams({
|
|
229
|
+
__aaid: '0',
|
|
230
|
+
__user: ctx.userID,
|
|
231
|
+
__a: '1',
|
|
232
|
+
__req: '1',
|
|
233
|
+
__hs: ctx.hs || '20577.HYP:comet_pkg.2.1...0',
|
|
234
|
+
dpr: '1',
|
|
235
|
+
__ccg: 'EXCELLENT',
|
|
236
|
+
__rev: ctx.revision || '',
|
|
237
|
+
__s: ctx.__s || '',
|
|
238
|
+
__hsi: ctx.__hsi || '',
|
|
239
|
+
__dyn: ctx.__dyn || '',
|
|
240
|
+
__csr: ctx.__csr || '',
|
|
241
|
+
__hsdp: ctx.__hsdp || '',
|
|
242
|
+
__hblp: ctx.__hblp || '',
|
|
243
|
+
__sjsp: ctx.__sjsp || '',
|
|
244
|
+
__comet_req: '15',
|
|
245
|
+
fb_dtsg_ag: ctx.fb_dtsg_ag || ctx.fb_dtsg,
|
|
246
|
+
jazoest: ctx.jazoest,
|
|
247
|
+
__spin_r: ctx.revision || '',
|
|
248
|
+
__spin_b: 'trunk',
|
|
249
|
+
__spin_t: String(Math.floor(Date.now() / 1000)),
|
|
250
|
+
__crn: ctx.__crn || 'comet.fbweb.CometHomeRoute',
|
|
251
|
+
}).toString();
|
|
252
|
+
// Try multiple rupload hosts (Facebook may route to different DCs)
|
|
253
|
+
const ruploadHosts = [
|
|
254
|
+
'rupload-hkg4-2.up.facebook.com',
|
|
255
|
+
'rupload-hkg1-2.up.facebook.com',
|
|
256
|
+
'rupload.facebook.com',
|
|
257
|
+
];
|
|
258
|
+
let ruploadBaseUrl = '';
|
|
259
|
+
let probeSuccess = false;
|
|
260
|
+
for (const host of ruploadHosts) {
|
|
261
|
+
ruploadBaseUrl = `https://${host}${ruploadPath}?${ruploadAuthParams}`;
|
|
262
|
+
try {
|
|
263
|
+
await utils.get(ruploadBaseUrl, ctx.jar, undefined, ctx.globalOptions, ctx, corsHeaders);
|
|
264
|
+
probeSuccess = true;
|
|
265
|
+
break;
|
|
266
|
+
}
|
|
267
|
+
catch (e) {
|
|
268
|
+
// Try next host
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
if (!probeSuccess) {
|
|
272
|
+
// Fallback: use first host anyway
|
|
273
|
+
ruploadBaseUrl = `https://${ruploadHosts[0]}${ruploadPath}?${ruploadAuthParams}`;
|
|
274
|
+
}
|
|
275
|
+
// ====================================================================
|
|
276
|
+
// STEP 3: Upload Binary Data (POST rupload)
|
|
277
|
+
// ====================================================================
|
|
278
|
+
const fileName = path.basename(videoPath);
|
|
279
|
+
const mimeTypes = {
|
|
280
|
+
mov: 'video/quicktime',
|
|
281
|
+
mp4: 'video/mp4',
|
|
282
|
+
avi: 'video/x-msvideo',
|
|
283
|
+
mkv: 'video/x-matroska',
|
|
284
|
+
webm: 'video/webm',
|
|
285
|
+
wmv: 'video/x-ms-wmv',
|
|
286
|
+
flv: 'video/x-flv',
|
|
287
|
+
mpg: 'video/mpeg',
|
|
288
|
+
mpeg: 'video/mpeg',
|
|
289
|
+
'3gp': 'video/3gpp',
|
|
290
|
+
'3g2': 'video/3gpp2',
|
|
291
|
+
m4v: 'video/x-m4v',
|
|
292
|
+
};
|
|
293
|
+
const mimeType = mimeTypes[fileExtension] || 'video/mp4';
|
|
294
|
+
const uploadHeaders = {
|
|
295
|
+
'Content-Type': 'application/octet-stream',
|
|
296
|
+
Product_media_id: String(videoId),
|
|
297
|
+
Id: String(uploadSessionId),
|
|
298
|
+
Start_offset: String(startOffset),
|
|
299
|
+
End_offset: String(endOffset),
|
|
300
|
+
Offset: String(startOffset),
|
|
301
|
+
'X-Entity-Length': String(fileSize),
|
|
302
|
+
'X-Total-Asset-Size': String(fileSize),
|
|
303
|
+
'X-Entity-Type': mimeType,
|
|
304
|
+
'X-Entity-Name': encodeURIComponent(fileName),
|
|
305
|
+
Composer_session_id: composerSessionId,
|
|
306
|
+
...corsHeaders,
|
|
307
|
+
};
|
|
308
|
+
const uploadRes = await utils.postRaw(ruploadBaseUrl, ctx.jar, fileBuffer, ctx.globalOptions, ctx, uploadHeaders);
|
|
309
|
+
let fileHandle = null;
|
|
310
|
+
try {
|
|
311
|
+
const uploadBody = typeof uploadRes.body === 'string'
|
|
312
|
+
? JSON.parse(uploadRes.body)
|
|
313
|
+
: uploadRes.body;
|
|
314
|
+
fileHandle = uploadBody?.h || null;
|
|
315
|
+
}
|
|
316
|
+
catch (_) {
|
|
317
|
+
/* parse error */
|
|
318
|
+
}
|
|
319
|
+
if (!fileHandle) {
|
|
320
|
+
throw new Error('Failed to get file handle from binary upload. Response: ' +
|
|
321
|
+
JSON.stringify(uploadRes.body));
|
|
322
|
+
}
|
|
323
|
+
utils.log('createReel', `Binary upload complete. Handle obtained.`);
|
|
324
|
+
// ====================================================================
|
|
325
|
+
// STEP 4: Confirm Upload (POST receive)
|
|
326
|
+
// ====================================================================
|
|
327
|
+
const receiveParams = new URLSearchParams({
|
|
328
|
+
av: ctx.userID,
|
|
329
|
+
composer_session_id: composerSessionId,
|
|
330
|
+
video_id: String(videoId),
|
|
331
|
+
start_offset: '0',
|
|
332
|
+
end_offset: String(endOffset),
|
|
333
|
+
source: 'reel_composer',
|
|
334
|
+
target_id: ctx.userID,
|
|
335
|
+
waterfall_id: composerSessionId,
|
|
336
|
+
composer_entry_point_ref: 'comet_pp_plus_reel_composer_feed_sprout',
|
|
337
|
+
composer_work_shared_draft_mode: '',
|
|
338
|
+
composer_dialog_version: '',
|
|
339
|
+
has_file_been_replaced: 'false',
|
|
340
|
+
supports_chunking: 'true',
|
|
341
|
+
upload_speed: '',
|
|
342
|
+
partition_start_offset: '0',
|
|
343
|
+
partition_end_offset: String(endOffset),
|
|
344
|
+
__aaid: '0',
|
|
345
|
+
__user: ctx.userID,
|
|
346
|
+
__a: '1',
|
|
347
|
+
__req: '1',
|
|
348
|
+
__hs: ctx.hs || '20577.HYP:comet_pkg.2.1...0',
|
|
349
|
+
dpr: '1',
|
|
350
|
+
__ccg: 'EXCELLENT',
|
|
351
|
+
__rev: ctx.revision || '',
|
|
352
|
+
__s: ctx.__s || '',
|
|
353
|
+
__hsi: ctx.__hsi || '',
|
|
354
|
+
__dyn: ctx.__dyn || '',
|
|
355
|
+
__csr: ctx.__csr || '',
|
|
356
|
+
__hsdp: ctx.__hsdp || '',
|
|
357
|
+
__hblp: ctx.__hblp || '',
|
|
358
|
+
__sjsp: ctx.__sjsp || '',
|
|
359
|
+
__comet_req: '15',
|
|
360
|
+
fb_dtsg: ctx.fb_dtsg,
|
|
361
|
+
jazoest: ctx.jazoest,
|
|
362
|
+
lsd,
|
|
363
|
+
__spin_r: ctx.revision || '',
|
|
364
|
+
__spin_b: 'trunk',
|
|
365
|
+
__spin_t: String(Math.floor(Date.now() / 1000)),
|
|
366
|
+
__crn: ctx.__crn || 'comet.fbweb.CometHomeRoute',
|
|
367
|
+
}).toString();
|
|
368
|
+
const receiveUrl = `https://vupload-edge.facebook.com/ajax/video/upload/requests/receive/?${receiveParams}`;
|
|
369
|
+
// Send as multipart/form-data with the file handle
|
|
370
|
+
const receiveRes = await defaultFuncs
|
|
371
|
+
.postFormData(receiveUrl, ctx.jar, {
|
|
372
|
+
fbuploader_video_file_chunk: fileHandle,
|
|
373
|
+
}, {
|
|
374
|
+
X_fb_video_waterfall_id: composerSessionId,
|
|
375
|
+
...corsHeaders,
|
|
376
|
+
})
|
|
377
|
+
.then(utils.parseAndCheckLogin(ctx, defaultFuncs));
|
|
378
|
+
const recvStart = receiveRes?.payload?.start_offset ?? receiveRes?.start_offset;
|
|
379
|
+
const recvEnd = receiveRes?.payload?.end_offset ?? receiveRes?.end_offset;
|
|
380
|
+
if (recvStart !== undefined &&
|
|
381
|
+
recvEnd !== undefined &&
|
|
382
|
+
recvStart !== recvEnd) {
|
|
383
|
+
utils.warn('createReel', `Upload may be incomplete: start_offset=${recvStart}, end_offset=${recvEnd}`);
|
|
384
|
+
}
|
|
385
|
+
utils.log('createReel', `Upload confirmed. start=${recvStart}, end=${recvEnd}`);
|
|
386
|
+
// ====================================================================
|
|
387
|
+
// STEP 5: Copyright Check (Mutation + Polling)
|
|
388
|
+
// ====================================================================
|
|
389
|
+
if (!skipCopyrightCheck) {
|
|
390
|
+
utils.log('createReel', 'Starting copyright check...');
|
|
391
|
+
// 5a. Trigger copyright check mutation
|
|
392
|
+
const copyrightMutationForm = {
|
|
393
|
+
av: ctx.userID,
|
|
394
|
+
__user: ctx.userID,
|
|
395
|
+
__a: '1',
|
|
396
|
+
__req: '1',
|
|
397
|
+
__ccg: 'EXCELLENT',
|
|
398
|
+
__comet_req: '15',
|
|
399
|
+
fb_dtsg: ctx.fb_dtsg,
|
|
400
|
+
jazoest: ctx.jazoest,
|
|
401
|
+
lsd,
|
|
402
|
+
fb_api_caller_class: 'RelayModern',
|
|
403
|
+
fb_api_req_friendly_name: 'useCometVideoEditorCopyrightCheckMutation',
|
|
404
|
+
variables: JSON.stringify({
|
|
405
|
+
input: {
|
|
406
|
+
actor_id: ctx.userID,
|
|
407
|
+
client_mutation_id: '5',
|
|
408
|
+
from_mbs: false,
|
|
409
|
+
video_id: String(videoId),
|
|
410
|
+
},
|
|
411
|
+
__relay_internal__pv__CometVideoEditorCopyrightCheckDetails_shouldUseMatchlessrelayprovider: false,
|
|
412
|
+
}),
|
|
413
|
+
server_timestamps: 'true',
|
|
414
|
+
doc_id: '25171698979168867',
|
|
415
|
+
};
|
|
416
|
+
await defaultFuncs
|
|
417
|
+
.post('https://www.facebook.com/api/graphql/', ctx.jar, copyrightMutationForm, {})
|
|
418
|
+
.then(utils.parseAndCheckLogin(ctx, defaultFuncs));
|
|
419
|
+
// 5b. Poll for copyright check completion
|
|
420
|
+
const pollStartTime = Date.now();
|
|
421
|
+
let copyrightPassed = false;
|
|
422
|
+
while (Date.now() - pollStartTime < copyrightCheckTimeout) {
|
|
423
|
+
await sleep(copyrightCheckInterval);
|
|
424
|
+
const pollForm = {
|
|
425
|
+
av: ctx.userID,
|
|
426
|
+
__user: ctx.userID,
|
|
427
|
+
__a: '1',
|
|
428
|
+
__req: '1',
|
|
429
|
+
__ccg: 'EXCELLENT',
|
|
430
|
+
__comet_req: '15',
|
|
431
|
+
fb_dtsg: ctx.fb_dtsg,
|
|
432
|
+
jazoest: ctx.jazoest,
|
|
433
|
+
lsd,
|
|
434
|
+
fb_api_caller_class: 'RelayModern',
|
|
435
|
+
fb_api_req_friendly_name: 'CometVideoEditorCopyrightCheckDetailsLiveQueryUpdaterQuery',
|
|
436
|
+
variables: JSON.stringify({
|
|
437
|
+
videoID: String(videoId),
|
|
438
|
+
__relay_internal__pv__CometVideoEditorCopyrightCheckDetails_shouldUseMatchlessrelayprovider: false,
|
|
439
|
+
}),
|
|
440
|
+
server_timestamps: 'true',
|
|
441
|
+
doc_id: '25488331727450621',
|
|
442
|
+
};
|
|
443
|
+
const pollRes = await defaultFuncs
|
|
444
|
+
.post('https://www.facebook.com/api/graphql/', ctx.jar, pollForm, {})
|
|
445
|
+
.then(utils.parseAndCheckLogin(ctx, defaultFuncs));
|
|
446
|
+
const video = pollRes?.data?.video || pollRes?.data?.node;
|
|
447
|
+
const precheck = video?.copyright_precheck_progress;
|
|
448
|
+
const percentage = precheck?.percentage;
|
|
449
|
+
const finished = video?.if_copyright_precheck_is_finished;
|
|
450
|
+
if (percentage >= 100 || finished !== null) {
|
|
451
|
+
copyrightPassed = true;
|
|
452
|
+
const earlyResult = precheck?.early_return_result;
|
|
453
|
+
if (earlyResult?.found_early_violation) {
|
|
454
|
+
utils.warn('createReel', 'Copyright violation detected! The reel may be blocked.');
|
|
455
|
+
}
|
|
456
|
+
break;
|
|
457
|
+
}
|
|
458
|
+
utils.log('createReel', `Copyright check: ${percentage || 0}%`);
|
|
459
|
+
}
|
|
460
|
+
if (!copyrightPassed) {
|
|
461
|
+
utils.warn('createReel', 'Copyright check timed out. Proceeding with publish anyway.');
|
|
462
|
+
}
|
|
463
|
+
else {
|
|
464
|
+
utils.log('createReel', 'Copyright check passed!');
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
// ====================================================================
|
|
468
|
+
// STEP 6: Publish Reel (ComposerStoryCreateMutation)
|
|
469
|
+
// ====================================================================
|
|
470
|
+
utils.log('createReel', 'Publishing reel...');
|
|
471
|
+
const privacyBaseState = privacy === 'ALL_FRIENDS' ? 'FRIENDS' : privacy;
|
|
472
|
+
const publishVariables = {
|
|
473
|
+
input: {
|
|
474
|
+
composer_entry_point: 'comet_pp_plus_reel_composer_feed_sprout',
|
|
475
|
+
composer_source_surface: 'short_form_video',
|
|
476
|
+
idempotence_token: `${composerSessionId}_FEED`,
|
|
477
|
+
source: 'WWW',
|
|
478
|
+
attachments: [
|
|
479
|
+
{
|
|
480
|
+
video: {
|
|
481
|
+
audio_descriptions: null,
|
|
482
|
+
id: String(videoId),
|
|
483
|
+
additional_video_metadata: {
|
|
484
|
+
translatedAudioMetadata: [],
|
|
485
|
+
autoGenCaptionsSettings: {
|
|
486
|
+
autogenerate_captions_enabled: true,
|
|
487
|
+
should_review_all_captions: false,
|
|
488
|
+
},
|
|
489
|
+
},
|
|
490
|
+
notify_when_processed: true,
|
|
491
|
+
transcriptions: null,
|
|
492
|
+
was_created_via_unified_video_flow: {
|
|
493
|
+
was_created_via_unified_video_flow: true,
|
|
494
|
+
},
|
|
495
|
+
story_media_audio_data: {
|
|
496
|
+
raw_media_type: 'VIDEO',
|
|
497
|
+
},
|
|
498
|
+
video_media_metadata: {
|
|
499
|
+
audio: {
|
|
500
|
+
audio_type: 'original_audio',
|
|
501
|
+
start_time_s: 0,
|
|
502
|
+
volume_level: 1,
|
|
503
|
+
},
|
|
504
|
+
is_audio_muted: false,
|
|
505
|
+
length_in_sec: 0, // FB doesn't strictly require this
|
|
506
|
+
},
|
|
507
|
+
},
|
|
508
|
+
},
|
|
509
|
+
],
|
|
510
|
+
fb_shorts: {
|
|
511
|
+
has_overridden_video_format: true,
|
|
512
|
+
is_fb_short: false,
|
|
513
|
+
remix_status: 'DISABLED',
|
|
514
|
+
},
|
|
515
|
+
post_publish_story_data: {
|
|
516
|
+
reshare_post_as_sticker: 'DISABLED',
|
|
517
|
+
},
|
|
518
|
+
message: {
|
|
519
|
+
ranges: [],
|
|
520
|
+
text: message,
|
|
521
|
+
},
|
|
522
|
+
audience: {
|
|
523
|
+
privacy: {
|
|
524
|
+
allow: [],
|
|
525
|
+
base_state: privacyBaseState,
|
|
526
|
+
deny: [],
|
|
527
|
+
tag_expansion_state: 'UNSPECIFIED',
|
|
528
|
+
},
|
|
529
|
+
},
|
|
530
|
+
with_tags_ids: null,
|
|
531
|
+
reels_remix: {
|
|
532
|
+
remix_status: 'DISABLED',
|
|
533
|
+
},
|
|
534
|
+
stars_receivable: {
|
|
535
|
+
is_receiving_stars_disabled: false,
|
|
536
|
+
},
|
|
537
|
+
logging: {
|
|
538
|
+
composer_session_id: composerSessionId,
|
|
539
|
+
},
|
|
540
|
+
navigation_data: {
|
|
541
|
+
attribution_id_v2: `CometHomeRoot.react,comet.home,logo,${Date.now()},631762,4748854339,,`,
|
|
542
|
+
},
|
|
543
|
+
tracking: [null],
|
|
544
|
+
event_share_metadata: {
|
|
545
|
+
surface: 'newsfeed',
|
|
546
|
+
},
|
|
547
|
+
actor_id: ctx.userID,
|
|
548
|
+
client_mutation_id: '1',
|
|
549
|
+
},
|
|
550
|
+
feedLocation: 'NEWSFEED',
|
|
551
|
+
feedbackSource: 1,
|
|
552
|
+
focusCommentID: null,
|
|
553
|
+
gridMediaWidth: null,
|
|
554
|
+
groupID: null,
|
|
555
|
+
scale: 1,
|
|
556
|
+
privacySelectorRenderLocation: 'COMET_STREAM',
|
|
557
|
+
checkPhotosToReelsUpsellEligibility: false,
|
|
558
|
+
referringStoryRenderLocation: null,
|
|
559
|
+
renderLocation: 'homepage_stream',
|
|
560
|
+
useDefaultActor: false,
|
|
561
|
+
inviteShortLinkKey: null,
|
|
562
|
+
isFeed: true,
|
|
563
|
+
isFundraiser: false,
|
|
564
|
+
isFunFactPost: false,
|
|
565
|
+
isGroup: false,
|
|
566
|
+
isEvent: false,
|
|
567
|
+
isTimeline: false,
|
|
568
|
+
isSocialLearning: false,
|
|
569
|
+
isPageNewsFeed: false,
|
|
570
|
+
isProfileReviews: false,
|
|
571
|
+
isWorkSharedDraft: false,
|
|
572
|
+
canUserManageOffers: false,
|
|
573
|
+
// Relay internal provider variables (required by ComposerStoryCreateMutation)
|
|
574
|
+
__relay_internal__pv__CometUFIShareActionMigrationrelayprovider: true,
|
|
575
|
+
__relay_internal__pv__GHLShouldChangeSponsoredDataFieldNamerelayprovider: true,
|
|
576
|
+
__relay_internal__pv__GHLShouldChangeAdIdFieldNamerelayprovider: true,
|
|
577
|
+
__relay_internal__pv__CometUFI_dedicated_comment_routable_dialog_gkrelayprovider: true,
|
|
578
|
+
__relay_internal__pv__CometUFICommentAutoTranslationTyperelayprovider: 'ORIGINAL',
|
|
579
|
+
__relay_internal__pv__CometUFICommentAvatarStickerAnimatedImagerelayprovider: false,
|
|
580
|
+
__relay_internal__pv__CometUFICommentActionLinksRewriteEnabledrelayprovider: false,
|
|
581
|
+
__relay_internal__pv__IsWorkUserrelayprovider: false,
|
|
582
|
+
__relay_internal__pv__CometUFIReactionsEnableShortNamerelayprovider: false,
|
|
583
|
+
__relay_internal__pv__CometUFISingleLineUFIrelayprovider: false,
|
|
584
|
+
__relay_internal__pv__CometFeedStory_enable_reactor_facepilerelayprovider: false,
|
|
585
|
+
__relay_internal__pv__CometFeedStory_enable_post_permalink_white_space_clickrelayprovider: false,
|
|
586
|
+
__relay_internal__pv__TestPilotShouldIncludeDemoAdUseCaserelayprovider: false,
|
|
587
|
+
__relay_internal__pv__FBReels_deprecate_short_form_video_context_gkrelayprovider: true,
|
|
588
|
+
__relay_internal__pv__FBReels_enable_view_dubbed_audio_type_gkrelayprovider: true,
|
|
589
|
+
__relay_internal__pv__CometImmersivePhotoCanUserDisable3DMotionrelayprovider: false,
|
|
590
|
+
__relay_internal__pv__WorkCometIsEmployeeGKProviderrelayprovider: false,
|
|
591
|
+
__relay_internal__pv__IsMergQAPollsrelayprovider: false,
|
|
592
|
+
__relay_internal__pv__FBReelsMediaFooter_comet_enable_reels_ads_gkrelayprovider: true,
|
|
593
|
+
__relay_internal__pv__FBReelsIFUTileContent_reelsIFUPlayOnHoverrelayprovider: true,
|
|
594
|
+
__relay_internal__pv__GroupsCometGYSJFeedItemHeightrelayprovider: 206,
|
|
595
|
+
__relay_internal__pv__ShouldEnableBakedInTextStoriesrelayprovider: false,
|
|
596
|
+
__relay_internal__pv__StoriesShouldIncludeFbNotesrelayprovider: false,
|
|
597
|
+
__relay_internal__pv__groups_comet_use_glvrelayprovider: false,
|
|
598
|
+
__relay_internal__pv__GHLShouldChangeSponsoredAuctionDistanceFieldNamerelayprovider: false,
|
|
599
|
+
__relay_internal__pv__GHLShouldUseSponsoredAuctionLabelFieldNameV1relayprovider: false,
|
|
600
|
+
__relay_internal__pv__GHLShouldUseSponsoredAuctionLabelFieldNameV2relayprovider: false,
|
|
601
|
+
};
|
|
602
|
+
const publishForm = {
|
|
603
|
+
av: ctx.userID,
|
|
604
|
+
__user: ctx.userID,
|
|
605
|
+
__a: '1',
|
|
606
|
+
__req: '1',
|
|
607
|
+
__hs: ctx.hs || '20577.HYP:comet_pkg.2.1...0',
|
|
608
|
+
__ccg: 'EXCELLENT',
|
|
609
|
+
__comet_req: '15',
|
|
610
|
+
fb_dtsg: ctx.fb_dtsg,
|
|
611
|
+
jazoest: ctx.jazoest,
|
|
612
|
+
lsd,
|
|
613
|
+
__spin_r: ctx.revision || '',
|
|
614
|
+
__spin_b: 'trunk',
|
|
615
|
+
__spin_t: Math.floor(Date.now() / 1000),
|
|
616
|
+
fb_api_caller_class: 'RelayModern',
|
|
617
|
+
fb_api_req_friendly_name: 'ComposerStoryCreateMutation',
|
|
618
|
+
variables: JSON.stringify(publishVariables),
|
|
619
|
+
server_timestamps: 'true',
|
|
620
|
+
doc_id: '26313541601679894',
|
|
621
|
+
};
|
|
622
|
+
const publishRes = await defaultFuncs
|
|
623
|
+
.post('https://www.facebook.com/api/graphql/', ctx.jar, publishForm, {})
|
|
624
|
+
.then(utils.parseAndCheckLogin(ctx, defaultFuncs));
|
|
625
|
+
if (publishRes.errors) {
|
|
626
|
+
throw new Error('Publish failed: ' + JSON.stringify(publishRes.errors));
|
|
627
|
+
}
|
|
628
|
+
const storyCreate = publishRes?.data?.story_create;
|
|
629
|
+
const result = {
|
|
630
|
+
success: true,
|
|
631
|
+
postID: storyCreate?.post_id || null,
|
|
632
|
+
storyID: storyCreate?.story_id || null,
|
|
633
|
+
videoID: String(videoId),
|
|
634
|
+
publishingFlow: storyCreate?.publishing_flow || null,
|
|
635
|
+
composerSessionId,
|
|
636
|
+
data: publishRes,
|
|
637
|
+
};
|
|
638
|
+
utils.log('createReel', `Reel published successfully! postID=${result.postID}`);
|
|
639
|
+
callback(null, result);
|
|
640
|
+
}
|
|
641
|
+
catch (err) {
|
|
642
|
+
utils.error('createReel', err);
|
|
643
|
+
callback(err);
|
|
644
|
+
}
|
|
645
|
+
return returnPromise;
|
|
646
|
+
}
|
|
647
|
+
return createReel;
|
|
648
|
+
}
|
|
649
|
+
//# sourceMappingURL=createReel.js.map
|