gemini-webapi 0.0.1__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/__init__.py +2 -0
- gemini/client.py +353 -0
- gemini/consts.py +8 -0
- gemini/types.py +64 -0
- gemini_webapi-0.0.1.dist-info/LICENSE +21 -0
- gemini_webapi-0.0.1.dist-info/METADATA +91 -0
- gemini_webapi-0.0.1.dist-info/RECORD +9 -0
- gemini_webapi-0.0.1.dist-info/WHEEL +5 -0
- gemini_webapi-0.0.1.dist-info/top_level.txt +1 -0
gemini/__init__.py
ADDED
gemini/client.py
ADDED
|
@@ -0,0 +1,353 @@
|
|
|
1
|
+
import re
|
|
2
|
+
import json
|
|
3
|
+
import asyncio
|
|
4
|
+
from asyncio import Task
|
|
5
|
+
from typing import Any, Optional
|
|
6
|
+
|
|
7
|
+
from httpx import AsyncClient
|
|
8
|
+
from loguru import logger
|
|
9
|
+
|
|
10
|
+
from .consts import HEADERS
|
|
11
|
+
from .types import Image, Candidate, ModelOutput
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def running(func) -> callable:
|
|
15
|
+
"""
|
|
16
|
+
Decorator to check if client is running before making a request.
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
async def wrapper(self: "GeminiClient", *args, **kwargs):
|
|
20
|
+
if not self.running:
|
|
21
|
+
await self.init(auto_close=self.auto_close, close_delay=self.close_delay)
|
|
22
|
+
if self.running:
|
|
23
|
+
return await func(self, *args, **kwargs)
|
|
24
|
+
|
|
25
|
+
raise Exception(
|
|
26
|
+
f"Invalid function call: GeminiClient.{func.__name__}. Client initialization failed."
|
|
27
|
+
)
|
|
28
|
+
else:
|
|
29
|
+
return await func(self, *args, **kwargs)
|
|
30
|
+
|
|
31
|
+
return wrapper
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class GeminiClient:
|
|
35
|
+
"""
|
|
36
|
+
Async httpx client interface for gemini.google.com
|
|
37
|
+
|
|
38
|
+
Parameters
|
|
39
|
+
----------
|
|
40
|
+
secure_1psid: `str`
|
|
41
|
+
__Secure-1PSID cookie value
|
|
42
|
+
secure_1psidts: `str`, optional
|
|
43
|
+
__Secure-1PSIDTS cookie value, some google accounts don't require this value, provide only if it's in the cookie list
|
|
44
|
+
proxy: `dict`, optional
|
|
45
|
+
Dict of proxies
|
|
46
|
+
"""
|
|
47
|
+
|
|
48
|
+
__slots__ = [
|
|
49
|
+
"cookies",
|
|
50
|
+
"proxy",
|
|
51
|
+
"client",
|
|
52
|
+
"access_token",
|
|
53
|
+
"running",
|
|
54
|
+
"auto_close",
|
|
55
|
+
"close_delay",
|
|
56
|
+
"close_task",
|
|
57
|
+
]
|
|
58
|
+
|
|
59
|
+
def __init__(
|
|
60
|
+
self,
|
|
61
|
+
secure_1psid: str,
|
|
62
|
+
secure_1psidts: Optional[str] = None,
|
|
63
|
+
proxy: Optional[dict] = None,
|
|
64
|
+
):
|
|
65
|
+
self.cookies = {
|
|
66
|
+
"__Secure-1PSID": secure_1psid,
|
|
67
|
+
"__Secure-1PSIDTS": secure_1psidts,
|
|
68
|
+
}
|
|
69
|
+
self.proxy = proxy
|
|
70
|
+
self.client: AsyncClient | None = None
|
|
71
|
+
self.access_token: Optional[str] = None
|
|
72
|
+
self.running: bool = False
|
|
73
|
+
self.auto_close: bool = False
|
|
74
|
+
self.close_delay: int = 0
|
|
75
|
+
self.close_task: Task | None = None
|
|
76
|
+
|
|
77
|
+
async def init(
|
|
78
|
+
self, timeout: float = 30, auto_close: bool = False, close_delay: int = 300
|
|
79
|
+
) -> None:
|
|
80
|
+
"""
|
|
81
|
+
Get SNlM0e value as access token. Without this token posting will fail with 400 bad request.
|
|
82
|
+
|
|
83
|
+
Parameters
|
|
84
|
+
----------
|
|
85
|
+
timeout: `int`, optional
|
|
86
|
+
Request timeout of the client in seconds. Used to limit the max waiting time when sending a request
|
|
87
|
+
auto_close: `bool`, optional
|
|
88
|
+
If `True`, the client will close connections and clear resource usage after a certain period
|
|
89
|
+
of inactivity. Useful for keep-alive services
|
|
90
|
+
close_delay: `int`, optional
|
|
91
|
+
Time to wait before auto-closing the client in seconds. Effective only if `auto_close` is `True`
|
|
92
|
+
"""
|
|
93
|
+
try:
|
|
94
|
+
self.client = AsyncClient(
|
|
95
|
+
timeout=timeout,
|
|
96
|
+
proxies=self.proxy,
|
|
97
|
+
follow_redirects=True,
|
|
98
|
+
headers=HEADERS,
|
|
99
|
+
cookies=self.cookies,
|
|
100
|
+
)
|
|
101
|
+
|
|
102
|
+
response = await self.client.get("https://gemini.google.com/app")
|
|
103
|
+
|
|
104
|
+
if response.status_code != 200:
|
|
105
|
+
raise Exception(
|
|
106
|
+
f"Failed to initiate client. Request failed with status code {response.status_code}"
|
|
107
|
+
)
|
|
108
|
+
else:
|
|
109
|
+
match = re.search(r'"SNlM0e":"(.*?)"', response.text)
|
|
110
|
+
if match:
|
|
111
|
+
self.access_token = match.group(1)
|
|
112
|
+
self.running = True
|
|
113
|
+
logger.success("Gemini client initiated successfully.")
|
|
114
|
+
else:
|
|
115
|
+
raise Exception(
|
|
116
|
+
"Failed to initiate client. SNlM0e not found in response, make sure cookie values are valid."
|
|
117
|
+
)
|
|
118
|
+
|
|
119
|
+
self.auto_close = auto_close
|
|
120
|
+
self.close_delay = close_delay
|
|
121
|
+
if self.auto_close:
|
|
122
|
+
await self.reset_close_task()
|
|
123
|
+
except Exception:
|
|
124
|
+
await self.close(0)
|
|
125
|
+
raise
|
|
126
|
+
|
|
127
|
+
async def close(self, wait: int | None = None) -> None:
|
|
128
|
+
"""
|
|
129
|
+
Close the client after a certain period of inactivity, or call manually to close immediately.
|
|
130
|
+
|
|
131
|
+
Parameters
|
|
132
|
+
----------
|
|
133
|
+
wait: `int`, optional
|
|
134
|
+
Time to wait before closing the client in seconds
|
|
135
|
+
"""
|
|
136
|
+
await asyncio.sleep(wait is not None and wait or self.close_delay)
|
|
137
|
+
await self.client.aclose()
|
|
138
|
+
self.running = False
|
|
139
|
+
|
|
140
|
+
async def reset_close_task(self) -> None:
|
|
141
|
+
"""
|
|
142
|
+
Reset the timer for closing the client when a new request is made.
|
|
143
|
+
"""
|
|
144
|
+
if self.close_task:
|
|
145
|
+
self.close_task.cancel()
|
|
146
|
+
self.close_task = None
|
|
147
|
+
self.close_task = asyncio.create_task(self.close())
|
|
148
|
+
|
|
149
|
+
@running
|
|
150
|
+
async def generate_content(
|
|
151
|
+
self, prompt: str, chat: Optional["ChatSession"] = None
|
|
152
|
+
) -> ModelOutput:
|
|
153
|
+
"""
|
|
154
|
+
Generates contents with prompt.
|
|
155
|
+
|
|
156
|
+
Parameters
|
|
157
|
+
----------
|
|
158
|
+
prompt: `str`
|
|
159
|
+
Prompt provided by user
|
|
160
|
+
chat: `ChatSession`, optional
|
|
161
|
+
Chat data to retrieve conversation history. If None, will automatically generate a new chat id when sending post request
|
|
162
|
+
|
|
163
|
+
Returns
|
|
164
|
+
-------
|
|
165
|
+
:class:`ModelOutput`
|
|
166
|
+
Output data from gemini.google.com, use `ModelOutput.text` to get the default text reply, `ModelOutput.images` to get a list
|
|
167
|
+
of images in the default reply, `ModelOutput.candidates` to get a list of all answer candidates in the output
|
|
168
|
+
"""
|
|
169
|
+
assert prompt, "Prompt cannot be empty."
|
|
170
|
+
|
|
171
|
+
if self.auto_close:
|
|
172
|
+
await self.reset_close_task()
|
|
173
|
+
|
|
174
|
+
response = await self.client.post(
|
|
175
|
+
"https://gemini.google.com/_/BardChatUi/data/assistant.lamda.BardFrontendService/StreamGenerate",
|
|
176
|
+
data={
|
|
177
|
+
"at": self.access_token,
|
|
178
|
+
"f.req": json.dumps(
|
|
179
|
+
[None, json.dumps([[prompt], None, chat and chat.metadata])]
|
|
180
|
+
),
|
|
181
|
+
},
|
|
182
|
+
)
|
|
183
|
+
|
|
184
|
+
if response.status_code != 200:
|
|
185
|
+
raise Exception(
|
|
186
|
+
f"Failed to generate contents. Request failed with status code {response.status_code}"
|
|
187
|
+
)
|
|
188
|
+
else:
|
|
189
|
+
body = json.loads(json.loads(response.text.split("\n")[2])[0][2])
|
|
190
|
+
|
|
191
|
+
candidates = []
|
|
192
|
+
for candidate in body[4]:
|
|
193
|
+
images = (
|
|
194
|
+
candidate[4]
|
|
195
|
+
and [
|
|
196
|
+
Image(url=image[0][0][0], title=image[2], alt=image[0][4])
|
|
197
|
+
for image in candidate[4]
|
|
198
|
+
]
|
|
199
|
+
or []
|
|
200
|
+
)
|
|
201
|
+
candidates.append(
|
|
202
|
+
Candidate(rcid=candidate[0], text=candidate[1][0], images=images)
|
|
203
|
+
)
|
|
204
|
+
if not candidates:
|
|
205
|
+
raise Exception(
|
|
206
|
+
"Failed to generate contents. No output data found in response."
|
|
207
|
+
)
|
|
208
|
+
|
|
209
|
+
output = ModelOutput(metadata=body[1], candidates=candidates)
|
|
210
|
+
|
|
211
|
+
if isinstance(chat, ChatSession):
|
|
212
|
+
chat.last_output = output
|
|
213
|
+
|
|
214
|
+
return output
|
|
215
|
+
|
|
216
|
+
def start_chat(self, **kwargs) -> "ChatSession":
|
|
217
|
+
"""
|
|
218
|
+
Returns a `ChatSession` object attached to this model.
|
|
219
|
+
|
|
220
|
+
Returns
|
|
221
|
+
-------
|
|
222
|
+
:class:`ChatSession`
|
|
223
|
+
Empty chat object for retrieving conversation history
|
|
224
|
+
"""
|
|
225
|
+
return ChatSession(geminiclient=self, **kwargs)
|
|
226
|
+
|
|
227
|
+
|
|
228
|
+
class ChatSession:
|
|
229
|
+
"""
|
|
230
|
+
Chat data to retrieve conversation history. Only if all 3 ids are provided will the conversation history be retrieved.
|
|
231
|
+
|
|
232
|
+
Parameters
|
|
233
|
+
----------
|
|
234
|
+
geminiclient: `GeminiClient`
|
|
235
|
+
Async httpx client interface for gemini.google.com
|
|
236
|
+
metadata: `list[str]`, optional
|
|
237
|
+
List of chat metadata `[cid, rid, rcid]`, can be shorter than 3 elements, like `[cid, rid]` or `[cid]` only
|
|
238
|
+
cid: `str`, optional
|
|
239
|
+
Chat id, if provided together with metadata, will override the first value in it
|
|
240
|
+
rid: `str`, optional
|
|
241
|
+
Reply id, if provided together with metadata, will override the second value in it
|
|
242
|
+
rcid: `str`, optional
|
|
243
|
+
Reply candidate id, if provided together with metadata, will override the third value in it
|
|
244
|
+
"""
|
|
245
|
+
|
|
246
|
+
# @properties needn't have their slots pre-defined
|
|
247
|
+
__slots__ = ["__metadata", "geminiclient", "last_output"]
|
|
248
|
+
|
|
249
|
+
def __init__(
|
|
250
|
+
self,
|
|
251
|
+
geminiclient: GeminiClient,
|
|
252
|
+
metadata: Optional[list[str]] = None,
|
|
253
|
+
cid: Optional[str] = None, # chat id
|
|
254
|
+
rid: Optional[str] = None, # reply id
|
|
255
|
+
rcid: Optional[str] = None, # reply candidate id
|
|
256
|
+
):
|
|
257
|
+
self.__metadata: list[Optional[str]] = [None, None, None]
|
|
258
|
+
self.geminiclient: GeminiClient = geminiclient
|
|
259
|
+
self.last_output: Optional[ModelOutput] = None
|
|
260
|
+
|
|
261
|
+
if metadata:
|
|
262
|
+
self.metadata = metadata
|
|
263
|
+
if cid:
|
|
264
|
+
self.cid = cid
|
|
265
|
+
if rid:
|
|
266
|
+
self.rid = rid
|
|
267
|
+
if rcid:
|
|
268
|
+
self.rcid = rcid
|
|
269
|
+
|
|
270
|
+
def __str__(self):
|
|
271
|
+
return f"ChatSession(cid='{self.cid}', rid='{self.rid}', rcid='{self.rcid}')"
|
|
272
|
+
|
|
273
|
+
__repr__ = __str__
|
|
274
|
+
|
|
275
|
+
def __setattr__(self, name: str, value: Any) -> None:
|
|
276
|
+
super().__setattr__(name, value)
|
|
277
|
+
# update conversation history when last output is updated
|
|
278
|
+
if name == "last_output" and isinstance(value, ModelOutput):
|
|
279
|
+
self.metadata = value.metadata
|
|
280
|
+
self.rcid = value.rcid
|
|
281
|
+
|
|
282
|
+
async def send_message(self, prompt: str) -> ModelOutput:
|
|
283
|
+
"""
|
|
284
|
+
Generates contents with prompt.
|
|
285
|
+
Use as a shortcut for `GeminiClient.generate_content(prompt, self)`.
|
|
286
|
+
|
|
287
|
+
Parameters
|
|
288
|
+
----------
|
|
289
|
+
prompt: `str`
|
|
290
|
+
Prompt provided by user
|
|
291
|
+
|
|
292
|
+
Returns
|
|
293
|
+
-------
|
|
294
|
+
:class:`ModelOutput`
|
|
295
|
+
Output data from gemini.google.com, use `ModelOutput.text` to get the default text reply, `ModelOutput.images` to get a list
|
|
296
|
+
of images in the default reply, `ModelOutput.candidates` to get a list of all answer candidates in the output
|
|
297
|
+
"""
|
|
298
|
+
return await self.geminiclient.generate_content(prompt, self)
|
|
299
|
+
|
|
300
|
+
def choose_candidate(self, index: int) -> ModelOutput:
|
|
301
|
+
"""
|
|
302
|
+
Choose a candidate from the last `ModelOutput` to control the ongoing conversation flow.
|
|
303
|
+
|
|
304
|
+
Parameters
|
|
305
|
+
----------
|
|
306
|
+
index: `int`
|
|
307
|
+
Index of the candidate to choose, starting from 0
|
|
308
|
+
"""
|
|
309
|
+
if not self.last_output:
|
|
310
|
+
raise Exception("No previous output data found in this chat session.")
|
|
311
|
+
|
|
312
|
+
if index >= len(self.last_output.candidates):
|
|
313
|
+
raise ValueError(
|
|
314
|
+
f"Index {index} exceeds the number of candidates in last model output."
|
|
315
|
+
)
|
|
316
|
+
|
|
317
|
+
self.last_output.chosen = index
|
|
318
|
+
self.rcid = self.last_output.rcid
|
|
319
|
+
return self.last_output
|
|
320
|
+
|
|
321
|
+
@property
|
|
322
|
+
def metadata(self):
|
|
323
|
+
return self.__metadata
|
|
324
|
+
|
|
325
|
+
@metadata.setter
|
|
326
|
+
def metadata(self, value: list[str]):
|
|
327
|
+
if len(value) > 3:
|
|
328
|
+
raise ValueError("metadata cannot exceed 3 elements")
|
|
329
|
+
self.__metadata[: len(value)] = value
|
|
330
|
+
|
|
331
|
+
@property
|
|
332
|
+
def cid(self):
|
|
333
|
+
return self.__metadata[0]
|
|
334
|
+
|
|
335
|
+
@cid.setter
|
|
336
|
+
def cid(self, value: str):
|
|
337
|
+
self.__metadata[0] = value
|
|
338
|
+
|
|
339
|
+
@property
|
|
340
|
+
def rid(self):
|
|
341
|
+
return self.__metadata[1]
|
|
342
|
+
|
|
343
|
+
@rid.setter
|
|
344
|
+
def rid(self, value: str):
|
|
345
|
+
self.__metadata[1] = value
|
|
346
|
+
|
|
347
|
+
@property
|
|
348
|
+
def rcid(self):
|
|
349
|
+
return self.__metadata[2]
|
|
350
|
+
|
|
351
|
+
@rcid.setter
|
|
352
|
+
def rcid(self, value: str):
|
|
353
|
+
self.__metadata[2] = value
|
gemini/consts.py
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
HEADERS = {
|
|
2
|
+
"Content-Type": "application/x-www-form-urlencoded;charset=utf-8",
|
|
3
|
+
"Host": "gemini.google.com",
|
|
4
|
+
"Origin": "https://gemini.google.com",
|
|
5
|
+
"Referer": "https://gemini.google.com/",
|
|
6
|
+
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36",
|
|
7
|
+
"X-Same-Domain": "1",
|
|
8
|
+
}
|
gemini/types.py
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
from typing import Optional
|
|
2
|
+
|
|
3
|
+
from pydantic import BaseModel
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class Image(BaseModel):
|
|
7
|
+
url: str
|
|
8
|
+
title: Optional[str] = "[Image]"
|
|
9
|
+
alt: Optional[str] = ""
|
|
10
|
+
|
|
11
|
+
def __str__(self):
|
|
12
|
+
return f"{self.title}({self.url}) - {self.alt}"
|
|
13
|
+
|
|
14
|
+
def __repr__(self):
|
|
15
|
+
return f"Image(title='{self.title}', url='{len(self.url)<=20 and self.url or self.url[:8] + '...' + self.url[-12:]}', alt='{self.alt}')"
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class Candidate(BaseModel):
|
|
19
|
+
rcid: str
|
|
20
|
+
text: str
|
|
21
|
+
images: list[Image]
|
|
22
|
+
|
|
23
|
+
def __str__(self):
|
|
24
|
+
return self.text
|
|
25
|
+
|
|
26
|
+
def __repr__(self):
|
|
27
|
+
return f"Candidate(rcid='{self.rcid}', text='{len(self.text)<=20 and self.text or self.text[:20] + '...'}', images={self.images})"
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class ModelOutput(BaseModel):
|
|
31
|
+
"""
|
|
32
|
+
Classified output from gemini.google.com
|
|
33
|
+
|
|
34
|
+
Parameters
|
|
35
|
+
----------
|
|
36
|
+
metadata: `list[str]`
|
|
37
|
+
List of chat metadata `[cid, rid, rcid]`, can be shorter than 3 elements, like `[cid, rid]` or `[cid]` only
|
|
38
|
+
candidates: `list[Candidate]`
|
|
39
|
+
List of all candidates returned from gemini
|
|
40
|
+
chosen: `int`, optional
|
|
41
|
+
Index of the chosen candidate, by default will choose the first one
|
|
42
|
+
"""
|
|
43
|
+
|
|
44
|
+
metadata: list[str]
|
|
45
|
+
candidates: list[Candidate]
|
|
46
|
+
chosen: int = 0
|
|
47
|
+
|
|
48
|
+
def __str__(self):
|
|
49
|
+
return self.text
|
|
50
|
+
|
|
51
|
+
def __repr__(self):
|
|
52
|
+
return f"ModelOutput(metadata={self.metadata}, chosen={self.chosen}, candidates={self.candidates})"
|
|
53
|
+
|
|
54
|
+
@property
|
|
55
|
+
def text(self):
|
|
56
|
+
return self.candidates[self.chosen].text
|
|
57
|
+
|
|
58
|
+
@property
|
|
59
|
+
def images(self):
|
|
60
|
+
return self.candidates[self.chosen].images
|
|
61
|
+
|
|
62
|
+
@property
|
|
63
|
+
def rcid(self):
|
|
64
|
+
return self.candidates[self.chosen].rcid
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024 GM Development Department
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: gemini-webapi
|
|
3
|
+
Version: 0.0.1
|
|
4
|
+
Summary: A reverse-engineered async wrapper for Google Gemini web client
|
|
5
|
+
Author: UZQueen
|
|
6
|
+
License: MIT License
|
|
7
|
+
Project-URL: Repository, https://github.com/HanaokaYuzu/Gemini-API
|
|
8
|
+
Project-URL: Issues, https://github.com/HanaokaYuzu/Gemini-API/issues
|
|
9
|
+
Keywords: API,async,Gemini,Bard,Google,Generative AI,LLM
|
|
10
|
+
Classifier: Development Status :: 4 - Beta
|
|
11
|
+
Classifier: Intended Audience :: Developers
|
|
12
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
13
|
+
Classifier: Programming Language :: Python :: 3
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
16
|
+
Requires-Python: >=3.7
|
|
17
|
+
Description-Content-Type: text/markdown
|
|
18
|
+
License-File: LICENSE
|
|
19
|
+
Requires-Dist: httpx >=0.25.2
|
|
20
|
+
Requires-Dist: pydantic >=2.5.3
|
|
21
|
+
Requires-Dist: loguru >=0.7.2
|
|
22
|
+
|
|
23
|
+
# <img src="https://www.gstatic.com/lamda/images/favicon_v1_150160cddff7f294ce30.svg" width="35px" alt="Gemini Icon" /> Gemini-API
|
|
24
|
+
|
|
25
|
+
Reverse-engineered asynchronous python wrapper for Google Gemini (formerly Bard) web client, providing a simple and elegant interface inspired by Google GenerativeAI's official API.
|
|
26
|
+
|
|
27
|
+
## Installation
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
pip install gemini-webapi
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## Authentication
|
|
34
|
+
|
|
35
|
+
- Go to <https://gemini.google.com/> and login with your Google account
|
|
36
|
+
- Press F12 for web inspector, go to `Network` tab and refresh the page
|
|
37
|
+
- Click any request and copy cookie values of `__Secure-1PSID` and `__Secure-1PSIDTS`
|
|
38
|
+
|
|
39
|
+
## Usage
|
|
40
|
+
|
|
41
|
+
### Initialization
|
|
42
|
+
|
|
43
|
+
```python
|
|
44
|
+
from gemini import GeminiClient
|
|
45
|
+
|
|
46
|
+
# Replace "COOKIE VALUE HERE" with your actual cookie values as strings
|
|
47
|
+
Secure_1PSID = "COOKIE VALUE HERE"
|
|
48
|
+
Secure_1PSIDTS = "COOKIE VALUE HERE"
|
|
49
|
+
|
|
50
|
+
client = GeminiClient(Secure_1PSID, Secure_1PSIDTS, proxy=None)
|
|
51
|
+
await client.init()
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
### Generate contents from text inputs
|
|
55
|
+
|
|
56
|
+
```python
|
|
57
|
+
response = await client.generate_content("Hello World!")
|
|
58
|
+
print(response.text) # Note: simply use print(response) to get the same output if you just want to see the response text
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
### Retrieve images in response
|
|
62
|
+
|
|
63
|
+
```python
|
|
64
|
+
response = await client.generate_content("Send me some pictures of cats")
|
|
65
|
+
images = response.images
|
|
66
|
+
for image in images:
|
|
67
|
+
print(f"{image.title}({image.url}) - {image.alt}", sep="\n")
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
### Conversations across multiple turns
|
|
71
|
+
|
|
72
|
+
```python
|
|
73
|
+
chat = client.start_chat() # A chat stores the metadata to keep a conversation continuous. It will automatically get updated after each turn
|
|
74
|
+
response1 = await chat.send_message("Briefly introduce Europe")
|
|
75
|
+
response2 = await chat.send_message("What's the population there?")
|
|
76
|
+
print(response1.text, response2.text, sep="\n----------------------------------\n")
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
### Check and switch to other answer candidates
|
|
80
|
+
|
|
81
|
+
```python
|
|
82
|
+
chat = client.start_chat()
|
|
83
|
+
response = await chat.send_message("What's the best Japanese dish in your mind? Choose one only.")
|
|
84
|
+
for candidate in response.candidates:
|
|
85
|
+
print(candidate, "\n----------------------------------\n")
|
|
86
|
+
|
|
87
|
+
# Control the ongoing conversation flow by choosing candidate manually
|
|
88
|
+
new_candidate = chat.choose_candidate(index=1) # Choose the second candidate here
|
|
89
|
+
followup_response = await chat.send_message("Tell me more about it.") # Will generate contents based on the chosen candidate
|
|
90
|
+
print(new_candidate, followup_response, sep="\n----------------------------------\n")
|
|
91
|
+
```
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
gemini/__init__.py,sha256=0y3v6LIcyYaVHeOF0ncZsd-f1blH4SDGAvvOD5nt6hY,123
|
|
2
|
+
gemini/client.py,sha256=6-Nd3iEaqxknSM9KOK2bi-AdH3vVtzsqipvNUL5cmWQ,11641
|
|
3
|
+
gemini/consts.py,sha256=fM-qSEWK9wDBy_kTyl7_KZtxSVyF48KZucsujs26og8,365
|
|
4
|
+
gemini/types.py,sha256=M4gbw1RAlyss38tDrdYiyOIOrf0CA-5M33IYJzRxPPU,1680
|
|
5
|
+
gemini_webapi-0.0.1.dist-info/LICENSE,sha256=syoVjv1ye4WBYEgJOlRkRE8trYjBMFnqz2kP7ta9934,1082
|
|
6
|
+
gemini_webapi-0.0.1.dist-info/METADATA,sha256=_zE3rMMubULAE1lqew_tXH-dxVmQlfKF9cY-gIwjK8o,3292
|
|
7
|
+
gemini_webapi-0.0.1.dist-info/WHEEL,sha256=oiQVh_5PnQM0E3gPdiz09WCNmwiHDMaGer_elqB3coM,92
|
|
8
|
+
gemini_webapi-0.0.1.dist-info/top_level.txt,sha256=HC3nun2yFuNkNiRuKIq3MP_Izk5CqGNHxn2nl0-ozn0,7
|
|
9
|
+
gemini_webapi-0.0.1.dist-info/RECORD,,
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
gemini
|