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:
parent
bc4f6e5225
commit
2e68280d7f
132
core/bot.ts
132
core/bot.ts
@ -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
17
core/constants/const.ts
Normal 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
|
||||
}
|
@ -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
|
@ -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>`
|
||||
|
@ -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
|
||||
|
@ -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'
|
||||
}
|
||||
|
@ -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
103
dist/core/bot.js
vendored
@ -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
14
dist/core/constants/const.js
vendored
Normal 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
|
||||
};
|
1
dist/core/constants/limits.js
vendored
1
dist/core/constants/limits.js
vendored
@ -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;
|
||||
|
25
dist/core/constants/texts.js
vendored
25
dist/core/constants/texts.js
vendored
@ -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';
|
||||
|
4
dist/core/metrics.js
vendored
4
dist/core/metrics.js
vendored
@ -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 {
|
||||
|
42
dist/core/paymentsRepository.js
vendored
42
dist/core/paymentsRepository.js
vendored
@ -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
3
dist/index.js
vendored
@ -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);
|
||||
|
5
index.ts
5
index.ts
@ -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)
|
||||
|
@ -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"]
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user