Python SDK for the EasyTargetBot ad-delivery API
Project description
easytarget
Python SDK for the EasyTargetBot ad-delivery API. Forward a Telegram update; the platform picks an eligible campaign, sends it into the user's chat via your bot, and bills the advertiser. You get a typed result.
Install
pip install easytarget
Usage (sync)
from easytarget import EasyTargetClient
client = EasyTargetClient(api_key="…") # base_url defaults to the platform
result = client.show_ad(update) # dict | aiogram Update | PTB Update
if result.sent:
print(result.campaign_id, result.message_id)
else:
print(result.reason) # DeliveryReason.NO_AD / NOT_PRIVATE / …
Usage (async, aiogram)
from aiogram.types import Message, Update
from easytarget import AsyncEasyTargetClient
client = AsyncEasyTargetClient(api_key="…") # create once, reuse for every update
@dp.message(F.text & ~F.text.startswith("/")) # real messages, not commands
async def on_message(message: Message, event_update: Update) -> None:
await handle(message) # do your bot's actual work first
result = await client.show_ad(event_update) # pass the whole Update, not the Message
if not result.sent:
logging.debug("no ad shown: %s", result.reason)
@dp.shutdown()
async def on_shutdown():
await client.aclose() # close the HTTP pool on shutdown
Pass the whole Update, not the Message. aiogram injects the parent update
into every handler as event_update — declare that parameter and the SDK forwards
the full update, including the update_id the platform requires. Handing it
message instead drops update_id, and the request is rejected
(DeliveryReason.MISSING_UPDATE_ID).
Only api_key is required. base_url defaults to the platform endpoint shown
alongside your API key; pass base_url=… only if you were given a dedicated URL
or are pinned to an older one. Both fall back to the EASYTARGET_API_KEY /
EASYTARGET_BASE_URL environment variables.
Best practices — show ads on real actions, don't spam
⚠️ Spamming ads can get your bot banned. Showing an ad on every update, on bot commands (
/start,/help, …), or pumping a low-traffic / artificial-traffic bot violates Telegram's rules and EasyTargetBot's policy and will get the bot flagged and removed. Show one ad in response to a genuine user action, and let the platform's eligibility filter + per-user frequency cap do the rest.
Good — tied to a real action, deliberate:
# After the user finishes something meaningful, offer a single ad.
@dp.message(Command("results"))
async def on_results(message: Message, event_update: Update):
await send_results(message)
await client.show_ad(event_update) # ✅ one ad, on a real action
Avoid — spammy placement that risks a ban:
@dp.message() # fires on EVERYTHING, including commands
async def on_any(message: Message, event_update: Update):
await client.show_ad(event_update) # ❌ an ad on every message = spam
await client.show_ad(event_update) # ❌ never show several in a row
The server already filters out commands, non-private chats, and bots, and caps
how often a given user is shown an ad — but those are a safety net, not a license
to call show_ad indiscriminately. Be intentional about where you place it.
Results & errors
show_ad returns an AdResult(sent, campaign_id, unique, message_id, reason, raw).
When sent is False, reason is a DeliveryReason — a filter reason
(UNSUPPORTED_UPDATE, NO_USER, BOT_SENDER, NOT_PRIVATE, COMMAND) or a
delivery reason (BOT_NOT_SERVING, FREQUENCY_CAP, NO_AD, SEND_FAILED).
It raises InvalidApiKey / APIError on a rejected request and RequestError
on a network/timeout failure (all subclasses of EasyTargetError). APIError
carries status_code, code, message, detail, and retry_after.
Configuration
from easytarget import EasyTargetClient, RetryPolicy
client = EasyTargetClient(
api_key="…",
base_url="https://easytarget.jakhongir.dev", # optional override
timeout=10.0, # per-request timeout (seconds)
retries=2, # extra attempts after the first
)
Retries are deliberately conservative. Because /api/v1/ad/send delivers an ad and
charges the advertiser, the SDK only retries failures where the request provably
never reached the server (connection/pool errors) plus 429 / 503 responses
(honoring Retry-After, with exponential backoff + jitter). It never retries
read/write timeouts or other 5xx, which could mean the ad was already sent —
retrying those would double-deliver and double-charge. Set retries=0 to disable.
For full control pass a RetryPolicy:
client = EasyTargetClient(
api_key="…",
retry_policy=RetryPolicy(max_retries=3, backoff_factor=0.5, max_backoff=30.0),
)
Bring your own httpx client (proxies, custom TLS, connection-pool tuning); the
SDK still applies auth and the request path, and will not close a client you own:
import httpx
from easytarget import EasyTargetClient
http = httpx.Client(proxy="http://localhost:8080", timeout=5.0)
client = EasyTargetClient(api_key="…", http_client=http)
The package ships type hints (py.typed) for mypy/pyright.
Logging
Both clients log each ad request — on by default, to the stdlib easytarget
logger. Logging is privacy-safe: only the update_id and the outcome are logged,
never the update payload or message text.
import logging
logging.basicConfig(level=logging.INFO) # see the default "easytarget" logger
client = EasyTargetClient(api_key="…")
# INFO easytarget: ad sent update_id=85123456 campaign=cmp_42 unique=True
# INFO easytarget: ad not sent update_id=85123457 reason=frequency_cap
# WARNING easytarget: retry 1/2 after 503 update_id=85123458
# ERROR easytarget: request failed update_id=85123459 [401] invalid_api_key
The logger is pluggable — pass any object with debug/info/warning/error
methods. Stdlib logging.Logger and loguru's logger both work directly, no
adapter needed:
from loguru import logger
client = EasyTargetClient(api_key="…", logger=logger) # loguru
client = EasyTargetClient(api_key="…", logger=logging.getLogger("mybot")) # stdlib
Turn it off entirely with logging_enabled=False (wins even if a logger is
passed). Request start is logged at DEBUG, outcomes at INFO, retries at
WARNING, and final failures at ERROR. The LoggerLike protocol is exported
if you want to type your own logger. Both options work identically on
AsyncEasyTargetClient.
License
MIT
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file easytarget-0.2.0b1.tar.gz.
File metadata
- Download URL: easytarget-0.2.0b1.tar.gz
- Upload date:
- Size: 155.0 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.7.19
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
db1db2ee48b8709e4f32e9cac6db0cf4f7806a051dd9e5645bce37185d76fe1b
|
|
| MD5 |
9675d1e8c5b1bd1bf5aec34886cb0866
|
|
| BLAKE2b-256 |
9982315fcd0cc1748602a8339431e1996c8a6053771ce807a6dfaca2527e0634
|
File details
Details for the file easytarget-0.2.0b1-py3-none-any.whl.
File metadata
- Download URL: easytarget-0.2.0b1-py3-none-any.whl
- Upload date:
- Size: 14.0 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.7.19
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
815fe60979ee56ad8741193781bff1259ebf5b56b7c0866be8c3dc3b804b9479
|
|
| MD5 |
2ee3477da04777389565b95f43a49032
|
|
| BLAKE2b-256 |
88e07d7bf60190376b352385d26a10c33540e7d4fdc3d44e177e90464e10d5cb
|