Python SDK for the Sidekick ad-injection API
Project description
sidekick-ads
Python SDK for the Sidekick ad-injection API.
Installation
# Core (httpx only)
pip install sidekick-ads
# With aiogram support
pip install sidekick-ads[aiogram]
# With python-telegram-bot support
pip install sidekick-ads[ptb]
Quick start
aiogram 3.x with middleware
from aiogram import Bot, Dispatcher, Router
from aiogram.types import Message
from sidekick_ads import Sidekick
from sidekick_ads.middleware import SidekickMiddleware
bot = Bot(token="BOT_TOKEN")
dp = Dispatcher()
router = Router()
dp.include_router(router)
# Register the middleware — it injects a Sidekick instance into every handler
sidekick_middleware = SidekickMiddleware(api_key="sk_xxx", platform_id="plt_xxx")
dp.message.middleware(sidekick_middleware)
# Close the underlying HTTP pool on dispatcher shutdown (avoids leaks)
dp.shutdown.register(sidekick_middleware.aclose)
@router.message()
async def handle(message: Message, sidekick: Sidekick):
llm_response = "Here is your answer..."
user = message.from_user
user_id = user.id if user else 0
language_code = user.language_code if user else None
is_premium = user.is_premium if user else None
result = await sidekick.inject(
user_id=user_id,
message=llm_response,
language_code=language_code,
is_premium=is_premium,
keyboard=None, # or an existing InlineKeyboardMarkup
)
await message.answer(
result.message,
reply_markup=result.keyboard,
)
aiogram 3.x without middleware
from aiogram import Bot, Dispatcher, Router
from aiogram.types import Message
from sidekick_ads import Sidekick
bot = Bot(token="BOT_TOKEN")
dp = Dispatcher()
router = Router()
dp.include_router(router)
sidekick = Sidekick(api_key="sk_xxx", platform_id="plt_xxx")
@router.message()
async def handle(message: Message):
llm_response = "Here is your answer..."
user = message.from_user
user_id = user.id if user else 0
language_code = user.language_code if user else None
is_premium = user.is_premium if user else None
result = await sidekick.inject(
user_id=user_id,
message=llm_response,
language_code=language_code,
is_premium=is_premium,
keyboard=None, # or an existing InlineKeyboardMarkup
)
await message.answer(
result.message,
reply_markup=result.keyboard,
)
python-telegram-bot v20+
from telegram import InlineKeyboardButton, InlineKeyboardMarkup, Update
from telegram.ext import ApplicationBuilder, CommandHandler, ContextTypes
from sidekick_ads import Sidekick
sidekick = Sidekick(api_key="sk_xxx", platform_id="plt_xxx")
async def start(update: Update, context: ContextTypes.DEFAULT_TYPE):
llm_response = "Here is your answer..."
user = update.effective_user
user_id = user.id if user else 0
language_code = user.language_code if user else None
is_premium = user.is_premium if user else None
# Example: existing keyboard with one button
existing_kb = InlineKeyboardMarkup(
[[InlineKeyboardButton(text="My button", url="https://example.com")]]
)
result = await sidekick.inject(
user_id=user_id,
message=llm_response,
language_code=language_code,
is_premium=is_premium,
keyboard=existing_kb,
)
await update.message.reply_text(
result.message,
reply_markup=result.keyboard,
)
app = ApplicationBuilder().token("BOT_TOKEN").build()
app.add_handler(CommandHandler("start", start))
app.run_polling()
Raw usage (no framework)
import asyncio
from sidekick_ads import Sidekick
async def main():
sidekick = Sidekick(api_key="sk_xxx", platform_id="plt_xxx")
result = await sidekick.inject(
user_id=123456789,
message="Here is your answer...",
language_code="en",
is_premium=False,
)
print(result.message)
print(result.has_ad)
print(result.impression_id)
print(result.ad)
print(result.keyboard)
await sidekick.close()
asyncio.run(main())
fetch() — privacy-friendly alternative
If your LLM reply contains user data (names, query fragments) that you'd
prefer not to send to Sidekick, use fetch() instead of inject(). It
returns the ad as separate fields, and you render them into your reply
yourself.
import asyncio
from sidekick_ads import Sidekick
async def main():
sidekick = Sidekick(api_key="sk_live_xxx", platform_id="plt_xxx")
result = await sidekick.fetch(
user_id=123456789,
language_code="en",
parse_mode="HTML", # optional — adds `ad_text_formatted`
)
llm_reply = "Here is your answer..."
if result.has_ad and result.ad is not None:
text = f"{llm_reply}\n\n{result.ad.ad_text_formatted}"
keyboard = {"inline_keyboard": [[
{"text": result.ad.button_text, "url": result.ad.button_url}
]]}
# send `text` with `keyboard` via your bot framework
else:
# send `llm_reply` unchanged
pass
await sidekick.close()
asyncio.run(main())
await sidekick.fetch(...) -> FetchResult
| Parameter | Type | Default |
|---|---|---|
user_id |
int | required |
language_code |
str | required |
is_premium |
bool | None | None |
parse_mode |
str | None | None |
FetchResult and FetchAdData
@dataclass
class FetchAdData:
ad_text: str
ad_url: str
button_text: str
button_url: str
ad_text_formatted: Optional[str] = None # only when parse_mode is sent
@dataclass
class FetchResult:
has_ad: bool
impression_id: Optional[str]
ad: Optional[FetchAdData]
Like inject(), fetch() never raises — on any error (timeout, network
failure, 5xx) it returns FetchResult(has_ad=False, impression_id=None, ad=None).
See the /api/v1/ad/fetch HTTP endpoint
for the full wire format.
API reference
Sidekick(api_key, platform_id, *, base_url, timeout, platform)
| Parameter | Type | Default |
|---|---|---|
api_key |
str | required |
platform_id |
str | required |
base_url |
str | https://sidekick-ads.com |
timeout |
float | 3.0 |
platform |
str | "telegram" |
await sidekick.inject(...) -> InjectResult
| Parameter | Type | Default |
|---|---|---|
user_id |
int | required |
message |
str | required |
language_code |
str | None | None |
is_premium |
bool | None | None |
keyboard |
Any | None | None |
ad_button_position |
str | "bottom" |
first_name |
str | None | None |
user_message |
str | None | None |
InjectResult
| Field | Type |
|---|---|
message |
str |
has_ad |
bool |
impression_id |
str | None |
ad |
dict | None |
keyboard |
Any | None |
await sidekick.fetch(...) -> FetchResult
| Parameter | Type | Default |
|---|---|---|
user_id |
int | required |
language_code |
str | required |
is_premium |
bool | None | None |
parse_mode |
str | None | None |
FetchResult
| Field | Type |
|---|---|
has_ad |
bool |
impression_id |
str | None |
ad |
FetchAdData | None |
FetchAdData
| Field | Type |
|---|---|
ad_text |
str |
ad_url |
str |
button_text |
str |
button_url |
str |
ad_text_formatted |
str | None |
await sidekick.close()
Closes the underlying HTTP connection pool. The client is automatically
recreated on the next inject() call if needed.
Project details
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 sidekick_ads-0.6.0.tar.gz.
File metadata
- Download URL: sidekick_ads-0.6.0.tar.gz
- Upload date:
- Size: 7.7 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.14.6
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
b5decf553ea1484eae68c0aff0b2c623c4f2436fb8a7f94b57841c6bd0907060
|
|
| MD5 |
d123d16d87d270783ae4373f51b9f54d
|
|
| BLAKE2b-256 |
dc27dc01101cc64fda0fe35cb2bdc7d9cf665fb751ec29c688dbcc37593b4d8c
|
File details
Details for the file sidekick_ads-0.6.0-py3-none-any.whl.
File metadata
- Download URL: sidekick_ads-0.6.0-py3-none-any.whl
- Upload date:
- Size: 7.8 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.14.6
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
870b7ddfac4273c52e8fbb1ff2c051ba65da62160c64de02b0fc1c9b79044f4f
|
|
| MD5 |
04410b7f8600178fe97d5f502223343d
|
|
| BLAKE2b-256 |
cc0c09677233c6ff5e6f3866772296dc35330c71a48459c9cbdf67416d771a62
|