1- # Copyright 2024 Daniel Park, Antonio Cheang, MIT License
21import os
32import re
43import json
76import inspect
87import requests
98import urllib .parse
10- from typing import Optional , Tuple , Dict
9+ from typing import Optional , Tuple , Dict , Union
1110from requests .exceptions import ConnectionError , RequestException
1211
1312from .src .parser .custom_parser import ParseMethod1 , ParseMethod2
1413from .src .parser .response_parser import ResponseParser
1514from .src .model .output import GeminiCandidate , GeminiModelOutput
15+ from .src .misc .utils import upload_image
1616from .src .misc .constants import (
1717 HEADERS ,
1818 HOST ,
@@ -52,12 +52,16 @@ def __init__(
5252 auto_cookies : bool = False ,
5353 timeout : int = 30 ,
5454 proxies : Optional [dict ] = None ,
55+ rcid : str = None ,
5556 ) -> None :
5657 """
5758 Initializes the Gemini object with session, cookies, and other configurations.
5859 """
5960 self ._nonce = None
6061 self ._sid = None
62+ self ._rcid = rcid or None
63+ self ._rid = None
64+ self ._cid = None
6165 self .auto_cookies = auto_cookies
6266 self .cookie_fp = cookie_fp
6367 self .cookies = cookies
@@ -69,6 +73,14 @@ def __init__(
6973 self ._reqid = int ("" .join (random .choices (string .digits , k = 7 )))
7074 self .parser = ResponseParser (cookies = self .cookies )
7175
76+ @property
77+ def rcid (self ):
78+ return self ._rcid
79+
80+ @rcid .setter
81+ def rcid (self , value ):
82+ self ._rcid = value
83+
7284 def _initialize_session (
7385 self ,
7486 ) -> requests .Session :
@@ -87,15 +99,15 @@ def _initialize_session(
8799 if self .cookies :
88100 session .cookies .update (self .cookies )
89101 elif self .cookie_fp :
90- self ._load_cookies_from_file (self .cookie_fp )
102+ self ._set_cookies_from_file (self .cookie_fp )
91103 elif self .auto_cookies == True :
92104 self ._set_cookies_automatically ()
93105
94106 self ._set_sid_and_nonce ()
95107
96108 return session
97109
98- def _load_cookies_from_file (self , file_path : str ) -> None :
110+ def _set_cookies_from_file (self , file_path : str ) -> None :
99111 """Loads cookies from a file and updates the session."""
100112 try :
101113 if file_path .endswith (".json" ):
@@ -120,15 +132,17 @@ def _set_sid_and_nonce(self):
120132 response = requests .get (f"{ HOST } /app" , cookies = self .cookies )
121133 response .raise_for_status ()
122134
123- sid_match , nonce_match = self .extract_sid_nonce (response .text )
135+ sid_match = re .search (r'"FdrFJe":"([\d-]+)"' , response .text )
136+ nonce_match = re .search (r'"SNlM0e":"(.*?)"' , response .text )
124137
125- if sid_match and nonce_match :
138+ if sid_match :
126139 self ._sid = sid_match .group (1 )
127- self ._nonce = nonce_match .group (1 )
128140 else :
129141 raise ValueError (
130142 "Failed to parse SID or SNlM0e nonce from the response.\n Refresh the Gemini web page or access Gemini in a new incognito browser to resend cookies."
131143 )
144+ if nonce_match :
145+ self ._nonce = nonce_match .group (1 )
132146
133147 except requests .RequestException as e :
134148 raise ConnectionError (f"Request failed: { e } " )
@@ -137,12 +151,6 @@ def _set_sid_and_nonce(self):
137151 except Exception as e :
138152 raise RuntimeError (f"An unexpected error occurred: { e } " )
139153
140- @staticmethod
141- def extract_sid_nonce (response_text ):
142- sid_match = re .search (r'"FdrFJe":"([\d-]+)"' , response_text )
143- nonce_match = re .search (r'"SNlM0e":"(.*?)"' , response_text )
144- return sid_match , nonce_match
145-
146154 def _construct_params (self , sid : str ) -> str :
147155 """
148156 Constructs URL-encoded parameters for a request.
@@ -163,7 +171,9 @@ def _construct_params(self, sid: str) -> str:
163171 }
164172 )
165173
166- def _construct_payload (self , prompt : str , nonce : str ) -> str :
174+ def _construct_payload (
175+ self , prompt : str , image : Union [bytes , str ], nonce : str
176+ ) -> str :
167177 """
168178 Constructs URL-encoded payload for a request.
169179
@@ -177,15 +187,35 @@ def _construct_payload(self, prompt: str, nonce: str) -> str:
177187 return urllib .parse .urlencode (
178188 {
179189 "at" : nonce ,
180- "f.req" : json .dumps ([None , json .dumps ([[prompt ], None , None ])]),
181- }
190+ "f.req" : json .dumps (
191+ [
192+ None ,
193+ json .dumps (
194+ [
195+ image
196+ and [
197+ prompt ,
198+ 0 ,
199+ None ,
200+ [[[upload_image (image ), 1 ]]],
201+ ]
202+ or [prompt ],
203+ None ,
204+ [self ._cid , self ._rid , self ._rcid ],
205+ ]
206+ ),
207+ ]
208+ ),
209+ },
182210 )
183211
184- def send_request (self , prompt : str ) -> Tuple [str , int ]:
212+ def send_request (
213+ self , prompt : str , image : Union [bytes , str ] = None
214+ ) -> Tuple [str , int ]:
185215 """Sends a request and returns the response text and status code."""
186216 try :
187217 params = self ._construct_params (self ._sid )
188- data = self ._construct_payload (prompt , self ._nonce )
218+ data = self ._construct_payload (prompt , image , self ._nonce )
189219 response = self .session .post (
190220 POST_ENDPOINT ,
191221 params = params ,
@@ -200,7 +230,7 @@ def send_request(self, prompt: str) -> Tuple[str, int]:
200230 # self._update_cookies_from_browser()
201231 self ._set_sid_and_nonce ()
202232 params = self ._construct_params (self ._sid )
203- data = self ._construct_payload (prompt , self ._nonce )
233+ data = self ._construct_payload (prompt , image , self ._nonce )
204234 response = self .session .post (
205235 POST_ENDPOINT ,
206236 params = params ,
@@ -217,9 +247,11 @@ def send_request(self, prompt: str) -> Tuple[str, int]:
217247 response .raise_for_status ()
218248 return response .text , response .status_code
219249
220- def generate_content (self , prompt : str ) -> GeminiModelOutput :
250+ def generate_content (
251+ self , prompt : str , image : Union [bytes , str ] = None
252+ ) -> GeminiModelOutput :
221253 """Generates content based on the prompt and returns a GeminiModelOutput object."""
222- response_text , response_status_code = self .send_request (prompt )
254+ response_text , response_status_code = self .send_request (prompt , image )
223255 if response_status_code != 200 :
224256 raise ValueError (f"Response status: { response_status_code } " )
225257 else :
@@ -231,7 +263,12 @@ def generate_content(self, prompt: str) -> GeminiModelOutput:
231263 def _create_model_output (self , parsed_response : dict ) -> GeminiModelOutput :
232264 candidates = self .collect_candidates (parsed_response )
233265 metadata = parsed_response .get ("metadata" , [])
234-
266+ try :
267+ self ._cid = metadata [0 ]
268+ self ._rid = metadata [1 ]
269+ # self._rcid = candidates["candidates"][0]["rcid"]
270+ except :
271+ pass
235272 return GeminiModelOutput (
236273 metadata = metadata ,
237274 candidates = candidates ,
0 commit comments