Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
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
97 changes: 97 additions & 0 deletions bot/modules/templates/commands.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
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