Skip to content

top-tl/js

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

5 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

npm version npm downloads MIT License

toptl

Official TypeScript SDK for the TOP.TL Telegram directory API.

  • Zero dependencies (uses native fetch)
  • Full TypeScript types
  • Built-in autoposter with on-change detection and server-driven intervals
  • grammY plugin for automatic stat tracking
  • Webhook setup and testing
  • Batch stat posting for multi-listing bots
  • Works with Node.js 18+, Deno, Bun, and edge runtimes

Installation

npm install toptl
yarn add toptl
pnpm add toptl

Quick Start

import { TopTL } from 'toptl';

const toptl = new TopTL('toptl_your_api_key');

// Get listing info
const listing = await toptl.getListing('mybot');
console.log(listing.title, listing.votes);

Authentication

Get your API key from top.tl/settings/api. Pass it to the constructor:

const toptl = new TopTL('toptl_xxx');

All requests are authenticated via Authorization: Bearer toptl_xxx header.

Usage

Get Listing Info

const listing = await toptl.getListing('mybot');

console.log(listing.title);       // "My Bot"
console.log(listing.memberCount); // 12500
console.log(listing.votes);       // 342
console.log(listing.verified);    // true

Get Votes

// Get all active voters
const votes = await toptl.getVotes('mybot');
console.log(votes.totalVotes); // 342
console.log(votes.voters);     // [{ telegramId, firstName, votedAt }]

// Limit results
const top10 = await toptl.getVotes('mybot', 10);

Check if a User Voted

const result = await toptl.hasVoted('mybot', 123456789);

if (result.hasVoted) {
  console.log(`User voted at ${result.votedAt}`);
} else {
  console.log('User has not voted');
}

Post Stats

await toptl.postStats('mybot', {
  memberCount: 12500,
  groupCount: 35,
  channelCount: 12,
  botServes: ['en', 'ru', 'es'],
});

Batch Post Stats

Post stats for multiple listings in a single request. Useful for bots that manage several listings:

await toptl.batchPostStats([
  { username: 'mybot', memberCount: 12500, groupCount: 35 },
  { username: 'mygroup', memberCount: 8400, channelCount: 3 },
]);

Get Global Stats

const stats = await toptl.getStats();
console.log(`${stats.totalListings} listings, ${stats.totalVotes} votes`);

Webhooks

Set up webhooks to receive real-time vote notifications.

Set Webhook

await toptl.setWebhook('mybot', 'https://example.com/webhook');

// With a reward title shown to voters
await toptl.setWebhook('mybot', 'https://example.com/webhook', 'Premium Access');

Test Webhook

Send a test event to your configured webhook to verify it works:

const result = await toptl.testWebhook('mybot');
console.log(result.statusCode); // 200

Autoposter

The SDK includes a built-in autoposter that reports your bot's stats to TOP.TL. It posts immediately on start, then repeats on an interval.

Basic Usage

import { TopTL } from 'toptl';

const toptl = new TopTL('toptl_xxx');

// Start auto-posting every 30 minutes (default)
toptl.startAutopost('mybot', async () => {
  return { memberCount: await getUserCount() };
});

// Stop when needed (e.g., on shutdown)
process.on('SIGINT', () => {
  toptl.stopAutopost();
  process.exit(0);
});

Custom Interval

// Post every 10 minutes
toptl.startAutopost('mybot', getStats, { interval: 10 * 60 * 1000 });

On-Change Detection

Skip posting when stats haven't changed since the last post:

toptl.startAutopost('mybot', getStats, { onlyOnChange: true });

Server-Driven Intervals

When the server responds with a retryAfter value, the autoposter automatically uses it as the next interval instead of the configured default. This lets the server throttle or accelerate posting as needed.

grammY Plugin

The SDK provides a built-in grammY middleware that automatically tracks unique chat IDs from incoming updates and posts them as memberCount stats.

import { Bot } from 'grammy';
import { TopTL } from 'toptl';

const bot = new Bot('BOT_TOKEN');
const toptl = new TopTL('toptl_xxx');

// One line: tracks chats and auto-posts stats
bot.use(toptl.grammy('mybot'));

bot.start();

With options:

bot.use(toptl.grammy('mybot', { interval: 10 * 60 * 1000, onlyOnChange: true }));

Manual grammY Integration

If you need more control, use the autoposter directly:

import { Bot } from 'grammy';
import { TopTL } from 'toptl';

const bot = new Bot('BOT_TOKEN');
const toptl = new TopTL('toptl_xxx');

toptl.startAutopost('mybot', async () => {
  const count = await bot.api.getChatMemberCount('@mybot');
  return { memberCount: count };
});

