Skip to content

Instantly share code, notes, and snippets.

@artalar
Created April 3, 2022 20:44
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save artalar/41cd6333da03aa10c87e641ee696381a to your computer and use it in GitHub Desktop.
Save artalar/41cd6333da03aa10c87e641ee696381a to your computer and use it in GitHub Desktop.
import * as t from 'runtypes'
export const oneMillisecond = 1
export const oneSecond = oneMillisecond * 1000
export const oneMinute = oneSecond * 60
export const oneHour = oneMinute * 60
export const oneDay = oneHour * 24
/** 30 messages per second for all users
* and one message per second for one user
*/
export const TLG_MESSAGE_LIMIT = oneSecond / 30
export const TELEGRAM_API_BASE_URL = 'https://api.telegram.org'
export const commands = {
auth: `auth`,
info: `info`,
} as const
export const CallbackData = t.Record({
kind: t.Union(
t.Literal(commands.auth),
t.Literal(commands.info),
),
data: t.Unknown,
})
export type CallbackData = t.Static<typeof CallbackData>
export function createCallbackButton<T>(
kind: string,
text: string | number,
data: T,
) {
return {
text: text.toString(),
callback_data: JSON.stringify({
kind,
data,
}),
}
}
export function readCallbackQuery<T>(
kind: keyof typeof commands,
callback_query: any,
): (CallbackData & { data: T }) | null {
if (typeof callback_query !== 'string') return null
const payload = JSON.parse(callback_query)
return CallbackData.withConstraint((data) => data.kind === kind).guard(
payload,
)
? (payload as any)
: null
}
export function guardCallbackQuery(
kind: keyof typeof commands,
callback_query: any,
): boolean {
return CallbackData.withConstraint((data) => data.kind === kind).guard(
JSON.parse(callback_query),
)
}
export const deleteButton = createCallbackButton(
commands.delete_message,
`Удалить`,
null,
)
export const deleteButtonReply = {
reply_markup: {
inline_keyboard: [[deleteButton]],
},
}
if (process.env.NODE_ENV === 'development') {
const dotenv = require('dotenv')
const path = require('path')
dotenv.config({
path: path.join(__dirname, '..', '..', `.env.local`),
})
}
if (typeof process.env.API_TOKEN !== 'string') {
console.error('API token is not passed')
process.exit()
}
if (typeof process.env.BOT_TOKEN !== 'string') {
console.error('Bot token is not passed')
process.exit()
}
export const {
API_TOKEN,
BOT_TOKEN,
} = process.env
export function formatUserName({
id,
username,
first_name,
}: {
id: number | string
username?: string
first_name?: string
}) {
return `<a href="tg://user?id=${id}">${username || first_name}</a>`
}
import { Context } from 'telegraf'
import { APIService } from '../services/APIService'
import {
isDevUser,
isGroupMessage,
isDirectMessage,
} from '../types'
import { TLG_MESSAGE_LIMIT } from './const'
import { sleep } from './dates'
import { Fn } from './types'
export function forDevs<
F extends (ctx: Context, next?: Function) => any
>(api: APIService, cb: F) {
return async (ctx: Context, next?: Function) => {
if (ctx.message?.from && isDevUser(await api.getUser(ctx.message.from))) {
await cb(ctx)
}
return next?.()
}
}
export function forGroupMessage<
F extends (ctx: Context, next?: Function) => any
>(cb: F) {
return async (ctx: Context, next?: Function) => {
if (isGroupMessage(ctx)) {
await cb(ctx)
}
return next?.()
}
}
export function forDirectMessage(cb: Fn<[Context, Function?], any>) {
return async (ctx: Context, next?: Function) => {
if (isDirectMessage(ctx)) {
await cb(ctx)
}
return next?.()
}
}
/**
*
* @param percent from 0 to 100
*/
export function chanceGuard(percent: number) {
return percent / 100 > Math.random()
}
let lastLock = Date.now()
export function freeMsgLimit() {
const now = Date.now()
lastLock = Math.max(lastLock + TLG_MESSAGE_LIMIT, now)
const delay = lastLock - now
return sleep(delay)
}
import { Context } from 'telegraf'
import { log } from './log'
import { Fn } from './types'
export const handleError = Object.assign(
function handleError<T extends any[]>(cb: Fn<T, any>): Fn<T, any> {
return async (...a: T) =>
Promise.all([
new Promise((r) => r(cb(...a))).catch((error) => {
const errorMsg = `Error (${cb.name || `anonymous fn`}):`
const errorData = errorify(error)
log(errorMsg, errorData)
if (a[0] instanceof Context) {
a[0].reply(`${errorMsg}\n${JSON.stringify(errorData, null, 2)}`)
}
return error
}),
a[0] instanceof Context && typeof a[1] === 'function' && a[1](),
])
},
{
voided: <T extends any[]>(cb: Fn<T, Promise<any>>): Fn<T, void> => {
const fn = handleError(cb)
return (...a) => void fn(...a)
},
},
)
export function errorify(thing: any) {
const error = thing instanceof Error ? thing : new Error(thing)
return { message: error.message, stack: error.stack, data: thing }
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment