sidekick-sdk
v0.4.1
Published
Node.js SDK for Sidekick ad injection
Maintainers
Readme
sidekick-sdk
Node.js SDK for Sidekick ad injection in Telegram bots.
- Zero dependencies
- Node.js 18+ (uses built-in
fetch) - TypeScript with full type definitions
- Dual ESM + CJS
- Never breaks your bot: all errors return the original message unchanged
Installation
npm install sidekick-sdkQuick start
import { Sidekick } from "sidekick-sdk";
const sidekick = new Sidekick({
apiKey: "sk_live_xxx",
platformId: "plt_xxx",
});
const result = await sidekick.inject({
userId: 123456789,
message: "Here is your horoscope for today...",
languageCode: "en",
});
// result.message — original text (possibly with ad CTA appended)
// result.hasAd — whether an ad was injected
// result.keyboard — keyboard markup (with ad button if applicable)Examples
grammy with middleware
import { Bot } from "grammy";
import { sidekickMiddleware } from "sidekick-sdk";
const bot = new Bot("BOT_TOKEN");
bot.use(
sidekickMiddleware({
apiKey: "sk_live_xxx",
platformId: "plt_xxx",
})
);
bot.command("start", async (ctx) => {
const llmResponse = "Welcome! Here is your daily tip...";
const languageCode = ctx.from?.language_code ?? "";
const isPremium = ctx.from?.is_premium ?? false;
const result = await ctx.sidekick.inject({
userId: ctx.from!.id,
message: llmResponse,
languageCode,
isPremium,
keyboard: {
inline_keyboard: [[{ text: "Menu", callback_data: "menu" }]],
},
});
await ctx.reply(result.message, {
reply_markup: result.keyboard ?? undefined,
});
});
bot.start();grammy without middleware
import { Bot } from "grammy";
import { Sidekick } from "sidekick-sdk";
const bot = new Bot("BOT_TOKEN");
const sidekick = new Sidekick({
apiKey: "sk_live_xxx",
platformId: "plt_xxx",
});
bot.command("start", async (ctx) => {
const llmResponse = "Welcome! Here is your daily tip...";
const languageCode = ctx.from?.language_code ?? "";
const isPremium = ctx.from?.is_premium ?? false;
const result = await sidekick.inject({
userId: ctx.from!.id,
message: llmResponse,
languageCode,
isPremium,
keyboard: {
inline_keyboard: [[{ text: "Menu", callback_data: "menu" }]],
},
});
await ctx.reply(result.message, {
reply_markup: result.keyboard ?? undefined,
});
});
bot.start();telegraf with middleware
import { Telegraf } from "telegraf";
import { sidekickMiddleware } from "sidekick-sdk";
const bot = new Telegraf("BOT_TOKEN");
bot.use(
sidekickMiddleware({
apiKey: "sk_live_xxx",
platformId: "plt_xxx",
})
);
bot.command("start", async (ctx) => {
const llmResponse = "Welcome! Here is your daily tip...";
const languageCode = ctx.from?.language_code ?? "";
const isPremium = (ctx.from as any)?.is_premium ?? false;
const result = await (ctx as any).sidekick.inject({
userId: ctx.from!.id,
message: llmResponse,
languageCode,
isPremium,
keyboard: {
inline_keyboard: [[{ text: "Settings", callback_data: "settings" }]],
},
});
await ctx.reply(result.message, {
reply_markup: result.keyboard ?? undefined,
});
});
bot.launch();telegraf without middleware
import { Telegraf } from "telegraf";
import { Sidekick } from "sidekick-sdk";
const bot = new Telegraf("BOT_TOKEN");
const sidekick = new Sidekick({
apiKey: "sk_live_xxx",
platformId: "plt_xxx",
});
bot.command("start", async (ctx) => {
const llmResponse = "Welcome! Here is your daily tip...";
const languageCode = ctx.from?.language_code ?? "";
const isPremium = (ctx.from as any)?.is_premium ?? false;
const result = await sidekick.inject({
userId: ctx.from!.id,
message: llmResponse,
languageCode,
isPremium,
keyboard: {
inline_keyboard: [[{ text: "Settings", callback_data: "settings" }]],
},
});
await ctx.reply(result.message, {
reply_markup: result.keyboard ?? undefined,
});
});
bot.launch();node-telegram-bot-api
import TelegramBot from "node-telegram-bot-api";
import { Sidekick } from "sidekick-sdk";
const bot = new TelegramBot("BOT_TOKEN", { polling: true });
const sidekick = new Sidekick({
apiKey: "sk_live_xxx",
platformId: "plt_xxx",
});
bot.onText(/\/start/, async (msg) => {
const chatId = msg.chat.id;
const llmResponse = "Welcome! Here is your daily tip...";
const languageCode = msg.from?.language_code ?? "";
const isPremium = (msg.from as any)?.is_premium ?? false;
const result = await sidekick.inject({
userId: msg.from!.id,
message: llmResponse,
languageCode,
isPremium,
keyboard: {
inline_keyboard: [[{ text: "Help", callback_data: "help" }]],
},
});
await bot.sendMessage(chatId, result.message, {
reply_markup: result.keyboard ?? undefined,
});
});Raw usage without framework
import { Sidekick } from "sidekick-sdk";
const sidekick = new Sidekick({
apiKey: "sk_live_xxx",
platformId: "plt_xxx",
baseUrl: "https://sidekick-ads.com", // optional
timeout: 3.0, // seconds, optional (default)
platform: "telegram", // optional
});
async function handleMessage(userId: number, languageCode: string | undefined, isPremium: boolean | undefined) {
const llmResponse = "Your generated response text here...";
const result = await sidekick.inject({
userId,
message: llmResponse,
languageCode: languageCode ?? "",
isPremium: isPremium ?? false,
keyboard: {
inline_keyboard: [
[{ text: "Option A", callback_data: "a" }],
[{ text: "Option B", callback_data: "b" }],
],
},
adButtonPosition: "bottom", // or "top"
});
// result.message — text to send (original or with ad CTA)
// result.hasAd — boolean
// result.impressionId — "imp_xxx" or null
// result.ad — { text, buttonText, buttonUrl } or null
// result.keyboard — InlineKeyboardMarkup or null
return result;
}
// Cleanup when shutting down (optional, for forward-compatibility)
// sidekick.destroy();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 { Sidekick } from "sidekick-sdk";
const sidekick = new Sidekick({
apiKey: "sk_live_xxx",
platformId: "plt_xxx",
});
const result = await sidekick.fetch({
userId: ctx.from.id,
languageCode: ctx.from.language_code ?? "en",
parseMode: "HTML", // optional — adds `adTextFormatted`
});
if (result.hasAd) {
await ctx.reply(`${llmReply}\n\n${result.ad.adTextFormatted}`, {
parse_mode: "HTML",
reply_markup: {
inline_keyboard: [[
{ text: result.ad.buttonText, url: result.ad.buttonUrl },
]],
},
});
} else {
await ctx.reply(llmReply);
}sidekick.fetch(options): Promise<FetchResult>
| Option | Type | Required | Default |
| -------------- | ------------------------- | -------- | ---------- |
| userId | number | Yes | |
| languageCode | string | Yes | |
| isPremium | boolean \| null | No | false |
| parseMode | "HTML" \| "MarkdownV2" \| "Markdown" \| null | No | null |
FetchResult
type FetchResult =
| { hasAd: false; impressionId: null; ad: null }
| {
hasAd: true;
impressionId: string;
ad: {
adText: string;
adUrl: string;
buttonText: string;
buttonUrl: string;
adTextFormatted?: string; // present only when parseMode was sent
};
};Like inject(), fetch() never throws — on any error (timeout, network
failure, 5xx) it returns { hasAd: false, impressionId: null, ad: null }.
See the /api/v1/ad/fetch HTTP endpoint
for the full wire format.
API reference
new Sidekick(options)
| Option | Type | Required | Default |
| ------------ | -------- | -------- | ------------------------------------ |
| apiKey | string | Yes | |
| platformId | string | Yes | |
| baseUrl | string | No | https://sidekick-ads.com |
| timeout | number | No | 3.0 (seconds) |
| platform | string | No | "telegram" |
sidekick.inject(options): Promise<InjectResult>
| Option | Type | Required | Default |
| ----------------- | ------------------------- | -------- | ---------- |
| userId | number | Yes | |
| message | string | Yes | |
| languageCode | string \| null | No | "" |
| isPremium | boolean \| null | No | false |
| keyboard | InlineKeyboardMarkup \| null | No | null |
| adButtonPosition| "bottom" \| "top" | No | "bottom" |
InjectResult
{
message: string;
hasAd: boolean;
impressionId: string | null;
ad: { text: string; buttonText: string; buttonUrl: string } | null;
keyboard: InlineKeyboardMarkup | null;
}sidekick.fetch(options): Promise<FetchResult>
| Option | Type | Required | Default |
| -------------- | ------------------------- | -------- | ---------- |
| userId | number | Yes | |
| languageCode | string | Yes | |
| isPremium | boolean \| null | No | false |
| parseMode | "HTML" \| "MarkdownV2" \| "Markdown" \| null | No | null |
FetchResult
type FetchResult =
| { hasAd: false; impressionId: null; ad: null }
| {
hasAd: true;
impressionId: string;
ad: {
adText: string;
adUrl: string;
buttonText: string;
buttonUrl: string;
adTextFormatted?: string;
};
};sidekick.destroy()
Cleanup method for forward-compatibility. Safe to call at any time.
License
MIT
