+plugin.description = 'Add a trigger (auto-response pattern)'
+plugin.commands = { 'addtrigger' }
+plugin.help = '/addtrigger <pattern> <response> - Adds a trigger. Use /deltrigger <number> to remove.'
+plugin.group_only = true
+plugin.admin_only = true
+
+function plugin.on_message(api, message, ctx)
+ local tools = require('telegram-bot-lua.tools')
+
+ if not message.args then
+ return api.send_message(message.chat.id, 'Usage: /addtrigger <pattern> <response>\n\nThe pattern is a Lua pattern that will be matched against incoming messages. When matched, the response is sent.')
+plugin.description = 'Manage the group allowlist'
+plugin.commands = { 'allowlist' }
+plugin.help = '/allowlist add <user> - Adds a user to the allowlist. /allowlist remove <user> - Removes a user. /allowlist - Lists allowlisted users.'
+plugin.group_only = true
+plugin.admin_only = true
+
+function plugin.on_message(api, message, ctx)
+ local tools = require('telegram-bot-lua.tools')
+
+ if not message.args then
+ -- List allowlisted users
+ local result = ctx.db.execute(
+ "SELECT user_id FROM chat_members WHERE chat_id = $1 AND role = 'allowlisted'",
+ { message.chat.id }
+ )
+ if not result or #result == 0 then
+ return api.send_message(message.chat.id, 'No users are allowlisted.\nUsage: /allowlist add <user>')
+ end
+ local output = '<b>Allowlisted users:</b>\n\n'
+ for _, row in ipairs(result) do
+ local info = api.get_chat(row.user_id)
+ local name = info and info.result and tools.escape_html(info.result.first_name) or tostring(row.user_id)
+ { text = 'No, cancel', callback_data = callback_data_no }
+ } }
+ }
+
+ return api.send_message(
+ message.chat.id,
+ string.format(
+ 'Are you sure you want to delete the federation <b>%s</b>?\n\nThis will remove all bans, chats, and admins associated with it. This action cannot be undone.',
+ local user_id = ctx.redis.get('username:' .. username)
+ if user_id then
+ return tonumber(user_id), '@' .. username
+ end
+ end
+ return nil, nil
+end
+
+local function get_chat_federation(db, chat_id)
+ local result = db.execute(
+ 'SELECT f.id, f.name, f.owner_id FROM federations f JOIN federation_chats fc ON f.id = fc.federation_id WHERE fc.chat_id = $1',
+ { chat_id }
+ )
+ if result and #result > 0 then return result[1] end
+ return nil
+end
+
+local function is_fed_admin(db, fed_id, user_id)
+ local result = db.execute(
+ 'SELECT 1 FROM federation_admins WHERE federation_id = $1 AND user_id = $2',
+ { fed_id, user_id }
+ )
+ return result and #result > 0
+end
+
+function plugin.on_message(api, message, ctx)
+ local fed = get_chat_federation(ctx.db, message.chat.id)
+ if not fed then
+ return api.send_message(
+ message.chat.id,
+ 'This chat is not part of any federation.',
+ 'html'
+ )
+ end
+
+ local from_id = message.from.id
+ if fed.owner_id ~= from_id and not is_fed_admin(ctx.db, fed.id, from_id) then
+ return api.send_message(
+ message.chat.id,
+ 'Only the federation owner or a federation admin can manage the allowlist.',
+ 'html'
+ )
+ end
+
+ local target_id, target_name = resolve_user(message, ctx)
+ if not target_id then
+ return api.send_message(
+ message.chat.id,
+ 'Please specify a user to toggle on the allowlist by replying to their message or providing a user ID/username.\nUsage: <code>/fallowlist [user]</code>',
+ 'html'
+ )
+ end
+
+ -- Check if already allowlisted
+ local existing = ctx.db.execute(
+ 'SELECT 1 FROM federation_allowlist WHERE federation_id = $1 AND user_id = $2',
+ { fed.id, target_id }
+ )
+
+ if existing and #existing > 0 then
+ -- Remove from allowlist
+ ctx.db.execute(
+ 'DELETE FROM federation_allowlist WHERE federation_id = $1 AND user_id = $2',
+ 'Failed to create the federation. Please try again later.',
+ 'html'
+ )
+ end
+
+ local fed_id = result[1].id
+ local output = string.format(
+ 'Federation <b>%s</b> created successfully!\n\nFederation ID: <code>%s</code>\n\nUse <code>/joinfed %s</code> in a group to add it to this federation.',
+plugin.help = '/purge - Deletes all messages from the replied-to message up to the command message.'
+plugin.group_only = true
+plugin.admin_only = true
+
+function plugin.on_message(api, message, ctx)
+ local permissions = require('src.core.permissions')
+ if not permissions.can_delete(api, message.chat.id) then
+ return api.send_message(message.chat.id, 'I need the "Delete Messages" admin permission to use this command.')
+ end
+
+ if not message.reply then
+ return api.send_message(message.chat.id, 'Please reply to the first message you want to delete, and all messages from that point to your command will be purged.')
+ end
+
+ local start_id = message.reply.message_id
+ local end_id = message.message_id
+ local count = 0
+ local failed = 0
+
+ for msg_id = start_id, end_id do
+ local success = api.delete_message(message.chat.id, msg_id)
+ Must be enabled via AI_ENABLED=true in configuration.
+]]
+
+local plugin = {}
+plugin.name = 'ai'
+plugin.category = 'ai'
+plugin.description = 'Chat with an AI assistant'
+plugin.commands = { 'ai', 'ask' }
+plugin.help = '/ai <prompt> - Send a prompt to the AI assistant and receive a response.'
+
+local MAX_HISTORY = 10
+local SYSTEM_PROMPT = 'You are a helpful assistant embedded in a Telegram bot called mattata. Be concise and direct in your responses. Use Telegram-compatible formatting (bold, italic, code) when helpful.'
+plugin.description = 'View information about the bot'
+plugin.commands = { 'about' }
+plugin.help = '/about - View information about the bot.'
+plugin.permanent = true
+
+function plugin.on_message(api, message, ctx)
+ local tools = require('telegram-bot-lua.tools')
+ local config = require('src.core.config')
+ local output = string.format(
+ 'Created by <a href="tg://user?id=221714512">Matt</a>. Powered by <code>mattata v%s</code>. Source code available <a href="https://github.com/wrxck/mattata">on GitHub</a>.',
+ -- If argument given, show help for specific command
+ if message.args and message.args ~= '' then
+ local input = message.args:match('^/?(%w+)$')
+ if input then
+ local target = loader.get_by_command(input:lower())
+ if target and target.help then
+ return api.send_message(message.chat.id, 'Usage:\n' .. target.help .. '\n\nTo see all commands, send /help.')
+ end
+ return api.send_message(message.chat.id, 'No plugin found matching that command. Send /help to see all available commands.')
+ end
+ end
+
+ -- Show main help menu
+ local bot_name = ctx.config.bot_name()
+ local name = tools.escape_html(message.from.first_name)
+ local output = string.format(
+ 'Hey %s! I\'m <b>%s</b>, a feature-rich Telegram bot.\n\nUse the buttons below to navigate my commands, or type <code>/help <command></code> for details on a specific command.',
+ local name = tools.escape_html(callback_query.from.first_name)
+ local output = string.format(
+ 'Hey %s! I\'m <b>%s</b>, a feature-rich Telegram bot.\n\nUse the buttons below to navigate my commands, or type <code>/help <command></code> for details on a specific command.',
+plugin.description = 'View your Last.fm now playing and recent tracks'
+plugin.commands = { 'lastfm', 'np', 'fmset' }
+plugin.help = '/np - Show your currently playing or most recent track.\n/fmset <username> - Link your Last.fm account.\n/lastfm [username] - View recent tracks for a Last.fm user.'
+
+function plugin.on_message(api, message, ctx)
+ local https = require('ssl.https')
+ local json = require('dkjson')
+ local url = require('socket.url')
+ local tools = require('telegram-bot-lua.tools')
+ local config = require('src.core.config')
+
+ local api_key = config.get('LASTFM_API_KEY')
+ if not api_key or api_key == '' then
+ return api.send_message(message.chat.id, 'Last.fm is not configured. The bot admin needs to set LASTFM_API_KEY.')
+ end
+
+ -- /fmset: link Last.fm username
+ if message.command == 'fmset' then
+ local username = message.args
+ if not username or username == '' then
+ return api.send_message(message.chat.id, 'Please provide your Last.fm username. Usage: /fmset <username>')
+ if not reminder_text or reminder_text == '' then
+ return api.send_message(message.chat.id, 'Please include a reminder message after the duration.')
+ end
+
+ if duration < 30 then
+ return api.send_message(message.chat.id, 'Minimum reminder duration is 30 seconds.')
+ end
+
+ if duration > MAX_DURATION then
+ return api.send_message(message.chat.id, 'Maximum reminder duration is 7 days.')
+ end
+
+ -- Check reminder limit
+ local existing = get_user_reminders(redis, message.chat.id, message.from.id)
+ -- Filter to only count non-expired ones
+ local active_count = 0
+ for _, key in ipairs(existing) do
+ local expires = redis.hget(key, 'expires')
+ if expires and tonumber(expires) > os.time() then
+ active_count = active_count + 1
+ else
+ -- Clean up expired entry
+ redis.del(key)
+ end
+ end
+ if active_count >= MAX_REMINDERS then
+ return api.send_message(
+ message.chat.id,
+ string.format('You already have %d active reminders in this chat (max %d). Wait for one to expire or use /reminders to check them.', active_count, MAX_REMINDERS)
+plugin.help = '/stats - View top 10 most active users in this chat.\n/morestats - View extended stats.\n/stats reset - Reset statistics (admin only).'
+plugin.group_only = true
+
+function plugin.on_message(api, message, ctx)
+ local tools = require('telegram-bot-lua.tools')
+ local input = message.args
+
+ -- Handle reset
+ if input and input:lower() == 'reset' then
+ if not ctx.is_admin and not ctx.is_global_admin then
+ return api.send_message(message.chat.id, 'You need to be an admin to reset statistics.')
+ end
+ ctx.db.execute(
+ 'DELETE FROM message_stats WHERE chat_id = $1',
+ { message.chat.id }
+ )
+ return api.send_message(message.chat.id, 'Message statistics have been reset for this chat.')
+ end
+
+ -- Query top 10 users by message count
+ local result = ctx.db.execute(
+ [[SELECT ms.user_id, SUM(ms.message_count) AS total,
+ u.first_name, u.last_name, u.username
+ FROM message_stats ms
+ LEFT JOIN users u ON ms.user_id = u.user_id
+ WHERE ms.chat_id = $1
+ GROUP BY ms.user_id, u.first_name, u.last_name, u.username
+ ORDER BY total DESC
+ LIMIT 10]],
+ { message.chat.id }
+ )
+
+ if not result or #result == 0 then
+ return api.send_message(message.chat.id, 'No message statistics available for this chat yet.')
+ end
+
+ local lines = { '<b>Message Statistics</b>', '' }
+ local total_messages = 0
+ for i, row in ipairs(result) do
+ local name = tools.escape_html(row.first_name or 'Unknown')
+ if row.last_name then
+ name = name .. ' ' .. tools.escape_html(row.last_name)
+ Translates text using LibreTranslate public API.
+ Supports auto-detection of source language.
+]]
+
+local plugin = {}
+plugin.name = 'translate'
+plugin.category = 'utility'
+plugin.description = 'Translate text between languages'
+plugin.commands = { 'translate', 'tl' }
+plugin.help = '/translate [lang] <text> - Translate text to the specified language (default: en). Reply to a message to translate it, or provide text directly.'
+
+local https = require('ssl.https')
+local json = require('dkjson')
+local url = require('socket.url')
+local ltn12 = require('ltn12')
+local tools = require('telegram-bot-lua.tools')
+
+local BASE_URL = 'https://libretranslate.com'
+
+-- Common language code aliases
+local LANG_ALIASES = {
+ english = 'en', en = 'en',
+ spanish = 'es', es = 'es',
+ french = 'fr', fr = 'fr',
+ german = 'de', de = 'de',
+ italian = 'it', it = 'it',
+ portuguese = 'pt', pt = 'pt',
+ russian = 'ru', ru = 'ru',
+ chinese = 'zh', zh = 'zh',
+ japanese = 'ja', ja = 'ja',
+ korean = 'ko', ko = 'ko',
+ arabic = 'ar', ar = 'ar',
+ hindi = 'hi', hi = 'hi',
+ dutch = 'nl', nl = 'nl',
+ polish = 'pl', pl = 'pl',
+ turkish = 'tr', tr = 'tr',
+ swedish = 'sv', sv = 'sv',
+ czech = 'cs', cs = 'cs',
+ romanian = 'ro', ro = 'ro',
+ hungarian = 'hu', hu = 'hu',
+ ukrainian = 'uk', uk = 'uk',
+ indonesian = 'id', id = 'id',
+ finnish = 'fi', fi = 'fi',
+ hebrew = 'he', he = 'he',
+ thai = 'th', th = 'th',
+ vietnamese = 'vi', vi = 'vi',
+ greek = 'el', el = 'el'
+}
+
+local function translate_text(text, target, source)
+ source = source or 'auto'
+ local request_body = json.encode({
+ q = text,
+ source = source,
+ target = target,
+ format = 'text'
+ })
+ local body = {}
+ local _, code = https.request({
+ url = BASE_URL .. '/translate',
+ method = 'POST',
+ headers = {
+ ['Content-Type'] = 'application/json',
+ ['Content-Length'] = tostring(#request_body)
+ },
+ source = ltn12.source.string(request_body),
+ sink = ltn12.sink.table(body)
+ })
+ if code ~= 200 then
+ return nil, 'Translation service returned an error (HTTP ' .. tostring(code) .. '). The public instance may be rate-limited; try again shortly.'
+ end
+ local data = json.decode(table.concat(body))
+ if not data then
+ return nil, 'Failed to parse translation response.'