feat: config management

fuck it we're vibecoding now
This commit is contained in:
devilreef 2025-11-28 13:41:13 +06:00
parent 1ea3f3ba97
commit 1eb2200fbe
14 changed files with 1082 additions and 33 deletions

View file

@ -0,0 +1,25 @@
import type { BotType } from '@/bot/index.js'
import { bold, format } from 'gramio'
import { isChatAdmin, refreshAdminCache } from '@/bot/utilities/perms.js'
export default function (bot: BotType) {
// Command to refresh admin cache
bot.command('reload', async (ctx) => {
if (!['group', 'supergroup'].includes(ctx.chat.type)) {
return ctx.reply('This command can only be used in groups or supergroups')
}
// Any admin can refresh cache
const isAdmin = await isChatAdmin(ctx.chat.id, ctx.from.id)
if (!isAdmin) {
return ctx.reply('This command can only be used by admins')
}
// Refresh the admin cache for this chat
refreshAdminCache(ctx.chat.id)
return ctx.reply(
format`${bold('Admin cache refreshed!')} Permission checks will use fresh data from Telegram.`,
)
})
}

View file

@ -0,0 +1,302 @@
import type { BotType } from '@/bot/index.js'
import { bold, format, InlineKeyboard, italic } from 'gramio'
import { isChatAdmin, isChatOwner } from '@/bot/utilities/perms.js'
import { ChatConfigService, type RoleManagePermission, type RoleMentionPermission } from '@/shared/services/chatConfig.js'
import { PermissionService } from '@/shared/services/permission.js'
import { bot } from '@/bot/index.js'
// Shared keyboard builders for consistent UI
const MANAGE_PERMISSION_OPTIONS: { value: RoleManagePermission, label: string }[] = [
{ value: 'everyone', label: 'Everyone' },
{ value: 'all_admins', label: 'All Admins' },
{ value: 'admin_can_promote_members', label: 'Admins: Promote Members' },
{ value: 'admin_can_change_info', label: 'Admins: Change Info' },
{ value: 'admin_can_manage_chat', label: 'Admins: Manage Chat' },
{ value: 'only_owner', label: 'Only Owner' },
]
const MENTION_PERMISSION_OPTIONS: { value: RoleMentionPermission, label: string }[] = [
{ value: 'everyone', label: 'Everyone' },
{ value: 'all_admins', label: 'All Admins' },
{ value: 'only_owner', label: 'Only Owner' },
]
function buildManageKeyboard(currentValue: RoleManagePermission): InlineKeyboard {
const keyboard = new InlineKeyboard()
for (const option of MANAGE_PERMISSION_OPTIONS) {
const isSelected = option.value === currentValue
const label = isSelected ? `${option.label}` : option.label
keyboard.text(label, `config:manage:${option.value}`).row()
}
keyboard.text('« Back to Config', 'config:back')
return keyboard
}
function buildMentionKeyboard(currentValue: RoleMentionPermission): InlineKeyboard {
const keyboard = new InlineKeyboard()
for (const option of MENTION_PERMISSION_OPTIONS) {
const isSelected = option.value === currentValue
const label = isSelected ? `${option.label}` : option.label
keyboard.text(label, `config:mention:${option.value}`).row()
}
keyboard.text('« Back to Config', 'config:back')
return keyboard
}
function buildMainConfigKeyboard(isOwner: boolean): InlineKeyboard {
const keyboard = new InlineKeyboard()
if (isOwner) {
keyboard.text('⚙️ Role Management', 'config:show:manage').row()
keyboard.text('📢 Role Mentions', 'config:show:mention')
}
return keyboard
}
export default function (bot: BotType) {
// Command to show current configuration
bot.command('config', async (ctx) => {
if (!['group', 'supergroup'].includes(ctx.chat.type)) {
return ctx.reply('This command can only be used in groups or supergroups')
}
// Any admin can view config
const isAdmin = await isChatAdmin(ctx.chat.id, ctx.from.id)
if (!isAdmin) {
return ctx.reply('This command can only be used by admins')
}
const isOwner = await isChatOwner(ctx.chat.id, ctx.from.id)
const config = await ChatConfigService.getOrCreate(ctx.chat.id)
return ctx.reply(
format`
${bold('Chat Configuration')}
${bold('Role Management:')}
${italic(PermissionService.describeRoleManagePermission(config.roleManagePermission))}
${bold('Role Mentions:')}
${italic(PermissionService.describeRoleMentionPermission(config.roleMentionPermission))}
${isOwner ? '' : italic('\nOnly the chat owner can change settings.')}
`,
{
reply_markup: buildMainConfigKeyboard(isOwner),
},
)
})
// Handle navigation to manage settings
bot.callbackQuery('config:show:manage', async (ctx) => {
const chatId = ctx.message?.chat.id
const messageId = ctx.message?.id
if (!chatId || !messageId) {
return ctx.answerCallbackQuery({ text: 'Error: Could not determine chat', show_alert: true })
}
const isOwner = await isChatOwner(chatId, ctx.from.id)
if (!isOwner) {
return ctx.answerCallbackQuery({
text: 'Only the chat owner can change settings',
show_alert: true,
})
}
const config = await ChatConfigService.getOrCreate(chatId)
await bot.api.editMessageText({
chat_id: chatId,
message_id: messageId,
text: format`
${bold('Role Management Permission')}
Current: ${italic(PermissionService.describeRoleManagePermission(config.roleManagePermission))}
Select who can add/delete roles:
`,
reply_markup: buildManageKeyboard(config.roleManagePermission),
})
return ctx.answerCallbackQuery()
})
// Handle navigation to mention settings
bot.callbackQuery('config:show:mention', async (ctx) => {
const chatId = ctx.message?.chat.id
const messageId = ctx.message?.id
if (!chatId || !messageId) {
return ctx.answerCallbackQuery({ text: 'Error: Could not determine chat', show_alert: true })
}
const isOwner = await isChatOwner(chatId, ctx.from.id)
if (!isOwner) {
return ctx.answerCallbackQuery({
text: 'Only the chat owner can change settings',
show_alert: true,
})
}
const config = await ChatConfigService.getOrCreate(chatId)
await bot.api.editMessageText({
chat_id: chatId,
message_id: messageId,
text: format`
${bold('Role Mention Permission')}
Current: ${italic(PermissionService.describeRoleMentionPermission(config.roleMentionPermission))}
Select who can mention roles:
`,
reply_markup: buildMentionKeyboard(config.roleMentionPermission),
})
return ctx.answerCallbackQuery()
})
// Handle manage permission selection
bot.callbackQuery(/^config:manage:(.+)$/, async (ctx) => {
const chatId = ctx.message?.chat.id
const messageId = ctx.message?.id
if (!chatId || !messageId) {
return ctx.answerCallbackQuery({ text: 'Error: Could not determine chat', show_alert: true })
}
const isOwner = await isChatOwner(chatId, ctx.from.id)
if (!isOwner) {
return ctx.answerCallbackQuery({
text: 'Only the chat owner can change this setting',
show_alert: true,
})
}
const permission = ctx.queryPayload.split(':')[2] as RoleManagePermission
const validOptions: RoleManagePermission[] = [
'everyone',
'all_admins',
'admin_can_promote_members',
'admin_can_change_info',
'admin_can_manage_chat',
'only_owner',
]
if (!validOptions.includes(permission)) {
return ctx.answerCallbackQuery({
text: 'Invalid option',
show_alert: true,
})
}
const updated = await ChatConfigService.setRoleManagePermission(chatId, permission)
if (!updated) {
return ctx.answerCallbackQuery({
text: 'Failed to update configuration',
show_alert: true,
})
}
await bot.api.editMessageText({
chat_id: chatId,
message_id: messageId,
text: format`
${bold('Role Management Permission')}
Current: ${italic(PermissionService.describeRoleManagePermission(updated.roleManagePermission))}
Select who can add/delete roles:
`,
reply_markup: buildManageKeyboard(updated.roleManagePermission),
})
return ctx.answerCallbackQuery({
text: `Updated: ${PermissionService.describeRoleManagePermission(updated.roleManagePermission)}`,
})
})
// Handle mention permission selection
bot.callbackQuery(/^config:mention:(.+)$/, async (ctx) => {
const chatId = ctx.message?.chat.id
const messageId = ctx.message?.id
if (!chatId || !messageId) {
return ctx.answerCallbackQuery({ text: 'Error: Could not determine chat', show_alert: true })
}
const isOwner = await isChatOwner(chatId, ctx.from.id)
if (!isOwner) {
return ctx.answerCallbackQuery({
text: 'Only the chat owner can change this setting',
show_alert: true,
})
}
const permission = ctx.queryPayload.split(':')[2] as RoleMentionPermission
const validOptions: RoleMentionPermission[] = ['everyone', 'all_admins', 'only_owner']
if (!validOptions.includes(permission)) {
return ctx.answerCallbackQuery({
text: 'Invalid option',
show_alert: true,
})
}
const updated = await ChatConfigService.setRoleMentionPermission(chatId, permission)
if (!updated) {
return ctx.answerCallbackQuery({
text: 'Failed to update configuration',
show_alert: true,
})
}
await bot.api.editMessageText({
chat_id: chatId,
message_id: messageId,
text: format`
${bold('Role Mention Permission')}
Current: ${italic(PermissionService.describeRoleMentionPermission(updated.roleMentionPermission))}
Select who can mention roles:
`,
reply_markup: buildMentionKeyboard(updated.roleMentionPermission),
})
return ctx.answerCallbackQuery({
text: `Updated: ${PermissionService.describeRoleMentionPermission(updated.roleMentionPermission)}`,
})
})
// Handle back button to main config
bot.callbackQuery('config:back', async (ctx) => {
const chatId = ctx.message?.chat.id
const messageId = ctx.message?.id
if (!chatId || !messageId) {
return ctx.answerCallbackQuery({ text: 'Error: Could not determine chat', show_alert: true })
}
const isOwner = await isChatOwner(chatId, ctx.from.id)
const config = await ChatConfigService.getOrCreate(chatId)
await bot.api.editMessageText({
chat_id: chatId,
message_id: messageId,
text: format`
${bold('Chat Configuration')}
${bold('Role Management:')}
${italic(PermissionService.describeRoleManagePermission(config.roleManagePermission))}
${bold('Role Mentions:')}
${italic(PermissionService.describeRoleMentionPermission(config.roleMentionPermission))}
${isOwner ? '' : italic('\nOnly the chat owner can change settings.')}
`,
reply_markup: buildMainConfigKeyboard(isOwner),
})
return ctx.answerCallbackQuery()
})
}

