feat(telegram): добавить динамические инлайн-кнопки и инструменты диагностики в боте, установить права исполнения для скриптов
This commit is contained in:
@@ -80,6 +80,42 @@ function tg_request(token, method, payload) {
|
||||
}
|
||||
}
|
||||
|
||||
function clash_request(method, endpoint, payload) {
|
||||
let url = get_clash_url(endpoint);
|
||||
let payload_path = "/tmp/clash_payload_" + method + "_" + time() + "_" + clock()[1] + ".json";
|
||||
let res = null;
|
||||
try {
|
||||
let args = [ "curl", "-s", "-X", method ];
|
||||
if (payload) {
|
||||
fs.writefile(payload_path, sprintf("%J", payload));
|
||||
push(args, "-H", "Content-Type: application/json", "-d", "@" + payload_path);
|
||||
}
|
||||
push(args, url);
|
||||
res = command_capture(command_from_args(args));
|
||||
}
|
||||
catch (e) {
|
||||
if (payload) {
|
||||
try { fs.unlink(payload_path); } catch(err) {}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
if (payload) {
|
||||
try { fs.unlink(payload_path); } catch(err) {}
|
||||
}
|
||||
|
||||
if (!res || res.status != 0 || res.output == "") {
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
return json(res.output);
|
||||
}
|
||||
catch (e) {
|
||||
return res.output;
|
||||
}
|
||||
}
|
||||
|
||||
function send_message(token, chat_id, text, parse_mode) {
|
||||
let payload = {
|
||||
chat_id: int(chat_id),
|
||||
@@ -99,11 +135,12 @@ function send_message_with_keyboard(token, chat_id, text, parse_mode) {
|
||||
inline_keyboard: [
|
||||
[
|
||||
{ text: "📡 Серверы", callback_data: "/servers" },
|
||||
{ text: "🚀 Скорость", callback_data: "/speed" },
|
||||
{ text: "📊 Трафик", callback_data: "/traffic" }
|
||||
{ text: "🛡️ Правила", callback_data: "/rules" },
|
||||
{ text: "💻 Устройства", callback_data: "/devices" }
|
||||
],
|
||||
[
|
||||
{ text: "⚡ Проверить", callback_data: "/test" },
|
||||
{ text: "🚀 Скорость", callback_data: "/speed" },
|
||||
{ text: "📊 Трафик", callback_data: "/traffic" },
|
||||
{ text: "🩺 Диагностика", callback_data: "/doctor" }
|
||||
]
|
||||
]
|
||||
@@ -223,6 +260,10 @@ function handle_servers(token, chat_id) {
|
||||
let text = "📡 *Список серверов и задержка:*\n\n";
|
||||
let count = 0;
|
||||
|
||||
let keyboard = [];
|
||||
let row = [];
|
||||
let btn_count = 0;
|
||||
|
||||
for (let name in keys(data.proxies)) {
|
||||
let proxy = data.proxies[name];
|
||||
let p_type = lc(as_string(proxy.type || ""));
|
||||
@@ -237,16 +278,37 @@ function handle_servers(token, chat_id) {
|
||||
let marker = (name == active_server) ? "🔵" : "•";
|
||||
text += marker + " `" + name + "`: `" + delay + "`\n";
|
||||
count++;
|
||||
|
||||
if (btn_count < 18) {
|
||||
push(row, { text: (name == active_server ? "🔵 " : "") + name, callback_data: "/switch " + name });
|
||||
if (length(row) == 2) {
|
||||
push(keyboard, row);
|
||||
row = [];
|
||||
}
|
||||
btn_count++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (length(row) > 0) {
|
||||
push(keyboard, row);
|
||||
}
|
||||
|
||||
if (count == 0) {
|
||||
text += "_Серверы не найдены._\n";
|
||||
} else {
|
||||
text += "\nℹ️ Активный сервер: *" + active_server + "*";
|
||||
}
|
||||
|
||||
send_message_with_keyboard(token, chat_id, text, "Markdown");
|
||||
push(keyboard, [ { text: "⬅️ Назад в меню", callback_data: "/help" } ]);
|
||||
|
||||
let payload = {
|
||||
chat_id: int(chat_id),
|
||||
text: text,
|
||||
parse_mode: "Markdown",
|
||||
reply_markup: { inline_keyboard: keyboard }
|
||||
};
|
||||
tg_request(token, "sendMessage", payload);
|
||||
}
|
||||
|
||||
function handle_traffic(token, chat_id) {
|
||||
@@ -452,26 +514,140 @@ function handle_backup(token, chat_id) {
|
||||
}
|
||||
}
|
||||
|
||||
function handle_rules(token, chat_id) {
|
||||
let c = uci_core.cursor();
|
||||
if (!c) {
|
||||
send_message(token, chat_id, "❌ Не удалось прочитать конфигурацию.");
|
||||
return;
|
||||
}
|
||||
c.load(CONFIG_NAME);
|
||||
|
||||
let text = "🛡️ <b>Управление правилами обхода:</b>\n\n";
|
||||
let keyboard = [];
|
||||
let count = 0;
|
||||
|
||||
c.foreach(CONFIG_NAME, "section", function(s) {
|
||||
let name = s[".name"];
|
||||
let label = s.label || name;
|
||||
let enabled = s.enabled != "0";
|
||||
let status_icon = enabled ? "🟢" : "🔴";
|
||||
let action = s.action || "bypass";
|
||||
|
||||
text += status_icon + " <b>" + escape_html(label) + "</b> (" + action + ")\n";
|
||||
|
||||
push(keyboard, [
|
||||
{
|
||||
text: (enabled ? "🔴 Выключить " : "🟢 Включить ") + label,
|
||||
callback_data: "/toggle_rule " + name
|
||||
}
|
||||
]);
|
||||
count++;
|
||||
});
|
||||
|
||||
if (count == 0) {
|
||||
text += "<i>Правила отсутствуют.</i>\n";
|
||||
}
|
||||
|
||||
push(keyboard, [ { text: "⬅️ Назад в меню", callback_data: "/help" } ]);
|
||||
|
||||
let payload = {
|
||||
chat_id: int(chat_id),
|
||||
text: text,
|
||||
parse_mode: "HTML",
|
||||
reply_markup: { inline_keyboard: keyboard }
|
||||
};
|
||||
tg_request(token, "sendMessage", payload);
|
||||
}
|
||||
|
||||
function handle_devices(token, chat_id) {
|
||||
let lease_file = "/tmp/dhcp.leases";
|
||||
let data = fs.readfile(lease_file);
|
||||
|
||||
let firewall_c = uci_core.cursor();
|
||||
if (!firewall_c) {
|
||||
send_message(token, chat_id, "❌ Не удалось прочитать конфигурацию файрвола.");
|
||||
return;
|
||||
}
|
||||
firewall_c.load("firewall");
|
||||
let blocked_macs = {};
|
||||
firewall_c.foreach("firewall", "rule", function(r) {
|
||||
if (r.target == "REJECT" && r.src_mac) {
|
||||
blocked_macs[uc(as_string(r.src_mac))] = true;
|
||||
}
|
||||
});
|
||||
|
||||
let text = "💻 <b>Активные устройства в сети:</b>\n\n";
|
||||
let keyboard = [];
|
||||
let count = 0;
|
||||
|
||||
if (data) {
|
||||
for (let line in split(data, "\n")) {
|
||||
line = trim(line);
|
||||
if (line == "") continue;
|
||||
let fields = split(line, / /);
|
||||
if (length(fields) < 4) continue;
|
||||
|
||||
let mac = uc(as_string(fields[1]));
|
||||
let ip = fields[2];
|
||||
let hostname = fields[3] == "*" ? "Неизвестно" : fields[3];
|
||||
|
||||
let is_blocked = blocked_macs[mac] ? true : false;
|
||||
let status_icon = is_blocked ? "🚫" : "🟢";
|
||||
|
||||
text += status_icon + " <b>" + escape_html(hostname) + "</b>\n";
|
||||
text += "└ IP: <code>" + ip + "</code> | MAC: <code>" + mac + "</code>\n\n";
|
||||
|
||||
push(keyboard, [
|
||||
{
|
||||
text: (is_blocked ? "🔓 Разблокировать " : "🚫 Заблокировать ") + hostname,
|
||||
callback_data: "/toggle_mac " + mac
|
||||
}
|
||||
]);
|
||||
count++;
|
||||
}
|
||||
}
|
||||
|
||||
if (count == 0) {
|
||||
text += "<i>Устройства не найдены.</i>\n";
|
||||
}
|
||||
|
||||
push(keyboard, [ { text: "⬅️ Назад в меню", callback_data: "/help" } ]);
|
||||
|
||||
let payload = {
|
||||
chat_id: int(chat_id),
|
||||
text: text,
|
||||
parse_mode: "HTML",
|
||||
reply_markup: { inline_keyboard: keyboard }
|
||||
};
|
||||
tg_request(token, "sendMessage", payload);
|
||||
}
|
||||
|
||||
function process_command(token, chat_id, text) {
|
||||
let cmd = trim(as_string(text));
|
||||
if (cmd == "/start" || cmd == "/help") {
|
||||
let help_text = "🤖 <b>Tachyon Bot</b> — Панель управления роутером\n\n" +
|
||||
"<b>Доступные команды:</b>\n" +
|
||||
"📊 /status — Статус системы и RAM/CPU\n" +
|
||||
"📡 /servers — Список серверов и задержка\n" +
|
||||
"⚡ /test — Проверка интернет-соединения\n" +
|
||||
"🚀 /speed — Замер скорости интернета\n" +
|
||||
"📡 /servers — Выбрать активный прокси-сервер\n" +
|
||||
"🛡️ /rules — Управление правилами обхода\n" +
|
||||
"💻 /devices — Подключенные устройства (DHCP)\n" +
|
||||
"🔄 /sub_update — Обновить прокси-подписки\n" +
|
||||
"⚡ /test — Быстрая проверка интернет-соединения\n" +
|
||||
"🚀 /speed — Замер скорости интернета (5MB)\n" +
|
||||
"📈 /traffic — Статистика трафика sing-box\n" +
|
||||
"🩺 /doctor — Диагностика и ремонт ошибок\n" +
|
||||
"🩺 /doctor — Запуск диагностики и ремонта\n" +
|
||||
"🔄 /restart — Перезапустить службы Tachyon\n" +
|
||||
"📋 /logs — Посмотреть последние логи\n" +
|
||||
"💾 /backup — Резервная копия настроек роутера\n" +
|
||||
"💾 /backup — Скачать резервную копию настроек\n" +
|
||||
"🌐 /bypass_add <домен> — Добавить домен в обход\n" +
|
||||
"🌐 /bypass_del <домен> — Удалить домен из обхода\n" +
|
||||
"🌐 /direct_add <домен> — Добавить домен в прямой доступ\n" +
|
||||
"🌐 /direct_del <домен> — Удалить домен из прямого доступа\n" +
|
||||
"🚫 /block_mac <MAC> — Заблокировать устройство по MAC\n" +
|
||||
"🔓 /unblock_mac <MAC> — Разблокировать устройство по MAC\n" +
|
||||
"📡 /ping <хост> — Пинг сетевого узла напрямую\n" +
|
||||
"🌐 /curl <URL> — Запрос к URL напрямую и через прокси\n" +
|
||||
"🔍 /nslookup <домен> — DNS-запрос через localhost роутера\n" +
|
||||
"💻 /sh <команда> — Выполнить консольную команду\n" +
|
||||
"❓ /help — Справка по командам\n";
|
||||
send_message_with_keyboard(token, chat_id, help_text, "HTML");
|
||||
@@ -598,6 +774,93 @@ function process_command(token, chat_id, text) {
|
||||
send_message_with_keyboard(token, chat_id, "❌ " + res.error, "HTML");
|
||||
}
|
||||
}
|
||||
else if (cmd == "/rules") {
|
||||
handle_rules(token, chat_id);
|
||||
}
|
||||
else if (match(cmd, /^\/toggle_rule /) != null) {
|
||||
let section = trim(substr(cmd, 13));
|
||||
let c = uci_core.cursor();
|
||||
if (c) {
|
||||
c.load("tachyon");
|
||||
let current = c.get("tachyon", section, "enabled") == "1" ? "0" : "1";
|
||||
c.set("tachyon", section, "enabled", current);
|
||||
c.commit("tachyon");
|
||||
command_status("/usr/bin/tachyon reload");
|
||||
}
|
||||
handle_rules(token, chat_id);
|
||||
}
|
||||
else if (cmd == "/devices") {
|
||||
handle_devices(token, chat_id);
|
||||
}
|
||||
else if (match(cmd, /^\/toggle_mac /) != null) {
|
||||
let mac = trim(substr(cmd, 12));
|
||||
let blocked = false;
|
||||
let c = uci_core.cursor();
|
||||
if (c) {
|
||||
c.load("firewall");
|
||||
c.foreach("firewall", "rule", function(r) {
|
||||
if (uc(as_string(r.src_mac)) == uc(mac) && r.target == "REJECT") {
|
||||
blocked = true;
|
||||
return false;
|
||||
}
|
||||
});
|
||||
}
|
||||
manage_mac_block(mac, blocked);
|
||||
handle_devices(token, chat_id);
|
||||
}
|
||||
else if (cmd == "/sub_update") {
|
||||
send_message(token, chat_id, "🔄 <b>Запуск обновления подписок...</b>", "HTML");
|
||||
let sub_res = command_capture("/usr/bin/tachyon subscription_update");
|
||||
let output = sub_res.output || "Обновление завершено.";
|
||||
send_message_with_keyboard(token, chat_id, "✅ <b>Результат обновления подписок:</b>\n\n<pre>" + escape_html(output) + "</pre>", "HTML");
|
||||
}
|
||||
else if (match(cmd, /^\/ping /) != null) {
|
||||
let host = trim(substr(cmd, 6));
|
||||
if (host == "" || match(host, /^[a-zA-Z0-9\.\-]+$/) == null) {
|
||||
send_message_with_keyboard(token, chat_id, "❌ Укажите корректный хост. Пример: `/ping google.com`", "HTML");
|
||||
return;
|
||||
}
|
||||
send_message(token, chat_id, "⏳ <b>Выполняю пинг " + escape_html(host) + "...</b>", "HTML");
|
||||
let ping_res = command_capture("ping -c 4 " + shell_quote(host));
|
||||
let output = ping_res.output || "Нет ответа.";
|
||||
send_message_with_keyboard(token, chat_id, "📡 <b>Результат пинга:</b>\n\n<pre>" + escape_html(output) + "</pre>", "HTML");
|
||||
}
|
||||
else if (match(cmd, /^\/curl /) != null) {
|
||||
let url = trim(substr(cmd, 6));
|
||||
if (url == "" || match(url, /^https?:\/\/[a-zA-Z0-9\.\-\/]+$/) == null) {
|
||||
send_message_with_keyboard(token, chat_id, "❌ Укажите корректный URL. Пример: `/curl https://google.com`", "HTML");
|
||||
return;
|
||||
}
|
||||
send_message(token, chat_id, "⏳ <b>Запрос к " + escape_html(url) + "...</b>", "HTML");
|
||||
|
||||
let res_direct = command_capture("curl -I -s --connect-timeout 5 " + shell_quote(url));
|
||||
let out_direct = res_direct.output || "Нет ответа.";
|
||||
|
||||
let res_proxy = command_capture("curl -I -s --connect-timeout 5 --proxy http://127.0.0.1:4534 " + shell_quote(url));
|
||||
let out_proxy = res_proxy.output || "Нет ответа.";
|
||||
|
||||
let report = "🌐 <b>Результаты HTTP-запроса:</b>\n\n" +
|
||||
"<b>Напрямую:</b>\n<pre>" + escape_html(out_direct) + "</pre>\n" +
|
||||
"<b>Через прокси:</b>\n<pre>" + escape_html(out_proxy) + "</pre>";
|
||||
|
||||
send_message_with_keyboard(token, chat_id, report, "HTML");
|
||||
}
|
||||
else if (match(cmd, /^\/nslookup /) != null) {
|
||||
let domain = trim(substr(cmd, 10));
|
||||
if (domain == "" || match(domain, /^[a-zA-Z0-9\.\-]+$/) == null) {
|
||||
send_message_with_keyboard(token, chat_id, "❌ Укажите корректный домен. Пример: `/nslookup google.com`", "HTML");
|
||||
return;
|
||||
}
|
||||
send_message(token, chat_id, "⏳ <b>Выполняю nslookup " + escape_html(domain) + " через localhost...</b>", "HTML");
|
||||
let lookup_res = command_capture("nslookup " + shell_quote(domain) + " 127.0.0.1");
|
||||
let output = lookup_res.output || "Нет ответа.";
|
||||
send_message_with_keyboard(token, chat_id, "🔍 <b>Результат DNS-запроса через localhost:</b>\n\n<pre>" + escape_html(output) + "</pre>", "HTML");
|
||||
}
|
||||
else if (match(cmd, /^\/switch /) != null) {
|
||||
let server_name = trim(substr(cmd, 8));
|
||||
clash_request("PUT", "proxies/main-out", { name: server_name });
|
||||
handle_servers(token, chat_id);
|
||||
}
|
||||
else if (match(cmd, /^\/sh /) != null) {
|
||||
let shell_cmd = substr(cmd, 4);
|
||||
send_message(token, chat_id, "⏳ <b>Выполнение команды:</b> <code>" + escape_html(shell_cmd) + "</code>", "HTML");
|
||||
|
||||
Reference in New Issue
Block a user