init
This commit is contained in:
commit
635af85b2b
3
.dockerignore
Normal file
3
.dockerignore
Normal file
@ -0,0 +1,3 @@
|
||||
/node_modules
|
||||
/dist
|
||||
.env
|
||||
3
.gitignore
vendored
Normal file
3
.gitignore
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
.env
|
||||
node_modules
|
||||
.idea
|
||||
17
Dockerfile
Normal file
17
Dockerfile
Normal file
@ -0,0 +1,17 @@
|
||||
# Declare build-time argument
|
||||
ARG DOCKER_IMAGE_SOURCE
|
||||
|
||||
FROM ${DOCKER_IMAGE_SOURCE}
|
||||
|
||||
WORKDIR /usr/src/app
|
||||
|
||||
COPY package*.json ./
|
||||
|
||||
RUN npm install
|
||||
|
||||
COPY . .
|
||||
|
||||
EXPOSE ${PORT}
|
||||
|
||||
CMD npm run build;npm run start
|
||||
# CMD [ "npm", "run", "start" ]
|
||||
38
docker-compose.yml
Normal file
38
docker-compose.yml
Normal file
@ -0,0 +1,38 @@
|
||||
services:
|
||||
main:
|
||||
build:
|
||||
dockerfile: Dockerfile
|
||||
context: .
|
||||
args:
|
||||
DOCKER_IMAGE_SOURCE: ${DOCKER_IMAGE_SOURCE}
|
||||
image: ${IMAGE_NAME}
|
||||
container_name: ${CONTAINER_NAME}
|
||||
depends_on:
|
||||
- redis
|
||||
ports:
|
||||
- ${PORT}:8000
|
||||
tmpfs:
|
||||
- /tmp:size=${TMPFS_SIZE}
|
||||
- /var/log:size=50m
|
||||
deploy:
|
||||
resources:
|
||||
limits:
|
||||
cpus: ${CPU_LIMIT}
|
||||
memory: ${MEMORY_LIMIT}
|
||||
volumes:
|
||||
- .:/wrkdir
|
||||
restart: unless-stopped
|
||||
env_file: .env
|
||||
redis:
|
||||
image: ${REDIS_IMAGE_SOURCE}
|
||||
container_name: ${REDIS_CONTAINER_NAME}
|
||||
command: sh -c "redis-server --requirepass $REDIS_PASSWORD"
|
||||
# should work by internal docker network
|
||||
# ports:
|
||||
# - "6379:6379"
|
||||
volumes:
|
||||
- redis_data:/data
|
||||
restart: unless-stopped
|
||||
|
||||
volumes:
|
||||
redis_data:
|
||||
17
env.template
Normal file
17
env.template
Normal file
@ -0,0 +1,17 @@
|
||||
PORT=8000
|
||||
CONTAINER_NAME='cc'
|
||||
IMAGE_NAME='ii'
|
||||
TMPFS_SIZE='8192m'
|
||||
CPU_LIMIT='10'
|
||||
MEMORY_LIMIT='8g'
|
||||
DOCKER_IMAGE_SOURCE='node:20.12.1-alpine'
|
||||
#DOCKER_IMAGE_SOURCE="harbor.lesobirzha.ru:8008/library/node:20.12.1-alpine"
|
||||
NODE_ENV='LOCAL'
|
||||
TELEGRAM_TOKEN='secret_token'
|
||||
REDIS_HOST='redis'
|
||||
REDIS_PORT=6379
|
||||
REDIS_PASSWORD='gdgkjdgjapoie29u)(@9ghieihf2)'
|
||||
REDIS_CONTAINER_NAME='streamtreebot-redis'
|
||||
REDIS_IMAGE_SOURCE='redis:alpine'
|
||||
#REDIS_IMAGE_SOURCE='harbor.lesobirzha.ru:8008/library/redis:alpine'
|
||||
DEVCHATID='123456789'
|
||||
31
eslint.config.cjs
Normal file
31
eslint.config.cjs
Normal file
@ -0,0 +1,31 @@
|
||||
// eslint.config.cjs
|
||||
import js from "@eslint/js";
|
||||
import ts from "@typescript-eslint/eslint-plugin";
|
||||
import tsParser from "@typescript-eslint/parser";
|
||||
import globals from "globals";
|
||||
|
||||
module.exports = [
|
||||
js.configs.recommended,
|
||||
{
|
||||
files: ["**/*.ts", "**/*.tsx"],
|
||||
languageOptions: {
|
||||
parser: tsParser,
|
||||
ecmaVersion: "latest",
|
||||
sourceType: "module",
|
||||
globals: {
|
||||
...globals.node,
|
||||
},
|
||||
},
|
||||
plugins: {
|
||||
"@typescript-eslint": ts,
|
||||
},
|
||||
rules: {
|
||||
"no-unused-vars": "off",
|
||||
"@typescript-eslint/no-unused-vars": "error",
|
||||
indent: "off",
|
||||
"linebreak-style": "off",
|
||||
quotes: "off",
|
||||
semi: ["error", "always"],
|
||||
},
|
||||
},
|
||||
];
|
||||
4150
package-lock.json
generated
Normal file
4150
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
32
package.json
Normal file
32
package.json
Normal file
@ -0,0 +1,32 @@
|
||||
{
|
||||
"name": "streamtree",
|
||||
"version": "1.0.0",
|
||||
"type": "commonjs",
|
||||
"description": "",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"build": "tsc",
|
||||
"dev": "ts-node src/index.ts",
|
||||
"start": "node build/index.js",
|
||||
"lint": "npx eslint \"{src,apps,libs,test}/**/*.ts\" --fix"
|
||||
},
|
||||
"author": "Kiril Burlaka",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"@eslint/js": "^9.35.0",
|
||||
"axios": "^1.11.0",
|
||||
"dotenv": "^17.2.2",
|
||||
"globals": "^16.3.0",
|
||||
"node-telegram-bot-api": "^0.66.0",
|
||||
"redis": "^5.8.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^24.3.1",
|
||||
"@types/node-telegram-bot-api": "^0.64.10",
|
||||
"@typescript-eslint/eslint-plugin": "^8.42.0",
|
||||
"@typescript-eslint/parser": "^8.42.0",
|
||||
"eslint": "^9.35.0",
|
||||
"ts-node": "^10.9.2",
|
||||
"typescript": "^5.9.2"
|
||||
}
|
||||
}
|
||||
59
src/config.ts
Normal file
59
src/config.ts
Normal file
@ -0,0 +1,59 @@
|
||||
import * as process from "node:process";
|
||||
import {NODE_ENVS} from "./typesRelated/enums";
|
||||
|
||||
class Config {
|
||||
public readonly botToken: string;
|
||||
|
||||
public readonly redisHost: string;
|
||||
public readonly redisPort: number;
|
||||
public readonly redisPassword: string;
|
||||
|
||||
public readonly nodeEnv: NODE_ENVS;
|
||||
public readonly developersChatId: number;
|
||||
|
||||
constructor() {
|
||||
const {
|
||||
TELEGRAM_TOKEN,
|
||||
REDIS_HOST,
|
||||
REDIS_PORT,
|
||||
REDIS_PASSWORD,
|
||||
NODE_ENV,
|
||||
DEVCHATID,
|
||||
} = process.env;
|
||||
|
||||
|
||||
if (!TELEGRAM_TOKEN) {
|
||||
throw new Error('Config constructor no TELEGRAM_TOKEN')
|
||||
}
|
||||
this.botToken = TELEGRAM_TOKEN;
|
||||
|
||||
if (
|
||||
!REDIS_HOST ||
|
||||
isNaN(+REDIS_PORT) ||
|
||||
!REDIS_PASSWORD
|
||||
) {
|
||||
throw new Error('Config constructor no REDIS_HOST || REDIS_PORT || REDIS_PASSWORD')
|
||||
}
|
||||
this.redisHost = REDIS_HOST
|
||||
this.redisPort = +REDIS_PORT
|
||||
this.redisPassword = REDIS_PASSWORD
|
||||
|
||||
if (
|
||||
!NODE_ENV ||
|
||||
!Object.values(NODE_ENVS).some(
|
||||
el => el === NODE_ENV
|
||||
)
|
||||
) {
|
||||
throw new Error('invalid NODE_ENV');
|
||||
}
|
||||
this.nodeEnv = NODE_ENV as NODE_ENVS;
|
||||
|
||||
if (!DEVCHATID && NODE_ENV !== NODE_ENVS.PROD) {
|
||||
throw new Error('no DEVCHATID')
|
||||
}
|
||||
this.developersChatId = +DEVCHATID;
|
||||
console.log('Config is ready');
|
||||
}
|
||||
}
|
||||
|
||||
export default new Config();
|
||||
16
src/index.ts
Normal file
16
src/index.ts
Normal file
@ -0,0 +1,16 @@
|
||||
import TelegramBot from 'node-telegram-bot-api'
|
||||
import Config from "./config";
|
||||
import BotUtils from "./utils/bot.utils";
|
||||
import RedisUtils from "./utils/redis.utils";
|
||||
|
||||
async function main() {
|
||||
await RedisUtils.init();
|
||||
|
||||
const BOT_TOKEN = Config.botToken;
|
||||
const bot = new TelegramBot(BOT_TOKEN, { polling: true });
|
||||
|
||||
BotUtils.registerPaths(bot);
|
||||
|
||||
console.log('index.js started')
|
||||
}
|
||||
main();
|
||||
5
src/typesRelated/enums.ts
Normal file
5
src/typesRelated/enums.ts
Normal file
@ -0,0 +1,5 @@
|
||||
export enum NODE_ENVS {
|
||||
LOCAL = 'LOCAL',
|
||||
DEV = "DEV",
|
||||
PROD = "PROD",
|
||||
}
|
||||
0
src/typesRelated/types.ts
Normal file
0
src/typesRelated/types.ts
Normal file
80
src/utils/bot.utils.ts
Normal file
80
src/utils/bot.utils.ts
Normal file
@ -0,0 +1,80 @@
|
||||
import Config from "../config.js";
|
||||
import RedisUtils from "./redis.utils";
|
||||
import TelegramBot, { Message } from "node-telegram-bot-api";
|
||||
|
||||
class BotUtils {
|
||||
private bot: TelegramBot;
|
||||
|
||||
registerPaths(bot: TelegramBot) {
|
||||
this.bot = bot;
|
||||
|
||||
// bot.onText(/\/start_stream/, this.startStream.bind(this));
|
||||
// bot.onText(/\/stop_stream/, this.stopStream.bind(this));
|
||||
// bot.onText(/\/status/, this.getStatus.bind(this));
|
||||
//
|
||||
// const botCommands = [
|
||||
// { command: "/start_stream", description: "Start pod" },
|
||||
// { command: "/stop_stream", description: "Stop pod" },
|
||||
// { command: "/status", description: "Session status" }
|
||||
// ];
|
||||
//
|
||||
// if (Config.nodeEnv === NODE_ENVS.LOCAL || Config.nodeEnv === NODE_ENVS.DEV) {
|
||||
// botCommands.push(...this.registerDevPaths(bot));
|
||||
// }
|
||||
// bot.setMyCommands(botCommands);
|
||||
}
|
||||
|
||||
//dev
|
||||
// registerDevPaths(bot: TelegramBot): TelegramBot.BotCommand[] {
|
||||
// bot.onText(
|
||||
// /\/clearsession/,
|
||||
// this.devPathHandle.bind(
|
||||
// this,
|
||||
// this.clearSession.bind(this),
|
||||
// '/clearsession'
|
||||
// )
|
||||
// );
|
||||
// bot.onText(
|
||||
// /\/session/,
|
||||
// this.devPathHandle.bind(
|
||||
// this,
|
||||
// this.getSession.bind(this),
|
||||
// '/session'
|
||||
// )
|
||||
// );
|
||||
// bot.onText(
|
||||
// /\/podinfo/,
|
||||
// this.devPathHandle.bind(
|
||||
// this,
|
||||
// this.getPodInfo.bind(this),
|
||||
// '/podinfo'
|
||||
// )
|
||||
// );
|
||||
// bot.onText(
|
||||
// /\/dockerimage (.+)/,
|
||||
// this.devPathHandle.bind(
|
||||
// this,
|
||||
// this.setDockerImage.bind(this),
|
||||
// '/dockerimage (.+)'
|
||||
// )
|
||||
// );
|
||||
// bot.onText(
|
||||
// /\/listdockerimages/,
|
||||
// this.devPathHandle.bind(
|
||||
// this,
|
||||
// this.listDockerImages.bind(this),
|
||||
// '/listdockerimages'
|
||||
// )
|
||||
// );
|
||||
//
|
||||
// return [
|
||||
// { command: "/clearsession", description: "dev clear session" },
|
||||
// { command: "/session", description: "dev print session" },
|
||||
// { command: "/podinfo", description: "dev pod info" },
|
||||
// { command: "/dockerimage", description: "dev set dockerimage ending" },
|
||||
// { command: "/listdockerimages", description: "dev list some of known values for /dockerimage" },
|
||||
// ];
|
||||
// }
|
||||
}
|
||||
|
||||
export default new BotUtils();
|
||||
2
src/utils/helpers.utils.ts
Normal file
2
src/utils/helpers.utils.ts
Normal file
@ -0,0 +1,2 @@
|
||||
export const waitXSeconds = (s: number): Promise<void> =>
|
||||
new Promise((resolve) => setTimeout(resolve, s * 1000));
|
||||
73
src/utils/redis.utils.ts
Normal file
73
src/utils/redis.utils.ts
Normal file
@ -0,0 +1,73 @@
|
||||
import {createClient, RedisClientType} from 'redis';
|
||||
import Config from "../config";
|
||||
import {UserSession} from "../typesRelated/types";
|
||||
|
||||
class RedisUtils {
|
||||
private readonly client: RedisClientType;
|
||||
private readonly podIdToChatIdKey: string;
|
||||
|
||||
constructor() {
|
||||
this.client = createClient({
|
||||
socket: {
|
||||
host: Config.redisHost,
|
||||
port: Config.redisPort
|
||||
},
|
||||
password: Config.redisPassword
|
||||
});
|
||||
this.client.connect().catch(console.error);
|
||||
this.podIdToChatIdKey = 'pod_id_to_chat_id';
|
||||
}
|
||||
|
||||
async init() {
|
||||
await this.defineVariables();
|
||||
}
|
||||
|
||||
async setValue(key: string, value: any) {
|
||||
if (['string', 'number'].includes(typeof value)) {
|
||||
await this.client.set(key, value);
|
||||
} else {
|
||||
await this.client.set(key, JSON.stringify(value));
|
||||
}
|
||||
}
|
||||
|
||||
async getValue(key: string): Promise<any> {
|
||||
const value = await this.client.get(key);
|
||||
if (typeof value === 'object') {
|
||||
return value;
|
||||
}
|
||||
try {
|
||||
return JSON.parse(value);
|
||||
} catch (e) {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
async deleteValue(key: string) {
|
||||
return this.client.del(key);
|
||||
}
|
||||
|
||||
async defineVariables() {
|
||||
// const pIdToCId = this.getValue(this.podIdToChatIdKey);
|
||||
// if (!pIdToCId || typeof pIdToCId !== 'object') {
|
||||
// await this.setValue(this.podIdToChatIdKey, {});
|
||||
// }
|
||||
}
|
||||
// podIdToChatId(podId: string) {
|
||||
// const pIdToCId = this.getValue(this.podIdToChatIdKey);
|
||||
// return pIdToCId[podId];
|
||||
// }
|
||||
|
||||
saveSession(userId: number, session: UserSession) {
|
||||
return this.setValue(`user:${userId}`, session);
|
||||
}
|
||||
|
||||
getSession(userId: number): Promise<UserSession> {
|
||||
return this.getValue(`user:${userId}`);
|
||||
}
|
||||
|
||||
deleteSession(userId: number) {
|
||||
return this.deleteValue(`user:${userId}`);
|
||||
}
|
||||
}
|
||||
|
||||
export default new RedisUtils();
|
||||
24
tsconfig.json
Normal file
24
tsconfig.json
Normal file
@ -0,0 +1,24 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "es2022",
|
||||
"module": "commonjs",
|
||||
"esModuleInterop": true,
|
||||
"strict": true,
|
||||
"removeComments": true,
|
||||
"rootDir": "src",
|
||||
"outDir": "build",
|
||||
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"resolveJsonModule": true,
|
||||
|
||||
"strictNullChecks": false,
|
||||
"noImplicitAny": false,
|
||||
"strictBindCallApply": false,
|
||||
"forceConsistentCasingInFileNames": false,
|
||||
"noFallthroughCasesInSwitch": false,
|
||||
|
||||
"declaration": true,
|
||||
"incremental": true,
|
||||
"skipLibCheck": true,
|
||||
},
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user