diff --git a/.env-sample b/.env-sample index dd490bb2..1e44746b 100644 --- a/.env-sample +++ b/.env-sample @@ -96,3 +96,9 @@ 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' + +# The maximum size of the command log file in gigabytes +COMMAND_LOG_SIZE_GB=4 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..ddee3d8f --- /dev/null +++ b/bot/middleware/commandlogging.ts @@ -0,0 +1,85 @@ +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; + +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: maxSizeGB * 1024 ** 3, // 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) { + // 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 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}. '${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()); 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';