feat(create-bot): various improvements

- improved bun support
- deno support
- use antfu/eslint-config
- fixed some issues
- removed pre-commit hooks
This commit is contained in:
alina 🌸 2024-06-24 00:20:10 +03:00
parent 7f7f513bc8
commit 64da48926f
Signed by: teidesu
SSH key fingerprint: SHA256:uNeCpw6aTSU4aIObXLvHfLkDa82HWH9EiOj9AXOIRpI
29 changed files with 324 additions and 345 deletions

View file

@ -5,22 +5,24 @@ Starter kit for creating bots using `@mtcute/node` or `@mtcute/bun`.
[Learn more](https://mtcute.dev/guide/)
## Features
- **Linters**: can generate linting configs for `eslint` and `prettier`
- **Linters**: can set up linters using [@antfu/eslint-config](https://github.com/antfu/eslint-config)
- **TypeScript**: can generate a TypeScript project
- **Docker**: can generate a Dockerfile for easy deployment
- **I18n**: can generate a basic i18n setup using `@mtcute/i18n`
## Usage
Depending on your preferred package manager, run one of the following commands:
Depending on your preferred package manager and runtime, run one of the following commands:
```bash
pnpm create @mtcute/bot
pnpm create @mtcute/bot my-awesome-bot
# or
yarn create @mtcute/bot
yarn create @mtcute/bot my-awesome-bot
# or
npm create @mtcute/bot
npm create @mtcute/bot my-awesome-bot
# or
bun create @mtcute/bot
bun create @mtcute/bot my-awesome-bot
# or
deno run -A npm:@mtcute/create-bot my-awesome-bot
```
and follow the instructions

View file

@ -8,7 +8,8 @@
"type": "module",
"scripts": {
"build": "pnpm run -w build-package create-bot",
"run": "ts-node-esm src/main.ts"
"run": "ts-node-esm src/main.ts",
"run:deno": "node scripts/generate-import-map.js && deno run --import-map=./scripts/import-map.json -A --unstable-sloppy-imports src/main.ts"
},
"bin": {
"create-bot": "./src/main.js"

View file

@ -0,0 +1 @@
import-map.json

View file

@ -0,0 +1,11 @@
import * as fs from 'fs'
const packageJson = JSON.parse(fs.readFileSync(new URL('../package.json', import.meta.url)))
const importMap = {}
for (const [name, version] of Object.entries(packageJson.dependencies)) {
importMap[name] = `npm:${name}@${version}`
}
fs.writeFileSync(new URL('import-map.json', import.meta.url), JSON.stringify({ imports: importMap }, null, 2))

View file

@ -1,6 +1,7 @@
import { promises as fs } from 'fs'
import { homedir } from 'os'
import * as path from 'path'
import { promises as fs } from 'node:fs'
import { homedir } from 'node:os'
import * as path from 'node:path'
import process from 'node:process'
export interface UserConfigPersisted {
apiId: number

View file

@ -1,16 +1,27 @@
import { join } from 'node:path'
import { UserConfig } from './cli.js'
import { MtcuteFeature } from './features/types.js'
import { fetchAllLatestVersionsJsr } from './jsr.js'
import { getInstallCommand, PackageManager } from './package-manager.js'
import { exec } from './utils.js'
export interface DependenciesList {
dependencies: string[]
devDepdenencies: string[]
}
export function buildDependenciesList(config: UserConfig) {
const dependencies = []
const devDepdenencies = ['dotenv-cli']
const devDepdenencies = []
if (config.packageManager === PackageManager.Bun) {
dependencies.push('@mtcute/bun')
} else if (config.packageManager === PackageManager.Deno) {
dependencies.push('@std/dotenv', '@mtcute/deno')
} else {
dependencies.push('@mtcute/node')
// node
dependencies.push('dotenv-cli', '@mtcute/node')
}
if (config.features.includes(MtcuteFeature.Dispatcher)) {
@ -25,29 +36,19 @@ export function buildDependenciesList(config: UserConfig) {
dependencies.push('@mtcute/crypto-node')
}
if (config.features.includes(MtcuteFeature.TypeScript)) {
if (config.features.includes(MtcuteFeature.TypeScript) && config.packageManager !== PackageManager.Deno) {
devDepdenencies.push('typescript', '@types/node')
if (config.packageManager === PackageManager.Bun) {
devDepdenencies.push('@types/bun')
} else {
// node can't handle typescript natively
devDepdenencies.push('tsx')
}
}
if (config.features.includes(MtcuteFeature.Linters)) {
devDepdenencies.push(
'husky',
'lint-staged',
'eslint',
'eslint-config-prettier',
'eslint-plugin-ascii',
'eslint-plugin-import',
'eslint-plugin-simple-import-sort',
'prettier',
)
if (config.features.includes(MtcuteFeature.TypeScript)) {
devDepdenencies.push(
'eslint-import-resolver-typescript',
'@typescript-eslint/eslint-plugin',
'@typescript-eslint/parser',
)
}
devDepdenencies.push('@antfu/eslint-config')
}
return {
@ -59,6 +60,35 @@ export function buildDependenciesList(config: UserConfig) {
export async function installDependencies(cwd: string, config: UserConfig) {
const { dependencies, devDepdenencies } = buildDependenciesList(config)
if (config.packageManager === PackageManager.Deno) {
// deno doesn't have a package manager per se, but we can generate an import map
// to basically achieve the same thing
//
// since we don't have a package manager, we have to resolve "latest" versions ourselves :c
// additionally, there's no notion of "dev" dependencies is deno. though fairly enough,
// there should be no dev deps in our deno template (but let's just keep it for consistency)
const allDeps = [...dependencies, ...devDepdenencies]
const versions = await fetchAllLatestVersionsJsr(allDeps)
const denoJson = {
imports: {} as Record<string, string>,
tasks: {
start: `deno run -A --unstable-ffi ./src/main.${
config.features.includes(MtcuteFeature.TypeScript) ? 'ts' : 'js'
}`,
},
}
for (const dep of allDeps) {
denoJson.imports[dep] = `jsr:${dep}@^${versions.get(dep)!}`
}
await Deno.writeTextFile(join(cwd, 'deno.json'), JSON.stringify(denoJson, null, 4))
return
}
await exec(cwd, ...getInstallCommand({ mgr: config.packageManager, packages: dependencies }))
await exec(cwd, ...getInstallCommand({ mgr: config.packageManager, packages: devDepdenencies, dev: true }))
}

View file

@ -23,14 +23,23 @@ export function getFeatureChoices(packageMananger: PackageManager): CheckboxChoi
checked: true,
},
{
name: ' 🥰 Setup Prettier & ESLint',
short: 'Linters',
value: MtcuteFeature.Linters,
name: ' 📦 Initialize git repository',
short: 'Git',
value: MtcuteFeature.Git,
checked: true,
},
]
if (packageMananger !== PackageManager.Bun) {
if (packageMananger !== PackageManager.Deno) {
arr.unshift({
name: ' 🥰 Setup ESLint with @antfu/eslint-config',
short: 'Linters',
value: MtcuteFeature.Linters,
checked: true,
})
}
if (packageMananger !== PackageManager.Bun && packageMananger !== PackageManager.Deno) {
arr.unshift({
name: ' 🚀 Native addon (better performance)',
short: 'Native addon',

View file

@ -5,4 +5,5 @@ export enum MtcuteFeature {
Docker = 'docker',
TypeScript = 'typescript',
Linters = 'linters',
Git = 'git',
}

View file

@ -0,0 +1,25 @@
export async function fetchLatestVersionJsr(pkg: string): Promise<string> {
const res = await fetch(`https://jsr.io/${pkg}/meta.json`)
if (!res.ok) {
throw new Error(`Failed to fetch ${pkg} metadata: ${await res.text()}`)
}
const meta = (await res.json()) as {
latest: string
}
return meta.latest
}
export async function fetchAllLatestVersionsJsr(pkgs: string[]): Promise<Map<string, string>> {
const res = new Map<string, string>()
await Promise.all(
pkgs.map(async (pkg) => {
res.set(pkg, await fetchLatestVersionJsr(pkg))
}),
)
return res
}

View file

@ -1,6 +1,7 @@
#!/usr/bin/env node
import * as colors from 'colorette'
import { dirname, join } from 'node:path'
import { basename, dirname, join } from 'node:path'
import process from 'node:process'
import { fileURLToPath } from 'node:url'
import { askForConfig } from './cli.js'
@ -23,9 +24,18 @@ if (packageManager === PackageManager.Bun) {
console.log(`${colors.red('‼️ Warning:')} ${colors.yellow('Bun')} support is ${colors.bold('experimental')}`)
}
if (packageManager === PackageManager.Deno) {
console.log(`${colors.red('‼️ Warning:')} ${colors.yellow('Deno')} support is ${colors.bold('experimental')}`)
}
const config = await askForConfig(packageManager)
config.name = projectName
const outDir = process.env.TARGET_DIR || join(process.cwd(), projectName)
config.name = basename(projectName)
let outDir = process.env.TARGET_DIR || projectName
if (!outDir.match(/^([A-Za-z]:)?[/\\]/)) {
// assume it's a relative path
outDir = join(process.cwd(), outDir)
}
const __dirname = dirname(fileURLToPath(import.meta.url))
@ -33,20 +43,25 @@ await runTemplater(join(__dirname, '../template'), outDir, config)
await installDependencies(outDir, config)
await exec(outDir, 'git', 'init')
if (config.features.includes(MtcuteFeature.Linters)) {
if (process.platform === 'win32') {
// windows doesn't track executable bit, but git does
await exec(outDir, 'git', 'update-index', '--chmod=+x', '.husky/pre-commit')
} else {
await exec(outDir, 'chmod', '+x', '.husky/pre-commit')
}
await exec(outDir, ...getExecCommand(config.packageManager, 'eslint', '--fix', '.'))
}
await exec(outDir, ...getExecCommand(config.packageManager, 'husky'))
if (config.features.includes(MtcuteFeature.Git)) {
await exec(outDir, 'git', 'init', '.', '--initial-branch', 'main')
await exec(outDir, 'git', 'add', '.')
await exec(outDir, 'git', 'commit', '-m', 'Initial commit')
}
console.log(`✅ Scaffolded new project at ${colors.blue(outDir)}`)
console.log('🚀 Run it with:')
console.log(` ${colors.blue('$')} cd ${projectName}`)
console.log(` ${colors.blue('$')} ${config.packageManager} start`)
if (config.packageManager === PackageManager.Deno) {
console.log(` ${colors.blue('$')} deno task start`)
// for whatever reason, deno keeps hanging after the we finish
// and doesn't even handle SIGINT. just exit lol
process.exit(0)
} else {
console.log(` ${colors.blue('$')} ${config.packageManager} start`)
}

View file

@ -1,20 +1,48 @@
import * as colors from 'colorette'
import process from 'node:process'
export function getPackageManagerVersion(): [string, string] | null {
if (typeof Deno !== 'undefined') {
return null
}
const userAgent = process.env.npm_config_user_agent
if (!userAgent) {
return null
}
const software = userAgent.split(' ')[0]
const manager = software.split('/')[0]
const version = software.split('/')[1]
if (!version.match(/^\d+\.\d+\.\d+$/)) {
return null
}
return [manager, version]
}
export enum PackageManager {
Npm = 'npm',
Yarn = 'yarn',
Pnpm = 'pnpm',
Bun = 'bun',
Deno = 'deno',
}
export function getPackageManager(): PackageManager {
const userAgent = process.env.npm_config_user_agent
const parsed = getPackageManagerVersion()
if (!parsed) {
if (typeof Deno !== 'undefined') return PackageManager.Deno
console.warn(colors.yellow('[warning] could not detect package manager, falling back to pnpm'))
if (!userAgent) {
return PackageManager.Pnpm // fall back to the most based one
}
const name = userAgent.split('/')[0]
switch (name) {
switch (parsed[0]) {
case 'pnpm':
return PackageManager.Pnpm
case 'yarn':
@ -24,7 +52,7 @@ export function getPackageManager(): PackageManager {
case 'bun':
return PackageManager.Bun
default:
throw new Error(`Unsupported package manager: ${name}`)
throw new Error(`Unsupported package manager: ${parsed[0]}`)
}
}
@ -66,5 +94,20 @@ export function getExecCommand(mgr: PackageManager, ...cmd: string[]) {
return ['pnpm', 'exec', ...cmd]
case PackageManager.Bun:
return ['bun', 'run', ...cmd]
case PackageManager.Deno:
throw new Error('Deno does not support exec commands')
}
}
export function packageManagerToRuntime(mgr: PackageManager) {
switch (mgr) {
case PackageManager.Npm:
case PackageManager.Yarn:
case PackageManager.Pnpm:
return 'node'
case PackageManager.Bun:
return 'bun'
case PackageManager.Deno:
return 'deno'
}
}

View file

@ -5,6 +5,7 @@ import * as path from 'node:path'
import type { UserConfig } from './cli.js'
import { MtcuteFeature } from './features/types.js'
import { getPackageManagerVersion, packageManagerToRuntime } from './package-manager.js'
const templater = Handlebars.create()
@ -37,6 +38,8 @@ export async function runTemplaterForFile(file: string, config: UserConfig): Pro
const result = execute({
...config,
runtime: packageManagerToRuntime(config.packageManager),
packageManagerVersion: getPackageManagerVersion()?.join('@'),
features: config.features.reduce<Record<MtcuteFeature, boolean>>(
(acc, f) => {
acc[f] = true

View file

@ -3,7 +3,7 @@ import { spawn } from 'cross-spawn'
export function exec(cwd: string, ...cmd: string[]) {
return new Promise<void>((resolve, reject) => {
console.log(`${colors.blue('$')} ${cmd.join(' ')}`)
console.log(`${colors.blue('$')} ${cmd.map((it) => (it.includes(' ') ? JSON.stringify(it) : it)).join(' ')}`)
const proc = spawn(cmd[0], cmd.slice(1), {
stdio: 'inherit',

View file

@ -1,8 +0,0 @@
{{emit_if features.linters}}
node_modules/
.husky/
.idea/
dist/
*.json

View file

@ -1,210 +0,0 @@
{{emit_if features.linters}}
module.exports = {
env: {
browser: true,
es2021: true,
node: true,
},
extends: ['eslint:recommended', 'plugin:import/recommended', 'prettier'],
parserOptions: {
ecmaVersion: 'latest',
sourceType: 'module',
},
plugins: ['ascii', 'import', 'simple-import-sort'],
reportUnusedDisableDirectives: true,
rules: {
// see https://github.com/airbnb/javascript/blob/master/packages/eslint-config-airbnb-base/rules/style.js#L122
indent: [
2,
4,
{
SwitchCase: 1,
VariableDeclarator: 1,
outerIIFEBody: 1,
// MemberExpression: null,
FunctionDeclaration: {
parameters: 1,
body: 1,
},
FunctionExpression: {
parameters: 1,
body: 1,
},
CallExpression: {
arguments: 1,
},
ArrayExpression: 1,
ObjectExpression: 1,
ImportDeclaration: 1,
flatTernaryExpressions: false,
// list derived from https://github.com/benjamn/ast-types/blob/HEAD/def/jsx.js
ignoredNodes: [
'JSXElement',
'JSXElement > *',
'JSXAttribute',
'JSXIdentifier',
'JSXNamespacedName',
'JSXMemberExpression',
'JSXSpreadAttribute',
'JSXExpressionContainer',
'JSXOpeningElement',
'JSXClosingElement',
'JSXText',
'JSXEmptyExpression',
'JSXSpreadChild',
],
ignoreComments: false,
},
],
semi: [2, 'never', { beforeStatementContinuationChars: 'never' }],
'semi-spacing': [2, { before: false, after: true }],
'wrap-iife': [2, 'inside'],
'no-caller': 2,
'no-cond-assign': [2, 'except-parens'],
'no-constant-condition': 0,
'no-debugger': 2,
'no-dupe-args': 2,
'no-dupe-keys': 2,
'no-duplicate-case': 2,
'no-empty': [2, { allowEmptyCatch: true }],
'no-empty-function': 'off',
'no-extra-boolean-cast': 2,
'no-extra-semi': 2,
'no-func-assign': 2,
'no-new': 2,
'no-sparse-arrays': 2,
'no-unexpected-multiline': 2,
'no-unreachable': 2,
'max-params': [1, 5],
'max-depth': [1, 4],
'no-eq-null': 0,
'no-unused-expressions': 0,
'dot-notation': 2,
'use-isnan': 2,
// Best practices
'block-scoped-var': 2,
complexity: [0, 11],
curly: [2, 'multi-line'],
eqeqeq: [2, 'always', { null: 'ignore' }],
'no-else-return': 2,
'no-extra-bind': 2,
'no-implicit-coercion': 2,
'no-return-assign': 0,
'no-sequences': 2,
yoda: 2,
// Variables
'no-restricted-globals': ['error'],
'no-var': 1,
// Codestyle
'arrow-parens': [2, 'always'],
'array-bracket-spacing': [2, 'never'],
'brace-style': [2, '1tbs', { allowSingleLine: true }],
camelcase: [2, { properties: 'never' }],
'comma-dangle': ['warn', 'always-multiline'],
'comma-spacing': [2, { before: false, after: true }],
'eol-last': 2,
'func-call-spacing': [2, 'never'],
'block-spacing': 2,
'keyword-spacing': [2, { before: true, after: true }],
'max-len': [
2,
{
code: 120,
ignoreUrls: true,
ignoreComments: false,
ignoreRegExpLiterals: true,
ignoreStrings: true,
ignoreTemplateLiterals: true,
ignorePattern: 'require',
},
],
'no-lonely-if': 2,
'no-mixed-spaces-and-tabs': 2,
'no-multi-spaces': 2,
'no-multiple-empty-lines': [2, { max: 1, maxBOF: 0, maxEOF: 0 }],
'no-trailing-spaces': 2,
'ascii/valid-name': 2,
'no-unneeded-ternary': 2,
'no-nested-ternary': 2,
'object-curly-spacing': [2, 'always'],
'one-var-declaration-per-line': [2, 'initializations'],
'one-var': [2, { let: 'never', const: 'never' }],
'operator-linebreak': [2, 'after'],
'padded-blocks': [2, 'never'],
'quote-props': [2, 'as-needed', { numbers: true }],
quotes: [2, 'single', { avoidEscape: true }],
'space-before-blocks': [2, 'always'],
'space-before-function-paren': [
2,
{
named: 'never',
anonymous: 'always',
},
],
'space-in-parens': 2,
'key-spacing': [2, { beforeColon: false, afterColon: true, mode: 'strict' }],
'space-infix-ops': 2,
'padding-line-between-statements': [
'error',
{ blankLine: 'always', prev: '*', next: 'return' },
{ blankLine: 'always', prev: '*', next: 'block-like' },
{ blankLine: 'any', prev: 'block-like', next: 'block-like' },
{ blankLine: 'any', prev: 'case', next: 'case' },
],
'simple-import-sort/imports': [
'error',
{
groups: [['^@mtcute'], ['^[a-z]'], ['^#']],
},
],
'simple-import-sort/exports': 'error',
'import/no-relative-packages': 'error',
'import/no-mutable-exports': 'error',
'import/no-default-export': 'error',
// https://github.com/import-js/eslint-plugin-import/issues/2903
'import/named': 'off',
},
{{#if features.typescript}}
overrides: [
{
files: ['**/*.ts'],
env: { browser: true, es6: true, node: true },
extends: ['plugin:@typescript-eslint/recommended'],
globals: { Atomics: 'readonly', SharedArrayBuffer: 'readonly' },
parser: '@typescript-eslint/parser',
plugins: ['@typescript-eslint'],
rules: {
'@typescript-eslint/consistent-type-assertions': 2,
'@typescript-eslint/no-explicit-any': 2,
'@typescript-eslint/no-unused-vars': [
2,
{
args: 'after-used',
argsIgnorePattern: '^_',
ignoreRestSiblings: true,
vars: 'all',
varsIgnorePattern: '^_',
},
],
},
settings: {
'import/resolver': {
node: true,
typescript: true,
},
},
},
],
{{/if}}
settings: {
'import/resolver': {
node: true,
},
},
}

View file

@ -1,6 +1,7 @@
{{#if (ne runtime "deno")}}
node_modules/
{{/if}}
dist/
private/
.nyc_output/
**/.DS_Store
.idea

View file

@ -1,5 +0,0 @@
{{emit_if features.linters}}
#!/usr/bin/env sh
. "$(dirname -- "$0")/_/husky.sh"
pnpm exec lint-staged

View file

@ -1,7 +0,0 @@
{{emit_if features.linters}}
module.exports = {
'*.{js,jsx,ts,tsx}': [
`prettier --write`,
`eslint --fix`,
]
}

View file

@ -1,20 +0,0 @@
{{emit_if features.linters}}
{
"arrowParens": "always",
"bracketSpacing": true,
"embeddedLanguageFormatting": "auto",
"htmlWhitespaceSensitivity": "css",
"insertPragma": false,
"jsxBracketSameLine": false,
"jsxSingleQuote": false,
"printWidth": 80,
"proseWrap": "preserve",
"quoteProps": "as-needed",
"requirePragma": false,
"semi": false,
"singleQuote": true,
"tabWidth": 4,
"trailingComma": "es5",
"useTabs": false,
"vueIndentScriptAndStyle": false
}

View file

@ -12,4 +12,4 @@ RUN pnpm install --frozen-lockfile
COPY src /app/src
RUN pnpm run build
CMD [ "node", "/app/dist/index.js" ]
CMD [ "node", "/app/dist/main.js" ]

View file

@ -1,14 +1,24 @@
# {{name}}
MTCute powered Telegram bot
mtcute powered Telegram bot
## Development
```bash
pnpm install --frozen-lockfile
{{#if (ne runtime "deno")}}
{{#if (eq packageManager "npm")}}
npm ci
{{else}}
{{packageManager}} install --frozen-lockfile
{{/if}}
{{/if}}
cp .env.example .env
# edit .env
pnpm start
{{#if (eq runtime "deno")}}
deno task start
{{else}}
{{packageManager}} start
{{/if}}
```
*generated with @mtcute/create-bot*

View file

@ -0,0 +1,21 @@
{{emit_if features.linters}}
import antfu from '@antfu/eslint-config'
export default antfu({
stylistic: {
indent: 4,
},
{{#if features.typescript}}
typescript: true,
{{/if}}
yaml: false,
rules: {
'curly': ['error', 'multi-line'],
'style/brace-style': ['error', '1tbs', { allowSingleLine: true }],
'style/quotes': ['error', 'single', { avoidEscape: true }],
'import/order': ['error', { 'newlines-between': 'always' }],
'antfu/if-newline': 'off',
'style/max-statements-per-line': ['error', { max: 2 }],
'no-console': 'off',
},
})

View file

@ -1,27 +1,31 @@
{{emit_if (ne runtime "deno")}}
{
"name": "{{ name }}",
"license": "MIT",
"version": "0.0.0",
"version": "0.0.1",
"type": "module",
{{#if packageManagerVersion}}
"packageManager": "{{packageManagerVersion}}",
{{/if}}
"scripts": {
{{#if features.linters}}
"prepare": "husky || true",
"lint": "eslint .",
"lint:fix": "eslint --fix .",
"format": "prettier --write \"src/**/*.ts\"",
{{/if}}
{{#if (eq packageManager "bun")}}
{{#if (eq runtime "bun")}}
{{#if features.typescript}}
"start": "bun ./src/index.ts"
"start": "bun ./src/main.ts",
"build": "tsc && bun build src/main.ts --target=bun --outdir=dist"
{{else}}
"start": "bun ./src/index.js"
"start": "bun ./src/main.js"
{{/if}}
{{else}}
{{#if features.typescript}}
"start": "tsc && dotenv node ./dist/index.js",
"start": "dotenv tsx ./src/main.ts",
"build": "tsc"
{{else}}
"start": "dotenv node ./src/index.js"
"start": "dotenv node ./src/main.js"
{{/if}}
{{/if}}
}

View file

@ -0,0 +1,24 @@
{{emit_if (not features.typescript)}}
{{#if (eq runtime "deno")}}
import '@std/dotenv/load'
const API_ID = Number.parseInt(Deno.env.get('API_ID'))
const API_HASH = Deno.env.get('API_HASH')
{{#if botToken}}
const BOT_TOKEN = Deno.env.get('BOT_TOKEN')
{{/if}}
{{else}}
import process from 'node:process'
const API_ID = Number.parseInt(process.env.API_ID)
const API_HASH = process.env.API_HASH
{{#if botToken}}
const BOT_TOKEN = process.env.BOT_TOKEN
{{/if}}
{{/if}}
if (Number.isNaN(API_ID) || !API_HASH) {
throw new Error('API_ID or API_HASH not set!')
}
export { API_HASH, API_ID{{#if botToken}}, BOT_TOKEN{{/if}} }

View file

@ -1,11 +1,23 @@
{{emit_if features.typescript}}
const API_ID = parseInt(process.env.API_ID!)
{{#if (eq runtime "deno")}}
import '@std/dotenv/load'
const API_ID = Number.parseInt(Deno.env.get('API_ID')!)
const API_HASH = Deno.env.get('API_HASH')!
{{#if botToken}}
const BOT_TOKEN = Deno.env.get('BOT_TOKEN')!
{{/if}}
{{else}}
import process from 'node:process'
const API_ID = Number.parseInt(process.env.API_ID!)
const API_HASH = process.env.API_HASH!
{{#if botToken}}
const BOT_TOKEN = process.env.BOT_TOKEN!
{{/if}}
{{/if}}
if (isNaN(API_ID) || !API_HASH) {
if (Number.isNaN(API_ID) || !API_HASH) {
throw new Error('API_ID or API_HASH not set!')
}

View file

@ -1,8 +1,14 @@
{{emit_if (and (not features.typescript) features.i18n)}}
import { createMtcuteI18n } from '@mtcute/i18n'
{{#if (eq runtime "node")}}
import { en } from './en.js'
import { ru } from './ru.js'
{{else}}
import { en } from './en.ts'
import { ru } from './ru.ts'
{{/if}}
export const tr = createMtcuteI18n({
primaryLanguage: {

View file

@ -2,8 +2,10 @@
{{#if features.dispatcher}}
import { Dispatcher, filters } from '@mtcute/dispatcher'
{{/if}}
{{#if (eq packageManager "bun")}}
{{#if (eq runtime "bun")}}
import { TelegramClient } from '@mtcute/bun'
{{else if (eq runtime "deno")}}
import { TelegramClient } from '@mtcute/deno'
{{else}}
import { TelegramClient } from '@mtcute/node'
{{/if}}
@ -31,11 +33,9 @@ dp.onNewMessage(filters.start, async (msg) => {
})
{{/if}}
tg.run(
{{#if botToken}}
{ botToken: env.BOT_TOKEN },
{{/if}}
(user) => {
console.log('Logged in as', user.username)
},
)
{{#if botToken}}
const user = await tg.start({ botToken: env.BOT_TOKEN })
{{else}}
const user = await tg.start()
{{/if}}
console.log('Logged in as', user.username)

View file

@ -2,15 +2,25 @@
{{#if features.dispatcher}}
import { Dispatcher, filters } from '@mtcute/dispatcher'
{{/if}}
{{#if (eq packageManager "bun")}}
{{#if (eq runtime "bun")}}
import { TelegramClient } from '@mtcute/bun'
{{else if (eq runtime "deno")}}
import { TelegramClient } from '@mtcute/deno'
{{else}}
import { TelegramClient } from '@mtcute/node'
{{/if}}
{{#if (eq runtime "node")}}
import * as env from './env.js'
{{else}}
import * as env from './env.ts'
{{/if}}
{{#if features.i18n}}
{{#if (eq runtime "node")}}
import { tr } from './i18n/index.js'
{{else}}
import { tr } from './i18n/index.ts'
{{/if}}
{{/if}}
const tg = new TelegramClient({
@ -31,11 +41,9 @@ dp.onNewMessage(filters.start, async (msg) => {
})
{{/if}}
tg.run(
{{#if botToken}}
{ botToken: env.BOT_TOKEN },
{{/if}}
(user) => {
console.log('Logged in as', user.username)
},
)
{{#if botToken}}
const user = await tg.start({ botToken: env.BOT_TOKEN })
{{else}}
const user = await tg.start()
{{/if}}
console.log('Logged in as', user.username)

View file

@ -1,28 +1,29 @@
{{emit_if features.typescript}}
{{emit_if (and features.typescript (ne runtime "deno"))}}
{
"compilerOptions": {
"outDir": "./dist",
{{#if (eq runtime "bun")}}
"module": "ESNext",
"moduleResolution": "Bundler",
"noEmit": true,
"allowImportingTsExtensions": true,
{{else}}
"module": "NodeNext",
"moduleResolution": "NodeNext",
"target": "es2020",
{{/if}}
"target": "es2022",
"allowJs": true,
"sourceMap": true,
"inlineSources": true,
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"strict": true,
"noImplicitAny": true,
"noImplicitThis": true,
"incremental": true,
"skipLibCheck": true,
},
"include": [
"./src"
],
"ts-node": {
"esm": true,
"experimentalSpecifierResolution": "node"
},
"exclude": [
"**/node_modules",
]