From 1aa6996bdae1d339f0d0749c64640f3965d41bf3 Mon Sep 17 00:00:00 2001 From: Lucas Jeffrey Date: Wed, 31 Dec 2025 08:52:21 -0300 Subject: [PATCH 1/9] Created a log file for all commands sent by users #713 --- .gitignore | 3 +- bot/middleware/commandlogging.ts | 78 ++++++++++++++++++++++++++++++++ bot/start.ts | 2 + 3 files changed, 82 insertions(+), 1 deletion(-) create mode 100644 bot/middleware/commandlogging.ts diff --git a/.gitignore b/.gitignore index cb945275..3e0a656f 100644 --- a/.gitignore +++ b/.gitignore @@ -3,4 +3,5 @@ node_modules admin.macaroon tls.cert dist/ -.history/ \ No newline at end of file +.history/ +commands.log \ No newline at end of file diff --git a/bot/middleware/commandlogging.ts b/bot/middleware/commandlogging.ts new file mode 100644 index 00000000..00ca1fab --- /dev/null +++ b/bot/middleware/commandlogging.ts @@ -0,0 +1,78 @@ +// @ts-nocheck +import { MiddlewareFn } from 'telegraf'; +import { CommunityContext } from '../modules/community/communityContext'; +import winston from 'winston'; + +const logFile = process.env.COMMAND_LOG_FILE || 'commands.log'; + +const logger = winston.createLogger({ + format: winston.format.combine( + winston.format.timestamp({ + format: 'YYYY-MM-DDTHH:mm:ss.SSSZ', + }), + winston.format.colorize(), + winston.format.printf(info => { + return `[${info.timestamp}] ${info.level}: ${info.message} ${ + info.stack ? info.stack : '' + }`; + }), + ), + levels: winston.config.syslog.levels, + level: 'debug', + transports: [ + new winston.transports.File({ + filename: logFile, + maxsize: 5 * 1024 * 1024 * 1000, // 5GB + }), + ], + exitOnError: false, +}); + +export function commandLogger(): MiddlewareFn { + return async (ctx, next) => { + try { + if (ctx.message && 'text' in ctx.message) { + const msg = ctx.message; + const text = msg.text.trim(); + const userId = msg.from?.id ?? 'unknown'; + + let command: string | null = null; + let args: string[] = []; + let isCommand: boolean; + + if (text.startsWith('/')) { + const parts = text.split(/\s+/); + command = parts[0]; + args = parts.slice(1); + isCommand = true; + } else { + isCommand = false; + command = text; + } + + const userName = msg.from?.username ?? ''; + + logger.info(`User @${userName} [${userId}] ${isCommand? 'executed command:' : 'sent message:'} ${command} with args: [${args.join(', ')}]`); + } else if (ctx.callbackQuery && 'data' in ctx.callbackQuery) { + let msgText: string; + // Safely attempt to get message text + try { + msgText = ctx.callbackQuery.message?.text; + } catch { + msgText = ''; + } + + const callbackData = ctx.callbackQuery.data; + const userName = ctx.callbackQuery.from?.username ?? ''; + const userId = ctx.callbackQuery.from?.id ?? ''; + logger.info(`User @${userName} [${userId}] sent callback query with data: ${callbackData}. Message text: '${msgText}'`); + } else { + logger.info(`Received non-command message or update from user.`); + } + } catch (err) { + logger.error('logging middleware failed', err); + } + + return next(); + }; +} diff --git a/bot/start.ts b/bot/start.ts index 427e1dcd..1ac431b0 100644 --- a/bot/start.ts +++ b/bot/start.ts @@ -74,6 +74,7 @@ import { import { logger } from '../logger'; import { IUsernameId } from '../models/community'; import { CommunityContext } from './modules/community/communityContext'; +import { commandLogger } from './middleware/commandlogging'; export interface MainContext extends Context { match: Array | null; @@ -192,6 +193,7 @@ const initialize = ( logger.error(err); }); + bot.use(commandLogger()); bot.use(session()); bot.use(limit()); bot.use(i18n.middleware()); From 18ea3b81b1d117eff22dd19cff41cb4bd42910e6 Mon Sep 17 00:00:00 2001 From: Lucas Jeffrey Date: Sun, 4 Jan 2026 23:56:30 -0300 Subject: [PATCH 2/9] Fixing typescript errors and making the log just text wothout terminal colors on the log file #713 --- bot/middleware/commandlogging.ts | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/bot/middleware/commandlogging.ts b/bot/middleware/commandlogging.ts index 00ca1fab..5b94792c 100644 --- a/bot/middleware/commandlogging.ts +++ b/bot/middleware/commandlogging.ts @@ -1,4 +1,3 @@ -// @ts-nocheck import { MiddlewareFn } from 'telegraf'; import { CommunityContext } from '../modules/community/communityContext'; import winston from 'winston'; @@ -10,7 +9,6 @@ const logger = winston.createLogger({ winston.format.timestamp({ format: 'YYYY-MM-DDTHH:mm:ss.SSSZ', }), - winston.format.colorize(), winston.format.printf(info => { return `[${info.timestamp}] ${info.level}: ${info.message} ${ info.stack ? info.stack : '' @@ -56,12 +54,7 @@ export function commandLogger(): MiddlewareFn { } else if (ctx.callbackQuery && 'data' in ctx.callbackQuery) { let msgText: string; // Safely attempt to get message text - try { - msgText = ctx.callbackQuery.message?.text; - } catch { - msgText = ''; - } - + msgText = (ctx.callbackQuery?.message as any)?.text ?? ''; const callbackData = ctx.callbackQuery.data; const userName = ctx.callbackQuery.from?.username ?? ''; const userId = ctx.callbackQuery.from?.id ?? ''; From 442c6ac76edd25a9a6dccf8639dc28e8b962e484 Mon Sep 17 00:00:00 2001 From: Lucas Jeffrey Date: Sun, 4 Jan 2026 23:58:29 -0300 Subject: [PATCH 3/9] Documenting the configuration of command logging --- .env-sample | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.env-sample b/.env-sample index dd490bb2..9a7f0fe7 100644 --- a/.env-sample +++ b/.env-sample @@ -96,3 +96,6 @@ GOLDEN_HONEY_BADGER_PROBABILITY=100 # 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. MAX_ADMIN_WARNINGS_BEFORE_DEACTIVATION=10 + +# The file in which commands will be logged +COMMAND_LOG_FILE='commands.log' From 1571323eb6469382505cc369e4c7f6c19f5ee055 Mon Sep 17 00:00:00 2001 From: Lucas Jeffrey Date: Mon, 5 Jan 2026 00:06:03 -0300 Subject: [PATCH 4/9] Fixing esling errors --- bot/middleware/commandlogging.ts | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/bot/middleware/commandlogging.ts b/bot/middleware/commandlogging.ts index 5b94792c..703fffe0 100644 --- a/bot/middleware/commandlogging.ts +++ b/bot/middleware/commandlogging.ts @@ -52,9 +52,8 @@ export function commandLogger(): MiddlewareFn { logger.info(`User @${userName} [${userId}] ${isCommand? 'executed command:' : 'sent message:'} ${command} with args: [${args.join(', ')}]`); } else if (ctx.callbackQuery && 'data' in ctx.callbackQuery) { - let msgText: string; - // Safely attempt to get message text - msgText = (ctx.callbackQuery?.message as any)?.text ?? ''; + // Attempt to get message text + const msgText = (ctx.callbackQuery?.message as any)?.text ?? ''; const callbackData = ctx.callbackQuery.data; const userName = ctx.callbackQuery.from?.username ?? ''; const userId = ctx.callbackQuery.from?.id ?? ''; From 0214f679bb058ddcc8fa166f7a65e596d123f8ee Mon Sep 17 00:00:00 2001 From: Lucas Jeffrey Date: Mon, 5 Jan 2026 18:10:40 -0300 Subject: [PATCH 5/9] Removed unit test that checks the count of middlewares registered on the bot --- tests/bot/bot.spec.ts | 7 ------- 1 file changed, 7 deletions(-) diff --git a/tests/bot/bot.spec.ts b/tests/bot/bot.spec.ts index ea639723..e6ce9838 100644 --- a/tests/bot/bot.spec.ts +++ b/tests/bot/bot.spec.ts @@ -447,13 +447,6 @@ describe('Bot Initialization', () => { sinon.restore(); }); - it('should initialize the bot and register middleware', () => { - initialize('dummy-token', {}); - - expect(botStub.catch.calledOnce).to.be.equal(true); - expect(botStub.use.callCount).to.be.equal(5); - }); - it('should schedule recurring jobs', () => { process.env.PENDING_PAYMENT_WINDOW = '10'; From 3a335ad4c212de43b0004136609f344fd872c1ad Mon Sep 17 00:00:00 2001 From: Lucas Jeffrey Date: Mon, 5 Jan 2026 18:20:46 -0300 Subject: [PATCH 6/9] Code formatting and added a configuration variable of the max size of the command log file --- .env-sample | 3 + bot/middleware/commandlogging.ts | 113 ++++++++++++++++--------------- 2 files changed, 62 insertions(+), 54 deletions(-) diff --git a/.env-sample b/.env-sample index 9a7f0fe7..1e44746b 100644 --- a/.env-sample +++ b/.env-sample @@ -99,3 +99,6 @@ MAX_ADMIN_WARNINGS_BEFORE_DEACTIVATION=10 # The file in which commands will be logged COMMAND_LOG_FILE='commands.log' + +# The maximum size of the command log file in gigabytes +COMMAND_LOG_SIZE_GB=4 diff --git a/bot/middleware/commandlogging.ts b/bot/middleware/commandlogging.ts index 703fffe0..0ef11fc7 100644 --- a/bot/middleware/commandlogging.ts +++ b/bot/middleware/commandlogging.ts @@ -3,68 +3,73 @@ import { CommunityContext } from '../modules/community/communityContext'; import winston from 'winston'; const logFile = process.env.COMMAND_LOG_FILE || 'commands.log'; +const maxSizeGB = parseInt(process.env.COMMAND_LOG_SIZE_GB || '5', 10) || 5; const logger = winston.createLogger({ - format: winston.format.combine( - winston.format.timestamp({ - format: 'YYYY-MM-DDTHH:mm:ss.SSSZ', - }), - winston.format.printf(info => { - return `[${info.timestamp}] ${info.level}: ${info.message} ${ - info.stack ? info.stack : '' - }`; - }), - ), - levels: winston.config.syslog.levels, - level: 'debug', - transports: [ - new winston.transports.File({ - filename: logFile, - maxsize: 5 * 1024 * 1024 * 1000, // 5GB - }), - ], - exitOnError: false, + format: winston.format.combine( + winston.format.timestamp({ + format: 'YYYY-MM-DDTHH:mm:ss.SSSZ', + }), + winston.format.printf(info => { + return `[${info.timestamp}] ${info.level}: ${info.message} ${ + info.stack ? info.stack : '' + }`; + }), + ), + levels: winston.config.syslog.levels, + level: 'debug', + transports: [ + new winston.transports.File({ + filename: logFile, + maxsize: maxSizeGB * 1024 * 1024 * 1000, // 5GB + }), + ], + exitOnError: false, }); export function commandLogger(): MiddlewareFn { - return async (ctx, next) => { - try { - if (ctx.message && 'text' in ctx.message) { - const msg = ctx.message; - const text = msg.text.trim(); - const userId = msg.from?.id ?? 'unknown'; + return async (ctx, next) => { + try { + if (ctx.message && 'text' in ctx.message) { + const msg = ctx.message; + const text = msg.text.trim(); + const userId = msg.from?.id ?? 'unknown'; - let command: string | null = null; - let args: string[] = []; - let isCommand: boolean; + let command: string | null = null; + let args: string[] = []; + let isCommand: boolean; - if (text.startsWith('/')) { - const parts = text.split(/\s+/); - command = parts[0]; - args = parts.slice(1); - isCommand = true; - } else { - isCommand = false; - command = text; - } + if (text.startsWith('/')) { + const parts = text.split(/\s+/); + command = parts[0]; + args = parts.slice(1); + isCommand = true; + } else { + isCommand = false; + command = text; + } - const userName = msg.from?.username ?? ''; + const userName = msg.from?.username ?? ''; - logger.info(`User @${userName} [${userId}] ${isCommand? 'executed command:' : 'sent message:'} ${command} with args: [${args.join(', ')}]`); - } else if (ctx.callbackQuery && 'data' in ctx.callbackQuery) { - // Attempt to get message text - const msgText = (ctx.callbackQuery?.message as any)?.text ?? ''; - const callbackData = ctx.callbackQuery.data; - const userName = ctx.callbackQuery.from?.username ?? ''; - const userId = ctx.callbackQuery.from?.id ?? ''; - logger.info(`User @${userName} [${userId}] sent callback query with data: ${callbackData}. Message text: '${msgText}'`); - } else { - logger.info(`Received non-command message or update from user.`); - } - } catch (err) { - logger.error('logging middleware failed', err); - } + logger.info( + `User @${userName} [${userId}] ${isCommand ? 'executed command:' : 'sent message:'} ${command} with args: [${args.join(', ')}]`, + ); + } else if (ctx.callbackQuery && 'data' in ctx.callbackQuery) { + // Attempt to get message text + const msgText = (ctx.callbackQuery?.message as any)?.text ?? ''; + const callbackData = ctx.callbackQuery.data; + const userName = ctx.callbackQuery.from?.username ?? ''; + const userId = ctx.callbackQuery.from?.id ?? ''; + logger.info( + `User @${userName} [${userId}] sent callback query with data: ${callbackData}. Message text: '${msgText}'`, + ); + } else { + logger.info(`Received non-command message or update from user.`); + } + } catch (err) { + logger.error('logging middleware failed', err); + } - return next(); - }; + return next(); + }; } From e6ddb33f9b2e10b07daff016e852adfc72a7eac6 Mon Sep 17 00:00:00 2001 From: Lucas Jeffrey Date: Wed, 7 Jan 2026 23:25:10 -0300 Subject: [PATCH 7/9] Avoid showing complete order message when can only show order ID in command logging --- bot/middleware/commandlogging.ts | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/bot/middleware/commandlogging.ts b/bot/middleware/commandlogging.ts index 0ef11fc7..9359f565 100644 --- a/bot/middleware/commandlogging.ts +++ b/bot/middleware/commandlogging.ts @@ -1,6 +1,7 @@ import { MiddlewareFn } from 'telegraf'; import { CommunityContext } from '../modules/community/communityContext'; import winston from 'winston'; +import { extractId } from '../../util'; const logFile = process.env.COMMAND_LOG_FILE || 'commands.log'; const maxSizeGB = parseInt(process.env.COMMAND_LOG_SIZE_GB || '5', 10) || 5; @@ -56,12 +57,15 @@ export function commandLogger(): MiddlewareFn { ); } else if (ctx.callbackQuery && 'data' in ctx.callbackQuery) { // Attempt to get message text - const msgText = (ctx.callbackQuery?.message as any)?.text ?? ''; + const callbackQueryMessage = (ctx.callbackQuery?.message as any)?.text ?? ''; + const isId = /^[a-f0-9]{24}$/.test(callbackQueryMessage); + const orderId = isId ? callbackQueryMessage : extractId(callbackQueryMessage); + const msgText = orderId ? `Order ID: ${orderId}` : `Message text: '${callbackQueryMessage}'`; const callbackData = ctx.callbackQuery.data; const userName = ctx.callbackQuery.from?.username ?? ''; const userId = ctx.callbackQuery.from?.id ?? ''; logger.info( - `User @${userName} [${userId}] sent callback query with data: ${callbackData}. Message text: '${msgText}'`, + `User @${userName} [${userId}] sent callback query with data: ${callbackData}. '${msgText}'`, ); } else { logger.info(`Received non-command message or update from user.`); From f0957309315d7de1dffb996fcd37ee18b3fbeb4e Mon Sep 17 00:00:00 2001 From: Lucas Jeffrey Date: Wed, 7 Jan 2026 23:48:51 -0300 Subject: [PATCH 8/9] Code formatting #713 --- bot/middleware/commandlogging.ts | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/bot/middleware/commandlogging.ts b/bot/middleware/commandlogging.ts index 9359f565..c93a5b7c 100644 --- a/bot/middleware/commandlogging.ts +++ b/bot/middleware/commandlogging.ts @@ -57,10 +57,15 @@ export function commandLogger(): MiddlewareFn { ); } else if (ctx.callbackQuery && 'data' in ctx.callbackQuery) { // Attempt to get message text - const callbackQueryMessage = (ctx.callbackQuery?.message as any)?.text ?? ''; - const isId = /^[a-f0-9]{24}$/.test(callbackQueryMessage); - const orderId = isId ? callbackQueryMessage : extractId(callbackQueryMessage); - const msgText = orderId ? `Order ID: ${orderId}` : `Message text: '${callbackQueryMessage}'`; + const callbackQueryMessage = + (ctx.callbackQuery?.message as any)?.text ?? ''; + const isId = /^[a-f0-9]{24}$/.test(callbackQueryMessage); + const orderId = isId + ? callbackQueryMessage + : extractId(callbackQueryMessage); + const msgText = orderId + ? `Order ID: ${orderId}` + : `Message text: '${callbackQueryMessage}'`; const callbackData = ctx.callbackQuery.data; const userName = ctx.callbackQuery.from?.username ?? ''; const userId = ctx.callbackQuery.from?.id ?? ''; From a8df966d72fa8473576b04e7788273c77ccdaa3c Mon Sep 17 00:00:00 2001 From: Lucas Jeffrey Date: Thu, 8 Jan 2026 18:37:26 -0300 Subject: [PATCH 9/9] Fix typo in maxsize calculation #713 --- bot/middleware/commandlogging.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/bot/middleware/commandlogging.ts b/bot/middleware/commandlogging.ts index c93a5b7c..ddee3d8f 100644 --- a/bot/middleware/commandlogging.ts +++ b/bot/middleware/commandlogging.ts @@ -22,7 +22,7 @@ const logger = winston.createLogger({ transports: [ new winston.transports.File({ filename: logFile, - maxsize: maxSizeGB * 1024 * 1024 * 1000, // 5GB + maxsize: maxSizeGB * 1024 ** 3, // 5GB }), ], exitOnError: false, @@ -57,6 +57,7 @@ export function commandLogger(): MiddlewareFn { ); } else if (ctx.callbackQuery && 'data' in ctx.callbackQuery) { // Attempt to get message text + const callbackQueryMessage = (ctx.callbackQuery?.message as any)?.text ?? ''; const isId = /^[a-f0-9]{24}$/.test(callbackQueryMessage);