Skip to content

Commit fad7e12

Browse files
committed
Merge remote-tracking branch 'upstream/main' into issue726
2 parents 1b7ca7e + 0ce91ee commit fad7e12

18 files changed

Lines changed: 514 additions & 59 deletions

File tree

.env-sample

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,3 +96,10 @@ GOLDEN_HONEY_BADGER_PROBABILITY=100
9696

9797
# Number of notification messages sent to the admin, informing them of lack of solvers before disabling the community. The admin receives `MAX_ADMIN_WARNINGS_BEFORE_DEACTIVATION - 1` notification messages.
9898
MAX_ADMIN_WARNINGS_BEFORE_DEACTIVATION=10
99+
100+
# The file in which commands will be logged. It can be both an absolute or relative path (realative to the directory in which your run `npm start`)
101+
COMMAND_LOG_FILE='commands.log'
102+
# The maximum size of each command log file in megabytes
103+
COMMAND_LOG_SIZE_MB=4
104+
# The maximum number of log files to keep
105+
MAX_LOG_FILES=5

.gitignore

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,5 @@ node_modules
33
admin.macaroon
44
tls.cert
55
dist/
6-
.history/
6+
.history/
7+
commands.log

bot/messages.ts

Lines changed: 50 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import { logger } from '../logger';
1818
import { HasTelegram, MainContext } from './start';
1919
import { UserDocument } from '../models/user';
2020
import { IOrder } from '../models/order';
21+
import { Order, User, Community } from '../models';
2122
import { I18nContext } from '@grammyjs/i18n';
2223
import { IConfig } from '../models/config';
2324
import { IPendingPayment } from '../models/pending_payment';
@@ -26,7 +27,6 @@ import { IFiat } from '../util/fiatModel';
2627
import { CommunityContext } from './modules/community/communityContext';
2728
import { imageCache } from '../util/imageCache';
2829
import { ImageProcessingError } from '../util/errors';
29-
import { Community } from '../models';
3030

3131
const { I18n } = require('@grammyjs/i18n');
3232

@@ -2000,10 +2000,11 @@ const showConfirmationButtons = async (
20002000
};
20012001
})
20022002
.map(ord => ({
2003-
text: `${ord._id.slice(0, 2)}..${ord._id.slice(-2)} - ${ord.type} - ${
2004-
ord.fiat
2005-
} ${ord.amount}`,
2006-
callback_data: `${commandString}_${ord._id}`,
2003+
text: `${ord._id.slice(0, 2)}..${ord._id.slice(-2)} - ${ord.type} - ${ord.fiat} ${ord.amount}`,
2004+
callback_data:
2005+
commandString === 'release'
2006+
? `show_release_confirmation_${ord._id}`
2007+
: `${commandString}_${ord._id}`,
20072008
}));
20082009
inlineKeyboard.push(lineBtn);
20092010
}
@@ -2021,6 +2022,49 @@ const showConfirmationButtons = async (
20212022
}
20222023
};
20232024