// Vote-lock command: only allow access if the user voted
bot.command('premium', async (ctx) => {
  const { hasVoted } = await toptl.hasVoted('mybot', ctx.from!.id);

  if (!hasVoted) {
    return ctx.reply(
      'Please vote for us on TOP.TL to unlock this feature!\nhttps://top.tl/mybot'
    );
  }

  return ctx.reply('Welcome, premium user!');
});

bot.start();

API Reference

new TopTL(apiKey, options?)

Create a new client.

Parameter Type Description
apiKey string Your TOP.TL API key (starts with toptl_)
options.baseUrl string Override API base URL (default: https://top.tl/api/v1)

getListing(username)

Get listing info. Requires scope listing:read.

Returns: Promise<Listing>

getVotes(username, limit?)

Get votes with active voters. Requires scope votes:read.

Parameter Type Description
username string Listing username
limit number Max voters to return (optional)

Returns: Promise<VotesResponse>

hasVoted(username, telegramId)

Check if a user has voted. Requires scope votes:check.

Returns: Promise<HasVotedResponse>

postStats(username, stats)

Post stats for a listing. Requires scope listing:write.

Parameter Type Description
username string Listing username
stats.memberCount number Current member count (optional)
stats.groupCount number Current group count (optional)
stats.channelCount number Current channel count (optional)
stats.botServes string[] Languages or regions the bot serves (optional)

Returns: Promise<PostStatsResponse>

batchPostStats(stats)

Post stats for multiple listings in one request. Requires scope listing:write.

Parameter Type Description
stats BatchStatsEntry[] Array of { username, memberCount?, groupCount?, channelCount?, botServes? }

Returns: Promise<BatchStatsResponse>

getStats()

Get global TOP.TL platform stats. Requires scope listing:read.

Returns: Promise<GlobalStats>

setWebhook(username, url, rewardTitle?)

Set or update the webhook URL for a listing. Requires scope listing:write.

Parameter Type Description
username string Listing username
url string Webhook endpoint URL
rewardTitle string Reward title shown to voters (optional)

Returns: Promise<WebhookResponse>

testWebhook(username)

Send a test event to the configured webhook. Requires scope listing:write.

Returns: Promise<WebhookTestResponse>

startAutopost(username, statsFn, options?)

Start auto-posting stats. Posts immediately, then on interval.

Parameter Type Description
username string Listing username
statsFn () => PostStatsBody | Promise<PostStatsBody> Function returning current stats
options.interval number Interval in ms (default: 30 min). Overridden by server retryAfter.
options.onlyOnChange boolean Skip posting if stats are unchanged (default: false)

Returns: Promise<void>

stopAutopost()

Stop the autoposter.

grammy(username, options?)

Returns grammY-compatible middleware that tracks unique chat IDs and auto-posts stats.

Parameter Type Description
username string Listing username
options AutopostOptions Same options as startAutopost (optional)

Returns: (ctx: any, next: () => Promise<void>) => Promise<void>

Types

interface Listing {
  username: string;
  title: string;
  description: string;
  category: string;
  memberCount: number;
  votes: number;
  verified: boolean;
  featured: boolean;
  createdAt: string;
  updatedAt: string;
}

interface VotesResponse {
  username: string;
  totalVotes: number;
  voters: Voter[];
}

interface Voter {
  telegramId: number;
  firstName: string;
  votedAt: string;
}

interface HasVotedResponse {
  hasVoted: boolean;
  votedAt: string | null;
}

interface PostStatsBody {
  memberCount?: number;
  groupCount?: number;
  channelCount?: number;
  botServes?: string[];
}

interface PostStatsResponse {
  success: boolean;
  retryAfter?: number;
}

interface GlobalStats {
  totalListings: number;
  totalVotes: number;
  totalUsers: number;
  categories: number;
}

interface WebhookResponse {
  success: boolean;
  url: string;
  rewardTitle?: string;
}

interface WebhookTestResponse {
  success: boolean;
  statusCode: number;
  body: unknown;
}

interface BatchStatsEntry {
  username: string;
  memberCount?: number;
  groupCount?: number;
  channelCount?: number;
  botServes?: string[];
}

interface BatchStatsResponse {
  success: boolean;
  processed: number;
}

interface AutopostOptions {
  interval?: number;
  onlyOnChange?: boolean;
}

Error Handling

All API errors throw a TopTLError with the HTTP status and response body:

import { TopTL, TopTLError } from 'toptl';

try {
  await toptl.getListing('nonexistent');
} catch (err) {
  if (err instanceof TopTLError) {
    console.log(err.status); // 404
    console.log(err.body);   // API error response
  }
}

License

MIT - see LICENSE for details.

About

Official TOP.TL SDK for JavaScript/TypeScript — npm install @toptl/sdk

Topics

Resources

License

Stars

Watchers

Forks

Packages

 
 
 

Contributors