Skip to content

Commit 830f536

Browse files
authored
Merge pull request #6 from Strvm/feat/auth_and_image_generation
feat: FB auth + image generation
2 parents 992a696 + 336b0eb commit 830f536

File tree

7 files changed

+319
-22
lines changed

7 files changed

+319
-22
lines changed

README.md

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ Meta AI is running Llama 3 LLM.
1010

1111
## Features
1212
- **Prompt AI**: Send a message to the AI and get a response from Llama 3.
13+
- **Image Generation**: Generate images using the AI. (Only for FB authenticated users)
1314
- **Get Up To Date Information**: Get the latest information from the AI thanks to its connection to the internet.
1415
- **Get Sources**: Get the sources of the information provided by the AI.
1516
- **Streaming**: Stream the AI's response in real-time or get the final response.
@@ -124,3 +125,49 @@ for r in response:
124125
...
125126
{'message': "The Golden State Warriors' last game was against the Sacramento Kings on April 16, 2024, at the Golden 1 Center in Sacramento, California. The Kings won the game with a score of 118-94, with the Warriors scoring 22 points in the first quarter, 28 in the second, 26 in the third and 18 in the fourth quarter ¹.\n", 'sources': [{'link': 'https://sportradar.com/', 'title': 'Game Info of NBA from sportradar.com'}]}
126127
```
128+
129+
**Generate Image**:
130+
131+
By default image generation is only available for FB authenticated users. If you go on https://www.meta.ai/ , and ask the AI to generate an image, you will be prompted to authenticate with Facebook.
132+
So if you want to generate images using this library, you need to authenticate using your FB credentials.
133+
134+
**Note**: There seems to be higher rate limits for authenticated users. So only authenticate to generate images.
135+
136+
```python
137+
from meta_ai_api import MetaAI
138+
ai = MetaAI(fb_email="your_fb_email", fb_password="your_fb_password")
139+
resp = ai.prompt(message="Generate an image of a tech CEO")
140+
print(resp)
141+
```
142+
143+
```json
144+
{
145+
"message":"\n",
146+
"sources":[
147+
148+
],
149+
"media":[
150+
{
151+
"url":"https://scontent-lax3-1.xx.fbcdn.net/o1/v/t0/f1/m247/4282108942387920518_1946149595_21-04-2024-14-17-48.jpeg?_nc_ht=scontent-lax3-1.xx.fbcdn.net&_nc_cat=103&ccb=9-4&oh=00_AfCnbCX7nl_J5kF6mahnams4d99Rs5WZA780HGS_scfc6A&oe=662771EE&_nc_sid=5b3566",
152+
"type":"IMAGE",
153+
"prompt":"a tech CEO"
154+
},
155+
{
156+
"url":"https://scontent-lax3-1.xx.fbcdn.net/o1/v/t0/f1/m247/3356467762794691754_1025991308_21-04-2024-14-17-48.jpeg?_nc_ht=scontent-lax3-1.xx.fbcdn.net&_nc_cat=108&ccb=9-4&oh=00_AfBLmSbTSqshNAL82KIFk8hGXyL8iK_CZLGcMmmddPrxuA&oe=66276EDD&_nc_sid=5b3566",
157+
"type":"IMAGE",
158+
"prompt":"a tech CEO"
159+
},
160+
{
161+
"url":"https://scontent-lax3-1.xx.fbcdn.net/o1/v/t0/f1/m247/127487551948523111_2181921077_21-04-2024-14-17-48.jpeg?_nc_ht=scontent-lax3-1.xx.fbcdn.net&_nc_cat=104&ccb=9-4&oh=00_AfAejXKeKPA4vyKXoc6UR0rEirvZwi41P3KiCSQmHRHsEw&oe=66276E45&_nc_sid=5b3566",
162+
"type":"IMAGE",
163+
"prompt":"a tech CEO"
164+
},
165+
{
166+
"url":"https://scontent-lax3-1.xx.fbcdn.net/o1/v/t0/f1/m247/3497663176351797954_3954783377_21-04-2024-14-17-47.jpeg?_nc_ht=scontent-lax3-1.xx.fbcdn.net&_nc_cat=110&ccb=9-4&oh=00_AfBp3bAfcuofqtI-z9D4bHw-GuGgCNPH_xhMM0PG_95S9Q&oe=66277AE9&_nc_sid=5b3566",
167+
"type":"IMAGE",
168+
"prompt":"a tech CEO"
169+
}
170+
]
171+
}
172+
```
173+
![Tech CEO](https://scontent-lax3-1.xx.fbcdn.net/o1/v/t0/f1/m247/3497663176351797954_3954783377_21-04-2024-14-17-47.jpeg?_nc_ht=scontent-lax3-1.xx.fbcdn.net&_nc_cat=110&ccb=9-4&oh=00_AfBp3bAfcuofqtI-z9D4bHw-GuGgCNPH_xhMM0PG_95S9Q&oe=66277AE9&_nc_sid=5b3566)

pyproject.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ python = "^3.7"
1313
requests = "2.31.0"
1414
requests-html = "0.10.0"
1515
lxml_html_cleaner = "0.1.1"
16+
bs4 = "0.0.2"
1617

1718
[build]
1819
script = "build.py"

requirements.txt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
requests==2.31.0
22
requests-html==0.10.0
3-
lxml_html_clean==0.1.1
3+
lxml_html_clean==0.1.1
4+
bs4==0.0.2

src/meta_ai_api/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
1-
__version__ = "1.1.2"
1+
__version__ = "1.1.3"
22
from .main import MetaAI # noqa

src/meta_ai_api/exceptions.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
class FacebookInvalidCredentialsException(Exception):
2+
pass

src/meta_ai_api/main.py

Lines changed: 78 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
format_response,
1515
)
1616

17+
from meta_ai_api.utils import get_fb_session
1718

1819
MAX_RETRIES = 3
1920

@@ -24,7 +25,7 @@ class MetaAI:
2425
and receiving messages from the Meta AI Chat API.
2526
"""
2627

27-
def __init__(self):
28+
def __init__(self, fb_email: str = None, fb_password: str = None):
2829
self.session = requests.Session()
2930
self.session.headers.update(
3031
{
@@ -33,7 +34,10 @@ def __init__(self):
3334
}
3435
)
3536
self.access_token = None
36-
self.cookies = None
37+
self.fb_email = fb_email
38+
self.fb_password = fb_password
39+
self.is_authed = fb_password is not None and fb_email is not None
40+
self.cookies = self.get_cookies()
3741

3842
def get_access_token(self) -> str:
3943
"""
@@ -42,9 +46,7 @@ def get_access_token(self) -> str:
4246
Returns:
4347
str: A valid access token.
4448
"""
45-
self.cookies = self.get_cookies()
4649
url = "https://www.meta.ai/api/graphql/"
47-
4850
payload = {
4951
"lsd": self.cookies["lsd"],
5052
"fb_api_caller_class": "RelayModern",
@@ -89,15 +91,20 @@ def prompt(
8991
Raises:
9092
Exception: If unable to obtain a valid response after several attempts.
9193
"""
92-
if not self.access_token:
94+
if not self.access_token and not self.is_authed:
9395
self.access_token = self.get_access_token()
96+
auth_payload = {"access_token": self.access_token}
97+
url = "https://graph.meta.ai/graphql?locale=user"
98+
99+
else:
100+
auth_payload = {"fb_dtsg": self.cookies["fb_dtsg"]}
101+
url = "https://www.meta.ai/api/graphql/"
94102

95103
# Need to sleep for a bit, for some reason the API doesn't like it when we send request too quickly
96104
# (maybe Meta needs to register Cookies on their side?)
97105
time.sleep(1)
98-
url = "https://graph.meta.ai/graphql?locale=user"
99106
payload = {
100-
"access_token": self.access_token,
107+
**auth_payload,
101108
"fb_api_caller_class": "RelayModern",
102109
"fb_api_req_friendly_name": "useAbraSendMessageMutation",
103110
"variables": json.dumps(
@@ -123,6 +130,10 @@ def prompt(
123130
"content-type": "application/x-www-form-urlencoded",
124131
"x-fb-friendly-name": "useAbraSendMessageMutation",
125132
}
133+
if self.is_authed:
134+
headers["cookie"] = f'abra_sess={self.cookies["abra_sess"]}'
135+
# Recreate the session to avoid cookie leakage when user is authenticated
136+
self.session = requests.Session()
126137

127138
response = self.session.post(url, headers=headers, data=payload, stream=stream)
128139
if not stream:
@@ -220,33 +231,78 @@ def extract_data(self, json_line: dict):
220231
response = format_response(response=json_line)
221232
fetch_id = bot_response_message.get("fetch_id")
222233
sources = self.fetch_sources(fetch_id) if fetch_id else []
223-
return {"message": response, "sources": sources}
234+
medias = self.extract_media(bot_response_message)
235+
return {"message": response, "sources": sources, "media": medias}
224236

225-
@staticmethod
226-
def get_cookies() -> dict:
237+
def extract_media(self, json_line: dict) -> List[Dict]:
238+
"""
239+
Extract media from a parsed JSON line.
240+
241+
Args:
242+
json_line (dict): Parsed JSON line.
243+
244+
Returns:
245+
list: A list of dictionaries containing the extracted media.
246+
"""
247+
medias = []
248+
imagine_card = json_line.get("imagine_card", {})
249+
session = imagine_card.get("session", {}) if imagine_card else {}
250+
media_sets = (
251+
(json_line.get("imagine_card", {}).get("session", {}).get("media_sets", []))
252+
if imagine_card and session
253+
else []
254+
)
255+
for media_set in media_sets:
256+
imagine_media = media_set.get("imagine_media", [])
257+
for media in imagine_media:
258+
medias.append(
259+
{
260+
"url": media.get("uri"),
261+
"type": media.get("media_type"),
262+
"prompt": media.get("prompt"),
263+
}
264+
)
265+
return medias
266+
267+
def get_cookies(self) -> dict:
227268
"""
228269
Extracts necessary cookies from the Meta AI main page.
229270
230271
Returns:
231272
dict: A dictionary containing essential cookies.
232273
"""
233274
session = HTMLSession()
234-
response = session.get("https://www.meta.ai/")
235-
return {
275+
headers = {}
276+
if self.fb_email is not None and self.fb_password is not None:
277+
fb_session = get_fb_session(self.fb_email, self.fb_password)
278+
headers = {"cookie": f"abra_sess={fb_session['abra_sess']}"}
279+
response = session.get(
280+
"https://www.meta.ai/",
281+
headers=headers,
282+
)
283+
cookies = {
236284
"_js_datr": extract_value(
237285
response.text, start_str='_js_datr":{"value":"', end_str='",'
238286
),
239-
"abra_csrf": extract_value(
240-
response.text, start_str='abra_csrf":{"value":"', end_str='",'
241-
),
242287
"datr": extract_value(
243288
response.text, start_str='datr":{"value":"', end_str='",'
244289
),
245290
"lsd": extract_value(
246291
response.text, start_str='"LSD",[],{"token":"', end_str='"}'
247292
),
293+
"fb_dtsg": extract_value(
294+
response.text, start_str='DTSGInitData",[],{"token":"', end_str='"'
295+
),
248296
}
249297

298+
if len(headers) > 0:
299+
cookies["abra_sess"] = fb_session["abra_sess"]
300+
else:
301+
cookies["abra_csrf"] = extract_value(
302+
response.text, start_str='abra_csrf":{"value":"', end_str='",'
303+
)
304+
return cookies
305+
250306
def fetch_sources(self, fetch_id: str) -> List[Dict]:
251307
"""
252308
Fetches sources from the Meta AI API based on the given query.
@@ -274,13 +330,18 @@ def fetch_sources(self, fetch_id: str) -> List[Dict]:
274330
"authority": "graph.meta.ai",
275331
"accept-language": "en-US,en;q=0.9,fr-FR;q=0.8,fr;q=0.7",
276332
"content-type": "application/x-www-form-urlencoded",
277-
"cookie": f'dpr=2; abra_csrf={self.cookies["abra_csrf"]}; datr={self.cookies["datr"]}; ps_n=1; ps_l=1',
333+
"cookie": f'dpr=2; abra_csrf={self.cookies.get("abra_csrf")}; datr={self.cookies.get("datr")}; ps_n=1; ps_l=1',
278334
"x-fb-friendly-name": "AbraSearchPluginDialogQuery",
279335
}
280336

281337
response = self.session.post(url, headers=headers, data=payload)
282338
response_json = response.json()
283-
search_results = response_json["data"]["message"]["searchResults"]
339+
message = response_json.get("data", {}).get("message", {})
340+
search_results = (
341+
(response_json.get("data", {}).get("message", {}).get("searchResults"))
342+
if message
343+
else None
344+
)
284345
if search_results is None:
285346
return []
286347

@@ -291,7 +352,4 @@ def fetch_sources(self, fetch_id: str) -> List[Dict]:
291352
if __name__ == "__main__":
292353
meta = MetaAI()
293354
resp = meta.prompt("What was the Warriors score last game?", stream=False)
294-
# for r in resp:
295-
# print(r)
296-
297355
print(resp)

0 commit comments

Comments
 (0)