Gemini.py 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276
  1. from __future__ import annotations
  2. import os
  3. import json
  4. import random
  5. import re
  6. import base64
  7. from aiohttp import ClientSession, BaseConnector
  8. from ..helper import get_connector
  9. try:
  10. from selenium.webdriver.common.by import By
  11. from selenium.webdriver.support.ui import WebDriverWait
  12. from selenium.webdriver.support import expected_conditions as EC
  13. except ImportError:
  14. pass
  15. from ... import debug
  16. from ...typing import Messages, Cookies, ImageType, AsyncResult, AsyncIterator
  17. from ..base_provider import AsyncGeneratorProvider
  18. from ..helper import format_prompt, get_cookies
  19. from ...requests.raise_for_status import raise_for_status
  20. from ...errors import MissingAuthError, MissingRequirementsError
  21. from ...image import to_bytes, ImageResponse, ImageDataResponse
  22. from ...webdriver import get_browser, get_driver_cookies
  23. REQUEST_HEADERS = {
  24. "authority": "gemini.google.com",
  25. "origin": "https://gemini.google.com",
  26. "referer": "https://gemini.google.com/",
  27. 'user-agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/111.0.0.0 Safari/537.36',
  28. 'x-same-domain': '1',
  29. }
  30. REQUEST_BL_PARAM = "boq_assistant-bard-web-server_20240421.18_p0"
  31. REQUEST_URL = "https://gemini.google.com/_/BardChatUi/data/assistant.lamda.BardFrontendService/StreamGenerate"
  32. UPLOAD_IMAGE_URL = "https://content-push.googleapis.com/upload/"
  33. UPLOAD_IMAGE_HEADERS = {
  34. "authority": "content-push.googleapis.com",
  35. "accept": "*/*",
  36. "accept-language": "en-US,en;q=0.7",
  37. "authorization": "Basic c2F2ZXM6cyNMdGhlNmxzd2F2b0RsN3J1d1U=",
  38. "content-type": "application/x-www-form-urlencoded;charset=UTF-8",
  39. "origin": "https://gemini.google.com",
  40. "push-id": "feeds/mcudyrk2a4khkz",
  41. "referer": "https://gemini.google.com/",
  42. "x-goog-upload-command": "start",
  43. "x-goog-upload-header-content-length": "",
  44. "x-goog-upload-protocol": "resumable",
  45. "x-tenant-id": "bard-storage",
  46. }
  47. class Gemini(AsyncGeneratorProvider):
  48. url = "https://gemini.google.com"
  49. needs_auth = True
  50. working = True
  51. image_models = ["gemini"]
  52. default_vision_model = "gemini"
  53. _cookies: Cookies = None
  54. @classmethod
  55. async def nodriver_login(cls, proxy: str = None) -> AsyncIterator[str]:
  56. try:
  57. import nodriver as uc
  58. except ImportError:
  59. return
  60. try:
  61. from platformdirs import user_config_dir
  62. user_data_dir = user_config_dir("g4f-nodriver")
  63. except:
  64. user_data_dir = None
  65. if debug.logging:
  66. print(f"Open nodriver with user_dir: {user_data_dir}")
  67. browser = await uc.start(
  68. user_data_dir=user_data_dir,
  69. browser_args=None if proxy is None else [f"--proxy-server={proxy}"],
  70. )
  71. login_url = os.environ.get("G4F_LOGIN_URL")
  72. if login_url:
  73. yield f"Please login: [Google Gemini]({login_url})\n\n"
  74. page = await browser.get(f"{cls.url}/app")
  75. await page.select("div.ql-editor.textarea", 240)
  76. cookies = {}
  77. for c in await page.browser.cookies.get_all():
  78. if c.domain.endswith(".google.com"):
  79. cookies[c.name] = c.value
  80. await page.close()
  81. cls._cookies = cookies
  82. @classmethod
  83. async def webdriver_login(cls, proxy: str) -> AsyncIterator[str]:
  84. driver = None
  85. try:
  86. driver = get_browser(proxy=proxy)
  87. try:
  88. driver.get(f"{cls.url}/app")
  89. WebDriverWait(driver, 5).until(
  90. EC.visibility_of_element_located((By.CSS_SELECTOR, "div.ql-editor.textarea"))
  91. )
  92. except:
  93. login_url = os.environ.get("G4F_LOGIN_URL")
  94. if login_url:
  95. yield f"Please login: [Google Gemini]({login_url})\n\n"
  96. WebDriverWait(driver, 240).until(
  97. EC.visibility_of_element_located((By.CSS_SELECTOR, "div.ql-editor.textarea"))
  98. )
  99. cls._cookies = get_driver_cookies(driver)
  100. except MissingRequirementsError:
  101. pass
  102. finally:
  103. if driver:
  104. driver.close()
  105. @classmethod
  106. async def create_async_generator(
  107. cls,
  108. model: str,
  109. messages: Messages,
  110. proxy: str = None,
  111. api_key: str = None,
  112. cookies: Cookies = None,
  113. connector: BaseConnector = None,
  114. image: ImageType = None,
  115. image_name: str = None,
  116. response_format: str = None,
  117. **kwargs
  118. ) -> AsyncResult:
  119. prompt = format_prompt(messages)
  120. if api_key is not None:
  121. if cookies is None:
  122. cookies = {}
  123. cookies["__Secure-1PSID"] = api_key
  124. cls._cookies = cookies or cls._cookies or get_cookies(".google.com", False, True)
  125. base_connector = get_connector(connector, proxy)
  126. async with ClientSession(
  127. headers=REQUEST_HEADERS,
  128. connector=base_connector
  129. ) as session:
  130. snlm0e = await cls.fetch_snlm0e(session, cls._cookies) if cls._cookies else None
  131. if not snlm0e:
  132. async for chunk in cls.nodriver_login(proxy):
  133. yield chunk
  134. if cls._cookies is None:
  135. async for chunk in cls.webdriver_login(proxy):
  136. yield chunk
  137. if not snlm0e:
  138. if cls._cookies is None or "__Secure-1PSID" not in cls._cookies:
  139. raise MissingAuthError('Missing "__Secure-1PSID" cookie')
  140. snlm0e = await cls.fetch_snlm0e(session, cls._cookies)
  141. if not snlm0e:
  142. raise RuntimeError("Invalid cookies. SNlM0e not found")
  143. image_url = await cls.upload_image(base_connector, to_bytes(image), image_name) if image else None
  144. async with ClientSession(
  145. cookies=cls._cookies,
  146. headers=REQUEST_HEADERS,
  147. connector=base_connector,
  148. ) as client:
  149. params = {
  150. 'bl': REQUEST_BL_PARAM,
  151. '_reqid': random.randint(1111, 9999),
  152. 'rt': 'c'
  153. }
  154. data = {
  155. 'at': snlm0e,
  156. 'f.req': json.dumps([None, json.dumps(cls.build_request(
  157. prompt,
  158. image_url=image_url,
  159. image_name=image_name
  160. ))])
  161. }
  162. async with client.post(
  163. REQUEST_URL,
  164. data=data,
  165. params=params,
  166. ) as response:
  167. await raise_for_status(response)
  168. response = await response.text()
  169. response_part = json.loads(json.loads(response.splitlines()[-5])[0][2])
  170. if response_part[4] is None:
  171. response_part = json.loads(json.loads(response.splitlines()[-7])[0][2])
  172. content = response_part[4][0][1][0]
  173. image_prompt = None
  174. match = re.search(r'\[Imagen of (.*?)\]', content)
  175. if match:
  176. image_prompt = match.group(1)
  177. content = content.replace(match.group(0), '')
  178. yield content
  179. if image_prompt:
  180. images = [image[0][3][3] for image in response_part[4][0][12][7][0]]
  181. resolved_images = []
  182. if response_format == "b64_json":
  183. for image in images:
  184. async with client.get(image) as response:
  185. data = base64.b64encode(await response.content.read()).decode()
  186. resolved_images.append(data)
  187. yield ImageDataResponse(resolved_images, image_prompt)
  188. else:
  189. preview = []
  190. for image in images:
  191. async with client.get(image, allow_redirects=False) as fetch:
  192. image = fetch.headers["location"]
  193. async with client.get(image, allow_redirects=False) as fetch:
  194. image = fetch.headers["location"]
  195. resolved_images.append(image)
  196. preview.append(image.replace('=s512', '=s200'))
  197. yield ImageResponse(resolved_images, image_prompt, {"orginal_links": images, "preview": preview})
  198. def build_request(
  199. prompt: str,
  200. conversation_id: str = "",
  201. response_id: str = "",
  202. choice_id: str = "",
  203. image_url: str = None,
  204. image_name: str = None,
  205. tools: list[list[str]] = []
  206. ) -> list:
  207. image_list = [[[image_url, 1], image_name]] if image_url else []
  208. return [
  209. [prompt, 0, None, image_list, None, None, 0],
  210. ["en"],
  211. [conversation_id, response_id, choice_id, None, None, []],
  212. None,
  213. None,
  214. None,
  215. [1],
  216. 0,
  217. [],
  218. tools,
  219. 1,
  220. 0,
  221. ]
  222. async def upload_image(connector: BaseConnector, image: bytes, image_name: str = None):
  223. async with ClientSession(
  224. headers=UPLOAD_IMAGE_HEADERS,
  225. connector=connector
  226. ) as session:
  227. async with session.options(UPLOAD_IMAGE_URL) as response:
  228. await raise_for_status(response)
  229. headers = {
  230. "size": str(len(image)),
  231. "x-goog-upload-command": "start"
  232. }
  233. data = f"File name: {image_name}" if image_name else None
  234. async with session.post(
  235. UPLOAD_IMAGE_URL, headers=headers, data=data
  236. ) as response:
  237. await raise_for_status(response)
  238. upload_url = response.headers["X-Goog-Upload-Url"]
  239. async with session.options(upload_url, headers=headers) as response:
  240. await raise_for_status(response)
  241. headers["x-goog-upload-command"] = "upload, finalize"
  242. headers["X-Goog-Upload-Offset"] = "0"
  243. async with session.post(
  244. upload_url, headers=headers, data=image
  245. ) as response:
  246. await raise_for_status(response)
  247. return await response.text()
  248. @classmethod
  249. async def fetch_snlm0e(cls, session: ClientSession, cookies: Cookies):
  250. async with session.get(cls.url, cookies=cookies) as response:
  251. await raise_for_status(response)
  252. text = await response.text()
  253. match = re.search(r'SNlM0e\":\"(.*?)\"', text)
  254. if match:
  255. return match.group(1)