45 lines
1.4 KiB
TypeScript
45 lines
1.4 KiB
TypeScript
import { createHash, randomBytes } from 'node:crypto';
|
|
|
|
const BOT_USERNAME_PATTERN = /^[a-z][a-z0-9_]{4,31}$/;
|
|
|
|
export function normalizeBotUsername(username: string) {
|
|
return username.trim().toLowerCase().replace(/[^a-z0-9_]/g, '');
|
|
}
|
|
|
|
export function assertValidBotUsername(username: string) {
|
|
const normalized = normalizeBotUsername(username);
|
|
if (!BOT_USERNAME_PATTERN.test(normalized)) {
|
|
throw new Error('INVALID_BOT_USERNAME');
|
|
}
|
|
if (normalized.endsWith('_bot')) {
|
|
throw new Error('INVALID_BOT_USERNAME');
|
|
}
|
|
return `${normalized}_bot`;
|
|
}
|
|
|
|
export function generateTelegramStyleBotToken() {
|
|
const botId = randomBytes(4).readUInt32BE(0) % 900_000_000 + 100_000_000;
|
|
const secret = randomBytes(32)
|
|
.toString('base64')
|
|
.replace(/\+/g, '-')
|
|
.replace(/\//g, '_')
|
|
.replace(/=+$/g, '');
|
|
const token = `${botId}:${secret}`;
|
|
return { token, tokenPrefix: `${botId}:${secret.slice(0, 6)}…` };
|
|
}
|
|
|
|
export function hashBotToken(token: string) {
|
|
return createHash('sha256').update(token.trim()).digest('hex');
|
|
}
|
|
|
|
export function deriveTelegramChatId(source: string) {
|
|
const hash = createHash('sha256').update(source).digest();
|
|
const value = hash.readBigInt64BE(0);
|
|
const positive = value < 0n ? -value : value;
|
|
return positive % 9_000_000_000_000n + 1_000_000_000_000n;
|
|
}
|
|
|
|
export function deriveTelegramUserId(userId: string) {
|
|
return Number(deriveTelegramChatId(`user:${userId}`));
|
|
}
|