better logging, stats tracking command, clean up url warning, user permissions on settings

This commit is contained in:
Aidan 2025-07-01 12:26:51 -04:00
parent 23ebd021f3
commit a952ddfc67
7 changed files with 340 additions and 251 deletions

View file

@ -48,23 +48,41 @@ type SettingsMenu = { text: string, reply_markup: any };
function getSettingsMenu(user: UserRow, Strings: any): SettingsMenu {
const langObj = langs.find(l => l.code === user.languageCode);
const langLabel = langObj ? langObj.label : user.languageCode;
const userId = user.telegramId;
return {
text: Strings.settings.selectSetting,
reply_markup: {
inline_keyboard: [
[
{ text: `${Strings.settings.ai.aiEnabled}: ${user.aiEnabled ? Strings.settings.enabled : Strings.settings.disabled}`, callback_data: 'settings_aiEnabled' },
{ text: `🧠 ${Strings.settings.ai.aiModel}: ${getModelLabelByName(user.customAiModel)}`, callback_data: 'settings_aiModel' }
{ text: `${Strings.settings.ai.aiEnabled}: ${user.aiEnabled ? Strings.settings.enabled : Strings.settings.disabled}`, callback_data: `settings_aiEnabled_${userId}` },
{ text: `🧠 ${Strings.settings.ai.aiModel}: ${getModelLabelByName(user.customAiModel)}`, callback_data: `settings_aiModel_${userId}` }
],
[
{ text: `🌡️ ${Strings.settings.ai.aiTemperature}: ${user.aiTemperature}`, callback_data: 'settings_aiTemperature' },
{ text: `🌐 ${langLabel}`, callback_data: 'settings_language' }
{ text: `🌡️ ${Strings.settings.ai.aiTemperature}: ${user.aiTemperature}`, callback_data: `settings_aiTemperature_${userId}` },
{ text: `🌐 ${langLabel}`, callback_data: `settings_language_${userId}` }
]
]
}
};
}
function extractUserIdFromCallback(data: string): string | null {
const match = data.match(/_(\d+)$/);
return match ? match[1] : null;
}
function getNotAllowedMessage(Strings: any) {
return Strings.gsmarenaNotAllowed;
}
function logSettingsAccess(action: string, ctx: Context, allowed: boolean, expectedUserId: string | null) {
if (process.env.longerLogs === 'true') {
const actualUserId = ctx.from?.id;
const username = ctx.from?.username || ctx.from?.first_name || 'unknown';
console.log(`[Settings] Action: ${action}, Callback from: ${username} (${actualUserId}), Expected: ${expectedUserId}, Allowed: ${allowed}`);
}
}
export default (bot: Telegraf<Context>, db: NodePgDatabase<typeof schema>) => {
bot.start(spamwatchMiddleware, async (ctx: Context) => {
const { user, Strings } = await getUserAndStrings(ctx, db);
@ -118,218 +136,297 @@ export default (bot: Telegraf<Context>, db: NodePgDatabase<typeof schema>) => {
await ctx.editMessageReplyMarkup(menu.reply_markup);
};
bot.action('settings_aiEnabled', async (ctx) => {
try {
await ctx.answerCbQuery();
const { user, Strings } = await getUserAndStrings(ctx, db);
if (!user) return;
await db.update(schema.usersTable)
.set({ aiEnabled: !user.aiEnabled })
.where(eq(schema.usersTable.telegramId, String(user.telegramId)));
const updatedUser = (await db.query.usersTable.findMany({ where: (fields, { eq }) => eq(fields.telegramId, String(user.telegramId)), limit: 1 }))[0];
await updateSettingsKeyboard(ctx, updatedUser, Strings);
} catch (err) {
console.error('Error handling settings_aiEnabled callback:', err);
bot.action(/^settings_aiEnabled_\d+$/, async (ctx) => {
const data = (ctx.callbackQuery as any).data;
const userId = extractUserIdFromCallback(data);
const allowed = !!userId && String(ctx.from.id) === userId;
logSettingsAccess('settings_aiEnabled', ctx, allowed, userId);
if (!allowed) {
const { Strings } = await getUserAndStrings(ctx, db);
return ctx.answerCbQuery(getNotAllowedMessage(Strings), { show_alert: true });
}
await ctx.answerCbQuery();
const { user, Strings } = await getUserAndStrings(ctx, db);
if (!user) return;
await db.update(schema.usersTable)
.set({ aiEnabled: !user.aiEnabled })
.where(eq(schema.usersTable.telegramId, String(user.telegramId)));
const updatedUser = (await db.query.usersTable.findMany({ where: (fields, { eq }) => eq(fields.telegramId, String(user.telegramId)), limit: 1 }))[0];
await updateSettingsKeyboard(ctx, updatedUser, Strings);
});
bot.action('settings_aiModel', async (ctx) => {
bot.action(/^settings_aiModel_\d+$/, async (ctx) => {
const data = (ctx.callbackQuery as any).data;
const userId = extractUserIdFromCallback(data);
const allowed = !!userId && String(ctx.from.id) === userId;
logSettingsAccess('settings_aiModel', ctx, allowed, userId);
if (!allowed) {
const { Strings } = await getUserAndStrings(ctx, db);
return ctx.answerCbQuery(getNotAllowedMessage(Strings), { show_alert: true });
}
await ctx.answerCbQuery();
const { user, Strings } = await getUserAndStrings(ctx, db);
if (!user) return;
try {
await ctx.answerCbQuery();
const { user, Strings } = await getUserAndStrings(ctx, db);
if (!user) return;
try {
await ctx.editMessageText(
`${Strings.settings.ai.selectSeries}`,
{
reply_markup: {
inline_keyboard: models.map(series => [
{ text: series.label, callback_data: `selectseries_${series.name}` }
]).concat([[
{ text: `${Strings.varStrings.varBack}`, callback_data: 'settings_back' }
]])
}
await ctx.editMessageText(
`${Strings.settings.ai.selectSeries}`,
{
reply_markup: {
inline_keyboard: models.map(series => [
{ text: series.label, callback_data: `selectseries_${series.name}_${user.telegramId}` }
]).concat([[
{ text: `${Strings.varStrings.varBack}`, callback_data: `settings_back_${user.telegramId}` }
]])
}
);
} catch (err) {
if (
!(
err.response.description?.includes('query is too old') ||
err.response.description?.includes('query ID is invalid') ||
err.response.description?.includes('message is not modified') ||
err.response.description?.includes('message to edit not found')
)
)
console.error('Unexpected Telegram error:', err);
}
}
);
} catch (err) {
console.error('Error handling settings_aiModel callback:', err);
if (
!(
err.response.description?.includes('query is too old') ||
err.response.description?.includes('query ID is invalid') ||
err.response.description?.includes('message is not modified') ||
err.response.description?.includes('message to edit not found')
)
)
console.error('Unexpected Telegram error:', err);
}
});
bot.action(/^selectseries_.+$/, async (ctx) => {
bot.action(/^selectseries_.+_\d+$/, async (ctx) => {
const data = (ctx.callbackQuery as any).data;
const userId = extractUserIdFromCallback(data);
const allowed = !!userId && String(ctx.from.id) === userId;
logSettingsAccess('selectseries', ctx, allowed, userId);
if (!allowed) {
const { Strings } = await getUserAndStrings(ctx, db);
return ctx.answerCbQuery(getNotAllowedMessage(Strings), { show_alert: true });
}
await ctx.answerCbQuery();
const { user, Strings } = await getUserAndStrings(ctx, db);
if (!user) return;
const seriesName = data.replace(/^selectseries_/, '').replace(/_\d+$/, '');
const series = models.find(s => s.name === seriesName);
if (!series) return;
const desc = user.languageCode === 'pt' ? series.descriptionPt : series.descriptionEn;
try {
await ctx.answerCbQuery();
const { user, Strings } = await getUserAndStrings(ctx, db);
if (!user) return;
const data = (ctx.callbackQuery as any).data;
const seriesName = data.replace('selectseries_', '');
const series = models.find(s => s.name === seriesName);
if (!series) return;
const desc = user.languageCode === 'pt' ? series.descriptionPt : series.descriptionEn;
try {
await ctx.editMessageText(
`${Strings.settings.ai.seriesDescription.replace('{seriesDescription}', desc)}\n\n${Strings.settings.ai.selectParameterSize.replace('{seriesLabel}', series.label)}\n\n${Strings.settings.ai.parameterSizeExplanation}`,
{
reply_markup: {
inline_keyboard: series.models.map(m => [
{ text: `${m.label} (${m.parameterSize})`, callback_data: `setmodel_${series.name}_${m.name}` }
]).concat([[
{ text: `${Strings.varStrings.varBack}`, callback_data: 'settings_aiModel' }
]])
}
await ctx.editMessageText(
`${Strings.settings.ai.seriesDescription.replace('{seriesDescription}', desc)}\n\n${Strings.settings.ai.selectParameterSize.replace('{seriesLabel}', series.label)}\n\n${Strings.settings.ai.parameterSizeExplanation}`,
{
reply_markup: {
inline_keyboard: series.models.map(m => [
{ text: `${m.label} (${m.parameterSize})`, callback_data: `setmodel_${series.name}_${m.name}_${user.telegramId}` }
]).concat([[
{ text: `${Strings.varStrings.varBack}`, callback_data: `settings_aiModel_${user.telegramId}` }
]])
}
);
} catch (err) {
if (
!(
err.response.description?.includes('query is too old') ||
err.response.description?.includes('query ID is invalid') ||
err.response.description?.includes('message is not modified') ||
err.response.description?.includes('message to edit not found')
)
)
console.error('Unexpected Telegram error:', err);
}
}
);
} catch (err) {
console.error('Error handling selectseries callback:', err);
if (
!(
err.response.description?.includes('query is too old') ||
err.response.description?.includes('query ID is invalid') ||
err.response.description?.includes('message is not modified') ||
err.response.description?.includes('message to edit not found')
)
)
console.error('Unexpected Telegram error:', err);
}
});
bot.action(/^setmodel_.+$/, async (ctx) => {
bot.action(/^setmodel_.+_\d+$/, async (ctx) => {
const data = (ctx.callbackQuery as any).data;
const userId = extractUserIdFromCallback(data);
const allowed = !!userId && String(ctx.from.id) === userId;
logSettingsAccess('setmodel', ctx, allowed, userId);
if (!allowed) {
const { Strings } = await getUserAndStrings(ctx, db);
return ctx.answerCbQuery(getNotAllowedMessage(Strings), { show_alert: true });
}
await ctx.answerCbQuery();
const { user, Strings } = await getUserAndStrings(ctx, db);
if (!user) return;
const parts = data.split('_');
const seriesName = parts[1];
const modelName = parts.slice(2, -1).join('_');
const series = models.find(s => s.name === seriesName);
const model = series?.models.find(m => m.name === modelName);
if (!series || !model) return;
await db.update(schema.usersTable)
.set({ customAiModel: model.name })
.where(eq(schema.usersTable.telegramId, String(user.telegramId)));
const updatedUser = (await db.query.usersTable.findMany({ where: (fields, { eq }) => eq(fields.telegramId, String(user.telegramId)), limit: 1 }))[0];
const menu = getSettingsMenu(updatedUser, Strings);
try {
await ctx.answerCbQuery();
const { user, Strings } = await getUserAndStrings(ctx, db);
if (!user) return;
const data = (ctx.callbackQuery as any).data;
const parts = data.split('_');
const seriesName = parts[1];
const modelName = parts.slice(2).join('_');
const series = models.find(s => s.name === seriesName);
const model = series?.models.find(m => m.name === modelName);
if (!series || !model) return;
await db.update(schema.usersTable)
.set({ customAiModel: model.name })
.where(eq(schema.usersTable.telegramId, String(user.telegramId)));
const updatedUser = (await db.query.usersTable.findMany({ where: (fields, { eq }) => eq(fields.telegramId, String(user.telegramId)), limit: 1 }))[0];
const menu = getSettingsMenu(updatedUser, Strings);
try {
if (ctx.callbackQuery.message) {
await ctx.editMessageText(
menu.text,
{
reply_markup: menu.reply_markup,
parse_mode: 'Markdown'
}
);
} else {
await ctx.reply(menu.text, {
if (ctx.callbackQuery.message) {
await ctx.editMessageText(
menu.text,
{
reply_markup: menu.reply_markup,
parse_mode: 'Markdown'
});
}
} catch (err) {
if (
!(
err.response.description?.includes('query is too old') ||
err.response.description?.includes('query ID is invalid') ||
err.response.description?.includes('message is not modified') ||
err.response.description?.includes('message to edit not found')
)
)
console.error('[Settings] Unexpected Telegram error:', err);
}
} catch (err) {
console.error('Error handling setmodel callback:', err);
}
});
bot.action('settings_aiTemperature', async (ctx) => {
try {
await ctx.answerCbQuery();
const { user, Strings } = await getUserAndStrings(ctx, db);
if (!user) return;
const temps = [0.2, 0.5, 0.7, 0.9, 1.2];
try {
await ctx.editMessageReplyMarkup({
inline_keyboard: temps.map(t => [{ text: t.toString(), callback_data: `settemp_${t}` }]).concat([[{ text: `${Strings.varStrings.varBack}`, callback_data: 'settings_back' }]])
}
);
} else {
await ctx.reply(menu.text, {
reply_markup: menu.reply_markup,
parse_mode: 'Markdown'
});
} catch (err) {
if (
!(
err.response.description?.includes('query is too old') ||
err.response.description?.includes('query ID is invalid') ||
err.response.description?.includes('message is not modified') ||
err.response.description?.includes('message to edit not found')
)
)
console.error('Unexpected Telegram error:', err);
}
} catch (err) {
console.error('Error handling settings_aiTemperature callback:', err);
if (
!(
err.response.description?.includes('query is too old') ||
err.response.description?.includes('query ID is invalid') ||
err.response.description?.includes('message is not modified') ||
err.response.description?.includes('message to edit not found')
)
)
console.error('[Settings] Unexpected Telegram error:', err);
}
});
bot.action(/^settemp_.+$/, async (ctx) => {
bot.action(/^settings_aiTemperature_\d+$/, async (ctx) => {
const data = (ctx.callbackQuery as any).data;
const userId = extractUserIdFromCallback(data);
const allowed = !!userId && String(ctx.from.id) === userId;
logSettingsAccess('settings_aiTemperature', ctx, allowed, userId);
if (!allowed) {
const { Strings } = await getUserAndStrings(ctx, db);
return ctx.answerCbQuery(getNotAllowedMessage(Strings), { show_alert: true });
}
await ctx.answerCbQuery();
const { user, Strings } = await getUserAndStrings(ctx, db);
if (!user) return;
const temps = [0.2, 0.5, 0.7, 0.9, 1.2];
try {
await ctx.answerCbQuery();
const { user, Strings } = await getUserAndStrings(ctx, db);
if (!user) return;
const data = (ctx.callbackQuery as any).data;
const temp = parseFloat(data.replace('settemp_', ''));
await db.update(schema.usersTable)
.set({ aiTemperature: temp })
.where(eq(schema.usersTable.telegramId, String(user.telegramId)));
const updatedUser = (await db.query.usersTable.findMany({ where: (fields, { eq }) => eq(fields.telegramId, String(user.telegramId)), limit: 1 }))[0];
await updateSettingsKeyboard(ctx, updatedUser, Strings);
await ctx.editMessageReplyMarkup({
inline_keyboard: temps.map(t => [{ text: t.toString(), callback_data: `settemp_${t}_${user.telegramId}` }]).concat([[{ text: `${Strings.varStrings.varBack}`, callback_data: `settings_back_${user.telegramId}` }]])
});
} catch (err) {
console.error('Error handling settemp callback:', err);
if (
!(
err.response.description?.includes('query is too old') ||
err.response.description?.includes('query ID is invalid') ||
err.response.description?.includes('message is not modified') ||
err.response.description?.includes('message to edit not found')
)
)
console.error('Unexpected Telegram error:', err);
}
});
bot.action('settings_language', async (ctx) => {
bot.action(/^settemp_.+_\d+$/, async (ctx) => {
const data = (ctx.callbackQuery as any).data;
const userId = extractUserIdFromCallback(data);
const allowed = !!userId && String(ctx.from.id) === userId;
logSettingsAccess('settemp', ctx, allowed, userId);
if (!allowed) {
const { Strings } = await getUserAndStrings(ctx, db);
return ctx.answerCbQuery(getNotAllowedMessage(Strings), { show_alert: true });
}
await ctx.answerCbQuery();
const { user, Strings } = await getUserAndStrings(ctx, db);
if (!user) return;
const temp = parseFloat(data.replace(/^settemp_/, '').replace(/_\d+$/, ''));
await db.update(schema.usersTable)
.set({ aiTemperature: temp })
.where(eq(schema.usersTable.telegramId, String(user.telegramId)));
const updatedUser = (await db.query.usersTable.findMany({ where: (fields, { eq }) => eq(fields.telegramId, String(user.telegramId)), limit: 1 }))[0];
await updateSettingsKeyboard(ctx, updatedUser, Strings);
});
bot.action(/^settings_language_\d+$/, async (ctx) => {
const data = (ctx.callbackQuery as any).data;
const userId = extractUserIdFromCallback(data);
const allowed = !!userId && String(ctx.from.id) === userId;
logSettingsAccess('settings_language', ctx, allowed, userId);
if (!allowed) {
const { Strings } = await getUserAndStrings(ctx, db);
return ctx.answerCbQuery(getNotAllowedMessage(Strings), { show_alert: true });
}
await ctx.answerCbQuery();
const { user, Strings } = await getUserAndStrings(ctx, db);
if (!user) return;
try {
await ctx.answerCbQuery();
const { user, Strings } = await getUserAndStrings(ctx, db);
if (!user) return;
try {
await ctx.editMessageReplyMarkup({
inline_keyboard: langs.map(l => [{ text: l.label, callback_data: `setlang_${l.code}` }]).concat([[{ text: `${Strings.varStrings.varBack}`, callback_data: 'settings_back' }]])
await ctx.editMessageReplyMarkup({
inline_keyboard: langs.map(l => [{ text: l.label, callback_data: `setlang_${l.code}_${user.telegramId}` }]).concat([[{ text: `${Strings.varStrings.varBack}`, callback_data: `settings_back_${user.telegramId}` }]])
});
} catch (err) {
if (
!(
err.response.description?.includes('query is too old') ||
err.response.description?.includes('query ID is invalid') ||
err.response.description?.includes('message is not modified') ||
err.response.description?.includes('message to edit not found')
)
)
console.error('Unexpected Telegram error:', err);
}
});
bot.action(/^settings_back_\d+$/, async (ctx) => {
const data = (ctx.callbackQuery as any).data;
const userId = extractUserIdFromCallback(data);
const allowed = !!userId && String(ctx.from.id) === userId;
logSettingsAccess('settings_back', ctx, allowed, userId);
if (!allowed) {
const { Strings } = await getUserAndStrings(ctx, db);
return ctx.answerCbQuery(getNotAllowedMessage(Strings), { show_alert: true });
}
await ctx.answerCbQuery();
const { user, Strings } = await getUserAndStrings(ctx, db);
if (!user) return;
await updateSettingsKeyboard(ctx, user, Strings);
});
bot.action(/^setlang_.+_\d+$/, async (ctx) => {
const data = (ctx.callbackQuery as any).data;
const userId = extractUserIdFromCallback(data);
const allowed = !!userId && String(ctx.from.id) === userId;
logSettingsAccess('setlang', ctx, allowed, userId);
if (!allowed) {
const { Strings } = await getUserAndStrings(ctx, db);
return ctx.answerCbQuery(getNotAllowedMessage(Strings), { show_alert: true });
}
await ctx.answerCbQuery();
const { user } = await getUserAndStrings(ctx, db);
if (!user) {
console.log('[Settings] No user found');
return;
}
const lang = data.replace(/^setlang_/, '').replace(/_\d+$/, '');
await db.update(schema.usersTable)
.set({ languageCode: lang })
.where(eq(schema.usersTable.telegramId, String(user.telegramId)));
const updatedUser = (await db.query.usersTable.findMany({ where: (fields, { eq }) => eq(fields.telegramId, String(user.telegramId)), limit: 1 }))[0];
const updatedStrings = getStrings(updatedUser.languageCode);
const menu = getSettingsMenu(updatedUser, updatedStrings);
try {
if (ctx.callbackQuery.message) {
await ctx.editMessageText(
menu.text,
{
reply_markup: menu.reply_markup,
parse_mode: 'Markdown'
}
);
} else {
await ctx.reply(menu.text, {
reply_markup: menu.reply_markup,
parse_mode: 'Markdown'
});
} catch (err) {
if (
!(
err.response.description?.includes('query is too old') ||
err.response.description?.includes('query ID is invalid') ||
err.response.description?.includes('message is not modified') ||
err.response.description?.includes('message to edit not found')
)
)
console.error('Unexpected Telegram error:', err);
}
} catch (err) {
console.error('Error handling settings_language callback:', err);
}
});
bot.action('settings_back', async (ctx) => {
try {
await ctx.answerCbQuery();
const { user, Strings } = await getUserAndStrings(ctx, db);
if (!user) return;
await updateSettingsKeyboard(ctx, user, Strings);
} catch (err) {
console.error('Error handling settings_back callback:', err);
if (
!(
err.response.description?.includes('query is too old') ||
err.response.description?.includes('query ID is invalid') ||
err.response.description?.includes('message is not modified') ||
err.response.description?.includes('message to edit not found')
)
)
console.error('[Settings] Unexpected Telegram error:', err);
}
});
@ -342,51 +439,4 @@ export default (bot: Telegraf<Context>, db: NodePgDatabase<typeof schema>) => {
reply_to_message_id: ctx.message.message_id
} as any);
});
bot.action(/^setlang_.+$/, async (ctx) => {
try {
await ctx.answerCbQuery();
const { user } = await getUserAndStrings(ctx, db);
if (!user) {
console.log('[Settings] No user found');
return;
}
const data = (ctx.callbackQuery as any).data;
const lang = data.replace('setlang_', '');
await db.update(schema.usersTable)
.set({ languageCode: lang })
.where(eq(schema.usersTable.telegramId, String(user.telegramId)));
const updatedUser = (await db.query.usersTable.findMany({ where: (fields, { eq }) => eq(fields.telegramId, String(user.telegramId)), limit: 1 }))[0];
const updatedStrings = getStrings(updatedUser.languageCode);
const menu = getSettingsMenu(updatedUser, updatedStrings);
try {
if (ctx.callbackQuery.message) {
await ctx.editMessageText(
menu.text,
{
reply_markup: menu.reply_markup,
parse_mode: 'Markdown'
}
);
} else {
await ctx.reply(menu.text, {
reply_markup: menu.reply_markup,
parse_mode: 'Markdown'
});
}
} catch (err) {
if (
!(
err.response.description?.includes('query is too old') ||
err.response.description?.includes('query ID is invalid') ||
err.response.description?.includes('message is not modified') ||
err.response.description?.includes('message to edit not found')
)
)
console.error('[Settings] Unexpected Telegram error:', err);
}
} catch (err) {
console.error('[Settings] Error handling setlang callback:', err);
}
});
};