metaai-sdk 2.3.3__py3-none-any.whl → 2.3.5__py3-none-any.whl
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.
- metaai_api/main.py +154 -32
- metaai_api/video_generation.py +819 -0
- {metaai_sdk-2.3.3.dist-info → metaai_sdk-2.3.5.dist-info}/METADATA +45 -9
- {metaai_sdk-2.3.3.dist-info → metaai_sdk-2.3.5.dist-info}/RECORD +7 -6
- {metaai_sdk-2.3.3.dist-info → metaai_sdk-2.3.5.dist-info}/WHEEL +1 -1
- {metaai_sdk-2.3.3.dist-info → metaai_sdk-2.3.5.dist-info}/licenses/LICENSE +0 -0
- {metaai_sdk-2.3.3.dist-info → metaai_sdk-2.3.5.dist-info}/top_level.txt +0 -0
metaai_api/main.py
CHANGED
|
@@ -141,6 +141,7 @@ class MetaAI:
|
|
|
141
141
|
media_ids: Optional[list] = None,
|
|
142
142
|
attachment_metadata: Optional[Dict[str, Any]] = None,
|
|
143
143
|
is_image_generation: bool = False,
|
|
144
|
+
orientation: Optional[str] = None,
|
|
144
145
|
) -> Union[Dict, Generator[Dict, None, None]]:
|
|
145
146
|
"""
|
|
146
147
|
Sends a message to the Meta AI and returns the response.
|
|
@@ -154,6 +155,7 @@ class MetaAI:
|
|
|
154
155
|
media_ids (list): List of media IDs from uploaded images to include in the prompt. Defaults to None.
|
|
155
156
|
attachment_metadata (dict): Optional dict with 'file_size' (int) and 'mime_type' (str). Defaults to None.
|
|
156
157
|
is_image_generation (bool): Whether this is for image generation (vs chat). Defaults to False.
|
|
158
|
+
orientation (str): Image orientation for generation. Valid values: "LANDSCAPE", "VERTICAL", "SQUARE". Defaults to "VERTICAL".
|
|
157
159
|
|
|
158
160
|
Returns:
|
|
159
161
|
dict: A dictionary containing the response message and sources.
|
|
@@ -167,7 +169,10 @@ class MetaAI:
|
|
|
167
169
|
url = "https://graph.meta.ai/graphql?locale=user"
|
|
168
170
|
|
|
169
171
|
else:
|
|
170
|
-
auth_payload = {
|
|
172
|
+
auth_payload = {
|
|
173
|
+
"fb_dtsg": self.cookies["fb_dtsg"],
|
|
174
|
+
"lsd": self.cookies.get("lsd", ""),
|
|
175
|
+
}
|
|
171
176
|
url = "https://www.meta.ai/api/graphql/"
|
|
172
177
|
|
|
173
178
|
if not self.external_conversation_id or new_conversation:
|
|
@@ -186,13 +191,15 @@ class MetaAI:
|
|
|
186
191
|
|
|
187
192
|
# Generate offline threading IDs
|
|
188
193
|
offline_threading_id = generate_offline_threading_id()
|
|
194
|
+
bot_offline_threading_id = str(int(offline_threading_id) + 1)
|
|
195
|
+
thread_session_id = str(uuid.uuid4())
|
|
189
196
|
|
|
190
197
|
# Determine entrypoint based on context
|
|
191
198
|
if images:
|
|
192
199
|
# Video generation with images uses CHAT
|
|
193
200
|
entrypoint = "KADABRA__CHAT__UNIFIED_INPUT_BAR"
|
|
194
|
-
elif media_ids:
|
|
195
|
-
#
|
|
201
|
+
elif media_ids or orientation:
|
|
202
|
+
# Image generation with orientation OR uploaded images uses DISCOVER
|
|
196
203
|
entrypoint = "KADABRA__DISCOVER__UNIFIED_INPUT_BAR"
|
|
197
204
|
else:
|
|
198
205
|
entrypoint = "ABRA__CHAT__TEXT"
|
|
@@ -200,42 +207,154 @@ class MetaAI:
|
|
|
200
207
|
# Set friendly name based on entrypoint
|
|
201
208
|
friendly_name = "useKadabraSendMessageMutation" if entrypoint.startswith("KADABRA") else "useAbraSendMessageMutation"
|
|
202
209
|
|
|
210
|
+
# Build variables dictionary
|
|
211
|
+
is_kadabra = entrypoint.startswith("KADABRA")
|
|
212
|
+
|
|
213
|
+
if is_kadabra:
|
|
214
|
+
# Full Kadabra variables for image generation
|
|
215
|
+
variables = {
|
|
216
|
+
"message": {"sensitive_string_value": message},
|
|
217
|
+
"externalConversationId": self.external_conversation_id,
|
|
218
|
+
"offlineThreadingId": offline_threading_id,
|
|
219
|
+
"threadSessionId": thread_session_id,
|
|
220
|
+
"isNewConversation": new_conversation or not self.offline_threading_id,
|
|
221
|
+
"suggestedPromptIndex": None,
|
|
222
|
+
"promptPrefix": None,
|
|
223
|
+
"entrypoint": entrypoint,
|
|
224
|
+
"attachments": [],
|
|
225
|
+
"attachmentsV2": attachments_v2,
|
|
226
|
+
"activeMediaSets": [],
|
|
227
|
+
"activeCardVersions": [],
|
|
228
|
+
"activeArtifactVersion": None,
|
|
229
|
+
"userUploadEditModeInput": None,
|
|
230
|
+
"reelComposeInput": None,
|
|
231
|
+
"qplJoinId": uuid.uuid4().hex[:17],
|
|
232
|
+
"sourceRemixPostId": None,
|
|
233
|
+
"gkPlannerOrReasoningEnabled": True,
|
|
234
|
+
"selectedModel": "BASIC_OPTION",
|
|
235
|
+
"conversationMode": None,
|
|
236
|
+
"selectedAgentType": "PLANNER",
|
|
237
|
+
"agentSettings": None,
|
|
238
|
+
"conversationStarterId": None,
|
|
239
|
+
"promptType": None,
|
|
240
|
+
"artifactRewriteOptions": None,
|
|
241
|
+
"imagineOperationRequest": None,
|
|
242
|
+
"imagineClientOptions": {"orientation": orientation.upper() if orientation else "VERTICAL"},
|
|
243
|
+
"spaceId": None,
|
|
244
|
+
"sparkSnapshotId": None,
|
|
245
|
+
"topicPageId": None,
|
|
246
|
+
"includeSpace": False,
|
|
247
|
+
"storybookId": None,
|
|
248
|
+
"messagePersistentInput": {
|
|
249
|
+
"attachment_size": attachment_metadata.get('file_size') if attachment_metadata else None,
|
|
250
|
+
"attachment_type": attachment_metadata.get('mime_type') if attachment_metadata else None,
|
|
251
|
+
"bot_message_offline_threading_id": bot_offline_threading_id,
|
|
252
|
+
"conversation_mode": None,
|
|
253
|
+
"external_conversation_id": self.external_conversation_id,
|
|
254
|
+
"is_new_conversation": new_conversation or not self.offline_threading_id,
|
|
255
|
+
"meta_ai_entry_point": entrypoint,
|
|
256
|
+
"offline_threading_id": offline_threading_id,
|
|
257
|
+
"prompt_id": None,
|
|
258
|
+
"prompt_session_id": thread_session_id,
|
|
259
|
+
},
|
|
260
|
+
"alakazam_enabled": True,
|
|
261
|
+
"skipInFlightMessageWithParams": None,
|
|
262
|
+
"__relay_internal__pv__KadabraSocialSearchEnabledrelayprovider": False,
|
|
263
|
+
"__relay_internal__pv__KadabraZeitgeistEnabledrelayprovider": False,
|
|
264
|
+
"__relay_internal__pv__alakazam_enabledrelayprovider": True,
|
|
265
|
+
"__relay_internal__pv__sp_kadabra_survey_invitationrelayprovider": True,
|
|
266
|
+
"__relay_internal__pv__enable_kadabra_partial_resultsrelayprovider": False,
|
|
267
|
+
"__relay_internal__pv__AbraArtifactsEnabledrelayprovider": True,
|
|
268
|
+
"__relay_internal__pv__KadabraMemoryEnabledrelayprovider": False,
|
|
269
|
+
"__relay_internal__pv__AbraPlannerEnabledrelayprovider": True,
|
|
270
|
+
"__relay_internal__pv__AbraWidgetsEnabledrelayprovider": False,
|
|
271
|
+
"__relay_internal__pv__KadabraDeepResearchEnabledrelayprovider": False,
|
|
272
|
+
"__relay_internal__pv__KadabraThinkHarderEnabledrelayprovider": False,
|
|
273
|
+
"__relay_internal__pv__KadabraVergeEnabledrelayprovider": False,
|
|
274
|
+
"__relay_internal__pv__KadabraSpacesEnabledrelayprovider": False,
|
|
275
|
+
"__relay_internal__pv__KadabraProductSearchEnabledrelayprovider": False,
|
|
276
|
+
"__relay_internal__pv__KadabraAreServiceEnabledrelayprovider": False,
|
|
277
|
+
"__relay_internal__pv__kadabra_render_reasoning_response_statesrelayprovider": True,
|
|
278
|
+
"__relay_internal__pv__kadabra_reasoning_cotrelayprovider": False,
|
|
279
|
+
"__relay_internal__pv__AbraSearchInlineReferencesEnabledrelayprovider": True,
|
|
280
|
+
"__relay_internal__pv__AbraComposedTextWidgetsrelayprovider": True,
|
|
281
|
+
"__relay_internal__pv__KadabraNewCitationsEnabledrelayprovider": True,
|
|
282
|
+
"__relay_internal__pv__WebPixelRatiorelayprovider": 1,
|
|
283
|
+
"__relay_internal__pv__KadabraVideoDeliveryRequestrelayprovider": {
|
|
284
|
+
"dash_manifest_requests": [{}],
|
|
285
|
+
"progressive_url_requests": [{"quality": "HD"}, {"quality": "SD"}]
|
|
286
|
+
},
|
|
287
|
+
"__relay_internal__pv__KadabraWidgetsRedesignEnabledrelayprovider": False,
|
|
288
|
+
"__relay_internal__pv__kadabra_enable_send_message_retryrelayprovider": True,
|
|
289
|
+
"__relay_internal__pv__KadabraEmailCalendarIntegrationrelayprovider": False,
|
|
290
|
+
"__relay_internal__pv__ClippyUIrelayprovider": False,
|
|
291
|
+
"__relay_internal__pv__kadabra_reels_connect_featuresrelayprovider": False,
|
|
292
|
+
"__relay_internal__pv__AbraBugNubrelayprovider": False,
|
|
293
|
+
"__relay_internal__pv__AbraRedteamingrelayprovider": False,
|
|
294
|
+
"__relay_internal__pv__AbraDebugDevOnlyrelayprovider": False,
|
|
295
|
+
"__relay_internal__pv__kadabra_enable_open_in_editor_message_actionrelayprovider": True,
|
|
296
|
+
"__relay_internal__pv__BloksDeviceContextrelayprovider": {"pixel_ratio": 1},
|
|
297
|
+
"__relay_internal__pv__AbraThreadsEnabledrelayprovider": False,
|
|
298
|
+
"__relay_internal__pv__kadabra_story_builder_enabledrelayprovider": False,
|
|
299
|
+
"__relay_internal__pv__kadabra_imagine_canvas_enable_dev_settingsrelayprovider": False,
|
|
300
|
+
"__relay_internal__pv__kadabra_create_media_deletionrelayprovider": False,
|
|
301
|
+
"__relay_internal__pv__kadabra_moodboardrelayprovider": False,
|
|
302
|
+
"__relay_internal__pv__AbraArtifactDragImagineFromConversationrelayprovider": True,
|
|
303
|
+
"__relay_internal__pv__kadabra_media_item_renderer_heightrelayprovider": 545,
|
|
304
|
+
"__relay_internal__pv__kadabra_media_item_renderer_widthrelayprovider": 620,
|
|
305
|
+
"__relay_internal__pv__AbraQPDocUploadNuxTriggerNamerelayprovider": "meta_dot_ai_abra_web_doc_upload_nux_tour",
|
|
306
|
+
"__relay_internal__pv__AbraSurfaceNuxIDrelayprovider": "12177",
|
|
307
|
+
"__relay_internal__pv__KadabraConversationRenamingrelayprovider": True,
|
|
308
|
+
"__relay_internal__pv__AbraIsLoggedOutrelayprovider": not self.is_authed,
|
|
309
|
+
"__relay_internal__pv__KadabraCanvasDisplayHeaderV2relayprovider": False,
|
|
310
|
+
"__relay_internal__pv__AbraArtifactEditorDebugModerelayprovider": False,
|
|
311
|
+
"__relay_internal__pv__AbraArtifactEditorDownloadHTMLEnabledrelayprovider": False,
|
|
312
|
+
"__relay_internal__pv__kadabra_create_row_hover_optionsrelayprovider": False,
|
|
313
|
+
"__relay_internal__pv__kadabra_media_info_pillsrelayprovider": True,
|
|
314
|
+
"__relay_internal__pv__KadabraConcordInternalProfileBadgeEnabledrelayprovider": False,
|
|
315
|
+
"__relay_internal__pv__KadabraSocialGraphrelayprovider": True,
|
|
316
|
+
}
|
|
317
|
+
else:
|
|
318
|
+
# Simpler Abra variables for chat
|
|
319
|
+
variables = {
|
|
320
|
+
"message": {"sensitive_string_value": message},
|
|
321
|
+
"externalConversationId": self.external_conversation_id,
|
|
322
|
+
"offlineThreadingId": offline_threading_id,
|
|
323
|
+
"suggestedPromptIndex": None,
|
|
324
|
+
"flashVideoRecapInput": flash_video_input,
|
|
325
|
+
"flashPreviewInput": None,
|
|
326
|
+
"promptPrefix": None,
|
|
327
|
+
"entrypoint": entrypoint,
|
|
328
|
+
"attachments": [],
|
|
329
|
+
"attachmentsV2": attachments_v2,
|
|
330
|
+
"messagePersistentInput": {
|
|
331
|
+
"attachment_size": attachment_metadata.get('file_size') if attachment_metadata else None,
|
|
332
|
+
"attachment_type": attachment_metadata.get('mime_type') if attachment_metadata else None,
|
|
333
|
+
"external_conversation_id": self.external_conversation_id,
|
|
334
|
+
"offline_threading_id": offline_threading_id,
|
|
335
|
+
"meta_ai_entry_point": entrypoint,
|
|
336
|
+
} if media_ids else None,
|
|
337
|
+
"icebreaker_type": "TEXT",
|
|
338
|
+
"__relay_internal__pv__AbraDebugDevOnlyrelayprovider": False,
|
|
339
|
+
"__relay_internal__pv__WebPixelRatiorelayprovider": 1,
|
|
340
|
+
}
|
|
341
|
+
|
|
203
342
|
payload = {
|
|
204
343
|
**auth_payload,
|
|
205
344
|
"fb_api_caller_class": "RelayModern",
|
|
206
345
|
"fb_api_req_friendly_name": friendly_name,
|
|
207
|
-
"variables": json.dumps(
|
|
208
|
-
{
|
|
209
|
-
"message": {"sensitive_string_value": message},
|
|
210
|
-
"externalConversationId": self.external_conversation_id,
|
|
211
|
-
"offlineThreadingId": offline_threading_id,
|
|
212
|
-
"suggestedPromptIndex": None,
|
|
213
|
-
"flashVideoRecapInput": flash_video_input,
|
|
214
|
-
"flashPreviewInput": None,
|
|
215
|
-
"promptPrefix": None,
|
|
216
|
-
"entrypoint": entrypoint,
|
|
217
|
-
"attachments": [],
|
|
218
|
-
"attachmentsV2": attachments_v2,
|
|
219
|
-
"messagePersistentInput": {
|
|
220
|
-
"attachment_size": attachment_metadata.get('file_size') if attachment_metadata else None,
|
|
221
|
-
"attachment_type": attachment_metadata.get('mime_type') if attachment_metadata else None,
|
|
222
|
-
"external_conversation_id": self.external_conversation_id,
|
|
223
|
-
"offline_threading_id": offline_threading_id,
|
|
224
|
-
"meta_ai_entry_point": entrypoint,
|
|
225
|
-
} if media_ids else None,
|
|
226
|
-
"icebreaker_type": "TEXT",
|
|
227
|
-
"__relay_internal__pv__AbraDebugDevOnlyrelayprovider": False,
|
|
228
|
-
"__relay_internal__pv__WebPixelRatiorelayprovider": 1,
|
|
229
|
-
}
|
|
230
|
-
),
|
|
346
|
+
"variables": json.dumps(variables),
|
|
231
347
|
"server_timestamps": "true",
|
|
232
|
-
"doc_id": "
|
|
348
|
+
"doc_id": "24895882500088854" if is_kadabra else "7783822248314888",
|
|
233
349
|
}
|
|
234
350
|
payload = urllib.parse.urlencode(payload) # noqa
|
|
235
351
|
headers = {
|
|
236
352
|
"content-type": "application/x-www-form-urlencoded",
|
|
237
353
|
"x-fb-friendly-name": friendly_name,
|
|
238
354
|
}
|
|
355
|
+
# Add lsd header for authenticated requests
|
|
356
|
+
if self.cookies.get("lsd"):
|
|
357
|
+
headers["x-fb-lsd"] = self.cookies["lsd"]
|
|
239
358
|
if self.is_authed:
|
|
240
359
|
headers["cookie"] = f'abra_sess={self.cookies["abra_sess"]}'
|
|
241
360
|
# Recreate the session to avoid cookie leakage when user is authenticated
|
|
@@ -248,7 +367,7 @@ class MetaAI:
|
|
|
248
367
|
raw_response = response.text
|
|
249
368
|
last_streamed_response = self.extract_last_response(raw_response)
|
|
250
369
|
if not last_streamed_response:
|
|
251
|
-
return self.retry(message, stream=stream, attempts=attempts, media_ids=media_ids, attachment_metadata=attachment_metadata, is_image_generation=is_image_generation)
|
|
370
|
+
return self.retry(message, stream=stream, attempts=attempts, new_conversation=new_conversation, images=images, media_ids=media_ids, attachment_metadata=attachment_metadata, is_image_generation=is_image_generation, orientation=orientation)
|
|
252
371
|
|
|
253
372
|
extracted_data = self.extract_data(last_streamed_response)
|
|
254
373
|
return extracted_data
|
|
@@ -257,10 +376,10 @@ class MetaAI:
|
|
|
257
376
|
lines = response.iter_lines()
|
|
258
377
|
is_error = json.loads(next(lines))
|
|
259
378
|
if len(is_error.get("errors", [])) > 0:
|
|
260
|
-
return self.retry(message, stream=stream, attempts=attempts, media_ids=media_ids, attachment_metadata=attachment_metadata, is_image_generation=is_image_generation)
|
|
379
|
+
return self.retry(message, stream=stream, attempts=attempts, new_conversation=new_conversation, images=images, media_ids=media_ids, attachment_metadata=attachment_metadata, is_image_generation=is_image_generation, orientation=orientation)
|
|
261
380
|
return self.stream_response(lines)
|
|
262
381
|
|
|
263
|
-
def retry(self, message: str, stream: bool = False, attempts: int = 0, media_ids: Optional[list] = None, attachment_metadata: Optional[Dict[str, Any]] = None, is_image_generation: bool = False):
|
|
382
|
+
def retry(self, message: str, stream: bool = False, attempts: int = 0, new_conversation: bool = False, images: Optional[list] = None, media_ids: Optional[list] = None, attachment_metadata: Optional[Dict[str, Any]] = None, is_image_generation: bool = False, orientation: Optional[str] = None):
|
|
264
383
|
"""
|
|
265
384
|
Retries the prompt function if an error occurs.
|
|
266
385
|
"""
|
|
@@ -269,7 +388,7 @@ class MetaAI:
|
|
|
269
388
|
f"Was unable to obtain a valid response from Meta AI. Retrying... Attempt {attempts + 1}/{MAX_RETRIES}."
|
|
270
389
|
)
|
|
271
390
|
time.sleep(3)
|
|
272
|
-
return self.prompt(message, stream=stream, attempts=attempts + 1, media_ids=media_ids, attachment_metadata=attachment_metadata, is_image_generation=is_image_generation)
|
|
391
|
+
return self.prompt(message, stream=stream, attempts=attempts + 1, new_conversation=new_conversation, images=images, media_ids=media_ids, attachment_metadata=attachment_metadata, is_image_generation=is_image_generation, orientation=orientation)
|
|
273
392
|
else:
|
|
274
393
|
raise Exception(
|
|
275
394
|
"Unable to obtain a valid response from Meta AI. Try again later."
|
|
@@ -587,6 +706,7 @@ class MetaAI:
|
|
|
587
706
|
prompt: str,
|
|
588
707
|
media_ids: Optional[list] = None,
|
|
589
708
|
attachment_metadata: Optional[Dict[str, Any]] = None,
|
|
709
|
+
orientation: Optional[str] = None,
|
|
590
710
|
wait_before_poll: int = 10,
|
|
591
711
|
max_attempts: int = 30,
|
|
592
712
|
wait_seconds: int = 5,
|
|
@@ -600,6 +720,7 @@ class MetaAI:
|
|
|
600
720
|
prompt: Text prompt for video generation
|
|
601
721
|
media_ids: Optional list of media IDs from uploaded images
|
|
602
722
|
attachment_metadata: Optional dict with 'file_size' (int) and 'mime_type' (str)
|
|
723
|
+
orientation: Video orientation. Valid values: "LANDSCAPE", "VERTICAL", "SQUARE". Defaults to None.
|
|
603
724
|
wait_before_poll: Seconds to wait before starting to poll (default: 10)
|
|
604
725
|
max_attempts: Maximum polling attempts (default: 30)
|
|
605
726
|
wait_seconds: Seconds between polling attempts (default: 5)
|
|
@@ -633,6 +754,7 @@ class MetaAI:
|
|
|
633
754
|
prompt=prompt,
|
|
634
755
|
media_ids=media_ids,
|
|
635
756
|
attachment_metadata=attachment_metadata,
|
|
757
|
+
orientation=orientation,
|
|
636
758
|
wait_before_poll=wait_before_poll,
|
|
637
759
|
max_attempts=max_attempts,
|
|
638
760
|
wait_seconds=wait_seconds,
|
|
@@ -0,0 +1,819 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Meta AI Video Generation Module
|
|
3
|
+
Advanced video generation and retrieval using Meta AI's GraphQL API
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import requests
|
|
7
|
+
import json
|
|
8
|
+
import time
|
|
9
|
+
import uuid
|
|
10
|
+
from typing import Dict, List, Optional, Any
|
|
11
|
+
from requests_html import HTMLSession
|
|
12
|
+
from metaai_api.utils import extract_value
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class VideoGenerator:
|
|
16
|
+
"""
|
|
17
|
+
A class to handle video generation requests to Meta AI's GraphQL API.
|
|
18
|
+
Supports creating videos from text prompts and retrieving video URLs.
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
GRAPHQL_URL = 'https://www.meta.ai/api/graphql/'
|
|
22
|
+
|
|
23
|
+
def __init__(
|
|
24
|
+
self,
|
|
25
|
+
cookies_str: Optional[str] = None,
|
|
26
|
+
cookies_dict: Optional[Dict[str, str]] = None
|
|
27
|
+
):
|
|
28
|
+
"""
|
|
29
|
+
Initialize the VideoGenerator.
|
|
30
|
+
Automatically fetches lsd and fb_dtsg tokens from Meta AI.
|
|
31
|
+
|
|
32
|
+
Args:
|
|
33
|
+
cookies_str: Cookie string in format "key=value; key=value"
|
|
34
|
+
cookies_dict: Pre-parsed cookies dictionary
|
|
35
|
+
"""
|
|
36
|
+
if cookies_dict:
|
|
37
|
+
self.cookies = cookies_dict
|
|
38
|
+
self.cookies_str = "; ".join([f"{k}={v}" for k, v in cookies_dict.items()])
|
|
39
|
+
elif cookies_str:
|
|
40
|
+
self.cookies = self._parse_cookies(cookies_str)
|
|
41
|
+
self.cookies_str = cookies_str
|
|
42
|
+
else:
|
|
43
|
+
raise ValueError("Either cookies_str or cookies_dict must be provided")
|
|
44
|
+
|
|
45
|
+
# Always auto-fetch tokens
|
|
46
|
+
try:
|
|
47
|
+
tokens = self.get_lsd_and_dtsg(self.cookies_str)
|
|
48
|
+
self.lsd = tokens['lsd']
|
|
49
|
+
self.fb_dtsg = tokens['fb_dtsg']
|
|
50
|
+
except Exception as e:
|
|
51
|
+
raise ValueError(f"Failed to auto-fetch tokens: {e}")
|
|
52
|
+
|
|
53
|
+
@staticmethod
|
|
54
|
+
def _parse_cookies(cookie_str: str) -> Dict[str, str]:
|
|
55
|
+
"""Parse cookie string into dictionary"""
|
|
56
|
+
cookies = {}
|
|
57
|
+
for item in cookie_str.split('; '):
|
|
58
|
+
if '=' in item:
|
|
59
|
+
key, value = item.split('=', 1)
|
|
60
|
+
cookies[key] = value
|
|
61
|
+
return cookies
|
|
62
|
+
|
|
63
|
+
@staticmethod
|
|
64
|
+
def get_lsd_and_dtsg(cookies_str: str) -> Dict[str, str]:
|
|
65
|
+
"""
|
|
66
|
+
Extract lsd and fb_dtsg from Meta AI page using provided cookies.
|
|
67
|
+
|
|
68
|
+
Args:
|
|
69
|
+
cookies_str: Cookie string in format "key1=value1; key2=value2; ..."
|
|
70
|
+
|
|
71
|
+
Returns:
|
|
72
|
+
Dictionary with 'lsd' and 'fb_dtsg' keys
|
|
73
|
+
"""
|
|
74
|
+
# Fetch Meta AI page with cookies
|
|
75
|
+
session = HTMLSession()
|
|
76
|
+
headers = {"cookie": cookies_str}
|
|
77
|
+
response = session.get("https://www.meta.ai/", headers=headers)
|
|
78
|
+
|
|
79
|
+
# Extract lsd and fb_dtsg
|
|
80
|
+
lsd = extract_value(response.text, start_str='"LSD",[],{"token":"', end_str='"')
|
|
81
|
+
fb_dtsg = extract_value(response.text, start_str='DTSGInitData",[],{"token":"', end_str='"')
|
|
82
|
+
|
|
83
|
+
return {
|
|
84
|
+
"lsd": lsd,
|
|
85
|
+
"fb_dtsg": fb_dtsg
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
@classmethod
|
|
89
|
+
def quick_generate(
|
|
90
|
+
cls,
|
|
91
|
+
cookies_str: str,
|
|
92
|
+
prompt: str,
|
|
93
|
+
media_ids: Optional[List[str]] = None,
|
|
94
|
+
attachment_metadata: Optional[Dict[str, Any]] = None,
|
|
95
|
+
orientation: Optional[str] = None,
|
|
96
|
+
verbose: bool = True
|
|
97
|
+
) -> Dict:
|
|
98
|
+
"""
|
|
99
|
+
Convenience method to generate a video with minimal setup.
|
|
100
|
+
Automatically fetches tokens and generates video in one call.
|
|
101
|
+
|
|
102
|
+
Args:
|
|
103
|
+
cookies_str: Cookie string in format "key1=value1; key2=value2; ..."
|
|
104
|
+
prompt: Text prompt for video generation
|
|
105
|
+
media_ids: Optional list of media IDs from uploaded images
|
|
106
|
+
attachment_metadata: Optional dict with 'file_size' (int) and 'mime_type' (str)
|
|
107
|
+
orientation: Video orientation ("LANDSCAPE", "VERTICAL", "SQUARE"). Defaults to "VERTICAL".
|
|
108
|
+
verbose: Whether to print status messages
|
|
109
|
+
|
|
110
|
+
Returns:
|
|
111
|
+
Dictionary with success status, conversation_id, prompt, video_urls, and timestamp
|
|
112
|
+
|
|
113
|
+
Example:
|
|
114
|
+
result = VideoGenerator.quick_generate(
|
|
115
|
+
cookies_str="datr=...; abra_sess=...",
|
|
116
|
+
prompt="Generate a video of a sunset",
|
|
117
|
+
media_ids=["1234567890"],
|
|
118
|
+
attachment_metadata={'file_size': 3310, 'mime_type': 'image/jpeg'},
|
|
119
|
+
orientation="LANDSCAPE"
|
|
120
|
+
)
|
|
121
|
+
"""
|
|
122
|
+
generator = cls(cookies_str=cookies_str)
|
|
123
|
+
return generator.generate_video(prompt=prompt, media_ids=media_ids, attachment_metadata=attachment_metadata, orientation=orientation, verbose=verbose)
|
|
124
|
+
|
|
125
|
+
def build_headers(
|
|
126
|
+
self,
|
|
127
|
+
content_type: str = 'application/x-www-form-urlencoded',
|
|
128
|
+
friendly_name: Optional[str] = None,
|
|
129
|
+
additional_headers: Optional[Dict[str, str]] = None
|
|
130
|
+
) -> Dict[str, str]:
|
|
131
|
+
"""
|
|
132
|
+
Build dynamic headers for Meta AI API requests.
|
|
133
|
+
|
|
134
|
+
Args:
|
|
135
|
+
content_type: Content-Type header value
|
|
136
|
+
friendly_name: Optional X-FB-Friendly-Name for the request
|
|
137
|
+
additional_headers: Optional dict of additional headers to merge
|
|
138
|
+
|
|
139
|
+
Returns:
|
|
140
|
+
Complete headers dictionary
|
|
141
|
+
"""
|
|
142
|
+
# Base headers common to all requests
|
|
143
|
+
headers = {
|
|
144
|
+
'accept': '*/*',
|
|
145
|
+
'accept-language': 'en-US,en;q=0.5',
|
|
146
|
+
'content-type': content_type,
|
|
147
|
+
'origin': 'https://www.meta.ai',
|
|
148
|
+
'referer': 'https://www.meta.ai/',
|
|
149
|
+
'sec-ch-ua': '"Brave";v="141", "Not?A_Brand";v="8", "Chromium";v="141"',
|
|
150
|
+
'sec-ch-ua-mobile': '?0',
|
|
151
|
+
'sec-ch-ua-platform': '"Windows"',
|
|
152
|
+
'sec-fetch-dest': 'empty',
|
|
153
|
+
'sec-fetch-mode': 'cors',
|
|
154
|
+
'sec-fetch-site': 'same-origin',
|
|
155
|
+
'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36',
|
|
156
|
+
'x-asbd-id': '359341',
|
|
157
|
+
'x-fb-lsd': self.lsd,
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
# Add optional friendly name
|
|
161
|
+
if friendly_name:
|
|
162
|
+
headers['x-fb-friendly-name'] = friendly_name
|
|
163
|
+
|
|
164
|
+
# Add additional headers specific to request type
|
|
165
|
+
if additional_headers:
|
|
166
|
+
headers.update(additional_headers)
|
|
167
|
+
|
|
168
|
+
return headers
|
|
169
|
+
|
|
170
|
+
def create_video_generation_request(
|
|
171
|
+
self,
|
|
172
|
+
prompt_text: str,
|
|
173
|
+
media_ids: Optional[List[str]] = None,
|
|
174
|
+
attachment_metadata: Optional[Dict[str, Any]] = None,
|
|
175
|
+
orientation: Optional[str] = None,
|
|
176
|
+
verbose: bool = True
|
|
177
|
+
) -> Optional[str]:
|
|
178
|
+
"""
|
|
179
|
+
Send video generation request to Meta AI using raw multipart body.
|
|
180
|
+
|
|
181
|
+
Args:
|
|
182
|
+
prompt_text: The text prompt for video generation
|
|
183
|
+
media_ids: Optional list of media IDs from uploaded images
|
|
184
|
+
attachment_metadata: Optional dict with 'file_size' (int) and 'mime_type' (str)
|
|
185
|
+
orientation: Video orientation ("LANDSCAPE", "VERTICAL", "SQUARE"). Defaults to "VERTICAL".
|
|
186
|
+
verbose: Whether to print status messages
|
|
187
|
+
|
|
188
|
+
Returns:
|
|
189
|
+
external_conversation_id if successful, None otherwise
|
|
190
|
+
"""
|
|
191
|
+
# Generate unique IDs
|
|
192
|
+
external_conversation_id = str(uuid.uuid4())
|
|
193
|
+
offline_threading_id = str(int(time.time() * 1000000000))[:19]
|
|
194
|
+
thread_session_id = str(uuid.uuid4())
|
|
195
|
+
bot_offline_threading_id = str(int(time.time() * 1000000000) + 1)[:19]
|
|
196
|
+
qpl_join_id = str(uuid.uuid4()).replace('-', '')
|
|
197
|
+
|
|
198
|
+
# Build headers with multipart-specific additions
|
|
199
|
+
multipart_headers = {
|
|
200
|
+
'priority': 'u=1, i',
|
|
201
|
+
'sec-ch-ua-full-version-list': '"Brave";v="141.0.0.0", "Not?A_Brand";v="8.0.0.0", "Chromium";v="141.0.0.0"',
|
|
202
|
+
'sec-ch-ua-model': '""',
|
|
203
|
+
'sec-ch-ua-platform-version': '"19.0.0"',
|
|
204
|
+
'sec-gpc': '1',
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
headers = self.build_headers(
|
|
208
|
+
content_type='multipart/form-data; boundary=----WebKitFormBoundaryu59CeaZS4ag939lz',
|
|
209
|
+
additional_headers=multipart_headers
|
|
210
|
+
)
|
|
211
|
+
|
|
212
|
+
# Build variables JSON (compact, no extra spaces)
|
|
213
|
+
variables = json.dumps({
|
|
214
|
+
"message": {"sensitive_string_value": prompt_text},
|
|
215
|
+
"externalConversationId": external_conversation_id,
|
|
216
|
+
"offlineThreadingId": offline_threading_id,
|
|
217
|
+
"threadSessionId": thread_session_id,
|
|
218
|
+
"isNewConversation": True,
|
|
219
|
+
"suggestedPromptIndex": None,
|
|
220
|
+
"promptPrefix": None,
|
|
221
|
+
"entrypoint": "KADABRA__CHAT__UNIFIED_INPUT_BAR",
|
|
222
|
+
"attachments": [],
|
|
223
|
+
"attachmentsV2": media_ids if media_ids else [],
|
|
224
|
+
"activeMediaSets": [],
|
|
225
|
+
"activeCardVersions": [],
|
|
226
|
+
"activeArtifactVersion": None,
|
|
227
|
+
"userUploadEditModeInput": None,
|
|
228
|
+
"reelComposeInput": None,
|
|
229
|
+
"qplJoinId": qpl_join_id,
|
|
230
|
+
"sourceRemixPostId": None,
|
|
231
|
+
"gkPlannerOrReasoningEnabled": True,
|
|
232
|
+
"selectedModel": "BASIC_OPTION",
|
|
233
|
+
"conversationMode": None,
|
|
234
|
+
"selectedAgentType": "PLANNER",
|
|
235
|
+
"conversationStarterId": None,
|
|
236
|
+
"promptType": None,
|
|
237
|
+
"artifactRewriteOptions": None,
|
|
238
|
+
"imagineOperationRequest": None,
|
|
239
|
+
"imagineClientOptions": {"orientation": orientation.upper() if orientation else "VERTICAL"},
|
|
240
|
+
"spaceId": None,
|
|
241
|
+
"sparkSnapshotId": None,
|
|
242
|
+
"topicPageId": None,
|
|
243
|
+
"includeSpace": False,
|
|
244
|
+
"storybookId": None,
|
|
245
|
+
"messagePersistentInput": {
|
|
246
|
+
"attachment_size": attachment_metadata.get('file_size') if attachment_metadata else None,
|
|
247
|
+
"attachment_type": attachment_metadata.get('mime_type') if attachment_metadata else None,
|
|
248
|
+
"bot_message_offline_threading_id": bot_offline_threading_id,
|
|
249
|
+
"conversation_mode": None,
|
|
250
|
+
"external_conversation_id": external_conversation_id,
|
|
251
|
+
"is_new_conversation": True,
|
|
252
|
+
"meta_ai_entry_point": "KADABRA__CHAT__UNIFIED_INPUT_BAR",
|
|
253
|
+
"offline_threading_id": offline_threading_id,
|
|
254
|
+
"prompt_id": None,
|
|
255
|
+
"prompt_session_id": thread_session_id
|
|
256
|
+
},
|
|
257
|
+
"alakazam_enabled": True,
|
|
258
|
+
"skipInFlightMessageWithParams": None,
|
|
259
|
+
"__relay_internal__pv__KadabraSocialSearchEnabledrelayprovider": False,
|
|
260
|
+
"__relay_internal__pv__KadabraZeitgeistEnabledrelayprovider": False,
|
|
261
|
+
"__relay_internal__pv__alakazam_enabledrelayprovider": True,
|
|
262
|
+
"__relay_internal__pv__sp_kadabra_survey_invitationrelayprovider": True,
|
|
263
|
+
"__relay_internal__pv__KadabraAINativeUXrelayprovider": False,
|
|
264
|
+
"__relay_internal__pv__enable_kadabra_partial_resultsrelayprovider": False,
|
|
265
|
+
"__relay_internal__pv__AbraArtifactsEnabledrelayprovider": True,
|
|
266
|
+
"__relay_internal__pv__KadabraMemoryEnabledrelayprovider": False,
|
|
267
|
+
"__relay_internal__pv__AbraPlannerEnabledrelayprovider": True,
|
|
268
|
+
"__relay_internal__pv__AbraWidgetsEnabledrelayprovider": False,
|
|
269
|
+
"__relay_internal__pv__KadabraDeepResearchEnabledrelayprovider": False,
|
|
270
|
+
"__relay_internal__pv__KadabraThinkHarderEnabledrelayprovider": False,
|
|
271
|
+
"__relay_internal__pv__KadabraVergeEnabledrelayprovider": False,
|
|
272
|
+
"__relay_internal__pv__KadabraSpacesEnabledrelayprovider": False,
|
|
273
|
+
"__relay_internal__pv__KadabraProductSearchEnabledrelayprovider": False,
|
|
274
|
+
"__relay_internal__pv__KadabraAreServiceEnabledrelayprovider": False,
|
|
275
|
+
"__relay_internal__pv__kadabra_render_reasoning_response_statesrelayprovider": True,
|
|
276
|
+
"__relay_internal__pv__kadabra_reasoning_cotrelayprovider": False,
|
|
277
|
+
"__relay_internal__pv__AbraSearchInlineReferencesEnabledrelayprovider": True,
|
|
278
|
+
"__relay_internal__pv__AbraComposedTextWidgetsrelayprovider": True,
|
|
279
|
+
"__relay_internal__pv__KadabraNewCitationsEnabledrelayprovider": True,
|
|
280
|
+
"__relay_internal__pv__WebPixelRatiorelayprovider": 1,
|
|
281
|
+
"__relay_internal__pv__KadabraVideoDeliveryRequestrelayprovider": {
|
|
282
|
+
"dash_manifest_requests": [{}],
|
|
283
|
+
"progressive_url_requests": [{"quality": "HD"}, {"quality": "SD"}]
|
|
284
|
+
},
|
|
285
|
+
"__relay_internal__pv__KadabraWidgetsRedesignEnabledrelayprovider": False,
|
|
286
|
+
"__relay_internal__pv__kadabra_enable_send_message_retryrelayprovider": True,
|
|
287
|
+
"__relay_internal__pv__KadabraEmailCalendarIntegrationrelayprovider": False,
|
|
288
|
+
"__relay_internal__pv__kadabra_reels_connect_featuresrelayprovider": False,
|
|
289
|
+
"__relay_internal__pv__AbraBugNubrelayprovider": False,
|
|
290
|
+
"__relay_internal__pv__AbraRedteamingrelayprovider": False,
|
|
291
|
+
"__relay_internal__pv__AbraDebugDevOnlyrelayprovider": False,
|
|
292
|
+
"__relay_internal__pv__kadabra_enable_open_in_editor_message_actionrelayprovider": True,
|
|
293
|
+
"__relay_internal__pv__AbraThreadsEnabledrelayprovider": False,
|
|
294
|
+
"__relay_internal__pv__kadabra_story_builder_enabledrelayprovider": False,
|
|
295
|
+
"__relay_internal__pv__kadabra_imagine_canvas_enable_dev_settingsrelayprovider": False,
|
|
296
|
+
"__relay_internal__pv__kadabra_create_media_deletionrelayprovider": False,
|
|
297
|
+
"__relay_internal__pv__kadabra_moodboardrelayprovider": False,
|
|
298
|
+
"__relay_internal__pv__AbraArtifactDragImagineFromConversationrelayprovider": True,
|
|
299
|
+
"__relay_internal__pv__kadabra_media_item_renderer_heightrelayprovider": 545,
|
|
300
|
+
"__relay_internal__pv__kadabra_media_item_renderer_widthrelayprovider": 620,
|
|
301
|
+
"__relay_internal__pv__AbraQPDocUploadNuxTriggerNamerelayprovider": "meta_dot_ai_abra_web_doc_upload_nux_tour",
|
|
302
|
+
"__relay_internal__pv__AbraSurfaceNuxIDrelayprovider": "12177",
|
|
303
|
+
"__relay_internal__pv__KadabraConversationRenamingrelayprovider": True,
|
|
304
|
+
"__relay_internal__pv__AbraIsLoggedOutrelayprovider": False,
|
|
305
|
+
"__relay_internal__pv__KadabraCanvasDisplayHeaderV2relayprovider": False,
|
|
306
|
+
"__relay_internal__pv__AbraArtifactEditorDebugModerelayprovider": False,
|
|
307
|
+
"__relay_internal__pv__AbraArtifactEditorDownloadHTMLEnabledrelayprovider": False,
|
|
308
|
+
"__relay_internal__pv__kadabra_create_row_hover_optionsrelayprovider": False,
|
|
309
|
+
"__relay_internal__pv__kadabra_media_info_pillsrelayprovider": True,
|
|
310
|
+
"__relay_internal__pv__KadabraConcordInternalProfileBadgeEnabledrelayprovider": False,
|
|
311
|
+
"__relay_internal__pv__KadabraSocialGraphrelayprovider": True
|
|
312
|
+
}, separators=(',', ':'))
|
|
313
|
+
|
|
314
|
+
# print(variables)
|
|
315
|
+
|
|
316
|
+
# Build raw multipart body (exactly as in working example)
|
|
317
|
+
spin_t = str(int(time.time()))
|
|
318
|
+
body = f"""------WebKitFormBoundaryu59CeaZS4ag939lz\r
|
|
319
|
+
Content-Disposition: form-data; name="av"\r
|
|
320
|
+
\r
|
|
321
|
+
813590375178585\r
|
|
322
|
+
------WebKitFormBoundaryu59CeaZS4ag939lz\r
|
|
323
|
+
Content-Disposition: form-data; name="__user"\r
|
|
324
|
+
\r
|
|
325
|
+
0\r
|
|
326
|
+
------WebKitFormBoundaryu59CeaZS4ag939lz\r
|
|
327
|
+
Content-Disposition: form-data; name="__a"\r
|
|
328
|
+
\r
|
|
329
|
+
1\r
|
|
330
|
+
------WebKitFormBoundaryu59CeaZS4ag939lz\r
|
|
331
|
+
Content-Disposition: form-data; name="__req"\r
|
|
332
|
+
\r
|
|
333
|
+
q\r
|
|
334
|
+
------WebKitFormBoundaryu59CeaZS4ag939lz\r
|
|
335
|
+
Content-Disposition: form-data; name="__hs"\r
|
|
336
|
+
\r
|
|
337
|
+
20413.HYP:kadabra_pkg.2.1...0\r
|
|
338
|
+
------WebKitFormBoundaryu59CeaZS4ag939lz\r
|
|
339
|
+
Content-Disposition: form-data; name="dpr"\r
|
|
340
|
+
\r
|
|
341
|
+
1\r
|
|
342
|
+
------WebKitFormBoundaryu59CeaZS4ag939lz\r
|
|
343
|
+
Content-Disposition: form-data; name="__ccg"\r
|
|
344
|
+
\r
|
|
345
|
+
GOOD\r
|
|
346
|
+
------WebKitFormBoundaryu59CeaZS4ag939lz\r
|
|
347
|
+
Content-Disposition: form-data; name="__rev"\r
|
|
348
|
+
\r
|
|
349
|
+
1030219547\r
|
|
350
|
+
------WebKitFormBoundaryu59CeaZS4ag939lz\r
|
|
351
|
+
Content-Disposition: form-data; name="__s"\r
|
|
352
|
+
\r
|
|
353
|
+
q59jx4:9bnqdw:3ats33\r
|
|
354
|
+
------WebKitFormBoundaryu59CeaZS4ag939lz\r
|
|
355
|
+
Content-Disposition: form-data; name="__hsi"\r
|
|
356
|
+
\r
|
|
357
|
+
7575127759957881428\r
|
|
358
|
+
------WebKitFormBoundaryu59CeaZS4ag939lz\r
|
|
359
|
+
Content-Disposition: form-data; name="__dyn"\r
|
|
360
|
+
\r
|
|
361
|
+
7xeUjG1mxu1syUqxemh0no6u5U4e2C1vzEdE98K360CEbo1nEhw2nVEtwMw6ywaq221FwpUO0n24oaEnxO0Bo7O2l0Fwqo31w9O1lwlE-U2zxe2GewbS361qw82dUlwhE-15wmo423-0j52oS0Io5d0bS1LBwNwKG0WE8oC1IwGw-wlUcE2-G2O7E5y1rwa211wo84y1iwfe1aw\r
|
|
362
|
+
------WebKitFormBoundaryu59CeaZS4ag939lz\r
|
|
363
|
+
Content-Disposition: form-data; name="__csr"\r
|
|
364
|
+
\r
|
|
365
|
+
gaJNBjWsAJvliQPqlWFFknAiUB2bBjWLmhyblepaGyVFGy8y2i5pEW68mwwwPwxgtNgv2AMEu6PAgrCwc7F212xxe5YyVC1pAg01sq99uQ1zK0dp75gKzAy8y0EjcgQ8Ek0yMJC6G1og5KrXD4GexS8wdasU8U1e4075UeEuwfCA8K0hWiU2tAyE5m0gm0Jo0xUGxh1veU0gGyWfe0iK1xo32yXhoKkw56pwMw1e25onU4i0TA0xaxu00B1Q2ha2K3V0eqCmawnEgg2Gw\r
|
|
366
|
+
------WebKitFormBoundaryu59CeaZS4ag939lz\r
|
|
367
|
+
Content-Disposition: form-data; name="__hsdp"\r
|
|
368
|
+
\r
|
|
369
|
+
gdDdNhMlJ8bNG7i42aHgWzckH57ylAt8NkkOGCVQ8Ay8myETxW1vh48gHx-UC9Bgpy87G0BUfU7i0JFUeo7Cm12wlo5OawRwDwzxW1zg33wgodU\r
|
|
370
|
+
------WebKitFormBoundaryu59CeaZS4ag939lz\r
|
|
371
|
+
Content-Disposition: form-data; name="__hblp"\r
|
|
372
|
+
\r
|
|
373
|
+
08W5EWt0BzUWp5Q4vz4HOk5kVogDGqmHgyi8xq9gNrxG1vh8B2K6pry4mVk8x28wuE5a1DxO1Qwr84Cu3C1VBwCxK2W2qi2y1LwDwzyK445Gwi63-0wUkxa9AyEjgogy3-\r
|
|
374
|
+
------WebKitFormBoundaryu59CeaZS4ag939lz\r
|
|
375
|
+
Content-Disposition: form-data; name="__sjsp"\r
|
|
376
|
+
\r
|
|
377
|
+
gdDtsAFMlJ8bNG7i47AG5lxmUmDiFQca9U\r
|
|
378
|
+
------WebKitFormBoundaryu59CeaZS4ag939lz\r
|
|
379
|
+
Content-Disposition: form-data; name="__comet_req"\r
|
|
380
|
+
\r
|
|
381
|
+
72\r
|
|
382
|
+
------WebKitFormBoundaryu59CeaZS4ag939lz\r
|
|
383
|
+
Content-Disposition: form-data; name="fb_dtsg"\r
|
|
384
|
+
\r
|
|
385
|
+
{self.fb_dtsg}\r
|
|
386
|
+
------WebKitFormBoundaryu59CeaZS4ag939lz\r
|
|
387
|
+
Content-Disposition: form-data; name="jazoest"\r
|
|
388
|
+
\r
|
|
389
|
+
25499\r
|
|
390
|
+
------WebKitFormBoundaryu59CeaZS4ag939lz\r
|
|
391
|
+
Content-Disposition: form-data; name="lsd"\r
|
|
392
|
+
\r
|
|
393
|
+
{self.lsd}\r
|
|
394
|
+
------WebKitFormBoundaryu59CeaZS4ag939lz\r
|
|
395
|
+
Content-Disposition: form-data; name="__spin_r"\r
|
|
396
|
+
\r
|
|
397
|
+
1030219547\r
|
|
398
|
+
------WebKitFormBoundaryu59CeaZS4ag939lz\r
|
|
399
|
+
Content-Disposition: form-data; name="__spin_b"\r
|
|
400
|
+
\r
|
|
401
|
+
trunk\r
|
|
402
|
+
------WebKitFormBoundaryu59CeaZS4ag939lz\r
|
|
403
|
+
Content-Disposition: form-data; name="__spin_t"\r
|
|
404
|
+
\r
|
|
405
|
+
{spin_t}\r
|
|
406
|
+
------WebKitFormBoundaryu59CeaZS4ag939lz\r
|
|
407
|
+
Content-Disposition: form-data; name="__jssesw"\r
|
|
408
|
+
\r
|
|
409
|
+
1\r
|
|
410
|
+
------WebKitFormBoundaryu59CeaZS4ag939lz\r
|
|
411
|
+
Content-Disposition: form-data; name="__crn"\r
|
|
412
|
+
\r
|
|
413
|
+
comet.kadabra.KadabraAssistantRoute\r
|
|
414
|
+
------WebKitFormBoundaryu59CeaZS4ag939lz\r
|
|
415
|
+
Content-Disposition: form-data; name="fb_api_caller_class"\r
|
|
416
|
+
\r
|
|
417
|
+
RelayModern\r
|
|
418
|
+
------WebKitFormBoundaryu59CeaZS4ag939lz\r
|
|
419
|
+
Content-Disposition: form-data; name="fb_api_req_friendly_name"\r
|
|
420
|
+
\r
|
|
421
|
+
useKadabraSendMessageMutation\r
|
|
422
|
+
------WebKitFormBoundaryu59CeaZS4ag939lz\r
|
|
423
|
+
Content-Disposition: form-data; name="server_timestamps"\r
|
|
424
|
+
\r
|
|
425
|
+
true\r
|
|
426
|
+
------WebKitFormBoundaryu59CeaZS4ag939lz\r
|
|
427
|
+
Content-Disposition: form-data; name="variables"\r
|
|
428
|
+
\r
|
|
429
|
+
{variables}\r
|
|
430
|
+
------WebKitFormBoundaryu59CeaZS4ag939lz\r
|
|
431
|
+
Content-Disposition: form-data; name="doc_id"\r
|
|
432
|
+
\r
|
|
433
|
+
25290947477183545\r
|
|
434
|
+
------WebKitFormBoundaryu59CeaZS4ag939lz--\r
|
|
435
|
+
"""
|
|
436
|
+
|
|
437
|
+
# URL with query parameters
|
|
438
|
+
url = f"{self.GRAPHQL_URL}?fb_dtsg={self.fb_dtsg}&jazoest=25499&lsd={self.lsd}"
|
|
439
|
+
|
|
440
|
+
try:
|
|
441
|
+
response = requests.post(
|
|
442
|
+
url,
|
|
443
|
+
cookies=self.cookies,
|
|
444
|
+
headers=headers,
|
|
445
|
+
data=body.encode('utf-8'),
|
|
446
|
+
timeout=30
|
|
447
|
+
)
|
|
448
|
+
|
|
449
|
+
if response.status_code == 200:
|
|
450
|
+
return external_conversation_id
|
|
451
|
+
else:
|
|
452
|
+
return None
|
|
453
|
+
|
|
454
|
+
except Exception as e:
|
|
455
|
+
return None
|
|
456
|
+
|
|
457
|
+
def fetch_video_urls(
|
|
458
|
+
self,
|
|
459
|
+
conversation_id: str,
|
|
460
|
+
max_attempts: int = 30,
|
|
461
|
+
wait_seconds: int = 5,
|
|
462
|
+
verbose: bool = True
|
|
463
|
+
) -> List[str]:
|
|
464
|
+
"""
|
|
465
|
+
Poll for video URLs from a conversation.
|
|
466
|
+
|
|
467
|
+
Args:
|
|
468
|
+
conversation_id: The conversation ID to fetch videos from
|
|
469
|
+
max_attempts: Maximum number of polling attempts
|
|
470
|
+
wait_seconds: Seconds to wait between attempts
|
|
471
|
+
verbose: Whether to print status messages
|
|
472
|
+
|
|
473
|
+
Returns:
|
|
474
|
+
List of video URLs
|
|
475
|
+
"""
|
|
476
|
+
import logging
|
|
477
|
+
logger = logging.getLogger(__name__)
|
|
478
|
+
|
|
479
|
+
# Build headers with query-specific friendly name
|
|
480
|
+
headers = self.build_headers(
|
|
481
|
+
content_type='application/x-www-form-urlencoded',
|
|
482
|
+
friendly_name='KadabraPromptRootQuery'
|
|
483
|
+
)
|
|
484
|
+
|
|
485
|
+
# Build variables
|
|
486
|
+
variables = {
|
|
487
|
+
"prompt_id": conversation_id,
|
|
488
|
+
"__relay_internal__pv__kadabra_voice_consumptionrelayprovider": False,
|
|
489
|
+
"__relay_internal__pv__AbraIsLoggedOutrelayprovider": False,
|
|
490
|
+
"__relay_internal__pv__KadabraConversationRenamingrelayprovider": True,
|
|
491
|
+
"__relay_internal__pv__KadabraSpacesEnabledrelayprovider": False,
|
|
492
|
+
"__relay_internal__pv__KadabraRecipesEnabledrelayprovider": False,
|
|
493
|
+
"__relay_internal__pv__KadabraFOASharingEnabledrelayprovider": True,
|
|
494
|
+
"__relay_internal__pv__KadabraFeedImageDimensionrelayprovider": 800,
|
|
495
|
+
"__relay_internal__pv__kadabra_story_builder_enabledrelayprovider": False,
|
|
496
|
+
"__relay_internal__pv__kadabra_imagine_canvas_enable_dev_settingsrelayprovider": False,
|
|
497
|
+
"__relay_internal__pv__enable_kadabra_partial_resultsrelayprovider": False,
|
|
498
|
+
"__relay_internal__pv__kadabra_create_media_deletionrelayprovider": False,
|
|
499
|
+
"__relay_internal__pv__kadabra_moodboardrelayprovider": False,
|
|
500
|
+
"__relay_internal__pv__KadabraVideoDeliveryRequestrelayprovider": {
|
|
501
|
+
"dash_manifest_requests": [{}],
|
|
502
|
+
"progressive_url_requests": [{"quality": "HD"}, {"quality": "SD"}]
|
|
503
|
+
},
|
|
504
|
+
"__relay_internal__pv__AbraSearchInlineReferencesEnabledrelayprovider": True,
|
|
505
|
+
"__relay_internal__pv__AbraComposedTextWidgetsrelayprovider": True,
|
|
506
|
+
"__relay_internal__pv__KadabraNewCitationsEnabledrelayprovider": True,
|
|
507
|
+
"__relay_internal__pv__WebPixelRatiorelayprovider": 1,
|
|
508
|
+
"__relay_internal__pv__KadabraWidgetsRedesignEnabledrelayprovider": False,
|
|
509
|
+
"__relay_internal__pv__AbraArtifactDragImagineFromConversationrelayprovider": True,
|
|
510
|
+
"__relay_internal__pv__kadabra_media_item_renderer_heightrelayprovider": 545,
|
|
511
|
+
"__relay_internal__pv__kadabra_media_item_renderer_widthrelayprovider": 620,
|
|
512
|
+
"__relay_internal__pv__AbraBugNubrelayprovider": False,
|
|
513
|
+
"__relay_internal__pv__AbraDebugDevOnlyrelayprovider": False,
|
|
514
|
+
"__relay_internal__pv__abra_silverstone_enable_hidden_commentsrelayprovider": True,
|
|
515
|
+
"__relay_internal__pv__kadabra_voicerelayprovider": True,
|
|
516
|
+
"__relay_internal__pv__KadabraSocialSearchEnabledrelayprovider": False,
|
|
517
|
+
"__relay_internal__pv__KadabraZeitgeistEnabledrelayprovider": False,
|
|
518
|
+
"__relay_internal__pv__alakazam_enabledrelayprovider": True,
|
|
519
|
+
"__relay_internal__pv__sp_kadabra_survey_invitationrelayprovider": True,
|
|
520
|
+
"__relay_internal__pv__KadabraAINativeUXrelayprovider": False,
|
|
521
|
+
"__relay_internal__pv__AbraArtifactsEnabledrelayprovider": True,
|
|
522
|
+
"__relay_internal__pv__KadabraMemoryEnabledrelayprovider": False,
|
|
523
|
+
"__relay_internal__pv__AbraPlannerEnabledrelayprovider": True,
|
|
524
|
+
"__relay_internal__pv__AbraWidgetsEnabledrelayprovider": False,
|
|
525
|
+
"__relay_internal__pv__KadabraDeepResearchEnabledrelayprovider": False,
|
|
526
|
+
"__relay_internal__pv__KadabraThinkHarderEnabledrelayprovider": False,
|
|
527
|
+
"__relay_internal__pv__KadabraVergeEnabledrelayprovider": False,
|
|
528
|
+
"__relay_internal__pv__KadabraProductSearchEnabledrelayprovider": False,
|
|
529
|
+
"__relay_internal__pv__KadabraAreServiceEnabledrelayprovider": False,
|
|
530
|
+
"__relay_internal__pv__kadabra_render_reasoning_response_statesrelayprovider": True,
|
|
531
|
+
"__relay_internal__pv__kadabra_reasoning_cotrelayprovider": False,
|
|
532
|
+
"__relay_internal__pv__kadabra_enable_send_message_retryrelayprovider": True,
|
|
533
|
+
"__relay_internal__pv__KadabraEmailCalendarIntegrationrelayprovider": False,
|
|
534
|
+
"__relay_internal__pv__kadabra_reels_connect_featuresrelayprovider": False,
|
|
535
|
+
"__relay_internal__pv__AbraRedteamingrelayprovider": False,
|
|
536
|
+
"__relay_internal__pv__kadabra_enable_open_in_editor_message_actionrelayprovider": True,
|
|
537
|
+
"__relay_internal__pv__AbraThreadsEnabledrelayprovider": False,
|
|
538
|
+
"__relay_internal__pv__AbraQPDocUploadNuxTriggerNamerelayprovider": "meta_dot_ai_abra_web_doc_upload_nux_tour",
|
|
539
|
+
"__relay_internal__pv__AbraSurfaceNuxIDrelayprovider": "12177"
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
# Build data payload
|
|
543
|
+
data = {
|
|
544
|
+
'av': '813590375178585',
|
|
545
|
+
'__user': '0',
|
|
546
|
+
'__a': '1',
|
|
547
|
+
'__req': 's',
|
|
548
|
+
'__hs': '20413.HYP:kadabra_pkg.2.1...0',
|
|
549
|
+
'dpr': '1',
|
|
550
|
+
'__ccg': 'GOOD',
|
|
551
|
+
'__rev': '1030219547',
|
|
552
|
+
'__s': 'q59jx4:9bnqdw:3ats33',
|
|
553
|
+
'__hsi': '7575127759957881428',
|
|
554
|
+
'__dyn': '7xeUjG1mxu1syUqxemh0no6u5U4e2C1vzEdE98K360CEbo1nEhw2nVEtwMw6ywaq221FwpUO0n24oaEnxO0Bo7O2l0Fwqo31w9O1lwlE-U2zxe2GewbS361qw82dUlwhE-15wmo423-0j52oS0Io5d0bS1LBwNwKG0WE8oC1IwGw-wlUcE2-G2O7E5y1rwa211wo84y1iwfe1aw',
|
|
555
|
+
'__csr': 'gaJNBjWsAJvliQPqlWFFknAiUB2bBjWLmhyblepaGyVFGy8y2i5pEW68mwwwPwxgtNgv2AMEu6PAgrCwc7F212xxe5YyVC1pAg01sq99uQ1zK0dp75gKzAy8y0EjcgQ8Ek0yMJC6G1og5KrXD4GexS8wdasU8U1e4075UeEuwfCA8K0hWiU2tAyE5m0gm0Jo0xUGxh1veU0gGyWfe0iK1xo32yXhoKkw56pwMw1e25onU4i0TA0xaxu00B1Q2ha2K3V0eqCmawnEgg2Gw',
|
|
556
|
+
'__hsdp': 'gdDdNhMlJ8bNG7i42aHgWzckH57ylAt8NkkOGCVQ8Ay8myETxW1vh48gHx-UC9Bgpy87G0BUfU7i0JFUeo7Cm12wlo5OawRwDwzxW1zg33wgodU',
|
|
557
|
+
'__hblp': '08W5EWt0BzUWp5Q4vz4HOk5kVogDGqmHgyi8xq9gNrxG1vh8B2K6pry4mVk8x28wuE5a1DxO1Qwr84Cu3C1VBwCxK2W2qi2y1LwDwzyK445Gwi63-0wUkxa9AyEjgogy3-',
|
|
558
|
+
'__sjsp': 'gdDtsAFMlJ8bNG7i47AG5lxmUmDiFQca9U',
|
|
559
|
+
'__comet_req': '72',
|
|
560
|
+
'fb_dtsg': self.fb_dtsg,
|
|
561
|
+
'jazoest': '25499',
|
|
562
|
+
'lsd': self.lsd,
|
|
563
|
+
'__spin_r': '1030219547',
|
|
564
|
+
'__spin_b': 'trunk',
|
|
565
|
+
'__spin_t': str(int(time.time())),
|
|
566
|
+
'__jssesw': '1',
|
|
567
|
+
'__crn': 'comet.kadabra.KadabraAssistantRoute',
|
|
568
|
+
'fb_api_caller_class': 'RelayModern',
|
|
569
|
+
'fb_api_req_friendly_name': 'KadabraPromptRootQuery',
|
|
570
|
+
'server_timestamps': 'true',
|
|
571
|
+
'variables': json.dumps(variables),
|
|
572
|
+
'doc_id': '25290569913909283',
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
for attempt in range(1, max_attempts + 1):
|
|
576
|
+
try:
|
|
577
|
+
if verbose and attempt % 5 == 1: # Log every 5th attempt
|
|
578
|
+
logger.info(f"[VIDEO POLLING] Attempt {attempt}/{max_attempts} for conversation {conversation_id}")
|
|
579
|
+
|
|
580
|
+
response = requests.post(
|
|
581
|
+
self.GRAPHQL_URL,
|
|
582
|
+
cookies=self.cookies,
|
|
583
|
+
headers=headers,
|
|
584
|
+
data=data,
|
|
585
|
+
timeout=30
|
|
586
|
+
)
|
|
587
|
+
|
|
588
|
+
if response.status_code == 200:
|
|
589
|
+
# Extract video URLs from response, passing attempt info to reduce noise
|
|
590
|
+
is_final_attempt = (attempt == max_attempts)
|
|
591
|
+
video_urls = self._extract_video_urls_from_response(
|
|
592
|
+
response.text,
|
|
593
|
+
is_final_attempt=is_final_attempt
|
|
594
|
+
)
|
|
595
|
+
|
|
596
|
+
if video_urls:
|
|
597
|
+
if verbose:
|
|
598
|
+
logger.info(f"[VIDEO POLLING] ✓ Successfully extracted {len(video_urls)} video URL(s) on attempt {attempt}")
|
|
599
|
+
return video_urls
|
|
600
|
+
else:
|
|
601
|
+
if verbose and attempt % 5 == 0: # Log every 5th attempt
|
|
602
|
+
logger.debug(f"[VIDEO POLLING] No URLs yet on attempt {attempt}, continuing...")
|
|
603
|
+
time.sleep(wait_seconds)
|
|
604
|
+
else:
|
|
605
|
+
if verbose:
|
|
606
|
+
logger.warning(f"[VIDEO POLLING] HTTP {response.status_code} on attempt {attempt}")
|
|
607
|
+
time.sleep(wait_seconds)
|
|
608
|
+
|
|
609
|
+
except Exception as e:
|
|
610
|
+
if verbose:
|
|
611
|
+
logger.error(f"[VIDEO POLLING] Error on attempt {attempt}: {e}")
|
|
612
|
+
time.sleep(wait_seconds)
|
|
613
|
+
|
|
614
|
+
if verbose:
|
|
615
|
+
logger.error(f"[VIDEO POLLING] ⚠️ Failed to extract video URLs after {max_attempts} attempts")
|
|
616
|
+
return []
|
|
617
|
+
|
|
618
|
+
@staticmethod
|
|
619
|
+
def _extract_video_urls_from_response(response_text: str, is_final_attempt: bool = False) -> List[str]:
|
|
620
|
+
"""
|
|
621
|
+
Extract video URLs from Meta AI GraphQL response.
|
|
622
|
+
Uses the CORRECT structure from the original GitHub repo.
|
|
623
|
+
|
|
624
|
+
Args:
|
|
625
|
+
response_text: The response text to extract URLs from
|
|
626
|
+
is_final_attempt: Whether this is the final polling attempt (for logging)
|
|
627
|
+
|
|
628
|
+
Returns:
|
|
629
|
+
List of video URLs
|
|
630
|
+
"""
|
|
631
|
+
import logging
|
|
632
|
+
logger = logging.getLogger(__name__)
|
|
633
|
+
|
|
634
|
+
# Only log detailed extraction info on final attempt or when verbose debugging
|
|
635
|
+
log_details = is_final_attempt or logger.isEnabledFor(logging.DEBUG)
|
|
636
|
+
|
|
637
|
+
if log_details:
|
|
638
|
+
logger.debug("[VIDEO URL EXTRACTION] Starting extraction with proper structure...")
|
|
639
|
+
logger.debug(f"[VIDEO URL EXTRACTION] Response length: {len(response_text)} characters")
|
|
640
|
+
|
|
641
|
+
urls: List[str] = []
|
|
642
|
+
|
|
643
|
+
try:
|
|
644
|
+
# Parse the response
|
|
645
|
+
data = json.loads(response_text)
|
|
646
|
+
if log_details:
|
|
647
|
+
logger.debug("[VIDEO URL EXTRACTION] Successfully parsed response as JSON")
|
|
648
|
+
|
|
649
|
+
# CORRECT STRUCTURE from original GitHub code:
|
|
650
|
+
# data -> xfb_genai_fetch_post (or xab_abra__xfb_genai_fetch_post)
|
|
651
|
+
# -> messages -> edges -> node -> content -> imagine_video
|
|
652
|
+
|
|
653
|
+
data_obj = data.get("data", {})
|
|
654
|
+
fetch_post = data_obj.get("xfb_genai_fetch_post") or data_obj.get("xab_abra__xfb_genai_fetch_post") or {}
|
|
655
|
+
|
|
656
|
+
if not fetch_post and log_details:
|
|
657
|
+
logger.debug("[VIDEO URL EXTRACTION] No xfb_genai_fetch_post or xab_abra__xfb_genai_fetch_post found in response")
|
|
658
|
+
elif log_details:
|
|
659
|
+
logger.debug(f"[VIDEO URL EXTRACTION] Found fetch_post: {list(fetch_post.keys())}")
|
|
660
|
+
|
|
661
|
+
messages = fetch_post.get("messages", {}).get("edges", [])
|
|
662
|
+
if log_details:
|
|
663
|
+
logger.debug(f"[VIDEO URL EXTRACTION] Found {len(messages)} message edges")
|
|
664
|
+
|
|
665
|
+
for edge_idx, edge in enumerate(messages):
|
|
666
|
+
node = edge.get("node", {})
|
|
667
|
+
content = node.get("content", {})
|
|
668
|
+
imagine_video = content.get("imagine_video") or {}
|
|
669
|
+
|
|
670
|
+
if not imagine_video:
|
|
671
|
+
if log_details:
|
|
672
|
+
logger.debug(f"[VIDEO URL EXTRACTION] Edge {edge_idx}: No imagine_video found")
|
|
673
|
+
continue
|
|
674
|
+
|
|
675
|
+
if log_details:
|
|
676
|
+
logger.debug(f"[VIDEO URL EXTRACTION] Edge {edge_idx}: Found imagine_video with keys: {list(imagine_video.keys())}")
|
|
677
|
+
|
|
678
|
+
# Extract from videos.nodes[] array
|
|
679
|
+
videos = imagine_video.get("videos", {}).get("nodes", [])
|
|
680
|
+
if log_details:
|
|
681
|
+
logger.debug(f"[VIDEO URL EXTRACTION] Edge {edge_idx}: Found {len(videos)} video nodes")
|
|
682
|
+
|
|
683
|
+
for video_idx, video in enumerate(videos):
|
|
684
|
+
# Try video_url or uri
|
|
685
|
+
uri = video.get("video_url") or video.get("uri")
|
|
686
|
+
if uri:
|
|
687
|
+
if log_details:
|
|
688
|
+
logger.debug(f"[VIDEO URL EXTRACTION] Found video_url/uri in videos.nodes[{video_idx}]: {uri[:100]}...")
|
|
689
|
+
urls.append(uri)
|
|
690
|
+
|
|
691
|
+
# Try videoDeliveryResponseResult.progressive_urls[]
|
|
692
|
+
delivery = video.get("videoDeliveryResponseResult") or {}
|
|
693
|
+
prog = delivery.get("progressive_urls", [])
|
|
694
|
+
if log_details and prog:
|
|
695
|
+
logger.debug(f"[VIDEO URL EXTRACTION] Found {len(prog)} progressive_urls in video {video_idx}")
|
|
696
|
+
|
|
697
|
+
for prog_idx, p in enumerate(prog):
|
|
698
|
+
pu = p.get("progressive_url")
|
|
699
|
+
if pu:
|
|
700
|
+
if log_details:
|
|
701
|
+
logger.debug(f"[VIDEO URL EXTRACTION] Found progressive_url[{prog_idx}]: {pu[:100]}...")
|
|
702
|
+
urls.append(pu)
|
|
703
|
+
|
|
704
|
+
# Extract from single video object
|
|
705
|
+
single_video = imagine_video.get("video") or {}
|
|
706
|
+
if isinstance(single_video, dict) and single_video:
|
|
707
|
+
if log_details:
|
|
708
|
+
logger.debug(f"[VIDEO URL EXTRACTION] Found single video object with keys: {list(single_video.keys())}")
|
|
709
|
+
|
|
710
|
+
uri = single_video.get("video_url") or single_video.get("uri")
|
|
711
|
+
if uri:
|
|
712
|
+
if log_details:
|
|
713
|
+
logger.debug(f"[VIDEO URL EXTRACTION] Found video_url/uri in single video: {uri[:100]}...")
|
|
714
|
+
urls.append(uri)
|
|
715
|
+
|
|
716
|
+
delivery = single_video.get("videoDeliveryResponseResult") or {}
|
|
717
|
+
prog = delivery.get("progressive_urls", [])
|
|
718
|
+
if log_details and prog:
|
|
719
|
+
logger.debug(f"[VIDEO URL EXTRACTION] Found {len(prog)} progressive_urls in single video")
|
|
720
|
+
|
|
721
|
+
for prog_idx, p in enumerate(prog):
|
|
722
|
+
pu = p.get("progressive_url")
|
|
723
|
+
if pu:
|
|
724
|
+
if log_details:
|
|
725
|
+
logger.debug(f"[VIDEO URL EXTRACTION] Found progressive_url[{prog_idx}]: {pu[:100]}...")
|
|
726
|
+
urls.append(pu)
|
|
727
|
+
|
|
728
|
+
except json.JSONDecodeError as e:
|
|
729
|
+
if is_final_attempt:
|
|
730
|
+
logger.error(f"[VIDEO URL EXTRACTION] JSON decode failed: {e}")
|
|
731
|
+
logger.debug(f"[VIDEO URL EXTRACTION] Response preview: {response_text[:500]}")
|
|
732
|
+
|
|
733
|
+
# Fallback to regex
|
|
734
|
+
if log_details:
|
|
735
|
+
logger.debug("[VIDEO URL EXTRACTION] Falling back to regex extraction...")
|
|
736
|
+
import re
|
|
737
|
+
urls = re.findall(r'https?://[^\s"\'<>]+fbcdn[^\s"\'<>]+\.mp4[^\s"\'<>]*', response_text)
|
|
738
|
+
if log_details:
|
|
739
|
+
logger.debug(f"[VIDEO URL EXTRACTION] Regex found {len(urls)} .mp4 URLs")
|
|
740
|
+
|
|
741
|
+
# Deduplicate while preserving order
|
|
742
|
+
seen = set()
|
|
743
|
+
unique_urls: List[str] = []
|
|
744
|
+
for u in urls:
|
|
745
|
+
if u and u not in seen:
|
|
746
|
+
seen.add(u)
|
|
747
|
+
unique_urls.append(u)
|
|
748
|
+
|
|
749
|
+
# Only log final result if we have URLs or if this is the final attempt
|
|
750
|
+
if unique_urls:
|
|
751
|
+
if log_details:
|
|
752
|
+
logger.debug(f"[VIDEO URL EXTRACTION] Final result: {len(unique_urls)} unique video URLs")
|
|
753
|
+
for idx, url in enumerate(unique_urls, 1):
|
|
754
|
+
logger.debug(f"[VIDEO URL EXTRACTION] Final URL {idx}: {url[:150]}...")
|
|
755
|
+
elif is_final_attempt:
|
|
756
|
+
logger.warning("[VIDEO URL EXTRACTION] ⚠️ NO VIDEO URLs FOUND!")
|
|
757
|
+
logger.debug(f"[VIDEO URL EXTRACTION] Response preview (first 1000 chars): {response_text[:1000]}")
|
|
758
|
+
|
|
759
|
+
return unique_urls
|
|
760
|
+
|
|
761
|
+
def generate_video(
|
|
762
|
+
self,
|
|
763
|
+
prompt: str,
|
|
764
|
+
media_ids: Optional[List[str]] = None,
|
|
765
|
+
attachment_metadata: Optional[Dict[str, Any]] = None,
|
|
766
|
+
orientation: Optional[str] = None,
|
|
767
|
+
wait_before_poll: int = 10,
|
|
768
|
+
max_attempts: int = 30,
|
|
769
|
+
wait_seconds: int = 5,
|
|
770
|
+
verbose: bool = True
|
|
771
|
+
) -> Dict:
|
|
772
|
+
"""
|
|
773
|
+
Main function to generate video and retrieve URLs.
|
|
774
|
+
|
|
775
|
+
Args:
|
|
776
|
+
prompt: Text prompt for video generation
|
|
777
|
+
media_ids: Optional list of media IDs from uploaded images
|
|
778
|
+
attachment_metadata: Optional dict with 'file_size' (int) and 'mime_type' (str)
|
|
779
|
+
orientation: Video orientation ("LANDSCAPE", "VERTICAL", "SQUARE"). Defaults to "VERTICAL".
|
|
780
|
+
wait_before_poll: Seconds to wait before starting to poll
|
|
781
|
+
max_attempts: Maximum polling attempts
|
|
782
|
+
wait_seconds: Seconds between polling attempts
|
|
783
|
+
verbose: Whether to print status messages
|
|
784
|
+
|
|
785
|
+
Returns:
|
|
786
|
+
Dictionary with success status, conversation_id, prompt, video_urls, and timestamp
|
|
787
|
+
"""
|
|
788
|
+
# Step 1: Create video generation request
|
|
789
|
+
conversation_id = self.create_video_generation_request(
|
|
790
|
+
prompt_text=prompt,
|
|
791
|
+
media_ids=media_ids,
|
|
792
|
+
attachment_metadata=attachment_metadata,
|
|
793
|
+
orientation=orientation,
|
|
794
|
+
verbose=verbose
|
|
795
|
+
)
|
|
796
|
+
|
|
797
|
+
if not conversation_id:
|
|
798
|
+
return {"success": False, "error": "Failed to create video generation request"}
|
|
799
|
+
|
|
800
|
+
# Step 2: Wait a bit before polling
|
|
801
|
+
time.sleep(wait_before_poll)
|
|
802
|
+
|
|
803
|
+
# Step 3: Poll for video URLs
|
|
804
|
+
video_urls = self.fetch_video_urls(
|
|
805
|
+
conversation_id=conversation_id,
|
|
806
|
+
max_attempts=max_attempts,
|
|
807
|
+
wait_seconds=wait_seconds,
|
|
808
|
+
verbose=verbose
|
|
809
|
+
)
|
|
810
|
+
|
|
811
|
+
result = {
|
|
812
|
+
"success": len(video_urls) > 0,
|
|
813
|
+
"conversation_id": conversation_id,
|
|
814
|
+
"prompt": prompt,
|
|
815
|
+
"video_urls": video_urls,
|
|
816
|
+
"timestamp": time.time()
|
|
817
|
+
}
|
|
818
|
+
|
|
819
|
+
return result
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: metaai-sdk
|
|
3
|
-
Version: 2.3.
|
|
3
|
+
Version: 2.3.5
|
|
4
4
|
Summary: Feature-rich Python SDK for Meta AI - Chat, Image & Video Generation powered by Llama 3
|
|
5
5
|
Author-email: Ashiq Hussain Mir <imseldrith@gmail.com>
|
|
6
6
|
License-Expression: MIT
|
|
@@ -27,8 +27,8 @@ Requires-Dist: lxml-html-clean>=0.1.1
|
|
|
27
27
|
Requires-Dist: beautifulsoup4>=4.9.0
|
|
28
28
|
Requires-Dist: python-multipart>=0.0.21
|
|
29
29
|
Provides-Extra: api
|
|
30
|
-
Requires-Dist: fastapi
|
|
31
|
-
Requires-Dist: uvicorn[standard]
|
|
30
|
+
Requires-Dist: fastapi>=0.95.2; extra == "api"
|
|
31
|
+
Requires-Dist: uvicorn[standard]>=0.22.0; extra == "api"
|
|
32
32
|
Requires-Dist: python-multipart>=0.0.6; extra == "api"
|
|
33
33
|
Requires-Dist: python-dotenv>=1.0.0; extra == "api"
|
|
34
34
|
Provides-Extra: dev
|
|
@@ -487,22 +487,29 @@ print(f"\n🎉 Generated {len(videos)} videos successfully!")
|
|
|
487
487
|
🎉 Generated 3 videos successfully!
|
|
488
488
|
```
|
|
489
489
|
|
|
490
|
-
### Example 3: Advanced Video Generation
|
|
490
|
+
### Example 3: Advanced Video Generation with Orientation
|
|
491
491
|
|
|
492
492
|
```python
|
|
493
493
|
from metaai_api import MetaAI
|
|
494
494
|
|
|
495
495
|
ai = MetaAI(cookies=cookies)
|
|
496
496
|
|
|
497
|
-
#
|
|
497
|
+
# Generate video with specific orientation (default is VERTICAL)
|
|
498
498
|
result = ai.generate_video(
|
|
499
499
|
prompt="A time-lapse of a flower blooming",
|
|
500
|
+
orientation="VERTICAL", # Options: "LANDSCAPE", "VERTICAL", "SQUARE"
|
|
500
501
|
wait_before_poll=15, # Wait 15 seconds before checking
|
|
501
502
|
max_attempts=50, # Try up to 50 times
|
|
502
503
|
wait_seconds=3, # Wait 3 seconds between attempts
|
|
503
504
|
verbose=True # Show detailed progress
|
|
504
505
|
)
|
|
505
506
|
|
|
507
|
+
# Generate landscape video for widescreen
|
|
508
|
+
result_landscape = ai.generate_video(
|
|
509
|
+
prompt="Panoramic view of sunset over mountains",
|
|
510
|
+
orientation="LANDSCAPE" # Wide format (16:9)
|
|
511
|
+
)
|
|
512
|
+
|
|
506
513
|
if result["success"]:
|
|
507
514
|
print(f"\n🎬 Your videos are ready!")
|
|
508
515
|
print(f"🔗 Generated {len(result['video_urls'])} videos:")
|
|
@@ -511,6 +518,14 @@ if result["success"]:
|
|
|
511
518
|
print(f"⏱️ Generated at: {result['timestamp']}")
|
|
512
519
|
```
|
|
513
520
|
|
|
521
|
+
**Supported Video Orientations:**
|
|
522
|
+
|
|
523
|
+
- `"LANDSCAPE"` - Wide/horizontal (16:9) - ideal for widescreen, cinematic content
|
|
524
|
+
- `"VERTICAL"` - Tall/vertical (9:16) - ideal for mobile, stories, reels (default)
|
|
525
|
+
- `"SQUARE"` - Equal dimensions (1:1) - ideal for social posts
|
|
526
|
+
|
|
527
|
+
````
|
|
528
|
+
|
|
514
529
|
📖 **Full Video Guide:** See [VIDEO_GENERATION_README.md](https://github.com/mir-ashiq/metaai-api/blob/main/VIDEO_GENERATION_README.md) for complete documentation!
|
|
515
530
|
|
|
516
531
|
---
|
|
@@ -565,7 +580,7 @@ if result["success"]:
|
|
|
565
580
|
)
|
|
566
581
|
if video["success"]:
|
|
567
582
|
print(f"🎬 Video: {video['video_urls'][0]}")
|
|
568
|
-
|
|
583
|
+
````
|
|
569
584
|
|
|
570
585
|
**Output:**
|
|
571
586
|
|
|
@@ -583,7 +598,7 @@ if result["success"]:
|
|
|
583
598
|
|
|
584
599
|
## 🎨 Image Generation
|
|
585
600
|
|
|
586
|
-
Generate AI-powered images (requires Facebook authentication):
|
|
601
|
+
Generate AI-powered images with customizable orientations (requires Facebook authentication):
|
|
587
602
|
|
|
588
603
|
```python
|
|
589
604
|
from metaai_api import MetaAI
|
|
@@ -591,9 +606,25 @@ from metaai_api import MetaAI
|
|
|
591
606
|
# Initialize with Facebook credentials
|
|
592
607
|
ai = MetaAI(fb_email="your_email@example.com", fb_password="your_password")
|
|
593
608
|
|
|
594
|
-
# Generate images
|
|
609
|
+
# Generate images with default orientation (VERTICAL)
|
|
595
610
|
response = ai.prompt("Generate an image of a cyberpunk cityscape at night with neon lights")
|
|
596
611
|
|
|
612
|
+
# Or specify orientation explicitly
|
|
613
|
+
response_landscape = ai.prompt(
|
|
614
|
+
"Generate an image of a panoramic mountain landscape",
|
|
615
|
+
orientation="LANDSCAPE" # Options: "LANDSCAPE", "VERTICAL", "SQUARE"
|
|
616
|
+
)
|
|
617
|
+
|
|
618
|
+
response_vertical = ai.prompt(
|
|
619
|
+
"Generate an image of a tall waterfall",
|
|
620
|
+
orientation="VERTICAL" # Tall/portrait format (default)
|
|
621
|
+
)
|
|
622
|
+
|
|
623
|
+
response_square = ai.prompt(
|
|
624
|
+
"Generate an image of a centered mandala pattern",
|
|
625
|
+
orientation="SQUARE" # Square format (1:1)
|
|
626
|
+
)
|
|
627
|
+
|
|
597
628
|
# Display results (Meta AI generates 4 images by default)
|
|
598
629
|
print(f"🎨 Generated {len(response['media'])} images:")
|
|
599
630
|
for i, image in enumerate(response['media'], 1):
|
|
@@ -601,6 +632,12 @@ for i, image in enumerate(response['media'], 1):
|
|
|
601
632
|
print(f" Prompt: {image['prompt']}")
|
|
602
633
|
```
|
|
603
634
|
|
|
635
|
+
**Supported Orientations:**
|
|
636
|
+
|
|
637
|
+
- `"LANDSCAPE"` - Wide/horizontal format (16:9) - ideal for panoramas, landscapes
|
|
638
|
+
- `"VERTICAL"` - Tall/vertical format (9:16) - ideal for portraits, mobile content (default)
|
|
639
|
+
- `"SQUARE"` - Equal dimensions (1:1) - ideal for social media, profile images
|
|
640
|
+
|
|
604
641
|
**Output:**
|
|
605
642
|
|
|
606
643
|
```
|
|
@@ -680,7 +717,6 @@ class MetaAI:
|
|
|
680
717
|
**Methods:**
|
|
681
718
|
|
|
682
719
|
- **`prompt(message, stream=False, new_conversation=False)`**
|
|
683
|
-
|
|
684
720
|
- Send a chat message
|
|
685
721
|
- Returns: `dict` with `message`, `sources`, and `media`
|
|
686
722
|
|
|
@@ -3,10 +3,11 @@ metaai_api/api_server.py,sha256=hb1C3rEarPOHF6W-qqENaxrZWwn8A0qUrjSVlRtNV2s,1324
|
|
|
3
3
|
metaai_api/client.py,sha256=Th46qW1l8OS8Hu5pj0aGFn4iQNz62A3sbXko-LP-SAU,5263
|
|
4
4
|
metaai_api/exceptions.py,sha256=MRRAppZa0OFA0QLSvC0nABgZN_Ll1dUq9JfhECTqV-Q,114
|
|
5
5
|
metaai_api/image_upload.py,sha256=DQ2xqKdM1I_pF1rZBsB7-QTvXLzke2_0XiIOxFhpc70,6563
|
|
6
|
-
metaai_api/main.py,sha256=
|
|
6
|
+
metaai_api/main.py,sha256=bZrao6uZI-CdtHvdu64jUH72h0R7atN2PNrP5TaHpMM,36738
|
|
7
7
|
metaai_api/utils.py,sha256=qzfIO3WkRH-gSV99b8RiECnMOku8lZEY3Jka9lTLExA,11979
|
|
8
|
-
|
|
9
|
-
metaai_sdk-2.3.
|
|
10
|
-
metaai_sdk-2.3.
|
|
11
|
-
metaai_sdk-2.3.
|
|
12
|
-
metaai_sdk-2.3.
|
|
8
|
+
metaai_api/video_generation.py,sha256=Kw0Hwqeai6EexPhJsN-0eLSmIL0zDp95EwAPk6UQ9Rs,37480
|
|
9
|
+
metaai_sdk-2.3.5.dist-info/licenses/LICENSE,sha256=hRLLSBixyX0tRh2k0iOGoF7nx-l-vBChNffFfVOIEtc,1290
|
|
10
|
+
metaai_sdk-2.3.5.dist-info/METADATA,sha256=adQytqJvLOkHunqgkZet0ImIU74pTSbZK2udaC0utN4,28998
|
|
11
|
+
metaai_sdk-2.3.5.dist-info/WHEEL,sha256=qELbo2s1Yzl39ZmrAibXA2jjPLUYfnVhUNTlyF1rq0Y,92
|
|
12
|
+
metaai_sdk-2.3.5.dist-info/top_level.txt,sha256=R6YCiIQLYFKKaqhNZXDwXbpj1u01P_YhcMCVbJiDUJs,11
|
|
13
|
+
metaai_sdk-2.3.5.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|