gemini-webapi 1.17.1__py3-none-any.whl → 1.17.3__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.
- gemini_webapi/client.py +6 -7
- gemini_webapi/constants.py +1 -0
- gemini_webapi/types/image.py +28 -11
- gemini_webapi/utils/decorators.py +4 -3
- gemini_webapi/utils/get_access_token.py +1 -7
- gemini_webapi/utils/parsing.py +4 -3
- gemini_webapi/utils/rotate_1psidts.py +1 -1
- gemini_webapi/utils/upload_file.py +1 -1
- {gemini_webapi-1.17.1.dist-info → gemini_webapi-1.17.3.dist-info}/METADATA +2 -2
- {gemini_webapi-1.17.1.dist-info → gemini_webapi-1.17.3.dist-info}/RECORD +13 -13
- {gemini_webapi-1.17.1.dist-info → gemini_webapi-1.17.3.dist-info}/WHEEL +0 -0
- {gemini_webapi-1.17.1.dist-info → gemini_webapi-1.17.3.dist-info}/licenses/LICENSE +0 -0
- {gemini_webapi-1.17.1.dist-info → gemini_webapi-1.17.3.dist-info}/top_level.txt +0 -0
gemini_webapi/client.py
CHANGED
|
@@ -68,7 +68,7 @@ class GeminiClient(GemMixin):
|
|
|
68
68
|
__slots__ = [
|
|
69
69
|
"cookies",
|
|
70
70
|
"proxy",
|
|
71
|
-
"
|
|
71
|
+
"_running",
|
|
72
72
|
"client",
|
|
73
73
|
"access_token",
|
|
74
74
|
"timeout",
|
|
@@ -91,7 +91,7 @@ class GeminiClient(GemMixin):
|
|
|
91
91
|
super().__init__()
|
|
92
92
|
self.cookies = {}
|
|
93
93
|
self.proxy = proxy
|
|
94
|
-
self.
|
|
94
|
+
self._running: bool = False
|
|
95
95
|
self.client: AsyncClient | None = None
|
|
96
96
|
self.access_token: str | None = None
|
|
97
97
|
self.timeout: float = 300
|
|
@@ -142,7 +142,6 @@ class GeminiClient(GemMixin):
|
|
|
142
142
|
)
|
|
143
143
|
|
|
144
144
|
self.client = AsyncClient(
|
|
145
|
-
http2=True,
|
|
146
145
|
timeout=timeout,
|
|
147
146
|
proxy=self.proxy,
|
|
148
147
|
follow_redirects=True,
|
|
@@ -152,7 +151,7 @@ class GeminiClient(GemMixin):
|
|
|
152
151
|
)
|
|
153
152
|
self.access_token = access_token
|
|
154
153
|
self.cookies = valid_cookies
|
|
155
|
-
self.
|
|
154
|
+
self._running = True
|
|
156
155
|
|
|
157
156
|
self.timeout = timeout
|
|
158
157
|
self.auto_close = auto_close
|
|
@@ -188,7 +187,7 @@ class GeminiClient(GemMixin):
|
|
|
188
187
|
if delay:
|
|
189
188
|
await asyncio.sleep(delay)
|
|
190
189
|
|
|
191
|
-
self.
|
|
190
|
+
self._running = False
|
|
192
191
|
|
|
193
192
|
if self.close_task:
|
|
194
193
|
self.close_task.cancel()
|
|
@@ -229,7 +228,7 @@ class GeminiClient(GemMixin):
|
|
|
229
228
|
|
|
230
229
|
if new_1psidts:
|
|
231
230
|
self.cookies["__Secure-1PSIDTS"] = new_1psidts
|
|
232
|
-
if self.
|
|
231
|
+
if self._running:
|
|
233
232
|
self.client.cookies.set("__Secure-1PSIDTS", new_1psidts)
|
|
234
233
|
logger.debug("Cookies refreshed. New __Secure-1PSIDTS applied.")
|
|
235
234
|
|
|
@@ -380,7 +379,7 @@ class GeminiClient(GemMixin):
|
|
|
380
379
|
|
|
381
380
|
try:
|
|
382
381
|
error_code = get_nested_value(response_json, [0, 5, 2, 0, 1, 0], -1)
|
|
383
|
-
match
|
|
382
|
+
match error_code:
|
|
384
383
|
case ErrorCode.USAGE_LIMIT_EXCEEDED:
|
|
385
384
|
raise UsageLimitExceeded(
|
|
386
385
|
f"Failed to generate contents. Usage limit of {model.model_name} model has exceeded. Please try switching to another model."
|
gemini_webapi/constants.py
CHANGED
|
@@ -103,6 +103,7 @@ class ErrorCode(IntEnum):
|
|
|
103
103
|
Known error codes returned from server.
|
|
104
104
|
"""
|
|
105
105
|
|
|
106
|
+
TEMPORARY_ERROR_1013 = 1013 # Randomly raised when generating with certain models, but disappears soon after
|
|
106
107
|
USAGE_LIMIT_EXCEEDED = 1037
|
|
107
108
|
MODEL_INCONSISTENT = 1050
|
|
108
109
|
MODEL_HEADER_INVALID = 1052
|
gemini_webapi/types/image.py
CHANGED
|
@@ -71,16 +71,17 @@ class Image(BaseModel):
|
|
|
71
71
|
"""
|
|
72
72
|
|
|
73
73
|
filename = filename or self.url.split("/")[-1].split("?")[0]
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
74
|
+
match = re.search(r"^(.*\.\w+)", filename)
|
|
75
|
+
if match:
|
|
76
|
+
filename = match.group()
|
|
77
|
+
else:
|
|
77
78
|
if verbose:
|
|
78
79
|
logger.warning(f"Invalid filename: {filename}")
|
|
79
80
|
if skip_invalid_filename:
|
|
80
81
|
return None
|
|
81
82
|
|
|
82
83
|
async with AsyncClient(
|
|
83
|
-
|
|
84
|
+
follow_redirects=True, cookies=cookies, proxy=self.proxy
|
|
84
85
|
) as client:
|
|
85
86
|
response = await client.get(self.url)
|
|
86
87
|
if response.status_code == 200:
|
|
@@ -137,19 +138,33 @@ class GeneratedImage(Image):
|
|
|
137
138
|
return v
|
|
138
139
|
|
|
139
140
|
# @override
|
|
140
|
-
async def save(
|
|
141
|
+
async def save(
|
|
142
|
+
self,
|
|
143
|
+
path: str = "temp",
|
|
144
|
+
filename: str | None = None,
|
|
145
|
+
cookies: dict | None = None,
|
|
146
|
+
verbose: bool = False,
|
|
147
|
+
skip_invalid_filename: bool = False,
|
|
148
|
+
full_size: bool = True,
|
|
149
|
+
) -> str | None:
|
|
141
150
|
"""
|
|
142
151
|
Save the image to disk.
|
|
143
152
|
|
|
144
153
|
Parameters
|
|
145
154
|
----------
|
|
155
|
+
path: `str`, optional
|
|
156
|
+
Path to save the image, by default will save to "./temp".
|
|
146
157
|
filename: `str`, optional
|
|
147
158
|
Filename to save the image, generated images are always in .png format, but file extension will not be included in the URL.
|
|
148
159
|
And since the URL ends with a long hash, by default will use timestamp + end of the hash as the filename.
|
|
160
|
+
cookies: `dict`, optional
|
|
161
|
+
Cookies used for requesting the content of the image. If not provided, will use the cookies from the GeneratedImage instance.
|
|
162
|
+
verbose : `bool`, optional
|
|
163
|
+
If True, will print the path of the saved file or warning for invalid file name, by default False.
|
|
164
|
+
skip_invalid_filename: `bool`, optional
|
|
165
|
+
If True, will only save the image if the file name and extension are valid, by default False.
|
|
149
166
|
full_size: `bool`, optional
|
|
150
|
-
If True, will modify the default preview (512*512) URL to get the full size image.
|
|
151
|
-
kwargs: `dict`, optional
|
|
152
|
-
Other arguments to pass to `Image.save`.
|
|
167
|
+
If True, will modify the default preview (512*512) URL to get the full size image, by default True.
|
|
153
168
|
|
|
154
169
|
Returns
|
|
155
170
|
-------
|
|
@@ -161,8 +176,10 @@ class GeneratedImage(Image):
|
|
|
161
176
|
self.url += "=s2048"
|
|
162
177
|
|
|
163
178
|
return await super().save(
|
|
164
|
-
|
|
179
|
+
path=path,
|
|
180
|
+
filename=filename
|
|
165
181
|
or f"{datetime.now().strftime('%Y%m%d%H%M%S')}_{self.url[-10:]}.png",
|
|
166
|
-
cookies=self.cookies,
|
|
167
|
-
|
|
182
|
+
cookies=cookies or self.cookies,
|
|
183
|
+
verbose=verbose,
|
|
184
|
+
skip_invalid_filename=skip_invalid_filename,
|
|
168
185
|
)
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import asyncio
|
|
2
2
|
import functools
|
|
3
|
+
from collections.abc import Callable
|
|
3
4
|
|
|
4
5
|
from ..exceptions import APIError, ImageGenerationError
|
|
5
6
|
|
|
6
7
|
|
|
7
|
-
def running(retry: int = 0) ->
|
|
8
|
+
def running(retry: int = 0) -> Callable:
|
|
8
9
|
"""
|
|
9
10
|
Decorator to check if GeminiClient is running before making a request.
|
|
10
11
|
|
|
@@ -18,7 +19,7 @@ def running(retry: int = 0) -> callable:
|
|
|
18
19
|
@functools.wraps(func)
|
|
19
20
|
async def wrapper(client, *args, retry=retry, **kwargs):
|
|
20
21
|
try:
|
|
21
|
-
if not client.
|
|
22
|
+
if not client._running:
|
|
22
23
|
await client.init(
|
|
23
24
|
timeout=client.timeout,
|
|
24
25
|
auto_close=client.auto_close,
|
|
@@ -27,7 +28,7 @@ def running(retry: int = 0) -> callable:
|
|
|
27
28
|
refresh_interval=client.refresh_interval,
|
|
28
29
|
verbose=False,
|
|
29
30
|
)
|
|
30
|
-
if client.
|
|
31
|
+
if client._running:
|
|
31
32
|
return await func(client, *args, **kwargs)
|
|
32
33
|
|
|
33
34
|
# Should not reach here
|
|
@@ -20,7 +20,6 @@ async def send_request(
|
|
|
20
20
|
"""
|
|
21
21
|
|
|
22
22
|
async with AsyncClient(
|
|
23
|
-
http2=True,
|
|
24
23
|
proxy=proxy,
|
|
25
24
|
headers=Headers.GEMINI.value,
|
|
26
25
|
cookies=cookies,
|
|
@@ -66,12 +65,7 @@ async def get_access_token(
|
|
|
66
65
|
If all requests failed.
|
|
67
66
|
"""
|
|
68
67
|
|
|
69
|
-
async with AsyncClient(
|
|
70
|
-
http2=True,
|
|
71
|
-
proxy=proxy,
|
|
72
|
-
follow_redirects=True,
|
|
73
|
-
verify=False,
|
|
74
|
-
) as client:
|
|
68
|
+
async with AsyncClient(proxy=proxy, follow_redirects=True, verify=False) as client:
|
|
75
69
|
response = await client.get(Endpoint.GOOGLE.value)
|
|
76
70
|
|
|
77
71
|
extra_cookies = {}
|
gemini_webapi/utils/parsing.py
CHANGED
|
@@ -24,15 +24,16 @@ def get_nested_value(data: list, path: list[int], default: Any = None) -> Any:
|
|
|
24
24
|
for i, key in enumerate(path):
|
|
25
25
|
try:
|
|
26
26
|
current = current[key]
|
|
27
|
-
except (IndexError, TypeError, KeyError)
|
|
27
|
+
except (IndexError, TypeError, KeyError):
|
|
28
28
|
current_repr = repr(current)
|
|
29
29
|
if len(current_repr) > 200:
|
|
30
30
|
current_repr = f"{current_repr[:197]}..."
|
|
31
31
|
|
|
32
32
|
logger.debug(
|
|
33
|
-
f"
|
|
34
|
-
f"
|
|
33
|
+
f"Safe navigation: path {path} ended at index {i} (key '{key}'), "
|
|
34
|
+
f"returning default. Context: {current_repr}"
|
|
35
35
|
)
|
|
36
|
+
|
|
36
37
|
return default
|
|
37
38
|
|
|
38
39
|
if current is None and default is not None:
|
|
@@ -43,7 +43,7 @@ async def rotate_1psidts(cookies: dict, proxy: str | None = None) -> str:
|
|
|
43
43
|
|
|
44
44
|
# Check if the cache file was modified in the last minute to avoid 429 Too Many Requests
|
|
45
45
|
if not (path.is_file() and time.time() - os.path.getmtime(path) <= 60):
|
|
46
|
-
async with AsyncClient(
|
|
46
|
+
async with AsyncClient(proxy=proxy) as client:
|
|
47
47
|
response = await client.post(
|
|
48
48
|
url=Endpoint.ROTATE_COOKIES.value,
|
|
49
49
|
headers=Headers.ROTATE_COOKIES.value,
|
|
@@ -33,7 +33,7 @@ async def upload_file(file: str | Path, proxy: str | None = None) -> str:
|
|
|
33
33
|
with open(file, "rb") as f:
|
|
34
34
|
file = f.read()
|
|
35
35
|
|
|
36
|
-
async with AsyncClient(
|
|
36
|
+
async with AsyncClient(proxy=proxy) as client:
|
|
37
37
|
response = await client.post(
|
|
38
38
|
url=Endpoint.UPLOAD.value,
|
|
39
39
|
headers=Headers.UPLOAD.value,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: gemini-webapi
|
|
3
|
-
Version: 1.17.
|
|
3
|
+
Version: 1.17.3
|
|
4
4
|
Summary: ✨ An elegant async Python wrapper for Google Gemini web app
|
|
5
5
|
Author: UZQueen
|
|
6
6
|
License: GNU AFFERO GENERAL PUBLIC LICENSE
|
|
@@ -676,7 +676,7 @@ Classifier: Programming Language :: Python :: 3.12
|
|
|
676
676
|
Requires-Python: >=3.10
|
|
677
677
|
Description-Content-Type: text/markdown
|
|
678
678
|
License-File: LICENSE
|
|
679
|
-
Requires-Dist: httpx
|
|
679
|
+
Requires-Dist: httpx~=0.28.1
|
|
680
680
|
Requires-Dist: loguru~=0.7.3
|
|
681
681
|
Requires-Dist: orjson~=3.11.1
|
|
682
682
|
Requires-Dist: pydantic~=2.12.2
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
gemini_webapi/__init__.py,sha256=7ELCiUoI10ea3daeJxnv0UwqLVKpM7rxsgOZsPMstO8,150
|
|
2
|
-
gemini_webapi/client.py,sha256=
|
|
3
|
-
gemini_webapi/constants.py,sha256
|
|
2
|
+
gemini_webapi/client.py,sha256=OJSScOnl6rUbDOJ7hz-jn_1u13_HdjczPJsstOgNTGw,29254
|
|
3
|
+
gemini_webapi/constants.py,sha256=-UHHwSoY4pzEfRTGpfb8UMgCNVbgSc3RLSOZpVOyNpM,3409
|
|
4
4
|
gemini_webapi/exceptions.py,sha256=qkXrIpr0L7LtGbq3VcTO8D1xZ50pJtt0dDRp5I3uDSg,1038
|
|
5
5
|
gemini_webapi/components/__init__.py,sha256=wolxuAJJ32-jmHOKgpsesexP7hXea1JMo5vI52wysTI,48
|
|
6
6
|
gemini_webapi/components/gem_mixin.py,sha256=WPJkYDS4yQpLMBNQ94LQo5w59RgkllWaSiHsFG1k5GU,8795
|
|
@@ -8,18 +8,18 @@ gemini_webapi/types/__init__.py,sha256=1DU4JEw2KHQJbtOgOuvoEXtzQCMwAkyo0koSFVdT9
|
|
|
8
8
|
gemini_webapi/types/candidate.py,sha256=67BhY75toE5mVuB21cmHcTFtw332V_KmCjr3-9VTbJo,1477
|
|
9
9
|
gemini_webapi/types/gem.py,sha256=3Ppjq9V22Zp4Lb9a9ZnDviDKQpfSQf8UZxqOEjeEWd4,4070
|
|
10
10
|
gemini_webapi/types/grpc.py,sha256=S64h1oeC7ZJC50kmS_C2CQ7WVTanhJ4kqTFx5ZYayXI,917
|
|
11
|
-
gemini_webapi/types/image.py,sha256=
|
|
11
|
+
gemini_webapi/types/image.py,sha256=9miYAu3htBjNScCWfBJ49b_RtWDW9XFWO1NRJDvLgM8,6173
|
|
12
12
|
gemini_webapi/types/modeloutput.py,sha256=h07kQOkL5r-oPLvZ59uVtO1eP4FGy5ZpzuYQzAeQdr8,1196
|
|
13
13
|
gemini_webapi/utils/__init__.py,sha256=k8hV2zn6tD_BEpd1Xya6ED0deijsmzb1e9XxdFhJzIE,418
|
|
14
|
-
gemini_webapi/utils/decorators.py,sha256=
|
|
15
|
-
gemini_webapi/utils/get_access_token.py,sha256=
|
|
14
|
+
gemini_webapi/utils/decorators.py,sha256=uzIXoZOC0_Om19bbVXf_nw2w2NhI2qJL1o45FU6o6fI,1780
|
|
15
|
+
gemini_webapi/utils/get_access_token.py,sha256=twZtTtvOnGxHhikCOj1HGErj2r5dT_BNcvqv5A0dtXg,7194
|
|
16
16
|
gemini_webapi/utils/load_browser_cookies.py,sha256=OHCfe27DpV_rloIDgW9Xpeb0mkfzbYONNiholw0ElXU,1791
|
|
17
17
|
gemini_webapi/utils/logger.py,sha256=0VcxhVLhHBRDQutNCpapP1y_MhPoQ2ud1uIFLqxC3Z8,958
|
|
18
|
-
gemini_webapi/utils/parsing.py,sha256=
|
|
19
|
-
gemini_webapi/utils/rotate_1psidts.py,sha256=
|
|
20
|
-
gemini_webapi/utils/upload_file.py,sha256=
|
|
21
|
-
gemini_webapi-1.17.
|
|
22
|
-
gemini_webapi-1.17.
|
|
23
|
-
gemini_webapi-1.17.
|
|
24
|
-
gemini_webapi-1.17.
|
|
25
|
-
gemini_webapi-1.17.
|
|
18
|
+
gemini_webapi/utils/parsing.py,sha256=wDQsK1RcfrPr7c6w1JpxoPjLtkFIkcPmTX8jjfleJf0,2080
|
|
19
|
+
gemini_webapi/utils/rotate_1psidts.py,sha256=SIqkqjZrEAyXB38qTEY9apxHVwczpFut4I0A77eWIFk,1790
|
|
20
|
+
gemini_webapi/utils/upload_file.py,sha256=z5p03l6oQP74wzF0MF-Kbnav5k1nb7uYyzx-f8pxSVI,1456
|
|
21
|
+
gemini_webapi-1.17.3.dist-info/licenses/LICENSE,sha256=hIahDEOTzuHCU5J2nd07LWwkLW7Hko4UFO__ffsvB-8,34523
|
|
22
|
+
gemini_webapi-1.17.3.dist-info/METADATA,sha256=Ld5hDcwuox-uqslhP83-gzyYVEZa7wgOs5hX2NcG7Y4,61756
|
|
23
|
+
gemini_webapi-1.17.3.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
24
|
+
gemini_webapi-1.17.3.dist-info/top_level.txt,sha256=dtWtug_ZrmnUqCYuu8NmGzTgWglHeNzhHU_hXmqZGWE,14
|
|
25
|
+
gemini_webapi-1.17.3.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|