Skip to content
Draft
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions bot/middleware/stage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@ import { Scenes } from 'telegraf';
import * as CommunityModule from '../modules/community';
import * as OrdersModule from '../modules/orders';
import * as UserModule from '../modules/user';
import * as templatesScenes from '../modules/templates/scenes';
import { CommunityContext } from '../modules/community/communityContext';

import {
addInvoiceWizard,
addFiatAmountWizard,
Expand All @@ -27,7 +29,9 @@ export const stageMiddleware = () => {
addInvoicePHIWizard,
OrdersModule.Scenes.createOrder,
UserModule.Scenes.Settings,
templatesScenes.templatesWizard,
];

scenes.forEach(addGenericCommands);
const stage = new Scenes.Stage(scenes, {
ttl: 1200, // All wizards live 20 minutes
Expand Down
95 changes: 95 additions & 0 deletions bot/modules/templates/commands.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
import { OrderTemplate, User } from '../../../models';
import { IOrderTemplate } from '../../../models/order_template';
import * as ordersActions from '../../ordersActions';
import * as messages from './messages';
import {
publishBuyOrderMessage,
publishSellOrderMessage,
tooManyPendingOrdersMessage,
} from '../../messages';
import { MainContext, HasTelegram } from '../../start';
import { isMaxPending } from '../orders/commands';
import { logger } from '../../../logger';
import { delay } from '../../../util';

export const renderTemplateList = async (
ctx: MainContext,
userId: string,
): Promise<number[]> => {
try {
const templates = await OrderTemplate.find({ creator_id: userId });
const messageIds: number[] = [];

if (templates.length === 0) {
const text = ctx.i18n.t('no_templates');
const { keyboard } = messages.newTemplateButtonData(ctx.i18n);
const res = await ctx.reply(text, keyboard);
if (res) messageIds.push(res.message_id);
} else {
for (const template of templates) {
const { text, keyboard } = messages.singleTemplateData(
ctx.i18n,
template,
);
const res = await ctx.reply(text, keyboard);
if (res) messageIds.push(res.message_id);
await delay(100);
}
const { text, keyboard } = messages.newTemplateButtonData(ctx.i18n);
const res = await ctx.reply(text, keyboard);
if (res) messageIds.push(res.message_id);
}

return messageIds;
} catch (error) {
logger.error('Error in renderTemplateList:', error);
return [];
}
};

export const listTemplates = async (ctx: MainContext) => {
try {
await renderTemplateList(ctx, ctx.user._id);
} catch (error) {
logger.error(error);
}
};

export const publishFromTemplate = async (ctx: MainContext, template: IOrderTemplate) => {
try {
const user = ctx.user || (await User.findOne({ tg_id: ctx.from?.id }));
if (!user) return;

if (await isMaxPending(user)) {
return await tooManyPendingOrdersMessage(ctx, user, ctx.i18n);
}

const order = await ordersActions.createOrder(
ctx.i18n,
ctx as any as HasTelegram,
user,
{
type: template.type,
amount: template.amount || 0,
fiatAmount: template.fiat_amount,
fiatCode: template.fiat_code,
paymentMethod: template.payment_method,
status: 'PENDING',
priceMargin: template.price_margin,
community_id: user.default_community_id,
},
);

if (order) {
const publishFn =
template.type === 'buy'
? publishBuyOrderMessage
: publishSellOrderMessage;
await publishFn(ctx as any, user, order, ctx.i18n, true);
}
} catch (error) {
logger.error(error);
await ctx.reply(ctx.i18n.t('generic_error'));
}
};

15 changes: 15 additions & 0 deletions bot/modules/templates/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { Telegraf } from 'telegraf';
import { CommunityContext } from '../community/communityContext';
import { userMiddleware } from '../../middleware';
import * as templatesScenes from './scenes';

export const configure = (bot: Telegraf<CommunityContext>) => {
bot.command('templates', userMiddleware, async ctx => {
await ctx.scene.enter(templatesScenes.TEMPLATES_WIZARD, {
user: ctx.user,
});
});

// Note: Actions like 'create_template', 'publish_tpl_', etc.
// are now handled locally within the TEMPLATES_WIZARD scene.
};
85 changes: 85 additions & 0 deletions bot/modules/templates/messages.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
import { Markup } from 'telegraf';
import { I18nContext } from '@grammyjs/i18n';
import { IOrderTemplate } from '../../../models/order_template';

export const singleTemplateData = (
i18n: I18nContext,
template: IOrderTemplate,
) => {
const action = template.type === 'buy' ? i18n.t('buying') : i18n.t('selling');
const fiatAmount =
template.fiat_amount.length === 2
? `${template.fiat_amount[0]}-${template.fiat_amount[1]}`
: `${template.fiat_amount[0]}`;

const amountStr =
template.amount > 0
? `${template.amount} ${i18n.t('sats')}`
: i18n.t('sats');

const isPremium = template.price_margin > 0;
const margin =
template.price_margin === 0
? '0'
: isPremium
? `+${template.price_margin}`
: `${template.price_margin}`;
const rateStr =
template.amount > 0 ? '' : i18n.t('template_rate', { margin });

const text = i18n.t('template_card', {
action,
amountStr,
fiatAmount,
fiatCode: template.fiat_code,
paymentMethod: template.payment_method,
rateStr,
});

const keyboard = Markup.inlineKeyboard([
Markup.button.callback(
i18n.t('template_publish_btn'),
`tpl_list_publish_${template._id}`,
),
Markup.button.callback(
i18n.t('template_delete_btn'),
`tpl_list_delete_${template._id}`,
),
]);

return { text, keyboard };
};

export const newTemplateButtonData = (i18n: I18nContext) => {
return {
text: i18n.t('template_new_prompt'),
keyboard: Markup.inlineKeyboard([
Markup.button.callback(
`➕ ${i18n.t('create_new_template')}`,
'tpl_list_create',
),
]),
};
};

export const templateSavedMessage = (i18n: I18nContext) => {
return i18n.t('template_saved');
};

export const templateDeletedMessage = (i18n: I18nContext) => {
return i18n.t('template_deleted');
};

export const confirmDeleteTemplateData = (
i18n: I18nContext,
templateId: string,
) => {
const keyboard = Markup.inlineKeyboard([
Markup.button.callback(
i18n.t('yes'),
`tpl_list_confirm_delete_${templateId}`,
),
Markup.button.callback(i18n.t('no'), 'tpl_list_back'),
]);
return { text: i18n.t('confirm_delete_template'), keyboard };
};
Loading
Loading