update and fix
This commit is contained in:
parent
eb437e4fbc
commit
d41932c930
|
@ -0,0 +1,9 @@
|
||||||
|
.idea
|
||||||
|
.git
|
||||||
|
node_modules
|
||||||
|
README.md
|
||||||
|
LICENSE
|
||||||
|
.env.example
|
||||||
|
Dockerfile
|
||||||
|
.dockerignore
|
||||||
|
yarn.lock
|
|
@ -5,4 +5,5 @@ ADMIN_ID = 1652565652
|
||||||
# Example ( BOT_CLUSTER_CORE = 2 ) or AutoScale ( BOT_CLUSTER_CORE = 0 )
|
# Example ( BOT_CLUSTER_CORE = 2 ) or AutoScale ( BOT_CLUSTER_CORE = 0 )
|
||||||
BOT_CLUSTER_CORE = 0
|
BOT_CLUSTER_CORE = 0
|
||||||
# Mongoose databse
|
# Mongoose databse
|
||||||
|
#MONGOOSE_URL = 'mongodb+srv://secven:[private]@cluster0.myekb.mongodb.net/secven'
|
||||||
MONGOOSE_URL = 'mongodb://127.0.0.1/secven'
|
MONGOOSE_URL = 'mongodb://127.0.0.1/secven'
|
||||||
|
|
|
@ -0,0 +1,42 @@
|
||||||
|
FROM node:alpine
|
||||||
|
|
||||||
|
RUN mkdir -p /usr/src/app
|
||||||
|
|
||||||
|
WORKDIR /usr/src/app
|
||||||
|
|
||||||
|
RUN echo -e " \n\
|
||||||
|
fs.file-max = 51200 \n\
|
||||||
|
\n\
|
||||||
|
net.core.rmem_max = 67108864 \n\
|
||||||
|
net.core.wmem_max = 67108864 \n\
|
||||||
|
net.ipv4.ip_forward = 1 \n\
|
||||||
|
net.core.netdev_max_backlog = 250000 \n\
|
||||||
|
net.core.somaxconn = 4096 \n\
|
||||||
|
\n\
|
||||||
|
net.ipv4.tcp_syncookies = 1 \n\
|
||||||
|
net.ipv4.tcp_tw_reuse = 1 \n\
|
||||||
|
net.ipv4.tcp_tw_recycle = 0 \n\
|
||||||
|
net.ipv4.tcp_fin_timeout = 30 \n\
|
||||||
|
net.ipv4.tcp_keepalive_time = 1200 \n\
|
||||||
|
net.ipv4.tcp_max_syn_backlog = 8192 \n\
|
||||||
|
net.ipv4.tcp_max_tw_buckets = 5000 \n\
|
||||||
|
net.ipv4.tcp_fastopen = 3 \n\
|
||||||
|
net.ipv4.tcp_mem = 25600 51200 102400 \n\
|
||||||
|
net.ipv4.tcp_rmem = 4096 87380 67108864 \n\
|
||||||
|
net.ipv4.tcp_wmem = 4096 65536 67108864 \n\
|
||||||
|
net.ipv4.tcp_mtu_probing = 1 \n\
|
||||||
|
net.ipv4.tcp_congestion_control = hybla \n\
|
||||||
|
# for low-latency network, use cubic instead \n\
|
||||||
|
# net.ipv4.tcp_congestion_control = cubic \n\
|
||||||
|
" | sed -e 's/^\s\+//g' | tee -a /etc/sysctl.conf && \
|
||||||
|
mkdir -p /etc/security && \
|
||||||
|
echo -e " \n\
|
||||||
|
* soft nofile 51200 \n\
|
||||||
|
* hard nofile 51200 \n\
|
||||||
|
" | sed -e 's/^\s\+//g' | tee -a /etc/security/limits.conf
|
||||||
|
|
||||||
|
COPY . .
|
||||||
|
|
||||||
|
RUN yarn install
|
||||||
|
|
||||||
|
CMD ["yarn", "prod"]
|
16
README.md
16
README.md
|
@ -1,6 +1,8 @@
|
||||||
# Telegraf Bot Pattern
|
## Telegraf Bot Pattern
|
||||||
|
|
||||||
### Change .env.example to .env
|
![example](https://i.imgur.com/5lbe3pl.png)
|
||||||
|
|
||||||
|
#### Change .env.example to .env
|
||||||
|
|
||||||
> Started init: `yarn install`
|
> Started init: `yarn install`
|
||||||
|
|
||||||
|
@ -8,12 +10,6 @@
|
||||||
|
|
||||||
> Mode Cluster: `yarn prod` - In Production bot
|
> Mode Cluster: `yarn prod` - In Production bot
|
||||||
|
|
||||||
|
> Mode Production PM2: `yarn pm:prod` `yarn pm:stop` `yarn pm:up` // pm2 startup
|
||||||
|
|
||||||
> Example `.env` ( BOT_CLUSTER_CORE = 2 ) or AutoScale ( BOT_CLUSTER_CORE = 0 )
|
> Example `.env` ( BOT_CLUSTER_CORE = 2 ) or AutoScale ( BOT_CLUSTER_CORE = 0 )
|
||||||
|
|
||||||
## Todo
|
|
||||||
|
|
||||||
* _Mongoose_ [Soon]
|
|
||||||
|
|
||||||
* _Encryption Mongoose_ [Soon]
|
|
||||||
|
|
||||||
* _Webhook Cluster_ [Soon]
|
|
||||||
|
|
|
@ -0,0 +1,14 @@
|
||||||
|
module.exports = {
|
||||||
|
apps: [
|
||||||
|
{
|
||||||
|
name: 'APP-BOT',
|
||||||
|
script: './index.js',
|
||||||
|
max_memory_restart: '1000M',
|
||||||
|
wait_ready: true,
|
||||||
|
restart_delay: 5000,
|
||||||
|
env_production: {
|
||||||
|
NODE_ENV: 'production'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
|
@ -7,7 +7,10 @@
|
||||||
"private": false,
|
"private": false,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "cross-env NODE_ENV=development nodemon index.js",
|
"dev": "cross-env NODE_ENV=development nodemon index.js",
|
||||||
"prod": "cross-env NODE_ENV=production node index.js"
|
"prod": "cross-env NODE_ENV=production node index.js",
|
||||||
|
"pm:stop": "cross-env NODE_ENV=production pm2 stop ecosystem.config.js",
|
||||||
|
"pm:prod": "cross-env NODE_ENV=production pm2 start ecosystem.config.js",
|
||||||
|
"pm:up": "pm2 startup"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"chalk": "^4.1.1",
|
"chalk": "^4.1.1",
|
||||||
|
@ -16,7 +19,7 @@
|
||||||
"mongoose": "^5.13.1",
|
"mongoose": "^5.13.1",
|
||||||
"ms": "^2.1.3",
|
"ms": "^2.1.3",
|
||||||
"telegraf": "git+https://notabug.org/Secven/telegraf",
|
"telegraf": "git+https://notabug.org/Secven/telegraf",
|
||||||
"telegraf-i18n": "^6.6.0",
|
"telegraf-i18n": "git+https://notabug.org/Secven/telegraf-i18n.git",
|
||||||
"telegraf-middleware-console-time": "^2.1.0"
|
"telegraf-middleware-console-time": "^2.1.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
|
10
src/app.js
10
src/app.js
|
@ -39,7 +39,7 @@ class App {
|
||||||
`****** Creating instances [ ${cS.green.bold(setWorker)} ] ******`
|
`****** Creating instances [ ${cS.green.bold(setWorker)} ] ******`
|
||||||
)
|
)
|
||||||
for (let i = 0; i < setWorker; i += 1) {
|
for (let i = 0; i < setWorker; i += 1) {
|
||||||
cluster.schedulingPolicy = cluster.SCHED_RR
|
cluster.schedulingPolicy = cluster.SCHED_NONE
|
||||||
const worker = cluster.fork()
|
const worker = cluster.fork()
|
||||||
_workers.push(worker)
|
_workers.push(worker)
|
||||||
}
|
}
|
||||||
|
@ -49,9 +49,7 @@ class App {
|
||||||
console.log(cS.red.bold(`Worker ${worker.process.pid} died`))
|
console.log(cS.red.bold(`Worker ${worker.process.pid} died`))
|
||||||
})
|
})
|
||||||
|
|
||||||
process.on('uncaughtException', err => {
|
console.log(cS.green.bold('****** Mode: Started to Production ******'))
|
||||||
console.error('Cluster crashed:', err.message)
|
|
||||||
})
|
|
||||||
} else {
|
} else {
|
||||||
console.info(cS.blue(`[ Worker to ${process.pid} started ]`))
|
console.info(cS.blue(`[ Worker to ${process.pid} started ]`))
|
||||||
process.on('message', update => {
|
process.on('message', update => {
|
||||||
|
@ -61,9 +59,7 @@ class App {
|
||||||
|
|
||||||
if (cluster.isMaster) {
|
if (cluster.isMaster) {
|
||||||
initTelegraf.use(ctx => handlerUpdater(ctx))
|
initTelegraf.use(ctx => handlerUpdater(ctx))
|
||||||
const starter = initTelegraf.launch().then(() => {
|
const starter = initTelegraf.launch()
|
||||||
console.log(cS.green.bold('****** Mode: Started to Production ******'))
|
|
||||||
})
|
|
||||||
this.killGetUpdates(starter).catch(err => console.error(err.message))
|
this.killGetUpdates(starter).catch(err => console.error(err.message))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -10,15 +10,18 @@ config.isDev = process.env.NODE_ENV === 'development'
|
||||||
|
|
||||||
config.consignOpts = {
|
config.consignOpts = {
|
||||||
cwd: process.cwd() + '/src',
|
cwd: process.cwd() + '/src',
|
||||||
extensions: ['.js', '.json'],
|
extensions: ['.js'],
|
||||||
verbose: false
|
verbose: false
|
||||||
}
|
}
|
||||||
|
|
||||||
config.limitSecurity = {
|
config.limitSecurity = {
|
||||||
window: ms('15s'),
|
window: ms('15s'),
|
||||||
limit: 6,
|
limit: 6,
|
||||||
onLimitExceeded: async ctx =>
|
onLimitExceeded: ({ message, i18n, replyWithHTML }) =>
|
||||||
await ctx.replyWithHTML('<b>❗️Please wait a second (5s)</b>')
|
replyWithHTML(i18n.t('default.rateLimit'), {
|
||||||
|
reply_to_message_id: message.message_id,
|
||||||
|
disable_notification: true
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
config.i18nOpts = {
|
config.i18nOpts = {
|
||||||
|
@ -27,6 +30,21 @@ config.i18nOpts = {
|
||||||
defaultLanguageOnMissing: true
|
defaultLanguageOnMissing: true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
config.settings = {
|
||||||
|
setCommand: async ctx => {
|
||||||
|
ctx.setMyCommands([
|
||||||
|
{
|
||||||
|
command: 'start',
|
||||||
|
description: 'Start bot or restart'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
command: 'lang',
|
||||||
|
description: 'Change language'
|
||||||
|
}
|
||||||
|
])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
config,
|
config,
|
||||||
readdirSync,
|
readdirSync,
|
||||||
|
|
|
@ -1,16 +1,12 @@
|
||||||
const consign = require('consign')
|
const { Telegraf, Router, Markup, Extra, session } = require('telegraf')
|
||||||
const I18n = require('telegraf-i18n')
|
const { I18n } = require('telegraf-i18n')
|
||||||
const Telegraf = require('telegraf')
|
|
||||||
const Router = require('telegraf/router')
|
|
||||||
const Markup = require('telegraf/markup')
|
|
||||||
const Extra = require('telegraf/extra')
|
|
||||||
const session = require('telegraf/session')
|
|
||||||
const { db } = require('../db/db')
|
const { db } = require('../db/db')
|
||||||
const { config } = require('./config')
|
const { config } = require('./config')
|
||||||
const { rateLimit, reportErr, updaterUser } = require('../middle')
|
const { rateLimit, reportErr, updaterUser } = require('../middle')
|
||||||
const {
|
const {
|
||||||
generateUpdateMiddleware: cliUpdateTime
|
generateUpdateMiddleware: cliUpdateTime
|
||||||
} = require('telegraf-middleware-console-time')
|
} = require('telegraf-middleware-console-time')
|
||||||
|
const consign = require('consign')
|
||||||
|
|
||||||
const bot = new Telegraf(process.env.BOT_TOKEN, {
|
const bot = new Telegraf(process.env.BOT_TOKEN, {
|
||||||
handlerTimeout: 1
|
handlerTimeout: 1
|
||||||
|
@ -30,6 +26,7 @@ const Route = new Router(({ callbackQuery }) => {
|
||||||
;(async () => {
|
;(async () => {
|
||||||
if (config.isDev) {
|
if (config.isDev) {
|
||||||
console.log(await bot.telegram.getMe())
|
console.log(await bot.telegram.getMe())
|
||||||
|
await bot.use(config.settings.setCommand)
|
||||||
// Dev handlers
|
// Dev handlers
|
||||||
bot.use(cliUpdateTime())
|
bot.use(cliUpdateTime())
|
||||||
bot.use(Telegraf.log())
|
bot.use(Telegraf.log())
|
||||||
|
@ -45,11 +42,23 @@ bot.use(rateLimit(config.limitSecurity))
|
||||||
bot.use(session())
|
bot.use(session())
|
||||||
bot.use(new I18n(config.i18nOpts))
|
bot.use(new I18n(config.i18nOpts))
|
||||||
bot.use(updaterUser)
|
bot.use(updaterUser)
|
||||||
|
|
||||||
bot.use(reportErr)
|
bot.use(reportErr)
|
||||||
|
|
||||||
bot.on('callback_query', Route)
|
bot.on('callback_query', Route)
|
||||||
|
|
||||||
consign(config.consignOpts).include('handlers').then('route').into(bot)
|
consign(config.consignOpts).include('handlers').then('route').into(bot)
|
||||||
|
|
||||||
|
bot.use(({ reply, i18n, message }) =>
|
||||||
|
reply(
|
||||||
|
i18n.t('default.noAnswer'),
|
||||||
|
Extra.HTML()
|
||||||
|
.inReplyTo(message.message_id)
|
||||||
|
.markup(m =>
|
||||||
|
m.inlineKeyboard([
|
||||||
|
m.callbackButton(i18n.t('default.closeMessage'), 'delete')
|
||||||
|
])
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
module.exports = { bot }
|
module.exports = { bot }
|
||||||
|
|
10
src/db/db.js
10
src/db/db.js
|
@ -8,4 +8,14 @@ Object.keys(__MODELS__).forEach(model => {
|
||||||
return (db[model.toString()] = __MODELS__[model.toString()])
|
return (db[model.toString()] = __MODELS__[model.toString()])
|
||||||
})
|
})
|
||||||
|
|
||||||
|
/*
|
||||||
|
readdirSync(path.resolve(__dirname, MODEL_PATTERN)).forEach(model => {
|
||||||
|
const setModels = model.split('.')[0].toString().trim().match(DEFAULT_CHECK)
|
||||||
|
if (!setModels)
|
||||||
|
throw new Error(`Error: secure loaded ${MODEL_PATTERN}${model.trim()}`)
|
||||||
|
// eslint-disable-next-line security/detect-non-literal-require
|
||||||
|
db[setModels.toString()] = require(MODEL_PATTERN + setModels)
|
||||||
|
})
|
||||||
|
*/
|
||||||
|
|
||||||
module.exports = { db }
|
module.exports = { db }
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
const mongoose = require('mongoose')
|
const mongoose = require('mongoose')
|
||||||
|
const { cS } = require('../config/config')
|
||||||
|
|
||||||
class Mongoose {
|
class Mongoose {
|
||||||
async mongooseConnect() {
|
async mongooseConnect() {
|
||||||
|
@ -12,7 +13,13 @@ class Mongoose {
|
||||||
connectTimeoutMS: 30000
|
connectTimeoutMS: 30000
|
||||||
})
|
})
|
||||||
|
|
||||||
.catch(err => console.error(err.stack))
|
.catch(err => {
|
||||||
|
console.error(err.stack)
|
||||||
|
console.error(
|
||||||
|
cS.red.bold(`Mongoose error: ${process.env.MONGOOSE_URL}`)
|
||||||
|
)
|
||||||
|
process.exit(0)
|
||||||
|
})
|
||||||
|
|
||||||
process.on('SIGINT', () => {
|
process.on('SIGINT', () => {
|
||||||
mongoose.connection.close(() => {
|
mongoose.connection.close(() => {
|
||||||
|
|
|
@ -19,10 +19,12 @@ stackError:
|
||||||
default:
|
default:
|
||||||
lang: |
|
lang: |
|
||||||
🤖 Language settings
|
🤖 Language settings
|
||||||
├ 🇺🇸 English
|
├ 🇺🇸 English (en)
|
||||||
└ 🇷🇺 Русский
|
└ 🇷🇺 Русский (ru)
|
||||||
|
|
||||||
langDone: |
|
langDone: |
|
||||||
✅ Language changed successfully (${value})
|
✅ Language changed successfully (${value})
|
||||||
|
noAnswer: 🤨 I do not understand you
|
||||||
|
closeMessage: ❌ Close
|
||||||
|
rateLimit: <b>❗️Please wait a second (5s)</b>
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -19,8 +19,10 @@ stackError:
|
||||||
default:
|
default:
|
||||||
lang: |
|
lang: |
|
||||||
🤖 Языковые настройки
|
🤖 Языковые настройки
|
||||||
├ 🇺🇸 English
|
├ 🇺🇸 English (en)
|
||||||
└ 🇷🇺 Русский
|
└ 🇷🇺 Русский (ru)
|
||||||
|
|
||||||
langDone: |
|
langDone: |
|
||||||
✅ Язык успешно изменен (${value})
|
✅ Язык успешно изменен (${value})
|
||||||
|
noAnswer: 🤨 Я не понимаю тебя
|
||||||
|
closeMessage: ❌ Закрыть
|
||||||
|
rateLimit: <b>❗️Пожалуйста, подождите секунд (5s)</b>
|
||||||
|
|
|
@ -1,29 +1,29 @@
|
||||||
const { escapeHtml } = require('../util/escapeHTML')
|
const { escapeHtml } = require('../util/escapeHTML')
|
||||||
|
|
||||||
module.exports = async (ctx, next) => {
|
module.exports = async ({ db, from, i18n, session }, next) => {
|
||||||
if (!ctx.from.id) return await next()
|
|
||||||
// eslint-disable-next-line camelcase
|
// eslint-disable-next-line camelcase
|
||||||
const { id, first_name, username, language_code } = ctx.from
|
const { id, first_name, username, language_code } = from
|
||||||
await ctx.db.User.findOne({ user_id: ctx.from.id })
|
if (!id) return await next()
|
||||||
|
await db.User.findOne({ user_id: id })
|
||||||
.then(root => {
|
.then(root => {
|
||||||
if (!root) {
|
if (!root) {
|
||||||
root = new ctx.db.User()
|
root = new db.User()
|
||||||
root.user_id = id
|
root.user_id = id
|
||||||
// eslint-disable-next-line camelcase
|
// eslint-disable-next-line camelcase
|
||||||
root.first_name = escapeHtml(first_name) ?? 'Unknown'
|
root.first_name = escapeHtml(first_name) ?? 'Unknown'
|
||||||
root.username = username ?? 'Unknown'
|
root.username = username ?? 'Unknown'
|
||||||
}
|
}
|
||||||
// eslint-disable-next-line camelcase
|
// eslint-disable-next-line camelcase
|
||||||
root.first_name = first_name
|
root.first_name = escapeHtml(first_name) ?? 'Unknown'
|
||||||
root.username = username
|
root.username = username
|
||||||
// eslint-disable-next-line camelcase
|
// eslint-disable-next-line camelcase
|
||||||
root.language_code = language_code
|
root.language_code = language_code
|
||||||
ctx.session.root = root
|
session.root = root
|
||||||
ctx.i18n.locale(ctx.session.root.settings.lang)
|
i18n.locale(session.root.settings.lang)
|
||||||
})
|
})
|
||||||
.finally(() => {
|
.finally(() => {
|
||||||
return next().then(async () => {
|
return next().then(async () => {
|
||||||
await ctx.session.root.save()
|
await session.root.save()
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
.catch(err => console.log(err.message))
|
.catch(err => console.log(err.message))
|
||||||
|
|
|
@ -0,0 +1,5 @@
|
||||||
|
const DEFAULT_CHECK = /^[a-zA-Z0-9.\-_]{3,20}$/gi
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
DEFAULT_CHECK
|
||||||
|
}
|
Loading…
Reference in New Issue