gemini-webapi 1.15.1__py3-none-any.whl → 1.16.0__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 CHANGED
@@ -32,7 +32,6 @@ from .utils import (
32
32
  parse_file_name,
33
33
  rotate_1psidts,
34
34
  get_access_token,
35
- load_browser_cookies,
36
35
  running,
37
36
  rotate_tasks,
38
37
  logger,
@@ -101,24 +100,14 @@ class GeminiClient(GemMixin):
101
100
  self.refresh_interval: float = 540
102
101
  self.kwargs = kwargs
103
102
 
104
- # Validate cookies
105
103
  if secure_1psid:
106
104
  self.cookies["__Secure-1PSID"] = secure_1psid
107
105
  if secure_1psidts:
108
106
  self.cookies["__Secure-1PSIDTS"] = secure_1psidts
109
- else:
110
- try:
111
- cookies = load_browser_cookies(domain_name="google.com")
112
- if not (cookies and cookies.get("__Secure-1PSID")):
113
- raise ValueError(
114
- "Failed to load cookies from local browser. Please pass cookie values manually."
115
- )
116
- except ImportError:
117
- pass
118
107
 
119
108
  async def init(
120
109
  self,
121
- timeout: float = 30,
110
+ timeout: float = 300,
122
111
  auto_close: bool = False,
123
112
  close_delay: float = 300,
124
113
  auto_refresh: bool = True,
@@ -371,6 +360,11 @@ class GeminiClient(GemMixin):
371
360
  raise UsageLimitExceeded(
372
361
  f"Failed to generate contents. Usage limit of {model.model_name} model has exceeded. Please try switching to another model."
373
362
  )
363
+ case ErrorCode.MODEL_INCONSISTENT:
364
+ raise ModelInvalid(
365
+ "Failed to generate contents. The specified model is inconsistent with the chat history. Please make sure to pass the same "
366
+ "`model` parameter when starting a chat session with previous metadata."
367
+ )
374
368
  case ErrorCode.MODEL_HEADER_INVALID:
375
369
  raise ModelInvalid(
376
370
  "Failed to generate contents. The specified model is not available. Please update gemini_webapi to the latest version. "
@@ -16,6 +16,7 @@ class GRPC(StrEnum):
16
16
  """
17
17
 
18
18
  # Chat methods
19
+ LIST_CHATS = "MaZiqc"
19
20
  READ_CHAT = "hNvQHb"
20
21
 
21
22
  # Gem methods
@@ -84,5 +85,6 @@ class ErrorCode(IntEnum):
84
85
  """
85
86
 
86
87
  USAGE_LIMIT_EXCEEDED = 1037
88
+ MODEL_INCONSISTENT = 1050
87
89
  MODEL_HEADER_INVALID = 1052
88
90
  IP_TEMPORARILY_BLOCKED = 1060
@@ -1,3 +1,4 @@
1
+ import os
1
2
  import re
2
3
  import asyncio
3
4
  from asyncio import Task
@@ -88,7 +89,11 @@ async def get_access_token(
88
89
  )
89
90
 
90
91
  # Cached cookies in local file
91
- cache_dir = Path(__file__).parent / "temp"
92
+ cache_dir = (
93
+ (GEMINI_COOKIE_PATH := os.getenv("GEMINI_COOKIE_PATH"))
94
+ and Path(GEMINI_COOKIE_PATH)
95
+ or (Path(__file__).parent / "temp")
96
+ )
92
97
  if "__Secure-1PSID" in base_cookies:
93
98
  filename = f".cached_1psidts_{base_cookies['__Secure-1PSID']}.txt"
94
99
  cache_file = cache_dir / filename
@@ -126,17 +131,35 @@ async def get_access_token(
126
131
 
127
132
  # Browser cookies (if browser-cookie3 is installed)
128
133
  try:
134
+ valid_browser_cookies = 0
129
135
  browser_cookies = load_browser_cookies(
130
136
  domain_name="google.com", verbose=verbose
131
137
  )
132
- if browser_cookies and (secure_1psid := browser_cookies.get("__Secure-1PSID")):
133
- local_cookies = {"__Secure-1PSID": secure_1psid}
134
- if secure_1psidts := browser_cookies.get("__Secure-1PSIDTS"):
135
- local_cookies["__Secure-1PSIDTS"] = secure_1psidts
136
- if nid := browser_cookies.get("NID"):
137
- local_cookies["NID"] = nid
138
- tasks.append(Task(send_request(local_cookies, proxy=proxy)))
139
- elif verbose:
138
+ if browser_cookies:
139
+ for browser, cookies in browser_cookies.items():
140
+ if secure_1psid := cookies.get("__Secure-1PSID"):
141
+ if (
142
+ "__Secure-1PSID" in base_cookies
143
+ and base_cookies["__Secure-1PSID"] != secure_1psid
144
+ ):
145
+ if verbose:
146
+ logger.debug(
147
+ f"Skipping loading local browser cookies from {browser}. "
148
+ f"__Secure-1PSID does not match the one provided."
149
+ )
150
+ continue
151
+
152
+ local_cookies = {"__Secure-1PSID": secure_1psid}
153
+ if secure_1psidts := cookies.get("__Secure-1PSIDTS"):
154
+ local_cookies["__Secure-1PSIDTS"] = secure_1psidts
155
+ if nid := cookies.get("NID"):
156
+ local_cookies["NID"] = nid
157
+ tasks.append(Task(send_request(local_cookies, proxy=proxy)))
158
+ valid_browser_cookies += 1
159
+ if verbose:
160
+ logger.debug(f"Loaded local browser cookies from {browser}")
161
+
162
+ if valid_browser_cookies == 0 and verbose:
140
163
  logger.debug(
141
164
  "Skipping loading local browser cookies. Login to gemini.google.com in your browser first."
142
165
  )
@@ -149,6 +172,11 @@ async def get_access_token(
149
172
  if verbose:
150
173
  logger.warning(f"Skipping loading local browser cookies. {e}")
151
174
 
175
+ if not tasks:
176
+ raise AuthError(
177
+ "No valid cookies available for initialization. Please pass __Secure-1PSID and __Secure-1PSIDTS manually."
178
+ )
179
+
152
180
  for i, future in enumerate(asyncio.as_completed(tasks)):
153
181
  try:
154
182
  response, request_cookies = await future
@@ -1,3 +1,5 @@
1
+ from http.cookiejar import CookieJar
2
+
1
3
  from .logger import logger
2
4
 
3
5
 
@@ -15,8 +17,9 @@ def load_browser_cookies(domain_name: str = "", verbose=True) -> dict:
15
17
 
16
18
  Returns
17
19
  -------
18
- `dict`
19
- Dictionary with cookie name as key and cookie value as value.
20
+ `dict[str, dict]`
21
+ Dictionary with browser as keys and their cookies for the specified domain as values.
22
+ Only browsers that have cookies for the specified domain will be included.
20
23
  """
21
24
 
22
25
  import browser_cookie3 as bc3
@@ -35,8 +38,11 @@ def load_browser_cookies(domain_name: str = "", verbose=True) -> dict:
35
38
  bc3.safari,
36
39
  ]:
37
40
  try:
38
- for cookie in cookie_fn(domain_name=domain_name):
39
- cookies[cookie.name] = cookie.value
41
+ jar: CookieJar = cookie_fn(domain_name=domain_name)
42
+ if jar:
43
+ cookies[cookie_fn.__name__] = {
44
+ cookie.name: cookie.value for cookie in jar
45
+ }
40
46
  except bc3.BrowserCookieError:
41
47
  pass
42
48
  except PermissionError as e:
@@ -32,7 +32,11 @@ async def rotate_1psidts(cookies: dict, proxy: str | None = None) -> str:
32
32
  If request failed with other status codes.
33
33
  """
34
34
 
35
- path = Path(__file__).parent / "temp"
35
+ path = (
36
+ (GEMINI_COOKIE_PATH := os.getenv("GEMINI_COOKIE_PATH"))
37
+ and Path(GEMINI_COOKIE_PATH)
38
+ or (Path(__file__).parent / "temp")
39
+ )
36
40
  path.mkdir(parents=True, exist_ok=True)
37
41
  filename = f".cached_1psidts_{cookies['__Secure-1PSID']}.txt"
38
42
  path = path / filename
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: gemini-webapi
3
- Version: 1.15.1
3
+ Version: 1.16.0
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
@@ -679,7 +679,7 @@ License-File: LICENSE
679
679
  Requires-Dist: httpx[http2]~=0.28.1
680
680
  Requires-Dist: loguru~=0.7.3
681
681
  Requires-Dist: orjson~=3.11.1
682
- Requires-Dist: pydantic~=2.11.5
682
+ Requires-Dist: pydantic~=2.12.2
683
683
  Dynamic: license-file
684
684
 
685
685
  <p align="center">
@@ -713,7 +713,7 @@ A reverse-engineered asynchronous python wrapper for [Google Gemini](https://gem
713
713
  ## Features
714
714
 
715
715
  - **Persistent Cookies** - Automatically refreshes cookies in background. Optimized for always-on services.
716
- - **Image Generation** - Natively supports generating and modifying images with natural language.
716
+ - **Image Generation** - Natively supports generating and editing images with natural language.
717
717
  - **System Prompt** - Supports customizing model's system prompt with [Gemini Gems](https://gemini.google.com/gems/view).
718
718
  - **Extension Support** - Supports generating contents with [Gemini extensions](https://gemini.google.com/extensions) on, like YouTube and Gmail.
719
719
  - **Classified Outputs** - Categorizes texts, thoughts, web images and AI generated images in the response.
@@ -740,7 +740,7 @@ A reverse-engineered asynchronous python wrapper for [Google Gemini](https://gem
740
740
  - [Delete a custom gem](#delete-a-custom-gem)
741
741
  - [Retrieve model's thought process](#retrieve-models-thought-process)
742
742
  - [Retrieve images in response](#retrieve-images-in-response)
743
- - [Generate images with Imagen4](#generate-images-with-imagen4)
743
+ - [Generate and edit images](#generate-and-edit-images)
744
744
  - [Generate contents with Gemini extensions](#generate-contents-with-gemini-extensions)
745
745
  - [Check and switch to other reply candidates](#check-and-switch-to-other-reply-candidates)
746
746
  - [Logging Configuration](#logging-configuration)
@@ -777,15 +777,17 @@ pip install -U browser-cookie3
777
777
 
778
778
  > [!NOTE]
779
779
  >
780
- > If your application is deployed in a containerized environment (e.g. Docker), you may want to persist the cookies with a volume to avoid re-authentication every time the container rebuilds.
780
+ > If your application is deployed in a containerized environment (e.g. Docker), you may want to persist the cookies with a volume to avoid re-authentication every time the container rebuilds. You can set `GEMINI_COOKIE_PATH` environment variable to specify the path where auto-refreshed cookies are stored. Make sure the path is writable by the application.
781
781
  >
782
782
  > Here's part of a sample `docker-compose.yml` file:
783
783
 
784
784
  ```yaml
785
785
  services:
786
- main:
787
- volumes:
788
- - ./gemini_cookies:/usr/local/lib/python3.12/site-packages/gemini_webapi/utils/temp
786
+ main:
787
+ environment:
788
+ GEMINI_COOKIE_PATH: /tmp/gemini_webapi
789
+ volumes:
790
+ - ./gemini_cookies:/tmp/gemini_webapi
789
791
  ```
790
792
 
791
793
  > [!NOTE]
@@ -1064,13 +1066,13 @@ async def main():
1064
1066
  asyncio.run(main())
1065
1067
  ```
1066
1068
 
1067
- ### Generate images with Imagen4
1069
+ ### Generate and edit images
1068
1070
 
1069
- You can ask Gemini to generate and modify images with Imagen4, Google's latest AI image generator, simply by natural language.
1071
+ You can ask Gemini to generate and edit images with Nano Banana, Google's latest image model, simply by natural language.
1070
1072
 
1071
1073
  > [!IMPORTANT]
1072
1074
  >
1073
- > Google has some limitations on the image generation feature in Gemini, so its availability could be different per region/account. Here's a summary copied from [official documentation](https://support.google.com/gemini/answer/14286560) (as of March 19th, 2025):
1075
+ > Google has some limitations on the image generation feature in Gemini, so its availability could be different per region/account. Here's a summary copied from [official documentation](https://support.google.com/gemini/answer/14286560) (as of Sep 10, 2025):
1074
1076
  >
1075
1077
  > > This feature’s availability in any specific Gemini app is also limited to the supported languages and countries of that app.
1076
1078
  > >
@@ -1,6 +1,6 @@
1
1
  gemini_webapi/__init__.py,sha256=7ELCiUoI10ea3daeJxnv0UwqLVKpM7rxsgOZsPMstO8,150
2
- gemini_webapi/client.py,sha256=G0nmRCeEmT0BpqgYY4zxb_ozFDmb39JqtddKpIN6JlA,26870
3
- gemini_webapi/constants.py,sha256=z8Jr6NQk79mgZOgmwx8vbgzyORHQVBjoJ4ZhUdDWWXw,2629
2
+ gemini_webapi/client.py,sha256=YlgBiIQWkxCjPaqMQ1Zs-MvRz6ww0U3uqdS__azqvwA,26827
3
+ gemini_webapi/constants.py,sha256=ZzyZRqwJsqjvDdj3GYIdk0SpQVbKa1Vd5NwEsvIz6pI,2685
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
@@ -12,13 +12,13 @@ gemini_webapi/types/image.py,sha256=NhW1QY2Agzb6UbrIjZLnqxqukabDSGbmoCsVguzko10,
12
12
  gemini_webapi/types/modeloutput.py,sha256=h07kQOkL5r-oPLvZ59uVtO1eP4FGy5ZpzuYQzAeQdr8,1196
13
13
  gemini_webapi/utils/__init__.py,sha256=RIU1MBqYNjih0XMMt7wCxAA-X9KVF53nDbAjaxaGTo8,352
14
14
  gemini_webapi/utils/decorators.py,sha256=AuY6sU1_6_ZqeL92dTAi3eRPQ7zubB5VuBZEiz16aBM,1741
15
- gemini_webapi/utils/get_access_token.py,sha256=eNn1omFO41wWXco1eM-KXR2CEi0Tb-chlph7H-PCNjg,6137
16
- gemini_webapi/utils/load_browser_cookies.py,sha256=A5n_VsB7Rm8ck5lpy856UNJEhv30l3dvQ3j0g3ln1fE,1535
15
+ gemini_webapi/utils/get_access_token.py,sha256=VjrHW8VMN3LPs6zCdXQtraWygurOjTY0SJZhe49aQwc,7265
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/rotate_1psidts.py,sha256=NyQ9OYPLBOcvpc8bodvEYDIVFrsYN0kdfc831lPEctM,1680
18
+ gemini_webapi/utils/rotate_1psidts.py,sha256=XjEeQnZS3ZI6wOl0Zb5CvsbIrg0BVVNas7cE6f3x_XE,1802
19
19
  gemini_webapi/utils/upload_file.py,sha256=SJOMr6kryK_ClrKmqI96fqZBNFOMPsyAvFINAGAU3rk,1468
20
- gemini_webapi-1.15.1.dist-info/licenses/LICENSE,sha256=hIahDEOTzuHCU5J2nd07LWwkLW7Hko4UFO__ffsvB-8,34523
21
- gemini_webapi-1.15.1.dist-info/METADATA,sha256=baIL2RVMPLqKe1l_lMxi5QDpTEJbNdehoF4uk2-467Q,61279
22
- gemini_webapi-1.15.1.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
23
- gemini_webapi-1.15.1.dist-info/top_level.txt,sha256=dtWtug_ZrmnUqCYuu8NmGzTgWglHeNzhHU_hXmqZGWE,14
24
- gemini_webapi-1.15.1.dist-info/RECORD,,
20
+ gemini_webapi-1.16.0.dist-info/licenses/LICENSE,sha256=hIahDEOTzuHCU5J2nd07LWwkLW7Hko4UFO__ffsvB-8,34523
21
+ gemini_webapi-1.16.0.dist-info/METADATA,sha256=vRnA79JpF9eMcgjzCqbpok_UGTvw5EPYKRO1bFtjZ5Q,61426
22
+ gemini_webapi-1.16.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
23
+ gemini_webapi-1.16.0.dist-info/top_level.txt,sha256=dtWtug_ZrmnUqCYuu8NmGzTgWglHeNzhHU_hXmqZGWE,14
24
+ gemini_webapi-1.16.0.dist-info/RECORD,,