update 2026-05-27 23:25:57

This commit is contained in:
action
2026-05-27 23:25:57 +08:00
parent 84f707b52b
commit d0159b49a5
759 changed files with 362365 additions and 0 deletions
@@ -0,0 +1,306 @@
--[[
Dependences:
curl
--]]
local unistd = require("posix.unistd")
local Module = {
name = "mod_telegram",
runPrio = 70,
syslog = function(level, msg) return true end,
debugOutput = function(msg) return true end,
writeValue = function(filePath, str) return false end,
readValue = function(filePath) return nil end,
deadPeriod = 0,
alivePeriod = 0,
mode = 0, -- 0: connected, 1: disconnected, 2: both
hostAlias = "OpenWrt",
connectTimeout = 5,
tgAPIToken = nil,
tgChatId = nil,
tgMsgURLpattern = "https://api.telegram.org/bot%s/sendMessage?chat_id=%s&parse_mode=html&text=%s",
msgTextPattern = "<strong>[%s] (%s)</strong> @ %s", -- Message (host, instance, message)
msgConnectPattern = "Connected: %s",
msgDisconnectPattern = "Disconnected: %s",
msgSeparator = " | ",
msgMaxItems = 50,
msgSendAttempts = 3,
msgSendTimeout = 5,
curlExec = "/usr/bin/curl",
curlParams = "-s -g --no-keepalive",
proxyAuthString = "",
proxyString = "",
ifaceString = "",
status = nil,
_enabled = false,
_deadCounter = 0,
_aliveCounter = 0,
_msgSentDisconnect = true,
_disconnected = true,
_msgSentConnect = true,
_connected = true,
_msgBuffer = {},
_msgSendCounter = 3,
_msgTimeoutCounter = 5,
}
local function prequire(package)
local retVal, pkg = pcall(require, package)
return retVal and pkg
end
function Module:init(t)
self._enabled = true
if t.mode ~= nil then
self.mode = tonumber(t.mode)
end
if t.dead_period ~= nil then
self.deadPeriod = tonumber(t.dead_period)
end
if t.alive_period ~= nil then
self.alivePeriod = tonumber(t.alive_period)
end
if t.host_alias then
self.hostAlias = t.host_alias
else
self.hostAlias = self.config.hostname
end
if t.api_token ~= nil then
self.tgAPIToken = t.api_token
end
if t.chat_id ~= nil then
self.tgChatId = t.chat_id
end
if (t.proxy_type and t.proxy_host and t.proxy_port) then
if t.proxy_user and t.proxy_passwd then
self.proxyAuthString = string.format(
' --proxy-user "%s:%s"',
t.proxy_user,
t.proxy_passwd)
end
self.proxyString = string.format(
" --proxy %s://%s:%d%s",
t.proxy_type,
t.proxy_host,
t.proxy_port,
self.proxyAuthString)
end
if t.iface then
self.ifaceString = " --interface " .. t.iface
end
if tonumber(t.message_at_startup) == 1 then
self._msgSentDisconnect = false
self._disconnected = false
self._msgSentConnect = false
self._connected = false
end
if unistd.access(self.curlExec, "x") then
self._enabled = true
else
self._enabled = false
self.syslog("err", string.format("%s: %s is not available", self.name, self.curlExec))
end
if not self.tgAPIToken then
self._enabled = false
self.syslog("err", string.format("%s: Telegram bot API token not specified.", self.name))
end
if not self.tgChatId then
self._enabled = false
self.syslog("err", string.format("%s: Telegram chat ID not specified.", self.name))
end
self._msgSendCounter = self.msgSendAttempts
end
function Module:escape(str)
local t = {}
for i in str:gmatch(".") do
if i:match("[^%w_]") then
t[#t + 1] = "%" .. string.format("%x", string.byte(i))
else
t[#t + 1] = i
end
end
return table.concat(t)
end
function Module:appendNotice(str)
self._msgBuffer[#self._msgBuffer + 1] = str
if #self._msgBuffer > self.msgMaxItems then
local t = {}
for i = #self._msgBuffer - self.msgMaxItems + 1, #self._msgBuffer do
t[#t + 1] = self._msgBuffer[i]
end
self._msgBuffer = t
end
end
function Module:httpRequest(url)
local retCode = 1, data
self.debugOutput(string.format("--- %s ---", self.name))
local curl = string.format(
'%s%s%s --connect-timeout %s %s "%s"; printf "\n$?";',
self.curlExec,
self.ifaceString,
self.proxyString,
self.connectTimeout,
self.curlParams,
url
)
self.debugOutput(curl)
local fh = io.popen(curl, "r")
if fh then
data = fh:read("*a")
fh:close()
if data ~= nil then
local s, e = data:find("[0-9]+\n?$")
retCode = tonumber(data:sub(s))
data = data:sub(0, s - 2)
if not data or data == "" then
data = nil
end
end
else
retCode = 1
end
self.debugOutput(string.format(
"data = %s; retCode = %s\n",
tostring(data),
tostring(retCode)))
return retCode, data
end
function Module:parseResponse(str)
local ok, errCode, desc
ok = str:match('"ok":(%w+)')
if ok == "false" then
errCode = tonumber(str:match('"error_code":(%d+)'))
if errCode then
desc = str:match('"description":"([%w%s%p_]+)"')
end
end
return ok, errCode, desc
end
function Module:messageRequest(msg, textPattern)
local retVal = 1
local tgMsg = string.format(
textPattern, self.hostAlias, self.config.serviceConfig.instance, msg)
local url = string.format(
self.tgMsgURLpattern, self.tgAPIToken, self.tgChatId, self:escape(tgMsg))
local ok, errCode, desc
local retCode, data = self:httpRequest(url)
if data then
ok, errCode, desc = self:parseResponse(data)
end
if retCode == 0 and ok == "true" then
retVal = 0
self.syslog("info", string.format(
"%s: Message sent to chat %s", self.name, self.tgChatId))
else
if errCode == 400 or errCode == 406 then
retVal = 2
elseif (errCode == 401 or
errCode == 403 or
errCode == 404 or
errCode == 420) then
retVal = 3
end
if errCode and desc then
self.syslog("warning", string.format(
"%s: %s %s", self.name, tostring(errCode), tostring(desc)))
end
end
return retVal
end
function Module:sendMessage(msg, textPattern)
local retVal = self:messageRequest(msg, textPattern)
if retVal == 0 then
self._msgBuffer = {}
elseif retVal == 2 then
self.syslog("err", string.format(
"%s: Server error (invalid API token or chat ID)", self.name))
else
self.syslog("err", string.format(
"%s: An error occured while sending message", self.name))
end
return retVal
end
function Module:run(currentStatus, lastStatus, timeDiff, timeNow, inetChecked)
if not self._enabled then
return
end
if currentStatus == 1 then
self._aliveCounter = 0
self._msgSentConnect = false
if not self._disconnected then
self._disconnected = true
self:appendNotice(string.format(
self.msgDisconnectPattern, os.date("%Y.%m.%d %H:%M:%S", os.time())))
end
if not self._msgSentDisconnect and (self.mode == 1 or self.mode == 2) then
if self._deadCounter >= self.deadPeriod then
self._msgSendCounter = 0
self._msgSentDisconnect = true
else
self._deadCounter = self._deadCounter + timeDiff
end
end
self._connected = false
elseif currentStatus == 0 then
self._deadCounter = 0
self._msgSentDisconnect = false
if not self._connected then
self._connected = true
self:appendNotice(string.format(
self.msgConnectPattern, os.date("%Y.%m.%d %H:%M:%S", os.time())))
end
if not self._msgSentConnect and (self.mode == 0 or self.mode == 2) then
if self._aliveCounter >= self.alivePeriod then
self._msgSendCounter = 0
self._msgSentConnect = true
else
self._aliveCounter = self._aliveCounter + timeDiff
end
end
self._disconnected = false
end
if self._msgSendCounter < self.msgSendAttempts then
if self._msgTimeoutCounter >= self.msgSendTimeout then
if #self._msgBuffer > 0 then
local retVal = self:sendMessage(table.concat(self._msgBuffer, self.msgSeparator), self.msgTextPattern)
if retVal == 1 then
self._msgSendCounter = self._msgSendCounter + 1
else
self._msgSendCounter = self.msgSendAttempts
end
end
self._msgTimeoutCounter = 0
else
self._msgTimeoutCounter = self._msgTimeoutCounter + timeDiff
end
else
self._msgTimeoutCounter = self.msgSendTimeout
end
end
function Module:onExit()
return true
end
return Module
@@ -0,0 +1,10 @@
{
"luci-app-internet-detector-mod-telegram": {
"description": "Grant access to internet-detector-mod-telegram procedures",
"read": {
"ubus": {
"luci.internet-detector-mod-telegram": [ "GetTgChatId" ]
}
}
}
}
@@ -0,0 +1,128 @@
'use strict';
import { popen, stat } from 'fs';
const curlPath = '/usr/bin/curl';
const curlExec = (stat(curlPath)?.perm?.user_exec) ? curlPath : null;
const curlParams = '-s -g --no-keepalive';
const curlConnectTimeout = 15;
const tgUpdatesURLPattern = 'https://api.telegram.org/bot%s/getUpdates';
if(!curlExec) {
die('Error! Curl not found!');
}
function httpRequest(url, proxyType, proxyHost, proxyPort, proxyUser, proxyPasswd, iface) {
let retCode = 1;
let data = '';
let proxyAuthString = '';
let proxyString = '';
let ifaceString = iface ? (' --interface ' + iface) : '';
if(!url) {
return { retCode: 1, data };
}
if(proxyType && proxyHost && proxyPort) {
if(proxyUser && proxyPasswd) {
proxyAuthString = sprintf(
' --proxy-user "%s:%s"',
proxyUser,
proxyPasswd);
}
proxyString = sprintf(
' --proxy %s://%s:%d%s',
proxyType,
proxyHost,
proxyPort,
proxyAuthString);
}
const curl = sprintf(
'%s%s%s --connect-timeout %s %s "%s"; printf "\n$?";',
curlExec,
ifaceString,
proxyString,
curlConnectTimeout,
curlParams,
url
);
const fd = popen(curl, 'r');
if(fd) {
data = fd.read('all');
fd.close();
if(data) {
const retCodeRegexp = /[0-9]+\n?$/;
const r = match(data, retCodeRegexp);
if(length(r) > 0) {
retCode = r[0];
}
data = replace(data, retCodeRegexp, '');
}
}
return { retCode, data };
}
function parseResponse(str) {
let ok = null;
let chatId = [];
let errCode = null;
let errDesc = null;
const data = json(str);
ok = data.ok;
if(ok == false) {
errCode = data.error_code;
errDesc = data.description;
} else {
let chats = [];
if(data.result) {
for(let i in data.result) {
if(i.message && i.message.chat && i.message.chat.id) {
push(chats, i.message.chat.id);
}
}
}
if(length(chats) > 0) {
chatId = uniq(chats);
}
}
return { ok, chatId, errCode, errDesc };
}
const methods = {
GetTgChatId: {
args: {
botToken : 'String',
proxyType : 'String',
proxyHost : 'String',
proxyPort : 'String',
proxyUser : 'String',
proxyPasswd: 'String',
iface : 'String',
},
call: function(request) {
const botToken = request.args?.botToken;
if(botToken) {
const ret = httpRequest(
sprintf(tgUpdatesURLPattern, botToken),
request.args.proxyType,
request.args.proxyHost,
request.args.proxyPort,
request.args.proxyUser,
request.args.proxyPasswd,
request.args.iface
);
if(ret.retCode == 0 && length(ret.data) > 0) {
return parseResponse(ret.data);
} else {
return { ok: null, chatId: [], errCode: ret.retCode, errDesc: 'Network request error' };
}
} else {
return { ok: null, chatId: [], errCode: 1, errDesc: 'Bot token missing' };
}
},
},
};
return { 'luci.internet-detector-mod-telegram': methods };