1
0
mirror of https://github.com/sadfsdfdsa/allbot.git synced 2024-11-19 00:31:42 +02:00

feat: telegram payments release

This commit is contained in:
Artem Shuvaev 2024-06-06 23:44:26 +05:00
parent bc4f6e5225
commit 2e68280d7f
16 changed files with 218 additions and 219 deletions

View File

@ -22,7 +22,6 @@ import {
ADDED_TO_CHAT_WELCOME_TEXT,
ALREADY_UNLIMITED,
CLEAN_UP_EMPTY_MENTION_TEXT,
DONATE_COMMAND_TEXT,
EMPTY_DELETE_FROM_MENTION_TEXT,
EMPTY_DELETE_MENTION_TEXT,
GET_MENTIONS_TEXT,
@ -32,12 +31,12 @@ import {
NEW_MENTION_EXAMPLE,
NOT_EXISTED_MENTION_TEXT,
ONLY_ADMIN_ACTION_TEXT,
PRIVACY_COMMAND_TEXT,
SETTINGS_TEXT,
} from './constants/texts.js'
import { LIMITS_MENTION_FOR_ADDING_PAY } from './constants/limits.js'
import { PaymentsRepository } from './paymentsRepository.js'
import { SettingsAction, SettingsRepository } from './settingsRepository.js'
import { BASE_INVOICE } from './constants/const.js'
type UniversalMessageOrActionUpdateCtx = NarrowedContext<
Context,
@ -53,23 +52,14 @@ export class Bot {
private readonly EMOJI_SET = ['👋', '🫰']
private readonly DONATE_LINK = 'https://www.buymeacoffee.com/karanarqq'
private readonly BUY_LINK = 'https://www.buymeacoffee.com/karanarqq/e/190652'
private readonly DONATE_URL_BUTTON = {
url: this.DONATE_LINK,
text: '☕️ Buy me a coffee',
}
private readonly EXAMPLES_BUTTON = {
callback_data: '/examples',
text: '🤔 Show examples',
}
private readonly BUY_MENTIONS_BUTTON = {
url: this.BUY_LINK,
text: '🔥 Buy Unlimited mentions',
callback_data: '/buy',
text: '⭐️ Buy Unlimited using Telegram Stars',
}
private isListening = false
@ -124,17 +114,13 @@ export class Bot {
description: 'Help information',
},
{
command: 'donate',
description: 'Support the project to pay for servers and new features',
command: 'buy',
description: 'Get Unlimited custom mentions. Forever.',
},
])
this.registerDonateCommand()
this.registerPrivacyCommand()
this.registerBuyCommandAndActions()
this.registerHelpCommand()
this.registerSettingsChangeAction()
this.registerSettingsCommand()
this.registerMentionCommand()
this.registerGetAllMentionCommand()
@ -145,17 +131,7 @@ export class Bot {
// Should be last for not overriding commands below
this.registerHandleMessage()
this.bot.action('/donate', async (ctx) => {
if (!ctx.chat?.id) return
const msg = this.handleDonateCommand(ctx.chat?.id, 'donate.btn')
await ctx
.reply(msg, {
parse_mode: 'HTML',
})
.catch(this.handleSendMessageError)
})
this.registerSettingsChangeAction()
this.bot.action('/examples', async (ctx) => {
if (!ctx.chat?.id) return
@ -286,19 +262,6 @@ ${INTRODUCE_CUSTOM_MENTIONS_TEXT}${
return true
}
private handleDonateCommand(chatId: Chat['id'], command = 'donate'): string {
console.log('[PAYMENT] Send payments info')
this.metricsService.commandsCounter.inc({
chatId: chatId.toString(),
command,
})
this.metricsService.updateLatestPaymentsCall(`${chatId}`)
return DONATE_COMMAND_TEXT
}
private registerSettingsChangeAction(): void {
this.bot.action(/^[/settings]+(_.+)?$/, async (ctx) => {
if (!ctx.chat?.id) return
@ -829,46 +792,61 @@ Contact us via support chat from /help`,
})
}
private registerDonateCommand(): void {
this.bot.command('donate', async (ctx) => {
const msg = this.handleDonateCommand(ctx.chat.id)
const hasUnlimited = this.paymentsRepository.getHasGroupUnlimited(
ctx.chat.id
)
const inlineKeyboard = [
[this.DONATE_URL_BUTTON],
hasUnlimited ? [] : [this.BUY_MENTIONS_BUTTON],
]
await ctx
.reply(msg, {
reply_to_message_id: ctx.message.message_id,
parse_mode: 'HTML',
reply_markup: {
inline_keyboard: inlineKeyboard,
},
})
.catch(this.handleSendMessageError)
})
}
private registerPrivacyCommand(): void {
this.bot.command('privacy', async (ctx) => {
console.log('[PRIVACY] Send privacy policy')
private registerBuyCommandAndActions(): void {
const sendInvoice = async (
ctx: UniversalMessageOrActionUpdateCtx
): Promise<void> => {
if (!ctx.chat?.id) return
console.log('[buy] Start buying', ctx.chat.id)
this.metricsService.commandsCounter.inc({
chatId: ctx.chat.id.toString(),
command: 'privacy',
command: 'buy',
})
await ctx
.reply(PRIVACY_COMMAND_TEXT, {
reply_to_message_id: ctx.message.message_id,
parse_mode: 'HTML',
.sendInvoice({
...BASE_INVOICE,
payload: this.paymentsRepository.getPayloadForInvoice(ctx.chat.id),
})
.catch(this.handleSendMessageError)
}
this.bot.command('buy', async (ctx) => {
await sendInvoice(ctx)
})
this.bot.action('/buy', async (ctx) => {
await sendInvoice(ctx)
})
this.bot.on('pre_checkout_query', async (ctx) => {
console.log('[buy] pre_checkout_query', ctx.from)
await ctx.answerPreCheckoutQuery(true).catch(this.handleSendMessageError)
})
this.bot.on('successful_payment', async (ctx) => {
const payload = ctx.update.message.successful_payment.invoice_payload
const chatId =
this.paymentsRepository.getParsedChatFromInvoicePayload(payload)
if (!chatId) {
console.error(
'[buy] Something wrong with parsed data: ',
ctx.chat.id,
ctx.update.message.successful_payment
)
return
}
console.log('[buy] successful_payment', ctx.chat.id)
this.metricsService.commandsCounter.inc({
chatId: ctx.chat.id.toString(),
command: 'successful_payment',
})
await this.paymentsRepository.setGroupLimit(chatId, 'unlimited')
await ctx.sendMessage('You successfully upgraded to Unlimited!')
})
}
@ -1126,7 +1104,7 @@ Someone should write something (read more /help).
const buttons: InlineKeyboardButton[] = []
if (options.includePay) {
buttons.push(this.DONATE_URL_BUTTON)
buttons.push(this.BUY_MENTIONS_BUTTON)
}
if (options.includePromo) {

17
core/constants/const.ts Normal file
View File

@ -0,0 +1,17 @@
import { TELEGRAM_STARS_PRICE } from "./limits.js";
export const BASE_INVOICE = {
currency: 'XTR', // Telegram Stars only
description:
'Buy Unlimited custom mentions for this group and tag @everyone you need. Forever.',
photo_url:
'https://cdn.buymeacoffee.com/uploads/rewards/2024-02-23/1/111305_IMG_0129.jpeg@1200w_0e.jpeg',
title: 'Unlimited custom mentions forever',
prices: [
{
amount: TELEGRAM_STARS_PRICE,
label: 'Unlimited',
},
],
provider_token: '', // empty for Telegram Stars
}

View File

@ -1,3 +1,5 @@
export const LIMITS_MENTION_FOR_ADDING_PAY = 10
export const CUSTOM_MENTIONS_PER_GROUP_LIMIT = 3
export const CUSTOM_MENTIONS_PER_GROUP_LIMIT = 3
export const TELEGRAM_STARS_PRICE = 200

View File

@ -1,3 +1,5 @@
import { TELEGRAM_STARS_PRICE } from "./limits.js"
const EMPTY_TEXT = `⚠️ Error while parsing mention (see all with /mention). Please use:\n`
export const NEW_MENTION_EXAMPLE = `<code>/add_to NEW_MENTION @user @user2</code>`
@ -13,27 +15,6 @@ export const NOT_EXISTED_MENTION_TEXT = `⚠️ Not existed or empty mention.
export const CLEAN_UP_EMPTY_MENTION_TEXT = `🧹 This is an empty mention. We clean it up.`
export const DONATE_COMMAND_TEXT = `
🙌 This bot is free to use, but hosting and database are paid options for project. So, if you have opportunity to support, it will be very helpful! 🙌
1<strong>Support via USDT-TRC20: <code>TJyEa6p3HvAHz34gn7hZHYNwY65iHryu3w</code></strong>👈
2<strong>Support via USDT-ETH: <code>0x7f49e01c13fE782aEB06Dc35a37d357b955b67B0</code></strong>👈
3<strong>Support via BTC: <code>bc1qgmq6033fnte2ata8ku3zgvj0n302zvr9cexcng</code></strong>👈
Thank you for using and supporting us!
`
export const PRIVACY_COMMAND_TEXT = `
🔐 Are you concerned about your security and personal data? <strong>This is right!</strong>
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 Github - https://github.com/sadfsdfdsa/allbot (also can Star or Fork the Bot project).
<strong> Be careful when using unfamiliar bots in your communication, it can be dangerous!</strong>
`
const CUSTOM_MENTIONS_CHEATSHEET = `<code>/add_to TEAM_1 @user @user2</code> - add members to custom mention
<code>Your message @TEAM_1</code> - instant tag without any commands inside the text
<code>/mention TEAM_1</code> - mention part of your group
@ -59,10 +40,7 @@ You should Promote it and permit "Manage Topics", or use Bot not in "Closed" Top
<b> How to buy unlimited custom mentions?</b>
Try to add more than 3 mentions, click to button for pay link, then pay with fill your group name or contact us in chat https://t.me/allsuperior_chat.
<strong>👀 Commands:</strong>
/settings - change settings to enable/disable mentions for users without administrator rights
/donate - help the project pay for servers 🫰
/privacy - info about personal data usage and codebase of the Bot 🔐
<strong>👀 Custom mentions cheat sheet:</strong>
${CUSTOM_MENTIONS_CHEATSHEET}
@ -103,7 +81,7 @@ export const ALREADY_UNLIMITED = `
`
export const NEED_TO_BUY_UNLIMITED = `
😎 <strong>Need more than 3? Unlimited Forever for 5$.</strong>
😎 <strong>Need more than 3? Unlimited Forever for ${TELEGRAM_STARS_PRICE} Telegram Stars.</strong>
`
export const SETTINGS_TEXT = `⚙️ <strong>Settings (can be edited only by group admins):</strong>`

View File

@ -8,7 +8,6 @@ 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
@ -148,10 +147,6 @@ export class MetricsService {
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 async getMetrics(): Promise<{
contentType: string
metrics: string

View File

@ -1,33 +1,68 @@
import { Chat } from 'telegraf/types'
import { CUSTOM_MENTIONS_PER_GROUP_LIMIT } from './constants/limits.js'
import { RedisClientType } from 'redis'
import { MetricsService } from './metrics.js'
type ChatId = Chat['id']
type Limit = number | 'unlimited'
const LIMITS_TABLE_NAME = 'SYSTEM_GROUPS_LIMITS'
export class PaymentsRepository {
private readonly mentionsLimitsPerGroup: Record<ChatId, Limit> = {}
private readonly LIMIT_FOR_GROUP = CUSTOM_MENTIONS_PER_GROUP_LIMIT
constructor(envString: string | undefined) {
if (!envString) return
constructor(
private readonly db: RedisClientType<any, any, any>,
private readonly metrics: MetricsService
) {
console.log('[LAUNCH] Init payments repository')
}
const parsed = envString.split(';')
parsed.forEach((teamWithLimit) => {
const [groupId, limit] = teamWithLimit.split('=')
if (!groupId) return
public async loadLimits(): Promise<void> {
const parsed = await this.db.hGetAll(LIMITS_TABLE_NAME)
this.metrics.dbOpsCounter.inc({
action: 'payments.loadLimits',
})
Object.entries(parsed).forEach(([chatId, limit]) => {
const finalLimit = limit === 'unlimited' ? limit : Number(limit)
this.mentionsLimitsPerGroup[Number(groupId)] = finalLimit
this.mentionsLimitsPerGroup[Number(chatId)] = finalLimit
})
console.log(
'[LAUNCH] Init payments repository',
'[LAUNCH] Init payments limits for groups',
this.mentionsLimitsPerGroup
)
}
public async setGroupLimit(chatId: Chat['id'], limit: Limit): Promise<void> {
await this.db.hSet(LIMITS_TABLE_NAME, chatId.toString(), limit.toString())
this.mentionsLimitsPerGroup[chatId] = limit
this.metrics.dbOpsCounter.inc({
action: 'payments.setGroupLimit',
})
console.log('[paymentsRepository] set limit for chat', limit, chatId)
}
public getPayloadForInvoice(chatId: Chat['id']): string {
return `invoice:${chatId}`
}
public getParsedChatFromInvoicePayload(
payload: string
): Chat['id'] | undefined {
const [, chatId] = payload.split(':')
return Number.parseInt(chatId)
}
public getHasGroupUnlimited(chatId: Chat['id']): boolean {
return this.mentionsLimitsPerGroup[chatId] === 'unlimited'
}

View File

@ -12,7 +12,6 @@ export class UserRepository {
console.log('[LAUNCH] Init User repository')
}
// TODO improve tests
public async addUsers(chatId: Chat['id'], users: User[]): Promise<void> {
const usernamesById: Record<string, string> = {}
users.forEach((user) => {

103
dist/core/bot.js vendored
View File

@ -1,8 +1,9 @@
import { Telegraf } from 'telegraf';
import { message } from 'telegraf/filters';
import { matchMentionsToUsers, getMentionsFromEntities, isChatGroup, createSettingsKeyboard, } from './utils/utils.js';
import { ADDED_TO_CHAT_WELCOME_TEXT, ALREADY_UNLIMITED, CLEAN_UP_EMPTY_MENTION_TEXT, DONATE_COMMAND_TEXT, EMPTY_DELETE_FROM_MENTION_TEXT, EMPTY_DELETE_MENTION_TEXT, GET_MENTIONS_TEXT, HELP_COMMAND_TEXT, INTRODUCE_CUSTOM_MENTIONS_TEXT, NEED_TO_BUY_UNLIMITED, NEW_MENTION_EXAMPLE, NOT_EXISTED_MENTION_TEXT, ONLY_ADMIN_ACTION_TEXT, PRIVACY_COMMAND_TEXT, SETTINGS_TEXT, } from './constants/texts.js';
import { ADDED_TO_CHAT_WELCOME_TEXT, ALREADY_UNLIMITED, CLEAN_UP_EMPTY_MENTION_TEXT, EMPTY_DELETE_FROM_MENTION_TEXT, EMPTY_DELETE_MENTION_TEXT, GET_MENTIONS_TEXT, HELP_COMMAND_TEXT, INTRODUCE_CUSTOM_MENTIONS_TEXT, NEED_TO_BUY_UNLIMITED, NEW_MENTION_EXAMPLE, NOT_EXISTED_MENTION_TEXT, ONLY_ADMIN_ACTION_TEXT, SETTINGS_TEXT, } from './constants/texts.js';
import { LIMITS_MENTION_FOR_ADDING_PAY } from './constants/limits.js';
import { BASE_INVOICE } from './constants/const.js';
export class Bot {
userRepository;
metricsService;
@ -13,19 +14,13 @@ export class Bot {
MENTION_COMMANDS = ['@all', '/all'];
INCLUDE_PAY_LIMIT = LIMITS_MENTION_FOR_ADDING_PAY;
EMOJI_SET = ['👋', '🫰'];
DONATE_LINK = 'https://www.buymeacoffee.com/karanarqq';
BUY_LINK = 'https://www.buymeacoffee.com/karanarqq/e/190652';
DONATE_URL_BUTTON = {
url: this.DONATE_LINK,
text: '☕️ Buy me a coffee',
};
EXAMPLES_BUTTON = {
callback_data: '/examples',
text: '🤔 Show examples',
};
BUY_MENTIONS_BUTTON = {
url: this.BUY_LINK,
text: '🔥 Buy Unlimited mentions',
callback_data: '/buy',
text: '⭐️ Buy Unlimited using Telegram Stars',
};
isListening = false;
activeQuery = new Set();
@ -72,14 +67,12 @@ export class Bot {
description: 'Help information',
},
{
command: 'donate',
description: 'Support the project to pay for servers and new features',
command: 'buy',
description: 'Get Unlimited custom mentions. Forever.',
},
]);
this.registerDonateCommand();
this.registerPrivacyCommand();
this.registerBuyCommandAndActions();
this.registerHelpCommand();
this.registerSettingsChangeAction();
this.registerSettingsCommand();
this.registerMentionCommand();
this.registerGetAllMentionCommand();
@ -88,16 +81,7 @@ export class Bot {
this.registerDeleteMentionCommand();
// Should be last for not overriding commands below
this.registerHandleMessage();
this.bot.action('/donate', async (ctx) => {
if (!ctx.chat?.id)
return;
const msg = this.handleDonateCommand(ctx.chat?.id, 'donate.btn');
await ctx
.reply(msg, {
parse_mode: 'HTML',
})
.catch(this.handleSendMessageError);
});
this.registerSettingsChangeAction();
this.bot.action('/examples', async (ctx) => {
if (!ctx.chat?.id)
return;
@ -185,15 +169,6 @@ ${INTRODUCE_CUSTOM_MENTIONS_TEXT}${isUnlimitedGroup ? ALREADY_UNLIMITED : NEED_T
}
return true;
}
handleDonateCommand(chatId, command = 'donate') {
console.log('[PAYMENT] Send payments info');
this.metricsService.commandsCounter.inc({
chatId: chatId.toString(),
command,
});
this.metricsService.updateLatestPaymentsCall(`${chatId}`);
return DONATE_COMMAND_TEXT;
}
registerSettingsChangeAction() {
this.bot.action(/^[/settings]+(_.+)?$/, async (ctx) => {
if (!ctx.chat?.id)
@ -550,38 +525,46 @@ Contact us via support chat from /help`, {
.catch(this.handleSendMessageError);
});
}
registerDonateCommand() {
this.bot.command('donate', async (ctx) => {
const msg = this.handleDonateCommand(ctx.chat.id);
const hasUnlimited = this.paymentsRepository.getHasGroupUnlimited(ctx.chat.id);
const inlineKeyboard = [
[this.DONATE_URL_BUTTON],
hasUnlimited ? [] : [this.BUY_MENTIONS_BUTTON],
];
await ctx
.reply(msg, {
reply_to_message_id: ctx.message.message_id,
parse_mode: 'HTML',
reply_markup: {
inline_keyboard: inlineKeyboard,
},
})
.catch(this.handleSendMessageError);
});
}
registerPrivacyCommand() {
this.bot.command('privacy', async (ctx) => {
console.log('[PRIVACY] Send privacy policy');
registerBuyCommandAndActions() {
const sendInvoice = async (ctx) => {
if (!ctx.chat?.id)
return;
console.log('[buy] Start buying', ctx.chat.id);
this.metricsService.commandsCounter.inc({
chatId: ctx.chat.id.toString(),
command: 'privacy',
command: 'buy',
});
await ctx
.reply(PRIVACY_COMMAND_TEXT, {
reply_to_message_id: ctx.message.message_id,
parse_mode: 'HTML',
.sendInvoice({
...BASE_INVOICE,
payload: this.paymentsRepository.getPayloadForInvoice(ctx.chat.id),
})
.catch(this.handleSendMessageError);
};
this.bot.command('buy', async (ctx) => {
await sendInvoice(ctx);
});
this.bot.action('/buy', async (ctx) => {
await sendInvoice(ctx);
});
this.bot.on('pre_checkout_query', async (ctx) => {
console.log('[buy] pre_checkout_query', ctx.from);
await ctx.answerPreCheckoutQuery(true).catch(this.handleSendMessageError);
});
this.bot.on('successful_payment', async (ctx) => {
const payload = ctx.update.message.successful_payment.invoice_payload;
const chatId = this.paymentsRepository.getParsedChatFromInvoicePayload(payload);
if (!chatId) {
console.error('[buy] Something wrong with parsed data: ', ctx.chat.id, ctx.update.message.successful_payment);
return;
}
console.log('[buy] successful_payment', ctx.chat.id);
this.metricsService.commandsCounter.inc({
chatId: ctx.chat.id.toString(),
command: 'successful_payment',
});
await this.paymentsRepository.setGroupLimit(chatId, 'unlimited');
await ctx.sendMessage('You successfully upgraded to Unlimited!');
});
}
registerHelpCommand() {
@ -754,7 +737,7 @@ Someone should write something (read more /help).
}
const buttons = [];
if (options.includePay) {
buttons.push(this.DONATE_URL_BUTTON);
buttons.push(this.BUY_MENTIONS_BUTTON);
}
if (options.includePromo) {
buttons.push({

14
dist/core/constants/const.js vendored Normal file
View File

@ -0,0 +1,14 @@
import { TELEGRAM_STARS_PRICE } from "./limits.js";
export const BASE_INVOICE = {
currency: 'XTR',
description: 'Buy Unlimited custom mentions for this group and tag @everyone you need. Forever.',
photo_url: 'https://cdn.buymeacoffee.com/uploads/rewards/2024-02-23/1/111305_IMG_0129.jpeg@1200w_0e.jpeg',
title: 'Unlimited custom mentions forever',
prices: [
{
amount: TELEGRAM_STARS_PRICE,
label: 'Unlimited',
},
],
provider_token: '', // empty for Telegram Stars
};

View File

@ -1,2 +1,3 @@
export const LIMITS_MENTION_FOR_ADDING_PAY = 10;
export const CUSTOM_MENTIONS_PER_GROUP_LIMIT = 3;
export const TELEGRAM_STARS_PRICE = 200;

View File

@ -1,3 +1,4 @@
import { TELEGRAM_STARS_PRICE } from "./limits.js";
const EMPTY_TEXT = `⚠️ Error while parsing mention (see all with /mention). Please use:\n`;
export const NEW_MENTION_EXAMPLE = `<code>/add_to NEW_MENTION @user @user2</code>`;
export const EMPTY_MENTION_TEXT = `${EMPTY_TEXT}<code>/mention some_existing_mention</code>`;
@ -6,25 +7,6 @@ export const EMPTY_DELETE_MENTION_TEXT = `${EMPTY_TEXT}<code>/delete_mention MEN
export const NOT_EXISTED_MENTION_TEXT = `⚠️ Not existed or empty mention.
\nSee all via /mention or create new one:\n${NEW_MENTION_EXAMPLE}`;
export const CLEAN_UP_EMPTY_MENTION_TEXT = `🧹 This is an empty mention. We clean it up.`;
export const DONATE_COMMAND_TEXT = `
🙌 This bot is free to use, but hosting and database are paid options for project. So, if you have opportunity to support, it will be very helpful! 🙌
1<strong>Support via USDT-TRC20: <code>TJyEa6p3HvAHz34gn7hZHYNwY65iHryu3w</code></strong>👈
2<strong>Support via USDT-ETH: <code>0x7f49e01c13fE782aEB06Dc35a37d357b955b67B0</code></strong>👈
3<strong>Support via BTC: <code>bc1qgmq6033fnte2ata8ku3zgvj0n302zvr9cexcng</code></strong>👈
Thank you for using and supporting us!
`;
export const PRIVACY_COMMAND_TEXT = `
🔐 Are you concerned about your security and personal data? <strong>This is right!</strong>
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 Github - https://github.com/sadfsdfdsa/allbot (also can Star or Fork the Bot project).
<strong> Be careful when using unfamiliar bots in your communication, it can be dangerous!</strong>
`;
const CUSTOM_MENTIONS_CHEATSHEET = `<code>/add_to TEAM_1 @user @user2</code> - add members to custom mention
<code>Your message @TEAM_1</code> - instant tag without any commands inside the text
<code>/mention TEAM_1</code> - mention part of your group
@ -49,10 +31,7 @@ You should Promote it and permit "Manage Topics", or use Bot not in "Closed" Top
<b> How to buy unlimited custom mentions?</b>
Try to add more than 3 mentions, click to button for pay link, then pay with fill your group name or contact us in chat https://t.me/allsuperior_chat.
<strong>👀 Commands:</strong>
/settings - change settings to enable/disable mentions for users without administrator rights
/donate - help the project pay for servers 🫰
/privacy - info about personal data usage and codebase of the Bot 🔐
<strong>👀 Custom mentions cheat sheet:</strong>
${CUSTOM_MENTIONS_CHEATSHEET}
@ -89,7 +68,7 @@ export const ALREADY_UNLIMITED = `
😎 <strong>You have unlimited mentions, thank you for buying!</strong>
`;
export const NEED_TO_BUY_UNLIMITED = `
😎 <strong>Need more than 3? Unlimited Forever for 5$.</strong>
😎 <strong>Need more than 3? Unlimited Forever for ${TELEGRAM_STARS_PRICE} Telegram Stars.</strong>
`;
export const SETTINGS_TEXT = `⚙️ <strong>Settings (can be edited only by group admins):</strong>`;
export const ONLY_ADMIN_SETTINGS_TEXT = '🛑 Only admins';

View File

@ -1,7 +1,6 @@
import { Registry, Counter, collectDefaultMetrics, Histogram, } from 'prom-client';
const KEY_FOR_TIMESTAMP = '!TIMESTAMP';
const KEY_FOR_COUNTER = '!COUNTER';
const KEY_FOR_PAYMENTS = '!PAYMENTS';
export class MetricsService {
db;
registry;
@ -108,9 +107,6 @@ export class MetricsService {
.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);
}
async getMetrics() {
const metrics = await this.registry.metrics();
return {

View File

@ -1,19 +1,39 @@
import { CUSTOM_MENTIONS_PER_GROUP_LIMIT } from './constants/limits.js';
const LIMITS_TABLE_NAME = 'SYSTEM_GROUPS_LIMITS';
export class PaymentsRepository {
db;
metrics;
mentionsLimitsPerGroup = {};
LIMIT_FOR_GROUP = CUSTOM_MENTIONS_PER_GROUP_LIMIT;
constructor(envString) {
if (!envString)
return;
const parsed = envString.split(';');
parsed.forEach((teamWithLimit) => {
const [groupId, limit] = teamWithLimit.split('=');
if (!groupId)
return;
const finalLimit = limit === 'unlimited' ? limit : Number(limit);
this.mentionsLimitsPerGroup[Number(groupId)] = finalLimit;
constructor(db, metrics) {
this.db = db;
this.metrics = metrics;
console.log('[LAUNCH] Init payments repository');
}
async loadLimits() {
const parsed = await this.db.hGetAll(LIMITS_TABLE_NAME);
this.metrics.dbOpsCounter.inc({
action: 'payments.loadLimits',
});
console.log('[LAUNCH] Init payments repository', this.mentionsLimitsPerGroup);
Object.entries(parsed).forEach(([chatId, limit]) => {
const finalLimit = limit === 'unlimited' ? limit : Number(limit);
this.mentionsLimitsPerGroup[Number(chatId)] = finalLimit;
});
console.log('[LAUNCH] Init payments limits for groups', this.mentionsLimitsPerGroup);
}
async setGroupLimit(chatId, limit) {
await this.db.hSet(LIMITS_TABLE_NAME, chatId.toString(), limit.toString());
this.mentionsLimitsPerGroup[chatId] = limit;
this.metrics.dbOpsCounter.inc({
action: 'payments.setGroupLimit',
});
}
getPayloadForInvoice(chatId) {
return `invoice:${chatId}`;
}
getParsedChatFromInvoicePayload(payload) {
const [, chatId] = payload.split(':');
return Number.parseInt(chatId);
}
getHasGroupUnlimited(chatId) {
return this.mentionsLimitsPerGroup[chatId] === 'unlimited';

3
dist/index.js vendored
View File

@ -9,11 +9,12 @@ import { MentionRepository } from './core/mentionRepository.js';
import { PaymentsRepository } from './core/paymentsRepository.js';
import { SettingsRepository } from './core/settingsRepository.js';
const main = async () => {
const paymentsRepository = new PaymentsRepository(process.env.MENTIONS_LIMIT);
const dbClient = await createDB(process.env.REDIS_URI);
const metricsService = new MetricsService(dbClient);
const server = new Server(metricsService, process.env.PORT);
const cache = new CacheService(metricsService, 2000);
const paymentsRepository = new PaymentsRepository(dbClient, metricsService);
await paymentsRepository.loadLimits();
const settingsRepository = new SettingsRepository(dbClient, metricsService);
const userRepository = new UserRepository(dbClient, metricsService, cache);
const mentionRepository = new MentionRepository(dbClient, metricsService, paymentsRepository);

View File

@ -10,8 +10,6 @@ import { PaymentsRepository } from './core/paymentsRepository.js'
import { SettingsRepository } from './core/settingsRepository.js'
const main = async (): Promise<void> => {
const paymentsRepository = new PaymentsRepository(process.env.MENTIONS_LIMIT)
const dbClient = await createDB(process.env.REDIS_URI)
const metricsService = new MetricsService(dbClient)
@ -20,6 +18,9 @@ const main = async (): Promise<void> => {
const cache = new CacheService(metricsService, 2000)
const paymentsRepository = new PaymentsRepository(dbClient, metricsService)
await paymentsRepository.loadLimits()
const settingsRepository = new SettingsRepository(dbClient, metricsService)
const userRepository = new UserRepository(dbClient, metricsService, cache)

View File

@ -68,5 +68,5 @@
// "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */
// "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */
},
"exclude": ["node_modules", "test", "types", "dist", "**/*.test.ts"]
"exclude": ["node_modules", "test", "types", "dist", "**/*.test.ts", "allbot"]
}