gemini-webapi 1.14.1__py3-none-any.whl → 1.14.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 CHANGED
@@ -7,9 +7,9 @@ from pathlib import Path
7
7
  from typing import Any, Optional
8
8
 
9
9
  import orjson as json
10
- from httpx import AsyncClient, ReadTimeout
10
+ from httpx import AsyncClient, ReadTimeout, Response
11
11
 
12
- from .constants import Endpoint, ErrorCode, Headers, Model
12
+ from .constants import Endpoint, ErrorCode, Headers, Model, GRPC
13
13
  from .exceptions import (
14
14
  AuthError,
15
15
  APIError,
@@ -20,7 +20,15 @@ from .exceptions import (
20
20
  ModelInvalid,
21
21
  TemporarilyBlocked,
22
22
  )
23
- from .types import WebImage, GeneratedImage, Candidate, ModelOutput, Gem, GemJar
23
+ from .types import (
24
+ WebImage,
25
+ GeneratedImage,
26
+ Candidate,
27
+ ModelOutput,
28
+ Gem,
29
+ GemJar,
30
+ RPCData,
31
+ )
24
32
  from .utils import (
25
33
  upload_file,
26
34
  parse_file_name,
@@ -301,66 +309,61 @@ class GeminiClient:
301
309
 
302
310
  return self._gems
303
311
 
304
- @running(retry=2)
305
- async def fetch_gems(self, **kwargs) -> GemJar:
312
+ async def fetch_gems(self, include_hidden: bool = False, **kwargs) -> GemJar:
306
313
  """
307
314
  Get a list of available gems from gemini, including system predefined gems and user-created custom gems.
308
315
 
309
316
  Note that network request will be sent every time this method is called.
310
317
  Once the gems are fetched, they will be cached and accessible via `GeminiClient.gems` property.
311
318
 
319
+ Parameters
320
+ ----------
321
+ include_hidden: `bool`, optional
322
+ There are some predefined gems that by default are not shown to users (and therefore may not work properly).
323
+ Set this parameter to `True` to include them in the fetched gem list.
324
+
312
325
  Returns
313
326
  -------
314
327
  :class:`GemJar`
315
328
  Refer to `gemini_webapi.types.GemJar`.
316
329
  """
317
330
 
318
- try:
319
- response = await self.client.post(
320
- Endpoint.BATCH_EXEC,
321
- data={
322
- "at": self.access_token,
323
- "f.req": json.dumps(
324
- [
325
- [
326
- ["CNgdBe", '[2,["en"],0]', None, "custom"],
327
- ["CNgdBe", '[3,["en"],0]', None, "system"],
328
- ]
329
- ]
330
- ).decode(),
331
- },
332
- **kwargs,
333
- )
334
- except ReadTimeout:
335
- raise TimeoutError(
336
- "Fetch gems request timed out, please try again. If the problem persists, "
337
- "consider setting a higher `timeout` value when initializing GeminiClient."
338
- )
331
+ response = await self._batch_execute(
332
+ [
333
+ RPCData(
334
+ rpcid=GRPC.LIST_GEMS,
335
+ payload="[4]" if include_hidden else "[3]",
336
+ identifier="system",
337
+ ),
338
+ RPCData(
339
+ rpcid=GRPC.LIST_GEMS,
340
+ payload="[2]",
341
+ identifier="custom",
342
+ ),
343
+ ],
344
+ **kwargs,
345
+ )
339
346
 
340
- if response.status_code != 200:
341
- raise APIError(
342
- f"Failed to fetch gems. Request failed with status code {response.status_code}"
343
- )
344
- else:
345
- try:
346
- response_json = json.loads(response.text.split("\n")[2])
347
+ try:
348
+ response_json = json.loads(response.text.split("\n")[2])
347
349
 
348
- predefined_gems, custom_gems = [], []
350
+ predefined_gems, custom_gems = [], []
349
351
 
350
- for part in response_json:
351
- if part[-1] == "system":
352
- predefined_gems = json.loads(part[2])[2]
353
- elif part[-1] == "custom":
354
- if custom_gems_container := json.loads(part[2]):
355
- custom_gems = custom_gems_container[2]
352
+ for part in response_json:
353
+ if part[-1] == "system":
354
+ predefined_gems = json.loads(part[2])[2]
355
+ elif part[-1] == "custom":
356
+ if custom_gems_container := json.loads(part[2]):
357
+ custom_gems = custom_gems_container[2]
356
358
 
357
- if not predefined_gems and not custom_gems:
358
- raise Exception
359
- except Exception:
360
- logger.debug(f"Invalid response: {response.text}")
361
- raise APIError(
362
- "Failed to fetch gems. Invalid response data received. Client will try to re-initialize on next request."
363
- )
359
+ if not predefined_gems and not custom_gems:
360
+ raise Exception
361
+ except Exception:
362
+ await self.close()
363
+ logger.debug(f"Invalid response: {response.text}")
364
+ raise APIError(
365
+ "Failed to fetch gems. Invalid response data received. Client will try to re-initialize on next request."
366
+ )
364
367
 
365
368
  self._gems = GemJar(
366
369
  itertools.chain(
@@ -664,6 +667,54 @@ class GeminiClient:
664
667
 
665
668
  return ChatSession(geminiclient=self, **kwargs)
666
669
 
670
+ @running(retry=2)
671
+ async def _batch_execute(self, payloads: list[RPCData], **kwargs) -> Response:
672
+ """
673
+ Execute a batch of requests to Gemini API.
674
+
675
+ Parameters
676
+ ----------
677
+ payloads: `list[GRPC]`
678
+ List of `gemini_webapi.types.GRPC` objects to be executed.
679
+ kwargs: `dict`, optional
680
+ Additional arguments which will be passed to the post request.
681
+ Refer to `httpx.AsyncClient.request` for more information.
682
+
683
+ Returns
684
+ -------
685
+ :class:`httpx.Response`
686
+ Response object containing the result of the batch execution.
687
+ """
688
+
689
+ try:
690
+ response = await self.client.post(
691
+ Endpoint.BATCH_EXEC,
692
+ data={
693
+ "at": self.access_token,
694
+ "f.req": json.dumps(
695
+ [[payload.serialize() for payload in payloads]]
696
+ ).decode(),
697
+ },
698
+ **kwargs,
699
+ )
700
+ except ReadTimeout:
701
+ raise TimeoutError(
702
+ "Batch execute request timed out, please try again. If the problem persists, "
703
+ "consider setting a higher `timeout` value when initializing GeminiClient."
704
+ )
705
+
706
+ if response.status_code != 200:
707
+ logger.debug(
708
+ f"Batch execution failed with status code {response.status_code}. "
709
+ f"RPC: {', '.join({payload.rpcid.name for payload in payloads})}; "
710
+ f"Invalid response: {response.text}"
711
+ )
712
+ raise APIError(
713
+ f"Batch execution failed with status code {response.status_code}"
714
+ )
715
+
716
+ return response
717
+
667
718
 
668
719
  class ChatSession:
669
720
  """
@@ -10,6 +10,18 @@ class Endpoint(StrEnum):
10
10
  BATCH_EXEC = "https://gemini.google.com/_/BardChatUi/data/batchexecute"
11
11
 
12
12
 
13
+ class GRPC(StrEnum):
14
+ """
15
+ Google RPC ids used in Gemini API.
16
+ """
17
+
18
+ # Chat methods
19
+ READ_CHAT = "hNvQHb"
20
+
21
+ # Gem methods
22
+ LIST_GEMS = "CNgdBe"
23
+
24
+
13
25
  class Headers(Enum):
14
26
  GEMINI = {
15
27
  "Content-Type": "application/x-www-form-urlencoded;charset=utf-8",
@@ -2,5 +2,6 @@
2
2
 
3
3
  from .candidate import Candidate
4
4
  from .gem import Gem, GemJar
5
+ from .grpc import RPCData
5
6
  from .image import Image, WebImage, GeneratedImage
6
7
  from .modeloutput import ModelOutput
@@ -0,0 +1,34 @@
1
+ from pydantic import BaseModel
2
+
3
+ from ..constants import GRPC
4
+
5
+
6
+ class RPCData(BaseModel):
7
+ """
8
+ Helper class containing necessary data for Google RPC calls.
9
+
10
+ Parameters
11
+ ----------
12
+ rpcid : GRPC
13
+ Google RPC ID.
14
+ payload : str
15
+ Payload for the RPC call.
16
+ identifier : str, optional
17
+ Identifier/order for the RPC call, defaults to "generic".
18
+ Makes sense if there are multiple RPC calls in a batch, where this identifier
19
+ can be used to distinguish between responses.
20
+ """
21
+
22
+ rpcid: GRPC
23
+ payload: str
24
+ identifier: str = "generic"
25
+
26
+ def __repr__(self):
27
+ return f"GRPC(rpcid='{self.rpcid}', payload='{self.payload}', identifier='{self.identifier}')"
28
+
29
+ def serialize(self) -> list:
30
+ """
31
+ Serializes object into formatted payload ready for RPC call.
32
+ """
33
+
34
+ return [self.rpcid, self.payload, None, self.identifier]
@@ -137,7 +137,7 @@ class GeneratedImage(Image):
137
137
  return v
138
138
 
139
139
  # @override
140
- async def save(self, **kwargs) -> str | None:
140
+ async def save(self, full_size=True, **kwargs) -> str | None:
141
141
  """
142
142
  Save the image to disk.
143
143
 
@@ -146,6 +146,8 @@ class GeneratedImage(Image):
146
146
  filename: `str`, optional
147
147
  Filename to save the image, generated images are always in .png format, but file extension will not be included in the URL.
148
148
  And since the URL ends with a long hash, by default will use timestamp + end of the hash as the filename.
149
+ full_size: `bool`, optional
150
+ If True, will modify the default preview (512*512) URL to get the full size image.
149
151
  kwargs: `dict`, optional
150
152
  Other arguments to pass to `Image.save`.
151
153
 
@@ -155,6 +157,9 @@ class GeneratedImage(Image):
155
157
  Absolute path of the saved image if successfully saved.
156
158
  """
157
159
 
160
+ if full_size:
161
+ self.url += "=s2048"
162
+
158
163
  return await super().save(
159
164
  filename=kwargs.pop("filename", None)
160
165
  or f"{datetime.now().strftime('%Y%m%d%H%M%S')}_{self.url[-10:]}.png",
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: gemini-webapi
3
- Version: 1.14.1
3
+ Version: 1.14.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
@@ -931,10 +931,14 @@ asyncio.run(main())
931
931
 
932
932
  System prompt can be applied to conversations via [Gemini Gems](https://gemini.google.com/gems/view). To use a gem, you can pass `gem` argument to `GeminiClient.generate_content` or `GeminiClient.start_chat`. `gem` can be either a string of gem id or a `gemini_webapi.Gem` object. Only one gem can be applied to a single conversation.
933
933
 
934
+ > [!TIP]
935
+ >
936
+ > There are some system predefined gems that by default are not shown to users (and therefore may not work properly). Use `client.fetch_gems(include_hidden=True)` to include them in the fetch result.
937
+
934
938
  ```python
935
939
  async def main():
936
940
  # Fetch all gems for the current account, including both predefined and user-created ones
937
- await client.fetch_gems()
941
+ await client.fetch_gems(include_hidden=False)
938
942
 
939
943
  # Once fetched, gems will be cached in `GeminiClient.gems`
940
944
  gems = client.gems
@@ -1,11 +1,12 @@
1
1
  gemini_webapi/__init__.py,sha256=7ELCiUoI10ea3daeJxnv0UwqLVKpM7rxsgOZsPMstO8,150
2
- gemini_webapi/client.py,sha256=9t_ytH4ClkjJWUwvLJ6KyArSRbNeGxd2MJVyPUmBIUA,30333
3
- gemini_webapi/constants.py,sha256=rkugkck9qXW_f-9dc0XXEjVIzJ9Ja4gHBVYDP71BESg,2352
2
+ gemini_webapi/client.py,sha256=XrPG5LtJDYpEOMyjL8zxRO9ExFwp9EO5UpsNxhouSvk,31820
3
+ gemini_webapi/constants.py,sha256=M2fbQSSVhF0ROv_9CTR4n8sDOujJmLRxwA6ARQ0Uf7k,2519
4
4
  gemini_webapi/exceptions.py,sha256=qkXrIpr0L7LtGbq3VcTO8D1xZ50pJtt0dDRp5I3uDSg,1038
5
- gemini_webapi/types/__init__.py,sha256=Xap_FGOKOOC9mU3bmp_VT_1pQgFMuqbys_fOcyvTnuE,166
5
+ gemini_webapi/types/__init__.py,sha256=1DU4JEw2KHQJbtOgOuvoEXtzQCMwAkyo0koSFVdT9JY,192
6
6
  gemini_webapi/types/candidate.py,sha256=67BhY75toE5mVuB21cmHcTFtw332V_KmCjr3-9VTbJo,1477
7
7
  gemini_webapi/types/gem.py,sha256=3Ppjq9V22Zp4Lb9a9ZnDviDKQpfSQf8UZxqOEjeEWd4,4070
8
- gemini_webapi/types/image.py,sha256=MflOex2tAxBF5zQkYTGTR78CuiqH3Wa6KxMKKXr5Yvo,5233
8
+ gemini_webapi/types/grpc.py,sha256=S64h1oeC7ZJC50kmS_C2CQ7WVTanhJ4kqTFx5ZYayXI,917
9
+ gemini_webapi/types/image.py,sha256=xCPaMzstIM5C09ZkJ0545vVPAsrF3y89XnRJrEWGz5k,5436
9
10
  gemini_webapi/types/modeloutput.py,sha256=h07kQOkL5r-oPLvZ59uVtO1eP4FGy5ZpzuYQzAeQdr8,1196
10
11
  gemini_webapi/utils/__init__.py,sha256=cJ9HQYxr8l0CsY61TFlho-5DdPxCaEOpRfjxPX-eils,320
11
12
  gemini_webapi/utils/get_access_token.py,sha256=eNn1omFO41wWXco1eM-KXR2CEi0Tb-chlph7H-PCNjg,6137
@@ -13,8 +14,8 @@ gemini_webapi/utils/load_browser_cookies.py,sha256=A5n_VsB7Rm8ck5lpy856UNJEhv30l
13
14
  gemini_webapi/utils/logger.py,sha256=0VcxhVLhHBRDQutNCpapP1y_MhPoQ2ud1uIFLqxC3Z8,958
14
15
  gemini_webapi/utils/rotate_1psidts.py,sha256=NyQ9OYPLBOcvpc8bodvEYDIVFrsYN0kdfc831lPEctM,1680
15
16
  gemini_webapi/utils/upload_file.py,sha256=SJOMr6kryK_ClrKmqI96fqZBNFOMPsyAvFINAGAU3rk,1468
16
- gemini_webapi-1.14.1.dist-info/licenses/LICENSE,sha256=hIahDEOTzuHCU5J2nd07LWwkLW7Hko4UFO__ffsvB-8,34523
17
- gemini_webapi-1.14.1.dist-info/METADATA,sha256=Qlym2uwFz03bgKIed0UtBmLMGXfpJwJ8j-2qMraLgJw,58925
18
- gemini_webapi-1.14.1.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
19
- gemini_webapi-1.14.1.dist-info/top_level.txt,sha256=dtWtug_ZrmnUqCYuu8NmGzTgWglHeNzhHU_hXmqZGWE,14
20
- gemini_webapi-1.14.1.dist-info/RECORD,,
17
+ gemini_webapi-1.14.3.dist-info/licenses/LICENSE,sha256=hIahDEOTzuHCU5J2nd07LWwkLW7Hko4UFO__ffsvB-8,34523
18
+ gemini_webapi-1.14.3.dist-info/METADATA,sha256=GNbHaHKIyQD0t3_MhkaxjHufYV7B5S1X5YUDG9-8e5o,59157
19
+ gemini_webapi-1.14.3.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
20
+ gemini_webapi-1.14.3.dist-info/top_level.txt,sha256=dtWtug_ZrmnUqCYuu8NmGzTgWglHeNzhHU_hXmqZGWE,14
21
+ gemini_webapi-1.14.3.dist-info/RECORD,,