// fastsub.d.ts — типы к fastsub.mjs. Положите рядом. // Версия 1.12.0 export declare const VERSION: string; /** Сколько link_id принимает checkMany за раз. */ export declare const MAX_CHECK_MANY: number; /** Умолчание requestOpCooldownMs. */ export declare const DEFAULT_COOLDOWN_MS: number; export declare const DEFAULT_BASE_URL: string; export declare class FastSubError extends Error { status: number; detail: string; payload: Record; } /** Одно задание для пользователя. */ export interface Task { linkId: string; title: string; buttonName: string; type: string; taskType: string; rewardForPublisher: string; /** Надбавка за удержание сверх выплаты. */ retentionBonusRub?: string; /** Единая ссылка: приглашение в канал или запуск бота. */ link: string | null; raw: Record; } /** Почему заданий мало или нет — числами и одной строкой. */ export interface Availability { inventory: number; offered: number; alreadyPaid?: number; liveOffers?: number; botFilters?: number; targeting?: number; dailyLimit?: number; noCapacity?: number; alreadyMember?: number; explain: string; } export interface OpAnswer { ok: boolean; tasks: Task[]; reason: string | null; delivered: boolean; /** Юзера надо отправить на эту страницу, иначе заданий не будет. */ onboardingUrl: string | null; /** Что за шаг: короткая анкета или умный редирект. */ onboardingKind: "quiz" | "redirect" | null; /** Чем открывать шаг: обычной кнопкой или web_app. */ onboardingOpen: "url" | "miniapp" | null; /** * Чем открывать кнопки заданий. "miniapp" приходит незнакомому юзеру, чьи * кнопки завёрнуты в наш редирект: страница снимет гео, попросит Telegram * открыть канал и закроется. Прямые t.me так открывать нельзя. */ linksOpen: "url" | "miniapp" | null; hasTasks: boolean; needsWebStep: boolean; explain: string; availability: Availability | Record; raw: Record; } export type IssueState = | "pending" | "subscribed" | "verified" | "paid" | "expired" | "unsubscribed" | "reverted" | "invalid"; export interface IssueStatus { linkId: string; status: IssueState; reason: string | null; title: string | null; username: string | null; rewardForPublisher: string; retentionBonusRub: string; payoutState: string; isOwn: boolean; /** Юзер своё сделал. */ done: boolean; /** Задание закрыто и выполнено уже не будет. */ closed: boolean; /** Ждём действия юзера. */ pending: boolean; raw: Record; } export interface TaskStatus { taskId: string; resources: IssueStatus[]; allDone: boolean; remaining: IssueStatus[]; raw: Record; } export interface SubscriptionCheck { linkId: string; subscribed: boolean; status: IssueState; checkedLive: boolean; reason: string | null; /** Повторять запрос смысла нет: ответ не изменится. */ retryIsPointless: boolean; raw: Record; } export type DeliveryMode = "keep" | "fresh" | "rotate" | "top_up"; export interface RequestOpParams { userId: number; count?: number; /** Что делать с тем, что юзер уже держит. По умолчанию keep. */ mode?: DeliveryMode; languageCode?: string; excludeChatIds?: number[]; hasTelegramPremium?: boolean; hasProfilePhoto?: boolean; hasUsername?: boolean; hasBio?: boolean; hasStories?: boolean; hasGifts?: boolean; } /** Деньги на счёте и конец окна проверки отписок. */ export interface Balance { balanceRub: string; /** Legacy прошлой модели, обычно 0: начисление теперь приходит на баланс сразу. */ holdRub: string; payoutHoldRub: string; /** Задолженность: начисления за отписавшихся, которые вы уже вывели. */ debtRub: string; totalEarnedRub: string; /** То же, что balanceRub: сколько можно вывести прямо сейчас. */ availableRub: string; /** ISO-время ближайшего сброса — до него отписка списывает начисление. */ holdUntil: string | null; holdPeriod: "week" | "day" | "month" | "off"; holdNote: string; raw: Record; } /** Оформление блока ОП. null в тексте — вернуть наше умолчание. */ export interface OpBlockSettings { text: string | null; done_text: string | null; row: string | null; button: string | null; check_button: string | null; show_check_button: boolean; auto_check: boolean; media_type: string | null; button_style: string | null; check_style: string | null; button_icon: string | null; check_icon: string | null; } export interface BotSettings { bot_id: number; name: string; is_active: boolean; sponsors_count: number; list_ttl_seconds: number; /** Сравнивается с итоговой ценой подписчика — с коэффициентами таргетинга. */ min_reward_rub: string; show_quiz: boolean; use_smart_link: boolean; redirect_open_type: "url" | "miniapp"; hide_sponsor_titles: boolean; /** true — блок рисуете вы; false — отправляем мы. */ get_links: boolean; excluded_themes: string[]; excluded_task_types: string[]; excluded_resource_types: string[]; op_block: OpBlockSettings; } export type BotSettingsPatch = Partial< Omit > & { op_block?: Partial & { reset?: boolean } }; export declare class FastSub { constructor(opts: { apiKey: string; baseUrl?: string; timeout?: number; /** Сколько раз повторять 429, 5xx и обрывы связи. По умолчанию 2, ноль отключает. */ maxRetries?: number; /** * Насколько часто одному юзеру повторять requestOp. По умолчанию 2000 мс, * ноль отключает. Защищает юзера вашего бота от одинаковых блоков подряд. */ requestOpCooldownMs?: number; }); static bootstrap(opts: { confirmKey: string; botToken: string; name?: string; baseUrl?: string; timeout?: number; }): Promise; apiKey: string; requestOp(params: RequestOpParams): Promise; checkTask(taskId: string): Promise; checkResource(linkId: string): Promise; checkSubscription(linkId: string): Promise; checkOp(userId: number, callbackQueryId?: string): Promise>; /** Статусы нескольких выдач одним запросом. Не больше MAX_CHECK_MANY. */ checkMany(linkIds: string[]): Promise; me(): Promise>; /** Деньги и когда закроется холд — без разбора всего ответа /me. */ balance(): Promise; settings(): Promise; /** Меняется только присланное; возвращается карточка целиком. */ updateSettings(changes: BotSettingsPatch): Promise; stats(from?: string, to?: string): Promise>; userHistory(userId: number, limit?: number): Promise>; /** Вся история юзера, страница за страницей. */ iterUserHistory( userId: number, opts?: { pageSize?: number }, ): AsyncIterableIterator>; configureWebhook( url: string, opts?: { events?: string[]; rotateSecret?: boolean }, ): Promise<{ secret?: string; url: string }>; webhookInfo(): Promise>; webhookTest(): Promise>; /** Конкурсы: пост в канале с кнопкой «Участвовать», задания и итоги. */ contests(params?: { status?: string; limit?: number; offset?: number }): Promise>; contest(id: number): Promise>; /** channel, text (HTML), media_url, media_type, button_text, button_style, * publish_at, finish_at, max_participants, winners_count, settings, submit… */ createContest(payload: { channel: string } & Record): Promise>; updateContest(id: number, changes: Record): Promise>; submitContest(id: number): Promise>; previewContest(id: number): Promise>; finishContest(id: number): Promise>; cancelContest(id: number): Promise>; contestParticipants( id: number, params?: { status?: string; limit?: number; offset?: number }, ): Promise>; contestStats(id: number): Promise>; contestSponsors(id: number): Promise>; addContestSponsor( id: number, link: string, opts?: { taskType?: "subscribe" | "boost" }, ): Promise>; removeContestSponsor(id: number, sponsorId: number): Promise>; /** Ждать подтверждения задания, опрашивая статус. */ waitDone( linkId: string, opts?: { attempts?: number; delay?: number; live?: boolean }, ): Promise; } /** Проверка подписи вебхука. Тело — сырое, не перекодированный JSON. */ export declare function verifyWebhook( secret: string, body: string | Uint8Array, signature: string, ): Promise; export declare class FastSubAdvertiser { constructor(opts: { apiKey: string; baseUrl?: string; timeout?: number; maxRetries?: number; }); targetingOptions(): Promise>; quote(payload: Record): Promise>; createOrder( payload: Record, idempotencyKey?: string, ): Promise>; orders(params?: Record): Promise>; order(id: number): Promise>; updateOrder(id: number, payload: Record): Promise>; cancelOrder(id: number): Promise>; subscribers(id: number, params?: Record): Promise>; /** Все заказы, страница за страницей. */ iterOrders( params?: Record, opts?: { pageSize?: number }, ): AsyncIterableIterator>; /** Все подписчики заказа, страница за страницей. */ iterSubscribers( id: number, params?: Record, opts?: { pageSize?: number }, ): AsyncIterableIterator>; postback(args: { linkId: string; event: string; valueRub: string | number; meta?: Record; }): Promise>; confirmStart(startParam: string, userId: number): Promise>; } // --------------------------------------------------------------------------- // Боты на Node: grammY и Telegraf зовут middleware одинаково, (ctx, next), // поэтому одна функция обслуживает оба. // --------------------------------------------------------------------------- export interface MiddlewareOptions { /** `gate` — не пускать дальше, пока задания не выполнены. */ mode?: "default" | "gate" | "skip"; count?: number; gateMessage?: string; /** Блок рисуем мы. Выключите, если рисуете сами через sponsorBlock. */ sendBlock?: boolean; onError?: (err: unknown) => void; } /** Мидлварь: кладёт ответ в `ctx.fastsub`, в режиме gate сама шлёт блок. */ export declare function fastsubMiddleware( fs: FastSub, opts?: MiddlewareOptions, ): (ctx: any, next: () => Promise | unknown) => Promise; /** * Текст и клавиатура блока спонсоров — если рисуете сами. * * Кнопка проверки одна на весь список. `checkButton: null` убирает её совсем. */ export declare function sponsorBlock( answer: OpAnswer, gateMessage?: string, opts?: { checkButton?: string | null; taskId?: string | null }, ): [string, { reply_markup: { inline_keyboard: unknown[][] } }]; /** Обработчик кнопки «Проверить». `true` — кнопка была наша. */ export declare function fastsubCheckHandler( fs: FastSub, opts?: { onAccessGranted?: (ctx: any) => unknown; onError?: (err: unknown) => void }, ): (ctx: any) => Promise; // --------------------------------------------------------------------------- // Приёмники вебхуков // --------------------------------------------------------------------------- export interface WebhookEventData { event: string; linkId: string; userId: number | null; taskId: string | null; status: string; publisherPayoutRub: string; payoutState: string; holdUntil: string | null; test: boolean; deliveryId: string | null; /** Начислено, но до holdUntil могут забрать. */ inHold: boolean; /** Холд дожил до конца — деньги ваши. */ moneyIsMine: boolean; /** Выплату отозвали: отписка внутри холда. */ moneyWasTaken: boolean; raw: Record; [key: string]: unknown; } export declare function webhookEvent( raw: Record, deliveryId?: string | null, ): WebhookEventData; /** Помнит последние `capacity` доставок; `true` — видим впервые. */ export declare function makeDedupe(capacity?: number): (deliveryId: string) => boolean; export declare class WebhookEvents { on(...args: [...names: string[], handler: (e: WebhookEventData) => unknown]): this; handlersFor(name: string): Array<(e: WebhookEventData) => unknown>; dispatch(event: WebhookEventData): Promise; } export type WebhookHandler = | ((event: WebhookEventData) => unknown) | WebhookEvents; export interface ReceiverOptions { /** `true` — помнить X-FastSub-Delivery-Id; `false` — звать всегда; функция — своя проверка. */ dedupe?: boolean | ((deliveryId: string) => boolean); } /** Веб-стандарт: Hono, Bun, Deno, Workers, Next.js route handlers. */ export declare function fastsubWebhookHandler( secret: string, handler: WebhookHandler, opts?: ReceiverOptions, ): (request: Request) => Promise; /** Express. Нужен `express.raw({ type: "application/json" })`. */ export declare function fastsubExpressHandler( secret: string, handler: WebhookHandler, opts?: ReceiverOptions, ): (req: any, res: any, next?: (err?: unknown) => void) => Promise; /** Fastify. Нужны сырые байты: `fastify-raw-body` или свой парсер. */ export declare function fastsubFastifyHandler( secret: string, handler: WebhookHandler, opts?: ReceiverOptions, ): (request: any, reply: any) => Promise;