mirror of
https://github.com/sadfsdfdsa/allbot.git
synced 2024-11-19 00:31:42 +02:00
feat(refactoring): big refactoring for bot commands
This commit is contained in:
parent
353b03df56
commit
fc98bf3c06
7
.prettierrc
Normal file
7
.prettierrc
Normal file
@ -0,0 +1,7 @@
|
||||
{
|
||||
"tabWidth": 2,
|
||||
"useTabs": false,
|
||||
"jsxSingleQuote": true,
|
||||
"semi": false,
|
||||
"singleQuote": true
|
||||
}
|
249
core/bot.ts
249
core/bot.ts
@ -1,30 +1,15 @@
|
||||
import { Context, Telegraf } from 'telegraf'
|
||||
import { Telegraf } from 'telegraf'
|
||||
import { UserRepository } from './repository.js'
|
||||
import { MetricsService } from './metrics.js'
|
||||
import { message } from 'telegraf/filters'
|
||||
import { Chat, Message, User } from 'telegraf/types'
|
||||
import { Chat, User } from 'telegraf/types'
|
||||
import { isChatGroup } from './utils/utils.js'
|
||||
|
||||
type HandleMessagePayload = {
|
||||
chatId: Chat['id']
|
||||
text: string
|
||||
from: NonNullable<Message['from']>
|
||||
messageId: Message['message_id']
|
||||
}
|
||||
|
||||
export class Bot {
|
||||
private bot: Telegraf
|
||||
|
||||
private readonly MENTION_COMMANDS = ['@all', '/all']
|
||||
|
||||
private readonly FEEDBACK_COMMAND = '/feedback'
|
||||
|
||||
private readonly CODE_COMMAND = '/code'
|
||||
|
||||
private readonly SUPPORT_PAY_COMMAND = '/support'
|
||||
|
||||
private readonly PRIVACY_COMMAND = '/privacy'
|
||||
|
||||
private readonly ADMIN_ID: number | undefined
|
||||
|
||||
private isListening = false
|
||||
@ -44,22 +29,36 @@ export class Bot {
|
||||
|
||||
this.bot = new Telegraf(token)
|
||||
|
||||
this.bot.on(message('text'), async (ctx) => {
|
||||
const {
|
||||
message: { from, text, message_id },
|
||||
chat: { id },
|
||||
} = ctx
|
||||
this.bot.telegram.setMyCommands([
|
||||
{
|
||||
command: 'all',
|
||||
description: 'Mention all users in a group',
|
||||
},
|
||||
{
|
||||
command: 'donate',
|
||||
description: 'Get crypto payments links for supporting the project',
|
||||
},
|
||||
{
|
||||
command: 'feedback',
|
||||
description: 'Send feedback or bug reports (English please)',
|
||||
},
|
||||
{
|
||||
command: 'code',
|
||||
description: 'Get link for bot opensource code',
|
||||
},
|
||||
{
|
||||
command: 'privacy',
|
||||
description: 'How the Bot takes care of your personal data',
|
||||
},
|
||||
])
|
||||
|
||||
await this.handleMessage(
|
||||
{
|
||||
from,
|
||||
text,
|
||||
messageId: message_id,
|
||||
chatId: id,
|
||||
},
|
||||
(...args: Parameters<Context['sendMessage']>) => ctx.reply(...args),
|
||||
)
|
||||
})
|
||||
this.registerDonateCommand()
|
||||
this.registerFeedbackCommand()
|
||||
this.registerPrivacyCommand()
|
||||
this.registerCodeCommand()
|
||||
|
||||
// Should be last for not overriding commands below
|
||||
this.registerHandleMessage()
|
||||
|
||||
this.bot.on(message('new_chat_members'), ({ message, chat }) =>
|
||||
this.handleAddMembers(chat.id, message.new_chat_members)
|
||||
@ -83,31 +82,8 @@ export class Bot {
|
||||
this.bot.launch()
|
||||
}
|
||||
|
||||
private async handleMessage(
|
||||
{ from, text, messageId, chatId }: HandleMessagePayload,
|
||||
reply: Context['reply'],
|
||||
): Promise<void> {
|
||||
if (text.startsWith(this.PRIVACY_COMMAND)) {
|
||||
console.log('Send privacy policy')
|
||||
|
||||
const message = `
|
||||
Are you concerned about your security and personal data? This is right!
|
||||
What do we use? Identifiers of your groups to store data about participants in them: usernames and identifiers to correctly call all users of the group.
|
||||
All data is transmitted only via encrypted channels and is not used for other purposes.
|
||||
We don't read your messages, don't log data about you in public systems and 3th party services except safe hosting and database.
|
||||
You can view the project's codebase using ${this.CODE_COMMAND}.
|
||||
Be careful when using unfamiliar bots in your communication, it can be dangerous!
|
||||
`
|
||||
|
||||
reply(message, {
|
||||
reply_to_message_id: messageId,
|
||||
parse_mode: 'HTML'
|
||||
})
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
if (text.startsWith(this.SUPPORT_PAY_COMMAND)) {
|
||||
private registerDonateCommand(): void {
|
||||
this.bot.command('donate', (ctx) => {
|
||||
console.log('Send payments info')
|
||||
|
||||
const message = `
|
||||
@ -118,82 +94,141 @@ Support via USDT-TRX: <code>TJyEa6p3HvAHz34gn7hZHYNwY65iHryu3w</code>
|
||||
Support via USDT-ETH: <code>0x7f49e01c13fE782aEB06Dc35a37d357b955b67B0</code>
|
||||
Support via BTC: <code>bc1qgmq6033fnte2ata8ku3zgvj0n302zvr9cexcng</code>
|
||||
Thank you for using and help!
|
||||
Note, than you can send ${this.FEEDBACK_COMMAND} with features or problems.
|
||||
Note, than you can send /feedback with features or problems.
|
||||
`
|
||||
|
||||
reply(message, {
|
||||
reply_to_message_id: messageId,
|
||||
parse_mode: 'HTML'
|
||||
this.metricsService.updateLatestPaymentsCall(`${ctx.chat.id}`)
|
||||
|
||||
ctx.reply(message, {
|
||||
reply_to_message_id: ctx.message.message_id,
|
||||
parse_mode: 'HTML',
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
private registerFeedbackCommand(): void {
|
||||
this.bot.command('feedback', (ctx) => {
|
||||
const {
|
||||
message: { from, text, message_id: messageId },
|
||||
chat: { id: chatId },
|
||||
} = ctx
|
||||
|
||||
if (text.startsWith(this.CODE_COMMAND)) {
|
||||
console.log('Send code info')
|
||||
console.log(ctx.message)
|
||||
|
||||
reply(`I am an opensource project, feel free to reuse code or make bot better via /feedback.\nGithub link: https://github.com/sadfsdfdsa/allbot`, {
|
||||
reply_to_message_id: messageId,
|
||||
})
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
if (text.startsWith(this.FEEDBACK_COMMAND)) {
|
||||
const feedback = text.split(this.FEEDBACK_COMMAND)[1] || undefined
|
||||
const feedback = text.split('/feedback')[1] || undefined
|
||||
if (!feedback) {
|
||||
console.log(`Receive empty feedback from user ${from.username} in ${chatId}: ${feedback}`)
|
||||
console.log(
|
||||
`Receive empty feedback from user ${from.username} in ${chatId}: ${feedback}`
|
||||
)
|
||||
|
||||
reply(`Add something in your feedback as feature or bug report`, {
|
||||
ctx.reply(`Add something in your feedback as feature or bug report`, {
|
||||
reply_to_message_id: messageId,
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
console.log(`Receive feedback from user ${from.username} in ${chatId}: ${feedback}`)
|
||||
console.log(
|
||||
`Receive feedback from user ${from.username} in ${chatId}: ${feedback}`
|
||||
)
|
||||
|
||||
reply(`Your review has been successfully registered, we will contact you, thank you!`, {
|
||||
reply_to_message_id: messageId,
|
||||
})
|
||||
ctx.reply(
|
||||
`Your review has been successfully registered, we will contact you, thank you!`,
|
||||
{
|
||||
reply_to_message_id: messageId,
|
||||
}
|
||||
)
|
||||
|
||||
if (!this.ADMIN_ID) return
|
||||
|
||||
this.bot.telegram.sendMessage(this.ADMIN_ID, `There is a new feedback from @${from.username} in chat group ${chatId}:\n${feedback}`)
|
||||
return
|
||||
}
|
||||
this.bot.telegram.sendMessage(
|
||||
this.ADMIN_ID,
|
||||
`There is a new feedback from @${from.username} in chat group ${chatId}:\n${feedback}`
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
if (!isChatGroup(chatId)) {
|
||||
console.log('Direct message from', from.username)
|
||||
private registerPrivacyCommand(): void {
|
||||
this.bot.command('privacy', (ctx) => {
|
||||
console.log('Send privacy policy')
|
||||
|
||||
reply(`Add me to your group, here is example @all mention for you:`)
|
||||
const message = `
|
||||
Are you concerned about your security and personal data? This is right!
|
||||
What do we use? Identifiers of your groups to store data about participants in them: usernames and identifiers to correctly call all users of the group.
|
||||
All data is transmitted only via encrypted channels and is not used for other purposes.
|
||||
We don't read your messages, don't log data about you in public systems and 3th party services except safe hosting and database.
|
||||
You can view the project's codebase using /code.
|
||||
Be careful when using unfamiliar bots in your communication, it can be dangerous!
|
||||
`
|
||||
|
||||
reply(`All from ${from.username}: @${from.username}`, {
|
||||
ctx.reply(message, {
|
||||
reply_to_message_id: ctx.message.message_id,
|
||||
parse_mode: 'HTML',
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
private registerCodeCommand(): void {
|
||||
this.bot.command('privacy', (ctx) => {
|
||||
console.log('Send code info')
|
||||
|
||||
ctx.reply(
|
||||
`I am an opensource project, feel free to reuse code or make bot better via /feedback.\nGithub link: https://github.com/sadfsdfdsa/allbot`,
|
||||
{
|
||||
reply_to_message_id: ctx.message.message_id,
|
||||
}
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
private registerHandleMessage(): void {
|
||||
this.bot.on(message('text'), async (ctx) => {
|
||||
const {
|
||||
message: { from, text, message_id: messageId },
|
||||
chat: { id: chatId },
|
||||
} = ctx
|
||||
|
||||
if (!isChatGroup(chatId)) {
|
||||
console.log('Direct message from', from.username)
|
||||
|
||||
ctx.reply(`Add me to your group, here is example @all mention for you:`)
|
||||
|
||||
ctx.reply(`All from ${from.username}: @${from.username}`, {
|
||||
reply_to_message_id: messageId,
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
await this.userRepository.addUsers(chatId, [from])
|
||||
|
||||
const isCallAll = this.MENTION_COMMANDS.some((command) =>
|
||||
text.includes(command)
|
||||
)
|
||||
if (!isCallAll) return
|
||||
|
||||
console.log(`Mention with pattern in group`, chatId)
|
||||
|
||||
const chatUsernames = await this.userRepository.getUsernamesByChatId(
|
||||
chatId
|
||||
)
|
||||
if (!Object.values(chatUsernames).length) return
|
||||
|
||||
const str = Object.values(chatUsernames).map(
|
||||
(username) => `@${username} `
|
||||
)
|
||||
|
||||
this.metricsService.addReply()
|
||||
|
||||
ctx.reply(`All from ${from.username}: ${str}`, {
|
||||
reply_to_message_id: messageId,
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
await this.userRepository.addUsers(chatId, [from])
|
||||
|
||||
const isCallAll = this.MENTION_COMMANDS.some((command) => text.includes(command))
|
||||
if (!isCallAll) return
|
||||
|
||||
console.log(`Mention with pattern in group`, chatId)
|
||||
|
||||
const chatUsernames = await this.userRepository.getUsernamesByChatId(chatId)
|
||||
if (!Object.values(chatUsernames).length) return
|
||||
|
||||
const str = Object.values(chatUsernames).map((username) => `@${username} `)
|
||||
|
||||
this.metricsService.addReply()
|
||||
|
||||
reply(`All from ${from.username}: ${str}`, {
|
||||
reply_to_message_id: messageId,
|
||||
})
|
||||
}
|
||||
|
||||
private handleAddMembers(chatId: Chat['id'], users: User[]): Promise<void> {
|
||||
console.log('Try add new members', users.map(item => item.username))
|
||||
console.log(
|
||||
'Try add new members',
|
||||
users.map((item) => item.username)
|
||||
)
|
||||
return this.userRepository.addUsers(chatId, users)
|
||||
}
|
||||
|
||||
|
@ -3,47 +3,47 @@ import { CacheService } from './cache'
|
||||
describe('CacheService', () => {
|
||||
describe('#addToCache', () => {
|
||||
test('should correct add unique values', async () => {
|
||||
const instance = new CacheService(10)
|
||||
const instance = new CacheService(10)
|
||||
|
||||
const first = instance.addToCache(['test'])
|
||||
const second = instance.addToCache(['test'])
|
||||
const first = instance.addToCache(['test'])
|
||||
const second = instance.addToCache(['test'])
|
||||
|
||||
expect(first).toBe(1)
|
||||
expect(second).toBe(1)
|
||||
expect(first).toBe(1)
|
||||
expect(second).toBe(1)
|
||||
})
|
||||
})
|
||||
|
||||
describe('#isInCache', () => {
|
||||
test('should correct check is in cache', async () => {
|
||||
const instance = new CacheService(10)
|
||||
const instance = new CacheService(10)
|
||||
|
||||
instance.addToCache(['test', 'test2', 'test'])
|
||||
instance.addToCache(['test', 'test2', 'test'])
|
||||
|
||||
const res1 = instance.isInCache('test')
|
||||
const res2 = instance.isInCache('test2')
|
||||
const res3 = instance.isInCache('test3')
|
||||
const res1 = instance.isInCache('test')
|
||||
const res2 = instance.isInCache('test2')
|
||||
const res3 = instance.isInCache('test3')
|
||||
|
||||
expect(res1).toBe(true)
|
||||
expect(res2).toBe(true)
|
||||
expect(res3).toBe(false)
|
||||
expect(res1).toBe(true)
|
||||
expect(res2).toBe(true)
|
||||
expect(res3).toBe(false)
|
||||
})
|
||||
})
|
||||
|
||||
describe('#tryClearCache', () => {
|
||||
test('should remove element from the start of the cached array', async () => {
|
||||
const instance = new CacheService(2)
|
||||
const instance = new CacheService(2)
|
||||
|
||||
instance.addToCache(['test', 'test2', 'test3'])
|
||||
instance.addToCache(['test', 'test2', 'test3'])
|
||||
|
||||
instance.tryClearCache()
|
||||
instance.tryClearCache()
|
||||
|
||||
const res1 = instance.isInCache('test')
|
||||
const res2 = instance.isInCache('test2')
|
||||
const res3 = instance.isInCache('test3')
|
||||
const res1 = instance.isInCache('test')
|
||||
const res2 = instance.isInCache('test2')
|
||||
const res3 = instance.isInCache('test3')
|
||||
|
||||
expect(res1).toBe(false)
|
||||
expect(res2).toBe(true)
|
||||
expect(res3).toBe(true)
|
||||
expect(res1).toBe(false)
|
||||
expect(res2).toBe(true)
|
||||
expect(res3).toBe(true)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
@ -1,38 +1,36 @@
|
||||
import { User } from "telegraf/types";
|
||||
import { User } from 'telegraf/types'
|
||||
|
||||
type EnsuredUsername = NonNullable<User['username']>
|
||||
|
||||
export class CacheService {
|
||||
private readonly cachedUsernames = new Array<EnsuredUsername>()
|
||||
private readonly cachedUsernames = new Array<EnsuredUsername>()
|
||||
|
||||
constructor(
|
||||
private readonly MAX_CACHE_SIZE = 1000
|
||||
) {
|
||||
console.log('Init Cache service')
|
||||
}
|
||||
constructor(private readonly MAX_CACHE_SIZE = 1000) {
|
||||
console.log('Init Cache service')
|
||||
}
|
||||
|
||||
public isInCache(username: EnsuredUsername): boolean {
|
||||
return this.cachedUsernames.includes(username)
|
||||
}
|
||||
public isInCache(username: EnsuredUsername): boolean {
|
||||
return this.cachedUsernames.includes(username)
|
||||
}
|
||||
|
||||
public addToCache(usernames: EnsuredUsername[]): number {
|
||||
usernames.forEach(this.addToCacheSingle.bind(this))
|
||||
public addToCache(usernames: EnsuredUsername[]): number {
|
||||
usernames.forEach(this.addToCacheSingle.bind(this))
|
||||
|
||||
return this.cachedUsernames.length
|
||||
}
|
||||
return this.cachedUsernames.length
|
||||
}
|
||||
|
||||
public tryClearCache(): void {
|
||||
if (this.cachedUsernames.length <= this.MAX_CACHE_SIZE) return
|
||||
public tryClearCache(): void {
|
||||
if (this.cachedUsernames.length <= this.MAX_CACHE_SIZE) return
|
||||
|
||||
const needToRemove = this.cachedUsernames.length - this.MAX_CACHE_SIZE
|
||||
const removed = this.cachedUsernames.splice(0, needToRemove)
|
||||
console.log('Remove users from cache', needToRemove, removed)
|
||||
}
|
||||
const needToRemove = this.cachedUsernames.length - this.MAX_CACHE_SIZE
|
||||
const removed = this.cachedUsernames.splice(0, needToRemove)
|
||||
console.log('Remove users from cache', needToRemove, removed)
|
||||
}
|
||||
|
||||
private addToCacheSingle(username: EnsuredUsername): void {
|
||||
if (this.isInCache(username)) return
|
||||
private addToCacheSingle(username: EnsuredUsername): void {
|
||||
if (this.isInCache(username)) return
|
||||
|
||||
console.log('Add to cache', username, this.cachedUsernames.length)
|
||||
this.cachedUsernames.push(username)
|
||||
}
|
||||
}
|
||||
console.log('Add to cache', username, this.cachedUsernames.length)
|
||||
this.cachedUsernames.push(username)
|
||||
}
|
||||
}
|
||||
|
@ -3,6 +3,7 @@ import { RedisClientType } from 'redis'
|
||||
|
||||
const KEY_FOR_TIMESTAMP = 'TIMESTAMP'
|
||||
const KEY_FOR_COUNTER = 'COUNTER'
|
||||
const KEY_FOR_PAYMENTS = 'PAYMENTS'
|
||||
|
||||
export class MetricsService {
|
||||
private readonly registry: Registry
|
||||
@ -32,13 +33,19 @@ export class MetricsService {
|
||||
public updateLatestUsage(key: string): void {
|
||||
const date = new Date()
|
||||
|
||||
this.db.hSet(KEY_FOR_TIMESTAMP, {
|
||||
[key]: date.toLocaleString('ru-RU', {timeZone: 'Asia/Yekaterinburg'})
|
||||
}).catch(console.error)
|
||||
this.db
|
||||
.hSet(KEY_FOR_TIMESTAMP, {
|
||||
[key]: date.toLocaleString('ru-RU', { timeZone: 'Asia/Yekaterinburg' }),
|
||||
})
|
||||
.catch(console.error)
|
||||
|
||||
this.db.hIncrBy(KEY_FOR_COUNTER, key, 1).catch(console.error)
|
||||
}
|
||||
|
||||
public updateLatestPaymentsCall(key: string): void {
|
||||
this.db.hIncrBy(KEY_FOR_PAYMENTS, key, 1).catch(console.error)
|
||||
}
|
||||
|
||||
public addReply(): void {
|
||||
this.replyCounter.inc()
|
||||
}
|
||||
|
@ -7,13 +7,13 @@ import { MetricsService } from './metrics.js'
|
||||
|
||||
describe('repository', () => {
|
||||
let dbMock = mock<RedisClientType<any, any, any>>()
|
||||
let metricsMock = mock<MetricsService>()
|
||||
let cacheMock = mock<CacheService>()
|
||||
let metricsMock = mock<MetricsService>()
|
||||
let cacheMock = mock<CacheService>()
|
||||
|
||||
beforeEach(() => {
|
||||
let dbMock = mock<RedisClientType<any, any, any>>()
|
||||
let metricsMock = mock<MetricsService>()
|
||||
let cacheMock = mock<CacheService>()
|
||||
dbMock = mock<RedisClientType<any, any, any>>()
|
||||
metricsMock = mock<MetricsService>()
|
||||
cacheMock = mock<CacheService>()
|
||||
})
|
||||
|
||||
describe('#addUsers', () => {
|
||||
|
@ -4,8 +4,6 @@ import { MetricsService } from './metrics.js'
|
||||
import { CacheService } from './cache.js'
|
||||
|
||||
export class UserRepository {
|
||||
|
||||
|
||||
constructor(
|
||||
private readonly db: RedisClientType<any, any, any>,
|
||||
private readonly metrics: MetricsService,
|
||||
@ -18,8 +16,8 @@ export class UserRepository {
|
||||
public async addUsers(chatId: Chat['id'], users: User[]): Promise<void> {
|
||||
const usernamesById: Record<string, string> = {}
|
||||
users.forEach((user) => {
|
||||
if (!user.username || user.is_bot || this.cache.isInCache(user.username)) return
|
||||
|
||||
if (!user.username || user.is_bot || this.cache.isInCache(user.username))
|
||||
return
|
||||
this.cache.addToCache([user.username])
|
||||
usernamesById[this.convertId(user.id)] = user.username
|
||||
})
|
||||
@ -40,7 +38,9 @@ export class UserRepository {
|
||||
const chatIdStr = this.convertId(chatId)
|
||||
|
||||
try {
|
||||
const promises = Object.entries(usernamesById).map(([id, username]) => this.db.hSet(chatIdStr, id, username))
|
||||
const promises = Object.entries(usernamesById).map(([id, username]) =>
|
||||
this.db.hSet(chatIdStr, id, username)
|
||||
)
|
||||
|
||||
await Promise.all(promises)
|
||||
} catch (err2) {
|
||||
|
@ -1,4 +1,3 @@
|
||||
|
||||
export const isChatGroup = (chatId: number): boolean => {
|
||||
return chatId < 0
|
||||
return chatId < 0
|
||||
}
|
||||
|
163
dist/core/bot.js
vendored
163
dist/core/bot.js
vendored
@ -6,10 +6,6 @@ export class Bot {
|
||||
metricsService;
|
||||
bot;
|
||||
MENTION_COMMANDS = ['@all', '/all'];
|
||||
FEEDBACK_COMMAND = '/feedback';
|
||||
CODE_COMMAND = '/code';
|
||||
SUPPORT_PAY_COMMAND = '/support';
|
||||
PRIVACY_COMMAND = '/privacy';
|
||||
ADMIN_ID;
|
||||
isListening = false;
|
||||
constructor(userRepository, metricsService, botName, adminId, token) {
|
||||
@ -21,15 +17,34 @@ export class Bot {
|
||||
this.MENTION_COMMANDS.push(botName);
|
||||
this.ADMIN_ID = adminId;
|
||||
this.bot = new Telegraf(token);
|
||||
this.bot.on(message('text'), async (ctx) => {
|
||||
const { message: { from, text, message_id }, chat: { id }, } = ctx;
|
||||
await this.handleMessage({
|
||||
from,
|
||||
text,
|
||||
messageId: message_id,
|
||||
chatId: id,
|
||||
}, (...args) => ctx.reply(...args));
|
||||
});
|
||||
this.bot.telegram.setMyCommands([
|
||||
{
|
||||
command: 'all',
|
||||
description: 'Mention all users in a group',
|
||||
},
|
||||
{
|
||||
command: 'donate',
|
||||
description: 'Get crypto payments links for supporting the project',
|
||||
},
|
||||
{
|
||||
command: 'feedback',
|
||||
description: 'Send feedback or bug reports (English please)',
|
||||
},
|
||||
{
|
||||
command: 'code',
|
||||
description: 'Get link for bot opensource code',
|
||||
},
|
||||
{
|
||||
command: 'privacy',
|
||||
description: 'How the Bot takes care of your personal data',
|
||||
},
|
||||
]);
|
||||
this.registerDonateCommand();
|
||||
this.registerFeedbackCommand();
|
||||
this.registerPrivacyCommand();
|
||||
this.registerCodeCommand();
|
||||
// Should be last for not overriding commands below
|
||||
this.registerHandleMessage();
|
||||
this.bot.on(message('new_chat_members'), ({ message, chat }) => this.handleAddMembers(chat.id, message.new_chat_members));
|
||||
this.bot.on(message('left_chat_member'), ({ chat, message }) => this.handleDelete(chat.id, message.left_chat_member));
|
||||
}
|
||||
@ -42,24 +57,8 @@ export class Bot {
|
||||
process.once('SIGTERM', () => this.bot.stop('SIGTERM'));
|
||||
this.bot.launch();
|
||||
}
|
||||
async handleMessage({ from, text, messageId, chatId }, reply) {
|
||||
if (text.startsWith(this.PRIVACY_COMMAND)) {
|
||||
console.log('Send privacy policy');
|
||||
const message = `
|
||||
Are you concerned about your security and personal data? This is right!
|
||||
What do we use? Identifiers of your groups to store data about participants in them: usernames and identifiers to correctly call all users of the group.
|
||||
All data is transmitted only via encrypted channels and is not used for other purposes.
|
||||
We don't read your messages, don't log data about you in public systems and 3th party services except safe hosting and database.
|
||||
You can view the project's codebase using ${this.CODE_COMMAND}.
|
||||
Be careful when using unfamiliar bots in your communication, it can be dangerous!
|
||||
`;
|
||||
reply(message, {
|
||||
reply_to_message_id: messageId,
|
||||
parse_mode: 'HTML'
|
||||
});
|
||||
return;
|
||||
}
|
||||
if (text.startsWith(this.SUPPORT_PAY_COMMAND)) {
|
||||
registerDonateCommand() {
|
||||
this.bot.command('donate', (ctx) => {
|
||||
console.log('Send payments info');
|
||||
const message = `
|
||||
This bot is free to use, but host and database are paid options for project.
|
||||
@ -69,63 +68,89 @@ Support via USDT-TRX: <code>TJyEa6p3HvAHz34gn7hZHYNwY65iHryu3w</code>
|
||||
Support via USDT-ETH: <code>0x7f49e01c13fE782aEB06Dc35a37d357b955b67B0</code>
|
||||
Support via BTC: <code>bc1qgmq6033fnte2ata8ku3zgvj0n302zvr9cexcng</code>
|
||||
Thank you for using and help!
|
||||
Note, than you can send ${this.FEEDBACK_COMMAND} with features or problems.
|
||||
Note, than you can send /feedback with features or problems.
|
||||
`;
|
||||
reply(message, {
|
||||
reply_to_message_id: messageId,
|
||||
parse_mode: 'HTML'
|
||||
this.metricsService.updateLatestPaymentsCall(`${ctx.chat.id}`);
|
||||
ctx.reply(message, {
|
||||
reply_to_message_id: ctx.message.message_id,
|
||||
parse_mode: 'HTML',
|
||||
});
|
||||
return;
|
||||
}
|
||||
if (text.startsWith(this.CODE_COMMAND)) {
|
||||
console.log('Send code info');
|
||||
reply(`I am an opensource project, feel free to reuse code or make bot better via /feedback.\nGithub link: https://github.com/sadfsdfdsa/allbot`, {
|
||||
reply_to_message_id: messageId,
|
||||
});
|
||||
return;
|
||||
}
|
||||
if (text.startsWith(this.FEEDBACK_COMMAND)) {
|
||||
const feedback = text.split(this.FEEDBACK_COMMAND)[1] || undefined;
|
||||
});
|
||||
}
|
||||
registerFeedbackCommand() {
|
||||
this.bot.command('feedback', (ctx) => {
|
||||
const { message: { from, text, message_id: messageId }, chat: { id: chatId }, } = ctx;
|
||||
console.log(ctx.message);
|
||||
const feedback = text.split('/feedback')[1] || undefined;
|
||||
if (!feedback) {
|
||||
console.log(`Receive empty feedback from user ${from.username} in ${chatId}: ${feedback}`);
|
||||
reply(`Add something in your feedback as feature or bug report`, {
|
||||
ctx.reply(`Add something in your feedback as feature or bug report`, {
|
||||
reply_to_message_id: messageId,
|
||||
});
|
||||
return;
|
||||
}
|
||||
console.log(`Receive feedback from user ${from.username} in ${chatId}: ${feedback}`);
|
||||
reply(`Your review has been successfully registered, we will contact you, thank you!`, {
|
||||
ctx.reply(`Your review has been successfully registered, we will contact you, thank you!`, {
|
||||
reply_to_message_id: messageId,
|
||||
});
|
||||
if (!this.ADMIN_ID)
|
||||
return;
|
||||
this.bot.telegram.sendMessage(this.ADMIN_ID, `There is a new feedback from @${from.username} in chat group ${chatId}:\n${feedback}`);
|
||||
return;
|
||||
}
|
||||
if (!isChatGroup(chatId)) {
|
||||
console.log('Direct message from', from.username);
|
||||
reply(`Add me to your group, here is example @all mention for you:`);
|
||||
reply(`All from ${from.username}: @${from.username}`, {
|
||||
});
|
||||
}
|
||||
registerPrivacyCommand() {
|
||||
this.bot.command('privacy', (ctx) => {
|
||||
console.log('Send privacy policy');
|
||||
const message = `
|
||||
Are you concerned about your security and personal data? This is right!
|
||||
What do we use? Identifiers of your groups to store data about participants in them: usernames and identifiers to correctly call all users of the group.
|
||||
All data is transmitted only via encrypted channels and is not used for other purposes.
|
||||
We don't read your messages, don't log data about you in public systems and 3th party services except safe hosting and database.
|
||||
You can view the project's codebase using /code.
|
||||
Be careful when using unfamiliar bots in your communication, it can be dangerous!
|
||||
`;
|
||||
ctx.reply(message, {
|
||||
reply_to_message_id: ctx.message.message_id,
|
||||
parse_mode: 'HTML',
|
||||
});
|
||||
});
|
||||
}
|
||||
registerCodeCommand() {
|
||||
this.bot.command('privacy', (ctx) => {
|
||||
console.log('Send code info');
|
||||
ctx.reply(`I am an opensource project, feel free to reuse code or make bot better via /feedback.\nGithub link: https://github.com/sadfsdfdsa/allbot`, {
|
||||
reply_to_message_id: ctx.message.message_id,
|
||||
});
|
||||
});
|
||||
}
|
||||
registerHandleMessage() {
|
||||
this.bot.on(message('text'), async (ctx) => {
|
||||
const { message: { from, text, message_id: messageId }, chat: { id: chatId }, } = ctx;
|
||||
if (!isChatGroup(chatId)) {
|
||||
console.log('Direct message from', from.username);
|
||||
ctx.reply(`Add me to your group, here is example @all mention for you:`);
|
||||
ctx.reply(`All from ${from.username}: @${from.username}`, {
|
||||
reply_to_message_id: messageId,
|
||||
});
|
||||
return;
|
||||
}
|
||||
await this.userRepository.addUsers(chatId, [from]);
|
||||
const isCallAll = this.MENTION_COMMANDS.some((command) => text.includes(command));
|
||||
if (!isCallAll)
|
||||
return;
|
||||
console.log(`Mention with pattern in group`, chatId);
|
||||
const chatUsernames = await this.userRepository.getUsernamesByChatId(chatId);
|
||||
if (!Object.values(chatUsernames).length)
|
||||
return;
|
||||
const str = Object.values(chatUsernames).map((username) => `@${username} `);
|
||||
this.metricsService.addReply();
|
||||
ctx.reply(`All from ${from.username}: ${str}`, {
|
||||
reply_to_message_id: messageId,
|
||||
});
|
||||
return;
|
||||
}
|
||||
await this.userRepository.addUsers(chatId, [from]);
|
||||
const isCallAll = this.MENTION_COMMANDS.some((command) => text.includes(command));
|
||||
if (!isCallAll)
|
||||
return;
|
||||
console.log(`Mention with pattern in group`, chatId);
|
||||
const chatUsernames = await this.userRepository.getUsernamesByChatId(chatId);
|
||||
if (!Object.values(chatUsernames).length)
|
||||
return;
|
||||
const str = Object.values(chatUsernames).map((username) => `@${username} `);
|
||||
this.metricsService.addReply();
|
||||
reply(`All from ${from.username}: ${str}`, {
|
||||
reply_to_message_id: messageId,
|
||||
});
|
||||
}
|
||||
handleAddMembers(chatId, users) {
|
||||
console.log('Try add new members', users.map(item => item.username));
|
||||
console.log('Try add new members', users.map((item) => item.username));
|
||||
return this.userRepository.addUsers(chatId, users);
|
||||
}
|
||||
handleDelete(chatId, user) {
|
||||
|
12
dist/core/metrics.js
vendored
12
dist/core/metrics.js
vendored
@ -1,6 +1,7 @@
|
||||
import { Registry, Counter, collectDefaultMetrics } from 'prom-client';
|
||||
const KEY_FOR_TIMESTAMP = 'TIMESTAMP';
|
||||
const KEY_FOR_COUNTER = 'COUNTER';
|
||||
const KEY_FOR_PAYMENTS = 'PAYMENTS';
|
||||
export class MetricsService {
|
||||
db;
|
||||
registry;
|
||||
@ -22,11 +23,16 @@ export class MetricsService {
|
||||
}
|
||||
updateLatestUsage(key) {
|
||||
const date = new Date();
|
||||
this.db.hSet(KEY_FOR_TIMESTAMP, {
|
||||
[key]: date.toLocaleString('ru-RU', { timeZone: 'Asia/Yekaterinburg' })
|
||||
}).catch(console.error);
|
||||
this.db
|
||||
.hSet(KEY_FOR_TIMESTAMP, {
|
||||
[key]: date.toLocaleString('ru-RU', { timeZone: 'Asia/Yekaterinburg' }),
|
||||
})
|
||||
.catch(console.error);
|
||||
this.db.hIncrBy(KEY_FOR_COUNTER, key, 1).catch(console.error);
|
||||
}
|
||||
updateLatestPaymentsCall(key) {
|
||||
this.db.hIncrBy(KEY_FOR_PAYMENTS, key, 1).catch(console.error);
|
||||
}
|
||||
addReply() {
|
||||
this.replyCounter.inc();
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user