create-verse-bot 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +2 -0
- package/dist/index.js +70 -0
- package/package.json +20 -0
- package/src/index.ts +76 -0
- package/template/.env.example +7 -0
- package/template/.github/workflows/deploy.yml +34 -0
- package/template/Dockerfile +13 -0
- package/template/docker-compose.prod.yml +25 -0
- package/template/docker-compose.yml +17 -0
- package/template/package.json +11 -0
- package/template/schema.sql +15 -0
- package/template/src/env.ts +3 -0
- package/template/src/index.ts +47 -0
- package/template/tsconfig.json +14 -0
- package/tsconfig.json +11 -0
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
4
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
5
|
+
};
|
|
6
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
+
const prompts_1 = require("@inquirer/prompts");
|
|
8
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
9
|
+
const fs_extra_1 = __importDefault(require("fs-extra"));
|
|
10
|
+
const node_child_process_1 = require("node:child_process");
|
|
11
|
+
async function main() {
|
|
12
|
+
const projectName = await (0, prompts_1.input)({
|
|
13
|
+
message: 'Project name:',
|
|
14
|
+
default: 'bot',
|
|
15
|
+
});
|
|
16
|
+
if (!projectName) {
|
|
17
|
+
console.log('Aborted.');
|
|
18
|
+
process.exit(1);
|
|
19
|
+
}
|
|
20
|
+
const platforms = await (0, prompts_1.checkbox)({
|
|
21
|
+
message: 'Select platforms:',
|
|
22
|
+
choices: [
|
|
23
|
+
{ name: 'Telegram', value: 'telegram' },
|
|
24
|
+
{ name: 'VK', value: 'vk' },
|
|
25
|
+
],
|
|
26
|
+
required: true,
|
|
27
|
+
});
|
|
28
|
+
if (platforms.length === 0) {
|
|
29
|
+
console.log('No platforms selected. Exiting.');
|
|
30
|
+
process.exit(1);
|
|
31
|
+
}
|
|
32
|
+
const useLocal = process.argv.includes('--local');
|
|
33
|
+
const targetDir = node_path_1.default.resolve(process.cwd(), projectName);
|
|
34
|
+
if (fs_extra_1.default.existsSync(targetDir)) {
|
|
35
|
+
console.error(`Directory "${projectName}" already exists.`);
|
|
36
|
+
process.exit(1);
|
|
37
|
+
}
|
|
38
|
+
// Копируем шаблон
|
|
39
|
+
const templateDir = node_path_1.default.resolve(__dirname, '../template');
|
|
40
|
+
fs_extra_1.default.copySync(templateDir, targetDir);
|
|
41
|
+
// Обновляем package.json для зависимостей
|
|
42
|
+
const pkgPath = node_path_1.default.join(targetDir, 'package.json');
|
|
43
|
+
const pkg = fs_extra_1.default.readJsonSync(pkgPath);
|
|
44
|
+
const deps = {};
|
|
45
|
+
// Определяем способ подключения пакетов
|
|
46
|
+
const resolvePackage = (name) => useLocal ? `file:${node_path_1.default.resolve(__dirname, '../..', name.split('/')[1])}` : 'latest';
|
|
47
|
+
deps['@verse-bot/shared'] = resolvePackage('@verse-bot/shared');
|
|
48
|
+
if (platforms.includes('telegram')) {
|
|
49
|
+
deps['@verse-bot/tg-core'] = resolvePackage('@verse-bot/tg-core');
|
|
50
|
+
}
|
|
51
|
+
if (platforms.includes('vk')) {
|
|
52
|
+
deps['@verse-bot/vk-core'] = resolvePackage('@verse-bot/vk-core');
|
|
53
|
+
}
|
|
54
|
+
pkg.dependencies = { ...pkg.dependencies, ...deps };
|
|
55
|
+
// Удаляем лишние зависимости, если платформа не выбрана
|
|
56
|
+
if (!platforms.includes('telegram')) {
|
|
57
|
+
delete pkg.dependencies['@verse-bot/tg-core'];
|
|
58
|
+
}
|
|
59
|
+
if (!platforms.includes('vk')) {
|
|
60
|
+
delete pkg.dependencies['@verse-bot/vk-core'];
|
|
61
|
+
}
|
|
62
|
+
fs_extra_1.default.writeJsonSync(pkgPath, pkg, { spaces: 2 });
|
|
63
|
+
console.log(`Project "${projectName}" created. Installing dependencies...`);
|
|
64
|
+
(0, node_child_process_1.execSync)('npm install', { cwd: targetDir, stdio: 'inherit' });
|
|
65
|
+
console.log('\nDone! Start your bot:\n');
|
|
66
|
+
console.log(` cd ${projectName}`);
|
|
67
|
+
console.log(' cp .env.example .env # fill in your tokens');
|
|
68
|
+
console.log(' npm run dev\n');
|
|
69
|
+
}
|
|
70
|
+
main().catch(console.error);
|
package/package.json
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "create-verse-bot",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "CLI to scaffold a new Verse Bot project",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"bin": {
|
|
7
|
+
"create-verse-bot": "./dist/index.js"
|
|
8
|
+
},
|
|
9
|
+
"scripts": {
|
|
10
|
+
"build": "tsc",
|
|
11
|
+
"dev": "tsc --watch"
|
|
12
|
+
},
|
|
13
|
+
"dependencies": {
|
|
14
|
+
"@inquirer/prompts": "^7.1.0",
|
|
15
|
+
"fs-extra": "^11.2.0"
|
|
16
|
+
},
|
|
17
|
+
"devDependencies": {
|
|
18
|
+
"@types/fs-extra": "^11.0.4"
|
|
19
|
+
}
|
|
20
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { input, checkbox } from '@inquirer/prompts';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import fs from 'fs-extra';
|
|
5
|
+
import { execSync } from 'node:child_process';
|
|
6
|
+
|
|
7
|
+
async function main() {
|
|
8
|
+
const projectName = await input({
|
|
9
|
+
message: 'Project name:',
|
|
10
|
+
default: 'bot',
|
|
11
|
+
});
|
|
12
|
+
if (!projectName) {
|
|
13
|
+
console.log('Aborted.');
|
|
14
|
+
process.exit(1);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const platforms = await checkbox({
|
|
18
|
+
message: 'Select platforms:',
|
|
19
|
+
choices: [
|
|
20
|
+
{ name: 'Telegram', value: 'telegram' },
|
|
21
|
+
{ name: 'VK', value: 'vk' },
|
|
22
|
+
],
|
|
23
|
+
required: true,
|
|
24
|
+
});
|
|
25
|
+
if (platforms.length === 0) {
|
|
26
|
+
console.log('No platforms selected. Exiting.');
|
|
27
|
+
process.exit(1);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const useLocal = process.argv.includes('--local');
|
|
31
|
+
const targetDir = path.resolve(process.cwd(), projectName);
|
|
32
|
+
if (fs.existsSync(targetDir)) {
|
|
33
|
+
console.error(`Directory "${projectName}" already exists.`);
|
|
34
|
+
process.exit(1);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// Копируем шаблон
|
|
38
|
+
const templateDir = path.resolve(__dirname, '../template');
|
|
39
|
+
fs.copySync(templateDir, targetDir);
|
|
40
|
+
|
|
41
|
+
// Обновляем package.json для зависимостей
|
|
42
|
+
const pkgPath = path.join(targetDir, 'package.json');
|
|
43
|
+
const pkg = fs.readJsonSync(pkgPath);
|
|
44
|
+
const deps: Record<string, string> = {};
|
|
45
|
+
|
|
46
|
+
// Определяем способ подключения пакетов
|
|
47
|
+
const resolvePackage = (name: string) =>
|
|
48
|
+
useLocal ? `file:${path.resolve(__dirname, '../..', name.split('/')[1])}` : 'latest';
|
|
49
|
+
|
|
50
|
+
deps['@verse-bot/shared'] = resolvePackage('@verse-bot/shared');
|
|
51
|
+
if (platforms.includes('telegram')) {
|
|
52
|
+
deps['@verse-bot/tg-core'] = resolvePackage('@verse-bot/tg-core');
|
|
53
|
+
}
|
|
54
|
+
if (platforms.includes('vk')) {
|
|
55
|
+
deps['@verse-bot/vk-core'] = resolvePackage('@verse-bot/vk-core');
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
pkg.dependencies = { ...pkg.dependencies, ...deps };
|
|
59
|
+
// Удаляем лишние зависимости, если платформа не выбрана
|
|
60
|
+
if (!platforms.includes('telegram')) {
|
|
61
|
+
delete pkg.dependencies['@verse-bot/tg-core'];
|
|
62
|
+
}
|
|
63
|
+
if (!platforms.includes('vk')) {
|
|
64
|
+
delete pkg.dependencies['@verse-bot/vk-core'];
|
|
65
|
+
}
|
|
66
|
+
fs.writeJsonSync(pkgPath, pkg, { spaces: 2 });
|
|
67
|
+
|
|
68
|
+
console.log(`Project "${projectName}" created. Installing dependencies...`);
|
|
69
|
+
execSync('npm install', { cwd: targetDir, stdio: 'inherit' });
|
|
70
|
+
console.log('\nDone! Start your bot:\n');
|
|
71
|
+
console.log(` cd ${projectName}`);
|
|
72
|
+
console.log(' cp .env.example .env # fill in your tokens');
|
|
73
|
+
console.log(' npm run dev\n');
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
main().catch(console.error);
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
name: Deploy Bot
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
workflow_dispatch:
|
|
7
|
+
|
|
8
|
+
jobs:
|
|
9
|
+
deploy:
|
|
10
|
+
runs-on: ubuntu-latest
|
|
11
|
+
steps:
|
|
12
|
+
- uses: actions/checkout@v4
|
|
13
|
+
- name: Deploy via SSH
|
|
14
|
+
uses: appleboy/ssh-action@v1.2.0
|
|
15
|
+
with:
|
|
16
|
+
host: ${{ secrets.VPS_HOST }}
|
|
17
|
+
username: ${{ secrets.VPS_USERNAME }}
|
|
18
|
+
key: ${{ secrets.SSH_PRIVATE_KEY }}
|
|
19
|
+
script: |
|
|
20
|
+
PROJECT_PATH=~/projects/${{ github.event.repository.name }}
|
|
21
|
+
if [ ! -d "$PROJECT_PATH" ]; then
|
|
22
|
+
mkdir -p ~/projects
|
|
23
|
+
git clone git@github.com:${{ github.repository }}.git "$PROJECT_PATH"
|
|
24
|
+
fi
|
|
25
|
+
cd "$PROJECT_PATH"
|
|
26
|
+
git pull origin main
|
|
27
|
+
echo "TELEGRAM_BOT_TOKEN=${{ secrets.TELEGRAM_BOT_TOKEN }}" > .env
|
|
28
|
+
echo "VK_GROUP_TOKEN=${{ secrets.VK_GROUP_TOKEN }}" >> .env
|
|
29
|
+
echo "VK_GROUP_ID=${{ secrets.VK_GROUP_ID }}" >> .env
|
|
30
|
+
echo "POSTGRES_DB=postgres" >> .env
|
|
31
|
+
echo "POSTGRES_USER=postgres" >> .env
|
|
32
|
+
echo "POSTGRES_PASSWORD=${{ secrets.POSTGRES_PASSWORD }}" >> .env
|
|
33
|
+
echo "POSTGRES_HOST=db" >> .env
|
|
34
|
+
docker compose -f docker-compose.prod.yml up -d --build
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
FROM node:20-alpine AS builder
|
|
2
|
+
WORKDIR /app
|
|
3
|
+
COPY package*.json ./
|
|
4
|
+
RUN npm ci --ignore-scripts
|
|
5
|
+
COPY . .
|
|
6
|
+
RUN npm run build
|
|
7
|
+
|
|
8
|
+
FROM node:20-alpine
|
|
9
|
+
WORKDIR /app
|
|
10
|
+
COPY --from=builder /app/dist ./dist
|
|
11
|
+
COPY --from=builder /app/node_modules ./node_modules
|
|
12
|
+
COPY package*.json ./
|
|
13
|
+
CMD ["node", "dist/index.js"]
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
name: verse-bot-prod
|
|
2
|
+
|
|
3
|
+
services:
|
|
4
|
+
db:
|
|
5
|
+
image: postgres:16-alpine
|
|
6
|
+
restart: always
|
|
7
|
+
env_file: .env
|
|
8
|
+
volumes:
|
|
9
|
+
- ./data/prod_db:/var/lib/postgresql/data
|
|
10
|
+
- ./schema.sql:/docker-entrypoint-initdb.d/01-schema.sql
|
|
11
|
+
healthcheck:
|
|
12
|
+
test: ['CMD-SHELL', 'pg_isready -U $$POSTGRES_USER -d $$POSTGRES_DB']
|
|
13
|
+
interval: 5s
|
|
14
|
+
timeout: 5s
|
|
15
|
+
retries: 5
|
|
16
|
+
|
|
17
|
+
bot:
|
|
18
|
+
build:
|
|
19
|
+
context: .
|
|
20
|
+
dockerfile: Dockerfile
|
|
21
|
+
restart: always
|
|
22
|
+
env_file: .env
|
|
23
|
+
depends_on:
|
|
24
|
+
db:
|
|
25
|
+
condition: service_healthy
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
name: verse-bot-dev
|
|
2
|
+
|
|
3
|
+
services:
|
|
4
|
+
db:
|
|
5
|
+
image: postgres:16-alpine
|
|
6
|
+
restart: always
|
|
7
|
+
env_file: .env
|
|
8
|
+
ports:
|
|
9
|
+
- '5432:5432'
|
|
10
|
+
volumes:
|
|
11
|
+
- ./data/dev_db:/var/lib/postgresql/data
|
|
12
|
+
- ./schema.sql:/docker-entrypoint-initdb.d/01-schema.sql
|
|
13
|
+
healthcheck:
|
|
14
|
+
test: ['CMD-SHELL', 'pg_isready -U $$POSTGRES_USER -d $$POSTGRES_DB']
|
|
15
|
+
interval: 5s
|
|
16
|
+
timeout: 5s
|
|
17
|
+
retries: 5
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
CREATE TABLE IF NOT EXISTS users (
|
|
2
|
+
id SERIAL PRIMARY KEY,
|
|
3
|
+
tg_id TEXT UNIQUE,
|
|
4
|
+
vk_id TEXT UNIQUE,
|
|
5
|
+
created_at TIMESTAMP DEFAULT now(),
|
|
6
|
+
updated_at TIMESTAMP DEFAULT now()
|
|
7
|
+
);
|
|
8
|
+
|
|
9
|
+
CREATE TABLE IF NOT EXISTS command_logs (
|
|
10
|
+
id SERIAL PRIMARY KEY,
|
|
11
|
+
user_id INTEGER REFERENCES users(id),
|
|
12
|
+
platform TEXT CHECK (platform IN ('telegram', 'vk')),
|
|
13
|
+
command TEXT,
|
|
14
|
+
created_at TIMESTAMP DEFAULT now()
|
|
15
|
+
);
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { initPool, getPool } from '@verse-bot/shared';
|
|
2
|
+
import { createUniversalTelegramBot } from '@verse-bot/tg-core';
|
|
3
|
+
import { createUniversalVKBot } from '@verse-bot/vk-core';
|
|
4
|
+
import { TELEGRAM_BOT_TOKEN, VK_GROUP_TOKEN, VK_GROUP_ID } from './env.js';
|
|
5
|
+
|
|
6
|
+
// Инициализация БД, если заданы переменные
|
|
7
|
+
if (process.env.POSTGRES_USER) {
|
|
8
|
+
const poolConfig = {
|
|
9
|
+
user: process.env.POSTGRES_USER,
|
|
10
|
+
password: process.env.POSTGRES_PASSWORD!,
|
|
11
|
+
host: process.env.POSTGRES_HOST || 'localhost',
|
|
12
|
+
database: process.env.POSTGRES_DB!,
|
|
13
|
+
port: 5432,
|
|
14
|
+
};
|
|
15
|
+
initPool(poolConfig);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
// Заглушки команд – замените на свои
|
|
19
|
+
const commands = {
|
|
20
|
+
ping: async (ctx: any) => {
|
|
21
|
+
await ctx.reply('pong');
|
|
22
|
+
},
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
// Telegram
|
|
26
|
+
if (TELEGRAM_BOT_TOKEN) {
|
|
27
|
+
const bot = createUniversalTelegramBot({
|
|
28
|
+
token: TELEGRAM_BOT_TOKEN,
|
|
29
|
+
commands,
|
|
30
|
+
buttons: [],
|
|
31
|
+
});
|
|
32
|
+
bot.start().then(() => console.log('🤖 Telegram bot started'));
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// VK
|
|
36
|
+
if (VK_GROUP_TOKEN && VK_GROUP_ID) {
|
|
37
|
+
const pool = getPool(); // если БД не используется, getPool() выбросит ошибку,
|
|
38
|
+
// но можно обернуть в try или проверять наличие process.env.POSTGRES_USER
|
|
39
|
+
const bot = createUniversalVKBot({
|
|
40
|
+
token: VK_GROUP_TOKEN,
|
|
41
|
+
groupId: VK_GROUP_ID,
|
|
42
|
+
commands,
|
|
43
|
+
buttons: [],
|
|
44
|
+
pool,
|
|
45
|
+
});
|
|
46
|
+
bot.start().then(() => console.log('🤖 VK bot started'));
|
|
47
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "es2022",
|
|
4
|
+
"module": "nodenext",
|
|
5
|
+
"moduleResolution": "nodenext",
|
|
6
|
+
"esModuleInterop": true,
|
|
7
|
+
"strict": true,
|
|
8
|
+
"skipLibCheck": true,
|
|
9
|
+
"outDir": "./dist",
|
|
10
|
+
"rootDir": "./src",
|
|
11
|
+
"types": ["node"]
|
|
12
|
+
},
|
|
13
|
+
"include": ["src/**/*.ts"]
|
|
14
|
+
}
|