MetaAI.py 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240
  1. from __future__ import annotations
  2. import json
  3. import uuid
  4. import random
  5. import time
  6. from typing import Dict, List
  7. from aiohttp import ClientSession, BaseConnector
  8. from ...typing import AsyncResult, Messages, Cookies
  9. from ...requests import raise_for_status, DEFAULT_HEADERS
  10. from ...image import ImageResponse, ImagePreview
  11. from ...errors import ResponseError
  12. from ..base_provider import AsyncGeneratorProvider, ProviderModelMixin
  13. from ..helper import format_prompt, get_connector, format_cookies
  14. class Sources():
  15. def __init__(self, link_list: List[Dict[str, str]]) -> None:
  16. self.list = link_list
  17. def __str__(self) -> str:
  18. return "\n\n" + ("\n".join([f"[{link['title']}]({link['link']})" for link in self.list]))
  19. class AbraGeoBlockedError(Exception):
  20. pass
  21. class MetaAI(AsyncGeneratorProvider, ProviderModelMixin):
  22. label = "Meta AI"
  23. url = "https://www.meta.ai"
  24. working = True
  25. default_model = 'meta-ai'
  26. def __init__(self, proxy: str = None, connector: BaseConnector = None):
  27. self.session = ClientSession(connector=get_connector(connector, proxy), headers=DEFAULT_HEADERS)
  28. self.cookies: Cookies = None
  29. self.access_token: str = None
  30. @classmethod
  31. async def create_async_generator(
  32. cls,
  33. model: str,
  34. messages: Messages,
  35. proxy: str = None,
  36. **kwargs
  37. ) -> AsyncResult:
  38. async for chunk in cls(proxy).prompt(format_prompt(messages)):
  39. yield chunk
  40. async def update_access_token(self, birthday: str = "1999-01-01"):
  41. url = "https://www.meta.ai/api/graphql/"
  42. payload = {
  43. "lsd": self.lsd,
  44. "fb_api_caller_class": "RelayModern",
  45. "fb_api_req_friendly_name": "useAbraAcceptTOSForTempUserMutation",
  46. "variables": json.dumps({
  47. "dob": birthday,
  48. "icebreaker_type": "TEXT",
  49. "__relay_internal__pv__WebPixelRatiorelayprovider": 1,
  50. }),
  51. "doc_id": "7604648749596940",
  52. }
  53. headers = {
  54. "x-fb-friendly-name": "useAbraAcceptTOSForTempUserMutation",
  55. "x-fb-lsd": self.lsd,
  56. "x-asbd-id": "129477",
  57. "alt-used": "www.meta.ai",
  58. "sec-fetch-site": "same-origin"
  59. }
  60. async with self.session.post(url, headers=headers, cookies=self.cookies, data=payload) as response:
  61. await raise_for_status(response, "Fetch access_token failed")
  62. auth_json = await response.json(content_type=None)
  63. self.access_token = auth_json["data"]["xab_abra_accept_terms_of_service"]["new_temp_user_auth"]["access_token"]
  64. async def prompt(self, message: str, cookies: Cookies = None) -> AsyncResult:
  65. if self.cookies is None:
  66. await self.update_cookies(cookies)
  67. if cookies is not None:
  68. self.access_token = None
  69. if self.access_token is None and cookies is None:
  70. await self.update_access_token()
  71. if self.access_token is None:
  72. url = "https://www.meta.ai/api/graphql/"
  73. payload = {"lsd": self.lsd, 'fb_dtsg': self.dtsg}
  74. headers = {'x-fb-lsd': self.lsd}
  75. else:
  76. url = "https://graph.meta.ai/graphql?locale=user"
  77. payload = {"access_token": self.access_token}
  78. headers = {}
  79. headers = {
  80. 'content-type': 'application/x-www-form-urlencoded',
  81. 'cookie': format_cookies(self.cookies),
  82. 'origin': 'https://www.meta.ai',
  83. 'referer': 'https://www.meta.ai/',
  84. 'x-asbd-id': '129477',
  85. 'x-fb-friendly-name': 'useAbraSendMessageMutation',
  86. **headers
  87. }
  88. payload = {
  89. **payload,
  90. 'fb_api_caller_class': 'RelayModern',
  91. 'fb_api_req_friendly_name': 'useAbraSendMessageMutation',
  92. "variables": json.dumps({
  93. "message": {"sensitive_string_value": message},
  94. "externalConversationId": str(uuid.uuid4()),
  95. "offlineThreadingId": generate_offline_threading_id(),
  96. "suggestedPromptIndex": None,
  97. "flashVideoRecapInput": {"images": []},
  98. "flashPreviewInput": None,
  99. "promptPrefix": None,
  100. "entrypoint": "ABRA__CHAT__TEXT",
  101. "icebreaker_type": "TEXT",
  102. "__relay_internal__pv__AbraDebugDevOnlyrelayprovider": False,
  103. "__relay_internal__pv__WebPixelRatiorelayprovider": 1,
  104. }),
  105. 'server_timestamps': 'true',
  106. 'doc_id': '7783822248314888'
  107. }
  108. async with self.session.post(url, headers=headers, data=payload) as response:
  109. await raise_for_status(response, "Fetch response failed")
  110. last_snippet_len = 0
  111. fetch_id = None
  112. async for line in response.content:
  113. if b"<h1>Something Went Wrong</h1>" in line:
  114. raise ResponseError("Response: Something Went Wrong")
  115. try:
  116. json_line = json.loads(line)
  117. except json.JSONDecodeError:
  118. continue
  119. if json_line.get("errors"):
  120. raise RuntimeError("\n".join([error.get("message") for error in json_line.get("errors")]))
  121. bot_response_message = json_line.get("data", {}).get("node", {}).get("bot_response_message", {})
  122. streaming_state = bot_response_message.get("streaming_state")
  123. fetch_id = bot_response_message.get("fetch_id") or fetch_id
  124. if streaming_state in ("STREAMING", "OVERALL_DONE"):
  125. imagine_card = bot_response_message.get("imagine_card")
  126. if imagine_card is not None:
  127. imagine_session = imagine_card.get("session")
  128. if imagine_session is not None:
  129. imagine_medias = imagine_session.get("media_sets", {}).pop().get("imagine_media")
  130. if imagine_medias is not None:
  131. image_class = ImageResponse if streaming_state == "OVERALL_DONE" else ImagePreview
  132. yield image_class([media["uri"] for media in imagine_medias], imagine_medias[0]["prompt"])
  133. snippet = bot_response_message["snippet"]
  134. new_snippet_len = len(snippet)
  135. if new_snippet_len > last_snippet_len:
  136. yield snippet[last_snippet_len:]
  137. last_snippet_len = new_snippet_len
  138. #if last_streamed_response is None:
  139. # if attempts > 3:
  140. # raise Exception("MetaAI is having issues and was not able to respond (Server Error)")
  141. # access_token = await self.get_access_token()
  142. # return await self.prompt(message=message, attempts=attempts + 1)
  143. if fetch_id is not None:
  144. sources = await self.fetch_sources(fetch_id)
  145. if sources is not None:
  146. yield sources
  147. async def update_cookies(self, cookies: Cookies = None):
  148. async with self.session.get("https://www.meta.ai/", cookies=cookies) as response:
  149. await raise_for_status(response, "Fetch home failed")
  150. text = await response.text()
  151. if "AbraGeoBlockedError" in text:
  152. raise AbraGeoBlockedError("Meta AI isn't available yet in your country")
  153. if cookies is None:
  154. cookies = {
  155. "_js_datr": self.extract_value(text, "_js_datr"),
  156. "abra_csrf": self.extract_value(text, "abra_csrf"),
  157. "datr": self.extract_value(text, "datr"),
  158. }
  159. self.lsd = self.extract_value(text, start_str='"LSD",[],{"token":"', end_str='"}')
  160. self.dtsg = self.extract_value(text, start_str='"DTSGInitialData",[],{"token":"', end_str='"}')
  161. self.cookies = cookies
  162. async def fetch_sources(self, fetch_id: str) -> Sources:
  163. if self.access_token is None:
  164. url = "https://www.meta.ai/api/graphql/"
  165. payload = {"lsd": self.lsd, 'fb_dtsg': self.dtsg}
  166. headers = {'x-fb-lsd': self.lsd}
  167. else:
  168. url = "https://graph.meta.ai/graphql?locale=user"
  169. payload = {"access_token": self.access_token}
  170. headers = {}
  171. payload = {
  172. **payload,
  173. "fb_api_caller_class": "RelayModern",
  174. "fb_api_req_friendly_name": "AbraSearchPluginDialogQuery",
  175. "variables": json.dumps({"abraMessageFetchID": fetch_id}),
  176. "server_timestamps": "true",
  177. "doc_id": "6946734308765963",
  178. }
  179. headers = {
  180. "authority": "graph.meta.ai",
  181. "x-fb-friendly-name": "AbraSearchPluginDialogQuery",
  182. **headers
  183. }
  184. async with self.session.post(url, headers=headers, cookies=self.cookies, data=payload) as response:
  185. await raise_for_status(response, "Fetch sources failed")
  186. text = await response.text()
  187. if "<h1>Something Went Wrong</h1>" in text:
  188. raise ResponseError("Response: Something Went Wrong")
  189. try:
  190. response_json = json.loads(text)
  191. message = response_json["data"]["message"]
  192. if message is not None:
  193. searchResults = message["searchResults"]
  194. if searchResults is not None:
  195. return Sources(searchResults["references"])
  196. except (KeyError, TypeError, json.JSONDecodeError):
  197. raise RuntimeError(f"Response: {text}")
  198. @staticmethod
  199. def extract_value(text: str, key: str = None, start_str = None, end_str = '",') -> str:
  200. if start_str is None:
  201. start_str = f'{key}":{{"value":"'
  202. start = text.find(start_str)
  203. if start >= 0:
  204. start+= len(start_str)
  205. end = text.find(end_str, start)
  206. if end >= 0:
  207. return text[start:end]
  208. def generate_offline_threading_id() -> str:
  209. """
  210. Generates an offline threading ID.
  211. Returns:
  212. str: The generated offline threading ID.
  213. """
  214. # Generate a random 64-bit integer
  215. random_value = random.getrandbits(64)
  216. # Get the current timestamp in milliseconds
  217. timestamp = int(time.time() * 1000)
  218. # Combine timestamp and random value
  219. threading_id = (timestamp << 22) | (random_value & ((1 << 22) - 1))
  220. return str(threading_id)