Page MenuHomePhabricator (Chris)

No OneTemporary

Authored By
Unknown
Size
17 KB
Referenced Files
None
Subscribers
None
diff --git a/.env.example b/.env.example
index 3f865a1..2504f69 100644
--- a/.env.example
+++ b/.env.example
@@ -1,46 +1,52 @@
# mattata v2.0 Configuration
# Copy to .env and fill in required values
# Required
BOT_TOKEN=
BOT_ADMINS=221714512
BOT_NAME=mattata
# PostgreSQL (primary database)
DATABASE_HOST=postgres
DATABASE_PORT=5432
DATABASE_NAME=mattata
DATABASE_USER=mattata
DATABASE_PASSWORD=changeme
# Redis (cache/sessions only)
REDIS_HOST=redis
REDIS_PORT=6379
REDIS_PASSWORD=
REDIS_DB=0
# Mode: polling (default) or webhook
WEBHOOK_ENABLED=false
WEBHOOK_URL=
WEBHOOK_PORT=8443
WEBHOOK_SECRET=
POLLING_TIMEOUT=60
POLLING_LIMIT=100
# AI (disabled by default)
AI_ENABLED=false
OPENAI_API_KEY=
OPENAI_MODEL=gpt-4o
ANTHROPIC_API_KEY=
ANTHROPIC_MODEL=claude-sonnet-4-5-20250929
# Optional API keys (core features work without any of these)
LASTFM_API_KEY=
YOUTUBE_API_KEY=
SPOTIFY_CLIENT_ID=
SPOTIFY_CLIENT_SECRET=
SPAMWATCH_TOKEN=
+# Bot links (optional, defaults shown)
+CHANNEL_URL=https://t.me/mattata
+SUPPORT_URL=https://t.me/mattataSupport
+GITHUB_URL=https://github.com/wrxck/mattata
+DEV_URL=https://t.me/mattataDev
+
# Logging
LOG_CHAT=
DEBUG=false
diff --git a/main.lua b/main.lua
index 703464d..7cde9aa 100644
--- a/main.lua
+++ b/main.lua
@@ -1,94 +1,120 @@
--[[
_ _ _
_ __ ___ __ _| |_| |_ __ _| |_ __ _
| '_ ` _ \ / _` | __| __/ _` | __/ _` |
| | | | | | (_| | |_| || (_| | || (_| |
|_| |_| |_|\__,_|\__|\__\__,_|\__\__,_|
v2.1
Copyright 2020-2026 Matthew Hesketh <matthew@matthewhesketh.com>
See LICENSE for details
]]
local config = require('src.core.config')
local logger = require('src.core.logger')
local database = require('src.core.database')
local redis = require('src.core.redis')
local session = require('src.core.session')
local i18n = require('src.core.i18n')
local loader = require('src.core.loader')
local router = require('src.core.router')
local migrations = require('src.db.init')
-- 1. Load configuration
config.load('.env')
logger.init()
logger.info('mattata v%s starting...', config.VERSION)
-- 2. Validate required config
assert(config.bot_token(), 'BOT_TOKEN is required. Set it in .env or as an environment variable.')
-- 3. Configure telegram-bot-lua
local api = require('telegram-bot-lua').configure(config.bot_token())
local tools = require('telegram-bot-lua.tools')
logger.info('Bot: @%s (%s) [%d]', api.info.username, api.info.first_name, api.info.id)
-- 4. Connect to PostgreSQL
local db_ok, db_err = database.connect()
if not db_ok then
logger.error('Cannot start without PostgreSQL: %s', tostring(db_err))
os.exit(1)
end
-- 5. Run database migrations
migrations.run(database)
-- 6. Connect to Redis
local redis_ok, redis_err = redis.connect()
if not redis_ok then
logger.error('Cannot start without Redis: %s', tostring(redis_err))
os.exit(1)
end
session.init(redis)
-- 7. Load languages
i18n.init()
-- 8. Load all plugins
loader.init(api, database, redis)
-- 9. Build context factory and start router
local ctx_base = {
api = api,
tools = tools,
db = database,
redis = redis,
session = session,
config = config,
i18n = i18n,
permissions = require('src.core.permissions'),
logger = logger
}
router.init(api, tools, loader, ctx_base)
--- 10. Notify admins
+-- 10. Register bot command menu with Telegram
+local json = require('dkjson')
+local user_commands = {}
+local admin_commands = {}
+for _, plugin in ipairs(loader.get_plugins()) do
+ if plugin.commands and #plugin.commands > 0 and plugin.description and plugin.description ~= '' then
+ local entry = { command = plugin.commands[1], description = plugin.description }
+ if plugin.admin_only or plugin.global_admin_only then
+ table.insert(admin_commands, entry)
+ else
+ table.insert(user_commands, entry)
+ end
+ end
+end
+-- Set default commands (visible to all users)
+api.set_my_commands(json.encode(user_commands))
+-- Set admin commands (visible to group admins only)
+if #admin_commands > 0 then
+ -- Merge user + admin so admins see everything
+ local all_commands = {}
+ for _, cmd in ipairs(user_commands) do table.insert(all_commands, cmd) end
+ for _, cmd in ipairs(admin_commands) do table.insert(all_commands, cmd) end
+ api.set_my_commands(json.encode(all_commands), { type = 'all_chat_administrators' })
+end
+logger.info('Registered %d user commands and %d admin commands with Telegram', #user_commands, #admin_commands)
+
+-- 11. Notify admins
local info_msg = string.format(
'<pre>mattata v%s connected!\n\n Username: @%s\n Name: %s\n ID: %d\n Plugins: %d</pre>',
config.VERSION,
tools.escape_html(api.info.username),
tools.escape_html(api.info.first_name),
api.info.id,
loader.count()
)
if config.log_chat() then
api.send_message(config.log_chat(), info_msg, 'html')
end
for _, admin_id in ipairs(config.bot_admins()) do
api.send_message(admin_id, info_msg, 'html')
end
--- 11. Start the bot
+-- 12. Start the bot
logger.info('Starting main loop...')
router.run()
diff --git a/src/plugins/utility/about.lua b/src/plugins/utility/about.lua
index 2fec74e..71bdf94 100644
--- a/src/plugins/utility/about.lua
+++ b/src/plugins/utility/about.lua
@@ -1,22 +1,24 @@
--[[
mattata v2.0 - About Plugin
]]
local plugin = {}
plugin.name = 'about'
plugin.category = 'utility'
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 config = require('src.core.config')
+ local owner_id = config.get_list('BOT_ADMINS')[1] or '221714512'
+ local github_url = config.get('GITHUB_URL', 'https://github.com/wrxck/mattata')
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>.',
- config.VERSION
+ 'Created by <a href="tg://user?id=%s">Matt</a>. Powered by <code>mattata v%s</code>. Source code available <a href="%s">on GitHub</a>.',
+ owner_id, config.VERSION, github_url
)
return api.send_message(message.chat.id, output, 'html')
end
return plugin
diff --git a/src/plugins/utility/help.lua b/src/plugins/utility/help.lua
index 31c64c4..51811b5 100644
--- a/src/plugins/utility/help.lua
+++ b/src/plugins/utility/help.lua
@@ -1,161 +1,166 @@
--[[
mattata v2.0 - Help Plugin
Displays help menus with inline keyboard navigation.
]]
local plugin = {}
plugin.name = 'help'
plugin.category = 'utility'
plugin.description = 'View bot help and command list'
plugin.commands = { 'help', 'start' }
plugin.help = '/help [command] - View help menu or get usage info for a specific command.'
plugin.permanent = true
local PER_PAGE = 10
local function get_page(items, page)
local start_idx = (page - 1) * PER_PAGE + 1
local end_idx = math.min(start_idx + PER_PAGE - 1, #items)
local result = {}
for i = start_idx, end_idx do
table.insert(result, items[i])
end
return result, math.ceil(#items / PER_PAGE)
end
local function format_help_list(help_items)
local lines = {}
for _, item in ipairs(help_items) do
local cmd = item.commands[1] and ('/' .. item.commands[1]) or ''
local desc = item.description or ''
table.insert(lines, string.format('%s %s - <em>%s</em>', '\xe2\x80\xa2', cmd, desc))
end
return table.concat(lines, '\n')
end
function plugin.on_message(api, message, ctx)
local tools = require('telegram-bot-lua.tools')
local loader = require('src.core.loader')
-- 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 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 &lt;command&gt;</code> for details on a specific command.',
name, tools.escape_html(api.info.first_name)
)
local keyboard = api.inline_keyboard():row(
api.row():callback_data_button('Commands', 'help:cmds:1')
:callback_data_button('Admin Help', 'help:acmds:1')
):row(
api.row():callback_data_button('Links', 'help:links')
:callback_data_button('Settings', 'help:settings')
)
return api.send_message(message.chat.id, output, 'html', true, false, nil, keyboard)
end
function plugin.on_callback_query(api, callback_query, message, ctx)
local tools = require('telegram-bot-lua.tools')
local loader = require('src.core.loader')
local data = callback_query.data
if data:match('^cmds:%d+$') then
local page = tonumber(data:match('^cmds:(%d+)$'))
local all_help = loader.get_help(nil)
-- Filter non-admin
local items = {}
for _, h in ipairs(all_help) do
if h.category ~= 'admin' then
table.insert(items, h)
end
end
local page_items, total_pages = get_page(items, page)
if page < 1 then page = total_pages end
if page > total_pages then page = 1 end
page_items, total_pages = get_page(items, page)
local output = format_help_list(page_items)
local keyboard = api.inline_keyboard():row(
api.row():callback_data_button('<', 'help:cmds:' .. (page - 1))
:callback_data_button(page .. '/' .. total_pages, 'help:noop')
:callback_data_button('>', 'help:cmds:' .. (page + 1))
):row(
api.row():callback_data_button('Back', 'help:back')
)
return api.edit_message_text(message.chat.id, message.message_id, output, 'html', true, keyboard)
elseif data:match('^acmds:%d+$') then
local page = tonumber(data:match('^acmds:(%d+)$'))
local items = loader.get_help('admin')
local page_items, total_pages = get_page(items, page)
if page < 1 then page = total_pages end
if page > total_pages then page = 1 end
page_items, total_pages = get_page(items, page)
local output = format_help_list(page_items)
local keyboard = api.inline_keyboard():row(
api.row():callback_data_button('<', 'help:acmds:' .. (page - 1))
:callback_data_button(page .. '/' .. total_pages, 'help:noop')
:callback_data_button('>', 'help:acmds:' .. (page + 1))
):row(
api.row():callback_data_button('Back', 'help:back')
)
return api.edit_message_text(message.chat.id, message.message_id, output, 'html', true, keyboard)
elseif data == 'links' then
+ local cfg = require('src.core.config')
+ local channel_url = cfg.get('CHANNEL_URL', 'https://t.me/mattata')
+ local support_url = cfg.get('SUPPORT_URL', 'https://t.me/mattataSupport')
+ local github_url = cfg.get('GITHUB_URL', 'https://github.com/wrxck/mattata')
+ local dev_url = cfg.get('DEV_URL', 'https://t.me/mattataDev')
local keyboard = api.inline_keyboard():row(
- api.row():url_button('Development', 'https://t.me/mattataDev')
- :url_button('Channel', 'https://t.me/mattata')
+ api.row():url_button('Development', dev_url)
+ :url_button('Channel', channel_url)
):row(
- api.row():url_button('GitHub', 'https://github.com/wrxck/mattata')
- :url_button('Support', 'https://t.me/mattataSupport')
+ api.row():url_button('GitHub', github_url)
+ :url_button('Support', support_url)
):row(
api.row():callback_data_button('Back', 'help:back')
)
return api.edit_message_text(message.chat.id, message.message_id, 'Useful links:', nil, true, keyboard)
elseif data == 'settings' then
local permissions = require('src.core.permissions')
if message.chat.type == 'supergroup' and not permissions.is_group_admin(api, message.chat.id, callback_query.from.id) then
return api.answer_callback_query(callback_query.id, 'You need to be an admin to change settings.')
end
local keyboard = api.inline_keyboard():row(
api.row():callback_data_button('Administration', 'administration:' .. message.chat.id .. ':page:1')
:callback_data_button('Plugins', 'plugins:' .. message.chat.id .. ':page:1')
):row(
api.row():callback_data_button('Back', 'help:back')
)
return api.edit_message_reply_markup(message.chat.id, message.message_id, nil, keyboard)
elseif data == 'back' then
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 &lt;command&gt;</code> for details on a specific command.',
name, tools.escape_html(api.info.first_name)
)
local keyboard = api.inline_keyboard():row(
api.row():callback_data_button('Commands', 'help:cmds:1')
:callback_data_button('Admin Help', 'help:acmds:1')
):row(
api.row():callback_data_button('Links', 'help:links')
:callback_data_button('Settings', 'help:settings')
)
return api.edit_message_text(message.chat.id, message.message_id, output, 'html', true, keyboard)
elseif data == 'noop' then
return api.answer_callback_query(callback_query.id)
end
end
return plugin
diff --git a/src/plugins/utility/id.lua b/src/plugins/utility/id.lua
index fcd1988..3aed6a9 100644
--- a/src/plugins/utility/id.lua
+++ b/src/plugins/utility/id.lua
@@ -1,62 +1,94 @@
--[[
- mattata v2.0 - ID Plugin
- Returns user/chat ID and information.
+ mattata v2.1 - ID Plugin
+ Returns user/chat ID and information with modern Telegram fields.
]]
local plugin = {}
plugin.name = 'id'
plugin.category = 'utility'
plugin.description = 'Get user or chat ID and info'
plugin.commands = { 'id', 'user', 'whoami' }
plugin.help = '/id [user] - Returns ID and info for the given user, or yourself if no argument is given.'
function plugin.on_message(api, message, ctx)
local tools = require('telegram-bot-lua.tools')
local target = message.from
local input = message.args
-- If replying to someone, use their info
if message.reply and message.reply.from then
target = message.reply.from
elseif input and input ~= '' then
-- Try to resolve username or ID
local resolved = input:match('^@?(.+)$')
local user_id = tonumber(resolved) or ctx.redis.get('username:' .. resolved:lower())
if user_id then
local result = api.get_chat(user_id)
if result and result.result then
target = result.result
end
end
end
local lines = {}
table.insert(lines, '<b>User Information</b>')
table.insert(lines, 'ID: <code>' .. target.id .. '</code>')
table.insert(lines, 'Name: ' .. tools.escape_html(target.first_name or ''))
if target.last_name then
table.insert(lines, 'Last name: ' .. tools.escape_html(target.last_name))
end
if target.username then
table.insert(lines, 'Username: @' .. target.username)
end
if target.language_code then
table.insert(lines, 'Language: <code>' .. target.language_code .. '</code>')
end
+ if target.is_bot then
+ table.insert(lines, 'Bot: Yes')
+ end
+ if target.is_premium then
+ table.insert(lines, 'Premium: Yes')
+ end
+ if target.added_to_attachment_menu then
+ table.insert(lines, 'Attachment menu: Yes')
+ end
+
+ -- Profile photo count
+ local photos = api.get_user_profile_photos(target.id, 0, 1)
+ if photos and photos.result and photos.result.total_count then
+ table.insert(lines, 'Profile photos: ' .. photos.result.total_count)
+ end
-- If in a group, also show chat info
if ctx.is_group then
table.insert(lines, '')
table.insert(lines, '<b>Chat Information</b>')
table.insert(lines, 'ID: <code>' .. message.chat.id .. '</code>')
table.insert(lines, 'Title: ' .. tools.escape_html(message.chat.title or ''))
table.insert(lines, 'Type: ' .. (message.chat.type or 'unknown'))
if message.chat.username then
table.insert(lines, 'Username: @' .. message.chat.username)
end
+ if message.chat.is_forum then
+ table.insert(lines, 'Forum: Yes')
+ end
+ -- Fetch full chat info for extra details
+ local chat_info = api.get_chat(message.chat.id)
+ if chat_info and chat_info.result then
+ local chat = chat_info.result
+ if chat.linked_chat_id then
+ table.insert(lines, 'Linked chat: <code>' .. chat.linked_chat_id .. '</code>')
+ end
+ if chat.has_hidden_members then
+ table.insert(lines, 'Hidden members: Yes')
+ end
+ if chat.has_aggressive_anti_spam_enabled then
+ table.insert(lines, 'Aggressive anti-spam: Yes')
+ end
+ end
end
return api.send_message(message.chat.id, table.concat(lines, '\n'), 'html')
end
return plugin

File Metadata

Mime Type
text/x-diff
Expires
Sun, May 17, 1:26 AM (1 d, 15 h)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
63029
Default Alt Text
(17 KB)

Event Timeline