Skip to content

Built for people who want to own their automations. Join the waitlist for an invite.

Package listing

@kody/telegram

src/validation.ts

90 lines · 3.2 KB · TypeScript
export type InputRecord = Record<string, unknown>

export function inputRecord(value: unknown): InputRecord {
	if (value === null || typeof value !== 'object' || Array.isArray(value)) {
		throw new Error('Input must be an object.')
	}
	return value as InputRecord
}

export function requiredString(input: InputRecord, key: string): string {
	const value = input[key]
	if (typeof value !== 'string' || value.trim().length === 0) {
		throw new Error(key + ' is required.')
	}
	return value
}

export function optionalString(input: InputRecord, key: string): string | undefined {
	const value = input[key]
	if (value === undefined || value === null || value === '') return undefined
	if (typeof value !== 'string') throw new Error(key + ' must be a string.')
	return value
}

export function optionalBoolean(input: InputRecord, key: string): boolean | undefined {
	const value = input[key]
	if (value === undefined || value === null) return undefined
	if (typeof value !== 'boolean') throw new Error(key + ' must be a boolean.')
	return value
}

export function optionalInteger(input: InputRecord, key: string): number | undefined {
	const value = input[key]
	if (value === undefined || value === null || value === '') return undefined
	if (typeof value !== 'number' || !Number.isInteger(value)) {
		throw new Error(key + ' must be an integer.')
	}
	return value
}

export function boundedLimit(value: unknown, fallback = 100, max = 100): number {
	if (value === undefined || value === null) return fallback
	if (!Number.isInteger(value) || Number(value) < 1 || Number(value) > max) {
		throw new Error('limit must be an integer from 1 through ' + max + '.')
	}
	return Number(value)
}

export function requiredChatId(input: InputRecord): string | number {
	const value = input.chatId ?? input.chat_id
	if (typeof value === 'number' && Number.isFinite(value)) return value
	if (typeof value === 'string' && value.trim().length > 0) return value.trim()
	throw new Error('chatId is required (numeric Telegram chat id or @username).')
}

export function optionalRecord(input: InputRecord, key: string): InputRecord | undefined {
	const value = input[key]
	if (value === undefined || value === null) return undefined
	if (typeof value !== 'object' || Array.isArray(value)) {
		throw new Error(key + ' must be an object when provided.')
	}
	return value as InputRecord
}

const DEFAULT_SECRET_NAME = 'telegramBotToken'

/** telegramBotToken, or telegramBotToken-<slug> for additional bots. */
export function secretNameFrom(input: InputRecord = {}): string {
	const value = optionalString(input, 'secretName') ?? DEFAULT_SECRET_NAME
	if (!/^telegramBotToken(?:-[a-z0-9]+)?$/.test(value)) {
		throw new Error(
			'secretName must be telegramBotToken or telegramBotToken-<slug> (lowercase letters and digits).',
		)
	}
	return value
}

export function secretPlaceholder(secretName: string): string {
	return '{{secret:' + secretName + '|scope=user}}'
}

export function secretSetupUrl(secretName: string): string {
	return (
		'https://kody.codes/account/secrets/new?name=' +
		encodeURIComponent(secretName) +
		'&description=' +
		encodeURIComponent('Telegram Bot API token from @BotFather') +
		'&allowedHosts=api.telegram.org&scope=user'
	)
}