mirror of
https://github.com/caiwx86/small-packages.git
synced 2026-09-14 04:14:40 +08:00
update 2026-05-27 23:25:57
This commit is contained in:
+306
@@ -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
|
||||
+10
@@ -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" ]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+128
@@ -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 };
|
||||
Reference in New Issue
Block a user