2025+
const showReleaseConfirmationMessage = async (
2026+
ctx: MainContext,
2027+
orderId: string,
2028+
) => {
2029+
try {
2030+
const order = await Order.findOne({ _id: orderId });
2031+
if (!order) return;
2032+
2033+
// Get user information
2034+
const seller = await User.findOne({ _id: order.seller_id });
2035+
const buyer = await User.findOne({ _id: order.buyer_id });
2036+
2037+
if (!seller || !buyer) return;
2038+
2039+
const message = ctx.i18n.t('release_confirmation_message', {
2040+
orderId: order._id,
2041+
counterparty: buyer.username || buyer.tg_id,
2042+
amount: order.amount,
2043+
fiatCode: order.fiat_code,
2044+
fiatAmount: order.fiat_amount,
2045+
});
2046+
2047+
const inlineKeyboard = [
2048+
[
2049+
{
2050+
text: ctx.i18n.t('release_confirm_button'),
2051+
callback_data: `confirm_release_${order._id}`,
2052+
},
2053+
{
2054+
text: ctx.i18n.t('release_cancel_button'),
2055+
callback_data: 'cancel_release',
2056+
},
2057+
],
2058+
];
2059+
2060+
await ctx.reply(message, {
2061+
reply_markup: { inline_keyboard: inlineKeyboard },
2062+
});
2063+
} catch (error) {
2064+
logger.error(error);
2065+
}
2066+
};
2067+
20242068
export {
20252069
startMessage,
20262070
initBotErrorMessage,
@@ -2135,6 +2179,7 @@ export {
21352179
notAuthorized,
21362180
mustBeANumber,
21372181
showConfirmationButtons,
2182+
showReleaseConfirmationMessage,
21382183
counterPartyCancelOrderMessage,
21392184
checkInvoiceMessage,
21402185
userTakerIsBlockedByUserOrder,

bot/middleware/commandlogging.ts

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
import { MiddlewareFn } from 'telegraf';
2+
import { CommunityContext } from '../modules/community/communityContext';
3+
import winston from 'winston';
4+
import { extractId } from '../../util';
5+
6+
const logFile = process.env.COMMAND_LOG_FILE || 'commands.log';
7+
const maxSizeMB = parseInt(process.env.COMMAND_LOG_SIZE_MB || '5', 10) || 5;
8+
const maxFiles = parseInt(process.env.MAX_LOG_FILES || '5', 10) || 5;
9+
10+
const logger = winston.createLogger({
11+
format: winston.format.combine(
12+
winston.format.timestamp({
13+
format: 'YYYY-MM-DDTHH:mm:ss.SSSZ',
14+
}),
15+
winston.format.printf(info => {
16+
return `[${info.timestamp}] ${info.level}: ${info.message} ${
17+
info.stack ? info.stack : ''
18+
}`;
19+
}),
20+
),
21+
levels: winston.config.syslog.levels,
22+
level: 'debug',
23+
transports: [
24+
new winston.transports.File({
25+
filename: logFile,
26+
maxsize: maxSizeMB * 1024 ** 2, // maxsize in MB
27+
maxFiles,
28+
tailable: true,
29+
zippedArchive: false,
30+
}),
31+
],
32+
exitOnError: false,
33+
});
34+
35+
export function commandLogger(): MiddlewareFn<CommunityContext> {
36+
return async (ctx, next) => {
37+
try {
38+
if (ctx.message && 'text' in ctx.message) {
39+
const msg = ctx.message;
40+
const text = msg.text.trim();
41+
const userId = msg.from?.id ?? 'unknown';
42+
43+
let command: string | null = null;
44+
let args: string[] = [];
45+
let isCommand: boolean;
46+
47+
if (text.startsWith('/')) {
48+
const parts = text.split(/\s+/);
49+
command = parts[0];
50+
args = parts.slice(1);
51+
isCommand = true;
52+
} else {
53+
isCommand = false;
54+
command = text;
55+
}
56+
57+
const userName = msg.from?.username ?? '';
58+
59+
logger.info(
60+
`User @${userName} [${userId}] ${isCommand ? 'executed command:' : 'sent message:'} ${command} with args: [${args.join(', ')}]`,
61+
);
62+
} else if (ctx.callbackQuery && 'data' in ctx.callbackQuery) {
63+
// Attempt to get message text
64+
65+
const callbackQueryMessage =
66+
(ctx.callbackQuery?.message as any)?.text ?? '';
67+
const isId = /^[a-f0-9]{24}$/.test(callbackQueryMessage);
68+
const orderId = isId
69+
? callbackQueryMessage
70+
: extractId(callbackQueryMessage);
71+
const msgText = orderId
72+
? `Order ID: ${orderId}`
73+
: `Message text: '${callbackQueryMessage}'`;
74+
const callbackData = ctx.callbackQuery.data;
75+
const userName = ctx.callbackQuery.from?.username ?? '';
76+
const userId = ctx.callbackQuery.from?.id ?? '';
77+
logger.info(
78+
`User @${userName} [${userId}] sent callback query with data: ${callbackData}. '${msgText}'`,
79+
);
80+
} else {
81+
logger.info(`Received non-command message or update from user.`);
82+
}
83+
} catch (err) {
84+
logger.error('logging middleware failed', err);
85+
}
86+
87+
return next();
88+
};
89+
}

bot/start.ts

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ import {
4747
release,
4848
showQrCode,
4949
} from './commands';
50+
import { showReleaseConfirmationMessage } from './messages';
5051
import {
5152
settleHoldInvoice,
5253
cancelHoldInvoice,
@@ -74,6 +75,7 @@ import {
7475
import { logger } from '../logger';
7576
import { IUsernameId } from '../models/community';
7677
import { CommunityContext } from './modules/community/communityContext';
78+
import { commandLogger } from './middleware/commandlogging';
7779

7880
export interface MainContext extends Context {
7981
match: Array<string> | null;
@@ -192,6 +194,7 @@ const initialize = (
192194
logger.error(err);
193195
});
194196

197+
bot.use(commandLogger());
195198
bot.use(session());
196199
bot.use(limit());
197200
bot.use(i18n.middleware());
@@ -917,6 +920,42 @@ const initialize = (
917920
},
918921
);
919922

923+
// Action to show release confirmation
924+
bot.action(
925+
/^show_release_confirmation_([0-9a-f]{24})$/,
926+
userMiddleware,
927+
async (ctx: CommunityContext) => {
928+
if (ctx.match === null) {
929+
throw new Error('ctx.match should not be null');
930+
}
931+
ctx.deleteMessage();
932+
await showReleaseConfirmationMessage(ctx, ctx.match[1]);
933+
},
934+
);
935+
936+
// Action to confirm release
937+
bot.action(
938+
/^confirm_release_([0-9a-f]{24})$/,
939+
userMiddleware,
940+
async (ctx: CommunityContext) => {
941+
if (ctx.match === null) {
942+
throw new Error('ctx.match should not be null');
943+
}
944+
ctx.deleteMessage();
945+
await release(ctx, ctx.match[1]);
946+
},
947+
);
948+
949+
// Action to cancel release
950+
bot.action(
951+
/^cancel_release$/,
952+
userMiddleware,
953+
async (ctx: CommunityContext) => {
954+
ctx.deleteMessage();
955+
await ctx.reply(ctx.i18n.t('release_operation_cancelled'));
956+
},
957+
);
958+
920959
bot.action(
921960
/^showqrcode_([0-9a-f]{24})$/,
922961
userMiddleware,

locales/de.yaml

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -510,7 +510,21 @@ not_wizard: Sie sind momentan nicht im Assistentenmodus. Für Hilfe verwenden Si
510510
wizard_help: Sie befinden sich im Assistentenmodus, wenn Sie wieder zum Befehlsmodus wollen, verwenden Sie /exit
511511
hold_invoice_memo: '@${botName} - Auftrag #${orderId}: BTC verkaufen für ${fiatCode} ${fiatAmount} - Der Betrag wird im gehalten, bis Sie diesen mit /release freigeben. Wenn der Käufer nicht bestätigt, wird der Betrag zurückbezahlt.'
512512
tap_button: Wählen Sie eine Bestellung, um den Vorgang auszuführen
513-
tap_release: Wählen Sie die Reihenfolge aus, um die Mittel freizugeben. Sobald Sie die Schaltfläche berühren, kann der Vorgang nicht rückgängig gemacht werden.
513+
tap_release: Wählen Sie die Bestellung aus, um die Mittel freizugeben. Sie sehen eine Bestätigung, bevor Sie fortfahren.
514+
release_confirmation_message: |
515+
Sie sind dabei, die folgende Bestellung freizugeben:
516+
517+
🆔 Bestell-ID: ${orderId}
518+
👤 Gegenpartei: @${counterparty}
519+
⚡ Betrag: ${amount} sats
520+
💰 Gegenwert: ${fiatAmount} ${fiatCode}
521+
522+
⚠️ Dieser Vorgang kann nicht rückgängig gemacht werden.
523+
524+
Möchten Sie diesen Vorgang bestätigen?
525+
release_confirm_button: Bestätigen
526+
release_cancel_button: Abbrechen
527+
release_operation_cancelled: Freigabe-Vorgang abgebrochen.
514528
earnings: Verdienste
515529
premium: Prämie
516530
discount: Rabatt

locales/en.yaml

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -516,7 +516,21 @@ not_wizard: You are not in wizard mode at the moment, if you have doubts you can
516516
wizard_help: You are in wizard mode, if you need to go back to command mode run /exit
517517
hold_invoice_memo: '@${botName} - Escrow amount Order #${orderId}: SELL BTC for ${fiatCode} ${fiatAmount} - It WILL FREEZE IN YOUR WALLET. It will release once you run /release. It will return if buyer does not confirm the payment'
518518
tap_button: Select an order to perform the operation
519-
tap_release: Select the order to release the funds, once you press the button, the operation cannot be reversed.
519+
tap_release: Select the order to release the funds, you will see a confirmation before proceeding.
520+
release_confirmation_message: |
521+
You are about to release the following order:
522+
523+
🆔 Order ID: ${orderId}
524+
👤 Counterparty: @${counterparty}
525+
⚡ Amount: ${amount} sats
526+
💰 Fiat equivalent: ${fiatAmount} ${fiatCode}
527+
528+
⚠️ This operation cannot be reversed.
529+
530+
Do you want to confirm this operation?
531+
release_confirm_button: Confirm
532+
release_cancel_button: Cancel
533+
release_operation_cancelled: Release operation cancelled.
520534
earnings: Earnings
521535
premium: Premium
522536
discount: Discount

locales/es.yaml

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -511,7 +511,21 @@ not_wizard: No estás en el modo asistente en este momento, si tienes dudas pued
511511
wizard_help: Estás en modo asistente, si necesitas volver al modo comando ejecuta /exit
512512
hold_invoice_memo: '@${botName} - Depósito en garantía Orden #${orderId} - VENTA x ${fiatCode} ${fiatAmount} - SE CONGELA EN BILLETERA. Se libera una vez ejecute /release. Retorna a la wallet si el comprador no confirma el pago'
513513
tap_button: Selecciona una orden para realizar la operación
514-
tap_release: Selecciona la orden para liberar los fondos, una vez toques el botón la operación no puede ser reversada.
514+
tap_release: Selecciona la orden para liberar los fondos, verás una confirmación antes de proceder.
515+
release_confirmation_message: |
516+
Estás a punto de liberar la siguiente orden:
517+
518+
🆔 ID de orden: ${orderId}
519+
👤 Contraparte: ${counterparty}
520+
⚡ Monto: ${amount} sats
521+
💰 Equivalente: ${fiatAmount} ${fiatCode}
522+
523+
⚠️ Esta operación no puede ser revertida.
524+
525+
¿Quieres confirmar esta operación?
526+
release_confirm_button: Confirmar
527+
release_cancel_button: Cancelar
528+
release_operation_cancelled: Operación de liberación cancelada.
515529
earnings: Ganancias
516530
premium: Prima
517531
discount: Descuento

locales/fa.yaml

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -510,7 +510,21 @@ not_wizard: شما در حال حاضر در حالت wizard نیستید، اگ
510510
wizard_help: شما در حالت wizard هستید، اگر نیاز به بازگشت به حالت فرمان دارید، /exit را اجرا کنید
511511
hold_invoice_memo: '@${botName} - Escrow amount Order #${orderId}: SELL BTC for ${fiatCode} ${fiatAmount} - It WILL FREEZE IN YOUR WALLET. It will release once you run /release. It will return if buyer does not confirm the payment'
512512
tap_button: دستوری را برای انجام عملیات انتخاب کنید
513-
tap_release: سفارش را انتخاب کنید تا وجه را آزاد کنید، پس از فشار دادن دکمه، عملیات قابل برگشت نیست.
513+
tap_release: برای آزادسازی وجه، سفارش را انتخاب کنید؛ قبل از ادامه تأییدی مشاهده خواهید کرد.
514+
release_confirmation_message: |
515+
شما در آستانه آزاد کردن سفارش زیر هستید:
516+
517+
🆔 شناسه سفارش: ${orderId}
518+
👤 طرف مقابل: @${counterparty}
519+
⚡ مبلغ: ${amount} sats
520+
💰 معادل: ${fiatAmount} ${fiatCode}
521+
522+
⚠️ این عملیات قابل برگشت نیست.
523+
524+
آیا می‌خواهید این عملیات را تأیید کنید؟
525+
release_confirm_button: تأیید
526+
release_cancel_button: لغو
527+
release_operation_cancelled: عملیات آزادسازی لغو شد.
514528
earnings: درآمد
515529
premium: حباب
516530
discount: تخفیف

locales/fr.yaml

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -509,7 +509,21 @@ not_wizard: Vous n'êtes pas en mode assistant pour le moment, si vous avez des
509509
wizard_help: Vous êtes en mode assistant, si vous souhaitez revenir en mode commande, exécutez /exit.
510510
hold_invoice_memo: "Montant du séquestre de l'offre #${orderId} : VENTE BTC pour ${fiatCode} ${fiatAmount} - CE SERA GELÉ DANS VOTRE PORTEFEUILLE. Il sera libéré une fois que vous aurez lancé la commande /release. Il vous sera remboursé si l'acheteur ne confirme pas le paiement."
511511
tap_button: Sélectionner une offre pour effectuer l'opération
512-
tap_release: Sélectionnez l'offre pour débloquer les fonds, une fois que vous avez appuyé sur le bouton, l'opération ne peut plus être annulée.
512+
tap_release: Sélectionnez l'offre pour débloquer les fonds, vous verrez une confirmation avant de procéder.
513+
release_confirmation_message: |
514+
Vous êtes sur le point de libérer la commande suivante :
515+
516+
🆔 ID de commande : ${orderId}
517+
👤 Contrepartie : @${counterparty}
518+
⚡ Montant : ${amount} sats
519+
💰 Équivalent : ${fiatAmount} ${fiatCode}
520+
521+
⚠️ Cette opération ne peut pas être annulée.
522+
523+
Voulez-vous confirmer cette opération ?
524+
release_confirm_button: Confirmer
525+
release_cancel_button: Annuler
526+
release_operation_cancelled: Opération de libération annulée.
513527
earnings: Revenus
514528
premium: Surcote
515529
discount: Décote

0 commit comments

Comments
 (0)