
Research
PyPI Package Disguised as Instagram Growth Tool Harvests User Credentials
A deceptive PyPI package posing as an Instagram growth tool collects user credentials and sends them to third-party bot services.
A reverse-engineered asynchronous python wrapper for Google Gemini web app (formerly Bard).
asyncio
to run generating tasks and return outputs efficiently.[!NOTE]
This package requires Python 3.10 or higher.
Install/update the package with pip.
pip install -U gemini_webapi
Optionally, package offers a way to automatically import cookies from your local browser. To enable this feature, install browser-cookie3
as well. Supported platforms and browsers can be found here.
pip install -U browser-cookie3
[!TIP]
If
browser-cookie3
is installed, you can skip this step and go directly to usage section. Just make sure you have logged in to https://gemini.google.com in your browser.
Network
tab and refresh the page__Secure-1PSID
and __Secure-1PSIDTS
[!NOTE]
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.
Here's part of a sample
docker-compose.yml
file:
services:
main:
volumes:
- ./gemini_cookies:/usr/local/lib/python3.12/site-packages/gemini_webapi/utils/temp
[!NOTE]
API's auto cookie refreshing feature doesn't require
browser-cookie3
, and by default is enabled. It allows you to keep the API service running without worrying about cookie expiration.This feature may cause that you need to re-login to your Google account in the browser. This is an expected behavior and won't affect the API's functionality.
To avoid such result, it's recommended to get cookies from a separate browser session and close it as asap for best utilization (e.g. a fresh login in browser's private mode). More details can be found here.
Import required packages and initialize a client with your cookies obtained from the previous step. After a successful initialization, the API will automatically refresh __Secure-1PSIDTS
in background as long as the process is alive.
import asyncio
from gemini_webapi import GeminiClient
# Replace "COOKIE VALUE HERE" with your actual cookie values.
# Leave Secure_1PSIDTS empty if it's not available for your account.
Secure_1PSID = "COOKIE VALUE HERE"
Secure_1PSIDTS = "COOKIE VALUE HERE"
async def main():
# If browser-cookie3 is installed, simply use `client = GeminiClient()`
client = GeminiClient(Secure_1PSID, Secure_1PSIDTS, proxy=None)
await client.init(timeout=30, auto_close=False, close_delay=300, auto_refresh=True)
asyncio.run(main())
[!TIP]
auto_close
andclose_delay
are optional arguments for automatically closing the client after a certain period of inactivity. This feature is disabled by default. In an always-on service like chatbot, it's recommended to setauto_close
toTrue
combined with reasonable seconds ofclose_delay
for better resource management.
You can specify which language model to use by passing model
argument to GeminiClient.generate_content
or GeminiClient.start_chat
. The default value is unspecified
.
Currently available models (as of Feb 5, 2025):
unspecified
- Default modelgemini-2.0-flash
- Gemini 2.0 Flashgemini-2.0-flash-thinking
- Gemini 2.0 Flash Thinking Experimentalgemini-2.5-flash
- Gemini 2.5 Flashgemini-2.5-pro
- Gemini 2.5 Pro (daily usage limit imposed)Models pending update (may not work as expected):
gemini-2.5-exp-advanced
- Gemini 2.5 Experimental Advanced (requires Gemini Advanced account)gemini-2.0-exp-advanced
- Gemini 2.0 Experimental Advanced (requires Gemini Advanced account)from gemini_webapi.constants import Model
async def main():
response1 = await client.generate_content(
"What's you language model version? Reply version number only.",
model=Model.G_2_0_FLASH,
)
print(f"Model version ({Model.G_2_0_FLASH.model_name}): {response1.text}")
chat = client.start_chat(model="gemini-2.0-flash-thinking")
response2 = await chat.send_message("What's you language model version? Reply version number only.")
print(f"Model version (gemini-2.0-flash-thinking): {response2.text}")
asyncio.run(main())
Ask a one-turn quick question by calling GeminiClient.generate_content
.
async def main():
response = await client.generate_content("Hello World!")
print(response.text)
asyncio.run(main())
[!TIP]
Simply use
print(response)
to get the same output if you just want to see the response text
Gemini supports file input, including images and documents. Optionally, you can pass files as a list of paths in str
or pathlib.Path
to GeminiClient.generate_content
together with text prompt.
async def main():
response = await client.generate_content(
"Introduce the contents of these two files. Is there any connection between them?",
files=["assets/sample.pdf", Path("assets/banner.png")],
)
print(response.text)
asyncio.run(main())
If you want to keep conversation continuous, please use GeminiClient.start_chat
to create a ChatSession
object and send messages through it. The conversation history will be automatically handled and get updated after each turn.
async def main():
chat = client.start_chat()
response1 = await chat.send_message(
"Introduce the contents of these two files. Is there any connection between them?",
files=["assets/sample.pdf", Path("assets/banner.png")],
)
print(response1.text)
response2 = await chat.send_message(
"Use image generation tool to modify the banner with another font and design."
)
print(response2.text, response2.images, sep="\n\n----------------------------------\n\n")
asyncio.run(main())
[!TIP]
Same as
GeminiClient.generate_content
,ChatSession.send_message
also acceptsimage
as an optional argument.
To manually retrieve previous conversations, you can pass previous ChatSession
's metadata to GeminiClient.start_chat
when creating a new ChatSession
. Alternatively, you can persist previous metadata to a file or db if you need to access them after the current Python process has exited.
async def main():
# Start a new chat session
chat = client.start_chat()
response = await chat.send_message("Fine weather today")
# Save chat's metadata
previous_session = chat.metadata
# Load the previous conversation
previous_chat = client.start_chat(metadata=previous_session)
response = await previous_chat.send_message("What was my previous message?")
print(response)
asyncio.run(main())
When using models with thinking capabilities, the model's thought process will be populated in ModelOutput.thoughts
.
async def main():
response = await client.generate_content(
"What's 1+1?", model="gemini-2.0-flash-thinking"
)
print(response.thoughts)
print(response.text)
asyncio.run(main())
Images in the API's output are stored as a list of Image
objects. You can access the image title, URL, and description by calling image.title
, image.url
and image.alt
respectively.
async def main():
response = await client.generate_content("Send me some pictures of cats")
for image in response.images:
print(image, "\n\n----------------------------------\n")
asyncio.run(main())
You can ask Gemini to generate and modify images with Imagen3, Google's latest AI image generator, simply by natural language.
[!IMPORTANT]
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 (as of March 19th, 2025):
This feature’s availability in any specific Gemini app is also limited to the supported languages and countries of that app.
For now, this feature isn’t available to users under 18.
To use this feature, you must be signed in to Gemini Apps.
You can save images returned from Gemini to local by calling Image.save()
. Optionally, you can specify the file path and file name by passing path
and filename
arguments to the function and skip images with invalid file names by passing skip_invalid_filename=True
. Works for both WebImage
and GeneratedImage
.
async def main():
response = await client.generate_content("Generate some pictures of cats")
for i, image in enumerate(response.images):
await image.save(path="temp/", filename=f"cat_{i}.png", verbose=True)
print(image, "\n\n----------------------------------\n")
asyncio.run(main())
[!NOTE]
by default, when asked to send images (like the previous example), Gemini will send images fetched from web instead of generating images with AI model, unless you specifically require to "generate" images in your prompt. In this package, web images and generated images are treated differently as
WebImage
andGeneratedImage
, and will be automatically categorized in the output.
[!IMPORTANT]
To access Gemini extensions in API, you must activate them on the Gemini website first. Same as image generation, Google also has limitations on the availability of Gemini extensions. Here's a summary copied from official documentation (as of March 19th, 2025):
To connect apps to Gemini, you must have Gemini Apps Activity on.
To use this feature, you must be signed in to Gemini Apps.
Important: If you’re under 18, Google Workspace and Maps apps currently only work with English prompts in Gemini.
After activating extensions for your account, you can access them in your prompts either by natural language or by starting your prompt with "@" followed by the extension keyword.
async def main():
response1 = await client.generate_content("@Gmail What's the latest message in my mailbox?")
print(response1, "\n\n----------------------------------\n")
response2 = await client.generate_content("@Youtube What's the latest activity of Taylor Swift?")
print(response2, "\n\n----------------------------------\n")
asyncio.run(main())
[!NOTE]
For the available regions limitation, it actually only requires your Google account's preferred language to be set to one of the three supported languages listed above. You can change your language settings here.
A response from Gemini usually contains multiple reply candidates with different generated contents. You can check all candidates and choose one to continue the conversation. By default, the first candidate will be chosen automatically.
async def main():
# Start a conversation and list all reply candidates
chat = client.start_chat()
response = await chat.send_message("Recommend a science fiction book for me.")
for candidate in response.candidates:
print(candidate, "\n\n----------------------------------\n")
if len(response.candidates) > 1:
# Control the ongoing conversation flow by choosing candidate manually
new_candidate = chat.choose_candidate(index=1) # Choose the second candidate here
followup_response = await chat.send_message("Tell me more about it.") # Will generate contents based on the chosen candidate
print(new_candidate, followup_response, sep="\n\n----------------------------------\n\n")
else:
print("Only one candidate available.")
asyncio.run(main())
This package uses loguru for logging, and exposes a function set_log_level
to control log level. You can set log level to one of the following values: DEBUG
, INFO
, WARNING
, ERROR
and CRITICAL
. The default value is INFO
.
from gemini_webapi import set_log_level
set_log_level("DEBUG")
[!NOTE]
Calling
set_log_level
for the first time will globally remove all existing loguru handlers. You may want to configure logging directly with loguru to avoid this issue and have more advanced control over logging behaviors.
FAQs
✨ An elegant async Python wrapper for Google Gemini web app
We found that gemini-webapi demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?
Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.
Research
A deceptive PyPI package posing as an Instagram growth tool collects user credentials and sends them to third-party bot services.
Product
Socket now supports pylock.toml, enabling secure, reproducible Python builds with advanced scanning and full alignment with PEP 751's new standard.
Security News
Research
Socket uncovered two npm packages that register hidden HTTP endpoints to delete all files on command.