View file

@ -1,7 +1,7 @@
import type { BotType } from '@/bot/index.js'
import { bold, code, format, italic } from 'gramio'
import { RESERVED_NAMES, ROLE_NAME_REGEX } from '@/bot/utilities/constants.js'
import { isChatAdmin } from '@/bot/utilities/perms.js'
import { PermissionService } from '@/shared/services/permission.js'
import { RoleService } from '@/shared/services/role.js'
export default function (bot: BotType) {
@ -10,9 +10,9 @@ export default function (bot: BotType) {
return ctx.reply('This command can only be used in groups or supergroups')
}
const isAdmin = await isChatAdmin(ctx.chat.id, ctx.from.id)
if (!isAdmin) {
return ctx.reply('this command can only be used by admins')
const canManage = await PermissionService.canManageRoles(ctx.chat.id, ctx.from.id)
if (!canManage) {
return ctx.reply('You don\'t have permission to manage roles')
}
let [slug] = (ctx.args ?? '').split(' ')
@ -45,9 +45,9 @@ export default function (bot: BotType) {
await bot.api.setMyCommands({
scope: {
chat_id: ctx.chatId,
type: 'chat'
type: 'chat',
},
commands: roles.map((role) => ({
commands: roles.map(role => ({
command: role.slug,
description: `Mention all members of this role`,
})),

View file

@ -1,7 +1,7 @@
import { BotType } from "@/bot/index.js"
import { isChatAdmin } from "@/bot/utilities/perms.js"
import { RoleService } from "@/shared/services/role.js"
import { bold, code, format } from "gramio"
import type { BotType } from '@/bot/index.js'
import { bold, code, format } from 'gramio'
import { PermissionService } from '@/shared/services/permission.js'
import { RoleService } from '@/shared/services/role.js'
export default (bot: BotType) => {
bot.command('roledel', async (ctx) => {
@ -9,9 +9,9 @@ export default (bot: BotType) => {
return ctx.reply('this command can only be used in groups or supergroups')
}
const isAdmin = await isChatAdmin(ctx.chat.id, ctx.from.id)
if (!isAdmin) {
return ctx.reply('this command can only be used by admins')
const canManage = await PermissionService.canManageRoles(ctx.chat.id, ctx.from.id)
if (!canManage) {
return ctx.reply('You don\'t have permission to manage roles')
}
let [roleSlug] = (ctx.args ?? '').trim().split(' ')
@ -27,7 +27,7 @@ export default (bot: BotType) => {
}
const members = await RoleService.getMemberIds(roleSlug, ctx.chatId)
await Promise.all(members.map((member) => RoleService.removeMember(role.id!, member)))
await Promise.all(members.map(member => RoleService.removeMember(role.id!, member)))
const deletedRole = await RoleService.delete(role.id!)
if (!deletedRole) {
@ -36,18 +36,18 @@ export default (bot: BotType) => {
setImmediate(async () => {
const roles = await RoleService.getByChatId(ctx.chatId)
await bot.api.setMyCommands({
scope: {
chat_id: ctx.chatId,
type: 'chat'
},
commands: roles.map((role) => ({
command: role.slug,
description: `Mention all members of this role`,
})),
})
await bot.api.setMyCommands({
scope: {
chat_id: ctx.chatId,
type: 'chat',
},
commands: roles.map(role => ({
command: role.slug,
description: `Mention all members of this role`,
})),
})
})
return ctx.reply(format`role ${bold(role.slug)} deleted`)
})
}
}