v1.6. New module: mod_telegram. mod_public_ip: Added support for HTTP services.

This commit is contained in:
gSpot
2025-07-04 00:02:25 +03:00
parent f9aa55ca4d
commit 4ff18a1269
16 changed files with 830 additions and 90 deletions

View File

@@ -13,26 +13,27 @@ Internet-detector is an application for checking the availability of the Interne
**OpenWrt >= 21.02.**
**Dependences:** lua, luaposix, libuci-lua.
**Recommended:** curl.
## Installation notes:
opkg update
wget --no-check-certificate -O /tmp/internet-detector_1.5.2-r1_all.ipk https://github.com/gSpotx2f/packages-openwrt/raw/master/current/internet-detector_1.5.2-r1_all.ipk
opkg install /tmp/internet-detector_1.5.2-r1_all.ipk
rm /tmp/internet-detector_1.5.2-r1_all.ipk
wget --no-check-certificate -O /tmp/internet-detector_1.6.0-r1_all.ipk https://github.com/gSpotx2f/packages-openwrt/raw/master/current/internet-detector_1.6.0-r1_all.ipk
opkg install /tmp/internet-detector_1.6.0-r1_all.ipk
rm /tmp/internet-detector_1.6.0-r1_all.ipk
service internet-detector start
service internet-detector enable
wget --no-check-certificate -O /tmp/luci-app-internet-detector_1.5.2-r1_all.ipk https://github.com/gSpotx2f/packages-openwrt/raw/master/current/luci-app-internet-detector_1.5.2-r1_all.ipk
opkg install /tmp/luci-app-internet-detector_1.5.2-r1_all.ipk
rm /tmp/luci-app-internet-detector_1.5.2-r1_all.ipk
wget --no-check-certificate -O /tmp/luci-app-internet-detector_1.6.0-r1_all.ipk https://github.com/gSpotx2f/packages-openwrt/raw/master/current/luci-app-internet-detector_1.6.0-r1_all.ipk
opkg install /tmp/luci-app-internet-detector_1.6.0-r1_all.ipk
rm /tmp/luci-app-internet-detector_1.6.0-r1_all.ipk
service rpcd restart
i18n-ru:
wget --no-check-certificate -O /tmp/luci-i18n-internet-detector-ru_1.5.2-r1_all.ipk https://github.com/gSpotx2f/packages-openwrt/raw/master/current/luci-i18n-internet-detector-ru_1.5.2-r1_all.ipk
opkg install /tmp/luci-i18n-internet-detector-ru_1.5.2-r1_all.ipk
rm /tmp/luci-i18n-internet-detector-ru_1.5.2-r1_all.ipk
wget --no-check-certificate -O /tmp/luci-i18n-internet-detector-ru_1.6.0-r1_all.ipk https://github.com/gSpotx2f/packages-openwrt/raw/master/current/luci-i18n-internet-detector-ru_1.6.0-r1_all.ipk
opkg install /tmp/luci-i18n-internet-detector-ru_1.6.0-r1_all.ipk
rm /tmp/luci-i18n-internet-detector-ru_1.6.0-r1_all.ipk
## Screenshots:
@@ -44,9 +45,9 @@ i18n-ru:
**Dependences:** modemmanager.
wget --no-check-certificate -O /tmp/internet-detector-mod-modem-restart_1.5.2-r1_all.ipk https://github.com/gSpotx2f/packages-openwrt/raw/master/current/internet-detector-mod-modem-restart_1.5.2-r1_all.ipk
opkg install /tmp/internet-detector-mod-modem-restart_1.5.2-r1_all.ipk
rm /tmp/internet-detector-mod-modem-restart_1.5.2-r1_all.ipk
wget --no-check-certificate -O /tmp/internet-detector-mod-modem-restart_1.6.0-r1_all.ipk https://github.com/gSpotx2f/packages-openwrt/raw/master/current/internet-detector-mod-modem-restart_1.6.0-r1_all.ipk
opkg install /tmp/internet-detector-mod-modem-restart_1.6.0-r1_all.ipk
rm /tmp/internet-detector-mod-modem-restart_1.6.0-r1_all.ipk
service internet-detector restart
![](https://github.com/gSpotx2f/luci-app-internet-detector/blob/master/screenshots/04.jpg)
@@ -55,9 +56,20 @@ i18n-ru:
**Dependences:** mailsend.
wget --no-check-certificate -O /tmp/internet-detector-mod-email_1.5.2-r1_all.ipk https://github.com/gSpotx2f/packages-openwrt/raw/master/current/internet-detector-mod-email_1.5.2-r1_all.ipk
opkg install /tmp/internet-detector-mod-email_1.5.2-r1_all.ipk
rm /tmp/internet-detector-mod-email_1.5.2-r1_all.ipk
wget --no-check-certificate -O /tmp/internet-detector-mod-email_1.6.0-r1_all.ipk https://github.com/gSpotx2f/packages-openwrt/raw/master/current/internet-detector-mod-email_1.6.0-r1_all.ipk
opkg install /tmp/internet-detector-mod-email_1.6.0-r1_all.ipk
rm /tmp/internet-detector-mod-email_1.6.0-r1_all.ipk
service internet-detector restart
![](https://github.com/gSpotx2f/luci-app-internet-detector/blob/master/screenshots/05.jpg)
## Telegram notification module (internet-detector-mod-telegram):
**Dependences:** curl.
wget --no-check-certificate -O /tmp/internet-detector-mod-telegram_1.6.0-r1_all.ipk https://github.com/gSpotx2f/packages-openwrt/raw/master/current/internet-detector-mod-telegram_1.6.0-r1_all.ipk
opkg install /tmp/internet-detector-mod-telegram_1.6.0-r1_all.ipk
rm /tmp/internet-detector-mod-telegram_1.6.0-r1_all.ipk
service internet-detector restart
![](https://github.com/gSpotx2f/luci-app-internet-detector/blob/master/screenshots/06.jpg)

View File

@@ -5,7 +5,7 @@
include $(TOPDIR)/rules.mk
PKG_NAME:=internet-detector-mod-email
PKG_VERSION:=1.5.2
PKG_VERSION:=1.6.0
PKG_RELEASE:=1
PKG_MAINTAINER:=gSpot <https://github.com/gSpotx2f/luci-app-internet-detector>

View File

@@ -86,7 +86,7 @@ function Module:init(t)
self._enabled = true
else
self._enabled = false
self.syslog("warning", string.format("%s: %s is not available", self.name, self.mta))
self.syslog("err", string.format("%s: %s is not available", self.name, self.mta))
end
if (not self.mailRecipient or
@@ -162,6 +162,7 @@ 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

View File

@@ -5,7 +5,7 @@
include $(TOPDIR)/rules.mk
PKG_NAME:=internet-detector-mod-modem-restart
PKG_VERSION:=1.5.2
PKG_VERSION:=1.6.0
PKG_RELEASE:=1
PKG_MAINTAINER:=gSpot <https://github.com/gSpotx2f/luci-app-internet-detector>

View File

@@ -72,7 +72,7 @@ function Module:init(t)
self._enabled = true
else
self._enabled = false
self.syslog("warning", string.format(
self.syslog("err", string.format(
"%s: modemmanager service is not available", self.name))
end
end

View File

@@ -0,0 +1,41 @@
#
# (с) 2025 gSpot (https://github.com/gSpotx2f/luci-app-internet-detector)
#
include $(TOPDIR)/rules.mk
PKG_NAME:=internet-detector-mod-telegram
PKG_VERSION:=1.6.0
PKG_RELEASE:=1
PKG_MAINTAINER:=gSpot <https://github.com/gSpotx2f/luci-app-internet-detector>
include $(INCLUDE_DIR)/package.mk
define Package/$(PKG_NAME)
SECTION:=net
CATEGORY:=Network
TITLE:=Telegram messenger module for internet-detector
URL:=https://github.com/gSpotx2f/luci-app-internet-detector
PKGARCH:=all
DEPENDS:=+internet-detector +curl
endef
define Package/$(PKG_NAME)/description
Telegram messenger support for internet-detector.
endef
define Package/$(PKG_NAME)/conffiles
endef
define Build/Configure
endef
define Build/Compile
endef
define Package/$(PKG_NAME)/install
$(INSTALL_DIR) $(1)/usr/lib/lua/internet-detector/modules
$(INSTALL_DATA) ./files/usr/lib/lua/internet-detector/modules/mod_telegram.lua $(1)/usr/lib/lua/internet-detector/modules/mod_telegram.lua
endef
$(eval $(call BuildPackage,$(PKG_NAME)))

View File

@@ -0,0 +1,264 @@
--[[
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",
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 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
local fh = io.popen(string.format(
'%s --connect-timeout %s %s "%s"; printf "\n$?";', self.curlExec, self.connectTimeout, self.curlParams, url), "r")
if fh then
data = fh:read("*a")
fh:close()
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
else
retCode = 1
end
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
else
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

View File

@@ -5,7 +5,7 @@
include $(TOPDIR)/rules.mk
PKG_NAME:=internet-detector
PKG_VERSION:=1.5.2
PKG_VERSION:=1.6.0
PKG_RELEASE:=1
PKG_MAINTAINER:=gSpot <https://github.com/gSpotx2f/luci-app-internet-detector>

View File

@@ -44,6 +44,11 @@ config instance 'internet'
option mod_email_mode '0'
option mod_email_alive_period '0'
option mod_email_mail_security 'tls'
option mod_telegram_enabled '0'
option mod_telegram_message_at_startup '0'
option mod_telegram_mode '2'
option mod_telegram_dead_period '0'
option mod_telegram_alive_period '0'
option mod_user_scripts_enabled '0'
option mod_user_scripts_alive_period '0'
option mod_user_scripts_up_script_attempts '1'

View File

@@ -20,39 +20,69 @@ local Module = {
port = 53,
runInterval = 600,
runIntervalFailed = 60,
runIntervalDNSFailed = 1,
runIntervalIPFailed = 1,
requestAttempts = 2,
timeout = 3,
curlExec = "/usr/bin/curl",
curlParams = "-s",
providers = {
opendns1 = {
name = "opendns1", host = "myip.opendns.com",
name = "opendns1", type = "dns", host = "myip.opendns.com",
server = "208.67.222.222", server6 = "2620:119:35::35",
port = 53, queryType = "A", queryType6 = "AAAA",
},
opendns2 = {
name = "opendns2", host = "myip.opendns.com",
opendns2 = {
name = "opendns2", type = "dns", host = "myip.opendns.com",
server = "208.67.220.220", server6 = "2620:119:35::35",
port = 53, queryType = "A", queryType6 = "AAAA",
},
opendns3 = {
name = "opendns3", host = "myip.opendns.com",
opendns3 = {
name = "opendns3", type = "dns", host = "myip.opendns.com",
server = "208.67.222.220", server6 = "2620:119:35::35",
port = 53, queryType = "A", queryType6 = "AAAA",
},
opendns4 = {
name = "opendns4", host = "myip.opendns.com",
opendns4 = {
name = "opendns4", type = "dns", host = "myip.opendns.com",
server = "208.67.220.222", server6 = "2620:119:35::35",
port = 53, queryType = "A", queryType6 = "AAAA",
},
akamai = {
name = "akamai", host = "whoami.akamai.net",
google = {
name = "google", type = "dns", host = "o-o.myaddr.l.google.com",
server = "ns1.google.com", server6 = "ns1.google.com",
port = 53, queryType = "TXT", queryType6 = "TXT",
},
akamai = {
name = "akamai", type = "dns", host = "whoami.akamai.net",
server = "ns1-1.akamaitech.net", server6 = "ns1-1.akamaitech.net",
port = 53, queryType = "A", queryType6 = "AAAA",
},
google = {
name = "google", host = "o-o.myaddr.l.google.com",
server = "ns1.google.com", server6 = "ns1.google.com",
port = 53, queryType = "TXT", queryType6 = "TXT",
akamai_http = {
name = "akamai_http", type = "http", url = "http://whatismyip.akamai.com/",
parseResponseFunc = nil,
},
amazonaws= {
name = "amazonaws", type = "http", url = "http://checkip.amazonaws.com/",
parseResponseFunc = nil,
},
wgetip= {
name = "wgetip", type = "http", url = "http://wgetip.com/",
parseResponseFunc = nil,
},
ifconfig= {
name = "ifconfig", type = "http", url = "http://ifconfig.me/",
parseResponseFunc = nil,
},
ipecho= {
name = "ipecho", type = "http", url = "http://ipecho.net/plain",
parseResponseFunc = nil,
},
canhazip= {
name = "canhazip", type = "http", url = "http://canhazip.com/",
parseResponseFunc = nil,
},
icanhazip = {
name = "icanhazip", type = "http", url = "http://icanhazip.com/",
parseResponseFunc = nil,
},
},
ipScript = "",
@@ -64,9 +94,10 @@ local Module = {
_lastResolvedIp = nil,
_enabled = false,
_counter = 0,
_DNSFalseCounter = 0,
_IPFalseCounter = 0,
_interval = 600,
_DNSPacket = nil,
_requestIP = nil,
}
function Module:runIpScript()
@@ -172,6 +203,7 @@ function Module:sendUDPMessage(message, server, port)
end
local ok, errMsg, errNum = socket.sendto(sock, message, saTable[1])
local response = {}
if ok then
local ret, resp, errNum = socket.recvfrom(sock, 1024)
@@ -212,13 +244,10 @@ end
function Module:parseParts(message, start, parts)
local partStart = start + 2
local partLen = message:sub(start, start + 1)
if #partLen == 0 then
return parts
end
local partEnd = partStart + (tonumber(partLen, 16) * 2)
parts[#parts + 1] = message:sub(partStart, partEnd - 1)
if message:sub(partEnd, partEnd + 1) == "00" or partEnd > #message then
return parts
@@ -240,6 +269,7 @@ function Module:decodeMessage(message)
local ARCOUNT = message:sub(21, 24)
local questionSectionStarts = 25
local questionParts = self:parseParts(message, questionSectionStarts, {})
local qtypeStarts = questionSectionStarts + (#table.concat(questionParts)) + (#questionParts * 2) + 1
local qclassStarts = qtypeStarts + 4
@@ -251,11 +281,11 @@ function Module:decodeMessage(message)
if numAnswers > 0 then
for answerCount = 1, numAnswers do
if answerSectionStarts < #message then
local ATYPE = tonumber(
local ATYPE = tonumber(
message:sub(answerSectionStarts + 5, answerSectionStarts + 8), 16)
local RDLENGTH = tonumber(
local RDLENGTH = tonumber(
message:sub(answerSectionStarts + 21, answerSectionStarts + 24), 16)
local RDDATA = message:sub(
local RDDATA = message:sub(
answerSectionStarts + 25, answerSectionStarts + 24 + (RDLENGTH * 2))
local RDDATA_decoded = ""
@@ -290,26 +320,23 @@ function Module:decodeMessage(message)
end
answerSectionStarts = answerSectionStarts + 24 + (RDLENGTH * 2)
if RDDATA_decoded:match("^[a-f0-9.:]+$") then
if RDDATA_decoded:match("^[a-fA-F0-9.:]+$") then
retTable[#retTable + 1] = RDDATA_decoded
end
end
end
end
return retTable
end
function Module:requestIP()
function Module:requestIPDNS()
local res
local qtype = self._qtype and self._provider.queryType6 or self._provider.queryType
local server = self._qtype and self._provider.server6 or self._provider.server
local port = self._provider.port or self.port
if not self._DNSPacket then
self._DNSPacket = self:buildMessage(self._provider.host, qtype)
end
local retCode, response = self:sendUDPMessage(self._DNSPacket, server, port)
if retCode == 0 and response then
local retTable = self:decodeMessage(response)
@@ -320,7 +347,57 @@ function Module:requestIP()
self.syslog("warning", string.format(
"%s: UDP error when requesting an IP address", self.name))
end
return res
end
function Module:httpRequest(url)
local retCode = 1, data
local iface = ""
if self.config.serviceConfig.iface then
iface = " --interface " .. self.config.serviceConfig.iface
end
local fh = io.popen(string.format(
'%s%s --connect-timeout %s %s "%s"; printf "\n$?";', self.curlExec, iface, self.timeout, self.curlParams, url), "r")
if fh then
data = fh:read("*a")
fh:close()
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
else
retCode = 1
end
return retCode, data
end
function Module:parseHTTPResponse(data)
data = data:gsub("^[%s%c]+", ""):gsub("[%s%c]+$", "")
if data:match("^[a-fA-F0-9.:]+$") then
return data
end
return
end
function Module:requestIPHTTP()
local res
local url = self._provider.url
local parseResponseFunc = self._provider.parseResponseFunc
if url then
local retCode, data = self:httpRequest(url)
if retCode == 0 and data then
if type(parseResponseFunc) == "function" then
res = parseResponseFunc(data)
else
res = self:parseHTTPResponse(data)
end
else
self.syslog("warning", string.format(
"%s: HTTP error when requesting an IP address", self.name))
end
end
return res
end
@@ -352,12 +429,28 @@ function Module:init(t)
if t.qtype ~= nil then
self._qtype = (tonumber(t.qtype) ~= 0)
end
self._currentIp = nil
self._lastResolvedIp = nil
self._DNSPacket = nil
self._interval = self.runInterval
self._DNSFalseCounter = 0
self._enabled = true
self._currentIp = nil
self._lastResolvedIp = nil
self._DNSPacket = nil
self._interval = self.runInterval
self._IPFalseCounter = 0
self._enabled = true
if not self._provider then
self._enabled = false
else
if self._provider.url and not unistd.access(self.curlExec, "x") then
self._enabled = false
self.syslog("err", string.format(
"%s: %s is not available. You need to install curl.", self.name, self.curlExec))
end
if self._provider.type == "dns" then
self._requestIP = self.requestIPDNS
elseif self._provider.type == "http" then
self._requestIP = self.requestIPHTTP
else
self._enabled = false
end
end
end
function Module:run(currentStatus, lastStatus, timeDiff, timeNow, inetChecked)
@@ -366,23 +459,20 @@ function Module:run(currentStatus, lastStatus, timeDiff, timeNow, inetChecked)
end
if currentStatus == 0 then
if self._counter == 0 or self._counter >= self._interval or currentStatus ~= lastStatus then
local ip = self:requestIP()
local ip = self:_requestIP()
if not ip then
ip = ""
self._DNSFalseCounter = self._DNSFalseCounter + 1
if self._DNSFalseCounter >= self.requestAttempts then
ip = ""
self._IPFalseCounter = self._IPFalseCounter + 1
if self._IPFalseCounter >= self.requestAttempts then
self._interval = self.runIntervalFailed
self._DNSFalseCounter = 0
self._IPFalseCounter = 0
else
self._interval = self.runIntervalDNSFailed
self._interval = self.runIntervalIPFailed
end
else
self._interval = self.runInterval
self._DNSFalseCounter = 0
self._interval = self.runInterval
self._IPFalseCounter = 0
end
if ip ~= self._currentIp then
self.status = ip
if ip ~= "" then
@@ -400,11 +490,11 @@ function Module:run(currentStatus, lastStatus, timeDiff, timeNow, inetChecked)
self._counter = 0
end
else
self._currentIp = nil
self.status = self._currentIp
self._DNSFalseCounter = 0
self._counter = 0
self._interval = self.runInterval
self._currentIp = nil
self.status = self._currentIp
self._IPFalseCounter = 0
self._counter = 0
self._interval = self.runInterval
end
self._counter = self._counter + timeDiff
end

View File

@@ -5,7 +5,7 @@
include $(TOPDIR)/rules.mk
PKG_NAME:=luci-app-internet-detector
PKG_VERSION:=1.5.2
PKG_VERSION:=1.6.0
PKG_RELEASE:=1
LUCI_TITLE:=LuCI support for internet-detector
LUCI_DEPENDS:=+internet-detector

View File

@@ -134,6 +134,33 @@ var Timefield = ui.Textfield.extend({
},
});
var TextfieldButton = ui.Textfield.extend({
render() {
let frameEl = E('div', { 'id': this.options.id }),
inputEl = E('input', {
'id' : this.options.id ? 'widget.' + this.options.id : null,
'name' : this.options.name,
'type' : 'text',
'class' : 'cbi-input-text',
'readonly' : this.options.readonly ? '' : null,
'disabled' : this.options.disabled ? '' : null,
'maxlength' : this.options.maxlength,
'placeholder': this.options.placeholder,
'value' : this.value,
});
frameEl.appendChild(E('div', { 'class': 'control-group' }, [
inputEl,
E('button', {
'class' : `cbi-button cbi-button-${this.options.btnstyle || 'neutral'}`,
'title' : this.options.btntitle,
'aria-label': this.options.btntitle,
'click' : this.options.onclick,
}, this.options.btntext,)
]));
return this.bind(frameEl);
},
});
return view.extend({
appName : 'internet-detector',
configDir : '/etc/internet-detector',
@@ -149,10 +176,13 @@ return view.extend({
ledsPath : '/sys/class/leds',
ledsPerInstance : 3,
leds : [],
tgUpdatesURLPattern : 'https://api.telegram.org/bot%s/getUpdates',
mm : false,
mmInit : false,
email : false,
emailExec : false,
telegram : false,
curlExec : false,
modRegularScriptNextRun: {},
callInitStatus: rpc.declare({
@@ -330,6 +360,61 @@ return view.extend({
});
},
getTgChatIdHandler(ev, instance) {
ev.preventDefault();
let botToken;
let botTokenInput = document.getElementById(
'widget.cbid.%s.%s.mod_telegram_api_token'.format(this.appName, instance));
if(botTokenInput) {
botToken = botTokenInput.value;
};
if(!botTokenInput || !botToken) {
alert(_('Bot API token is missing!'));
return;
};
let apiURL = this.tgUpdatesURLPattern.format(botToken);
console.log(`Requesting chat ID: ${apiURL}`);
return fetch(apiURL).then(r => {
if(r.ok) {
r.json().then(j => {
let chats = [];
if(j.ok && j.result) {
j.result.forEach(i => {
if(i.message && i.message.chat && i.message.chat.id) {
if(!chats.includes(i.message.chat.id)) {
chats.push(i.message.chat.id);
};
};
});
};
let tgChatIdInput = document.getElementById(
'widget.cbid.%s.%s.mod_telegram_chat_id'.format(this.appName, instance));
if(tgChatIdInput) {
if(chats.length == 0) {
alert(_('No messages available. Write something to the bot and try again.'));
} else {
tgChatIdInput.value = chats[chats.length - 1];
tgChatIdInput.focus();
tgChatIdInput.blur();
};
};
});
} else {
let status = r.status;
let errorString = `${_('Error')} ${r.status}.`;
if(status == 404) {
errorString += ` ${_('Incorrect bot token?')}`;
};
alert(errorString);
};
}).catch(e => {
alert(e.message);
throw e;
});
},
CBITimeInput: form.Value.extend({
__name__ : 'CBI.TimeInput',
@@ -354,6 +439,27 @@ return view.extend({
},
}),
CBITextfieldButtonInput: form.Value.extend({
__name__ : 'CBI.TextfieldButtonInput',
renderWidget(section_id, option_index, cfgvalue) {
let value = (cfgvalue != null) ? cfgvalue : this.default,
widget = new TextfieldButton(value, {
id : this.cbid(section_id),
optional : this.optional || this.rmempty,
datatype : this.datatype,
placeholder: this.placeholder,
validate : L.bind(this.validate, this, section_id),
disabled : (this.readonly != null) ? this.readonly : this.map.readonly,
btntext : this.btntext,
btntitle : this.btntitle,
btnstyle : this.btnstyle,
onclick : this.onclick,
});
return widget.render();
},
}),
CBIBlockInetStatus: form.Value.extend({
__name__ : 'CBI.BlockInetStatus',
@@ -542,6 +648,12 @@ return view.extend({
if(data[3].email_exec) {
this.emailExec = true;
};
if(data[3].telegram) {
this.telegram = true;
};
if(data[3].curl_exec) {
this.curlExec = true;
};
};
this.currentAppMode = uci.get(this.appName, 'config', 'mode');
@@ -756,6 +868,9 @@ return view.extend({
if(this.email) {
s.tab('email', _('Email notification'));
};
if(this.telegram) {
s.tab('telegram', _('Telegram notification'));
};
s.tab('user_scripts', _('User scripts'));
s.tab('regular_script', _('Regular script'));
};
@@ -1081,16 +1196,27 @@ return view.extend({
// provider
o = s.taboption('public_ip', form.ListValue,
'mod_public_ip_provider', _('DNS provider'),
_('Service for determining the public IP address through DNS.')
'mod_public_ip_provider', _('Provider'),
_('Service for determining the public IP address.') + '<br />' +
((this.curlExec) ? '' :
_('To support HTTP services you need to install curl.'))
);
o.modalonly = true;
o.value('opendns1');
o.value('opendns2');
o.value('opendns3');
o.value('opendns4');
o.value('akamai');
o.value('google');
o.value('opendns1', 'opendns1 (DNS)');
o.value('opendns2', 'opendns2 (DNS)');
o.value('opendns3', 'opendns3 (DNS)');
o.value('opendns4', 'opendns4 (DNS)');
o.value('google', 'google (DNS)');
o.value('akamai', 'akamai (DNS)');
if(this.curlExec) {
o.value('akamai_http', "akamai (HTTP)");
o.value('amazonaws', "amazonaws (HTTP)");
o.value('wgetip', "wgetip.com (HTTP)");
o.value('ifconfig', "ifconfig.me (HTTP)");
o.value('ipecho', "ipecho.net (HTTP)");
o.value('canhazip', "canhazip.com (HTTP)");
o.value('icanhazip', "icanhazip.com (HTTP)");
};
o.default = 'opendns1';
// ipv6
@@ -1102,6 +1228,12 @@ return view.extend({
o.value('0', 'A (IPv4)');
o.value('1', 'AAAA (IPv6)');
o.default = '0';
o.depends({ 'mod_public_ip_provider': 'opendns1' });
o.depends({ 'mod_public_ip_provider': 'opendns2' });
o.depends({ 'mod_public_ip_provider': 'opendns3' });
o.depends({ 'mod_public_ip_provider': 'opendns4' });
o.depends({ 'mod_public_ip_provider': 'google' });
o.depends({ 'mod_public_ip_provider': 'akamai' });
// interval
o = s.taboption('public_ip', form.ListValue,
@@ -1290,6 +1422,103 @@ return view.extend({
};
};
// Telegram notification
if(this.telegram) {
if(this.curlExec) {
o = s.taboption('telegram', form.DummyValue, '_dummy');
o.rawhtml = true;
o.default = '<div class="cbi-section-descr">' +
_('Telegram message will be sent when connected or disconnected from the Internet.') +
'<br />' +
_("You need to register a new %sTelegram bot%s. Then get the bot's API token and paste it into the <code>Bot token</code> field. After that, open a chat with the bot, write something (in the Telegram app) and you will be able to get the chat ID using the <code>ID</code> button.").format("<a href='https://core.telegram.org/bots#how-do-i-create-a-bot' target='_blank'>", '</a>') +
'</div>';
o.modalonly = true;
// enabled
o = s.taboption('telegram', form.Flag, 'mod_telegram_enabled',
_('Enable'));
o.rmempty = false;
o.modalonly = true;
// mode
o = s.taboption('telegram', form.ListValue,
'mod_telegram_mode', _('When message will be sent')
);
o.modalonly = true;
o.value(0, _('after connection'));
o.value(1, _('after disconnection'));
o.value(2, _('after connection or disconnection'));
o.default = '0';
// alive_period
o = s.taboption('telegram', this.CBITimeInput,
'mod_telegram_alive_period', _('Alive period'),
_('Period of time after connecting to the Internet before sending a message.')
);
o.rmempty = false;
o.modalonly = true;
o.depends({ 'mod_telegram_mode': '0' });
o.depends({ 'mod_telegram_mode': '2' });
o.default = '0';
// dead_period
o = s.taboption('telegram', this.CBITimeInput,
'mod_telegram_dead_period', _('Dead period'),
_('Period of time after disconnecting from Internet before sending a message.')
);
o.rmempty = false;
o.modalonly = true;
o.depends({ 'mod_telegram_mode': '1' });
o.depends({ 'mod_telegram_mode': '2' });
o.default = '0';
// host_alias
o = s.taboption('telegram', form.Value, 'mod_telegram_host_alias',
_('Host alias'),
_('Host identifier in messages. If not specified, hostname will be used.'));
o.modalonly = true;
// tg_api_token
o = s.taboption('telegram', form.Value,
'mod_telegram_api_token', _('Bot token'),
_('Telegram bot API token.'));
o.password = true;
o.modalonly = true;
// tg_chat_id
o = s.taboption('telegram', this.CBITextfieldButtonInput,
'mod_telegram_chat_id', _('Chat ID'),
_('ID of the Telegram chat to which messages will be sent.')
);
o.btntext = _('ID'),
o.btntitle = _('Request chat ID from bot API'),
o.btnstyle = 'action',
o.onclick = ui.createHandlerFn(this,
(ev) => this.getTgChatIdHandler(ev, s.section));
o.modalonly = true;
o.optional = false;
o.rmempty = false;
o.depends({ 'mod_telegram_api_token': /.+/ });
// message_at_startup
o = s.taboption('telegram', form.Flag, 'mod_telegram_message_at_startup',
_('On startup'),
_('Send message on service startup.')
);
o.rmempty = false;
o.modalonly = true;
} else {
o = s.taboption('telegram', form.DummyValue, '_dummy');
o.rawhtml = true;
o.default = '<label class="cbi-value-title"></label><div class="cbi-value-field"><em>' +
_('Curl is not available...') +
'</em></div>';
o.modalonly = true;
};
};
// User scripts
o = s.taboption('user_scripts', form.DummyValue, '_dummy');

View File

@@ -39,7 +39,7 @@ msgid "Alive period"
msgstr "Период после подключения"
msgid "An email will be sent when connected or disconnected from the Internet."
msgstr "Сообщение будет отправлено при подключении или отключении от Интернет."
msgstr "Сообщение будет отправлено на email при подключении или отключении от Интернет."
msgid "An error has occurred"
msgstr "Произошла ошибка"
@@ -56,6 +56,15 @@ msgstr "Большой: 248 байт"
msgid "Blink"
msgstr "Мигание"
msgid "Bot API token is missing!"
msgstr "Отсутствует API токен бота!"
msgid "Bot token"
msgstr "Токен бота"
msgid "ID чата"
msgstr ""
msgid "Check type"
msgstr "Тип проверки"
@@ -80,6 +89,9 @@ msgstr "Таймаут соединения"
msgid "Contents have been saved."
msgstr "Содержимое сохранено."
msgid "Curl is not available..."
msgstr "Curl недоступен..."
msgid "Dead interval"
msgstr "Интервал при отключении"
@@ -113,9 +125,6 @@ msgstr "Закрыть"
msgid "DNS query type"
msgstr "Тип DNS-запроса"
msgid "DNS provider"
msgstr "DNS провайдер"
msgid "Edit"
msgstr "Изменить"
@@ -152,6 +161,9 @@ msgstr "Включить public-ip-script"
msgid "Enabled"
msgstr "Включен"
msgid "Error"
msgstr "Ошибка"
msgid "Expecting:"
msgstr "Ожидается:"
@@ -180,7 +192,7 @@ msgid "Hosts"
msgstr "Хосты"
msgid "Hosts polling interval when the Internet is down."
msgstr "Интервал опроса хостов если Интернет не доступен."
msgstr "Интервал опроса хостов если Интернет недоступен."
msgid "Hosts polling interval when the Internet is up."
msgstr "Интервал опроса хостов если Интернет доступен."
@@ -195,12 +207,18 @@ msgstr ""
msgid "Huge: 1492 bytes"
msgstr "Огромный: 1492 байта"
msgid "ID of the Telegram chat to which messages will be sent."
msgstr "ID чата Telegram в который будут отправлены сообщения."
msgid "ICMP-echo request (ping)"
msgstr "Запрос ICMP-echo (ping)"
msgid "ICMP packet data size"
msgstr "Размер данных ICMP-пакета"
msgid "Incorrect bot token?"
msgstr "Неправильный токен бота?"
msgid "Instances"
msgstr "Экземпляры"
@@ -250,7 +268,7 @@ msgid "Loading"
msgstr "Загрузка"
msgid "Mailsend is not available..."
msgstr "Mailsend не доступен..."
msgstr "Mailsend недоступен..."
msgid "Main settings"
msgstr "Основные настройки"
@@ -280,7 +298,7 @@ msgid "Maximum timeout for waiting for a response from the host."
msgstr "Максимальный таймаут ожидания ответа от хоста."
msgid "ModemManager is not available..."
msgstr "ModemManager не доступен..."
msgstr "ModemManager недоступен..."
msgid "Modem will be restarted when the Internet is disconnected."
msgstr "Модем будет перезапущен при отключении Интернет."
@@ -314,6 +332,9 @@ msgstr "Следующий запуск"
msgid "No <abbr title=\"Light Emitting Diode\">LED</abbr>s available..."
msgstr "Нет доступных <abbr title=\"Светодиод\">LED</abbr>..."
msgid "No messages available. Write something to the bot and try again."
msgstr "Нет доступных сообщений. Напишите что-нибудь боту и попробуйте ещё раз."
msgid "Not scheduled"
msgstr "Не запланирован"
@@ -373,6 +394,9 @@ msgstr "Регулярный скрипт"
msgid "Polling interval"
msgstr "Интервал опроса"
msgid "Provider"
msgstr "Провайдер"
msgid "Public IP"
msgstr "Публичный IP"
@@ -388,6 +412,9 @@ msgstr "Перезагрузка устройства если Интренет
msgid "Recipient"
msgstr "Получатель"
msgid "Request chat ID from bot API"
msgstr "Запросить ID чата через API бота"
msgid "Restart"
msgstr "Перезапуск"
@@ -463,8 +490,8 @@ msgstr "Не удалось выполнить действие службы \"%
msgid "Service configuration"
msgstr "Конфигурация службы"
msgid "Service for determining the public IP address through DNS."
msgstr "Сервис для определения публичного IP адреса через DNS."
msgid "Service for determining the public IP address."
msgstr "Сервис для определения публичного IP адреса."
msgid "Service: detector always runs as a system service."
msgstr "Служба: детектор работает постоянно, как системная служба."
@@ -505,6 +532,12 @@ msgstr "TCP-порт"
msgid "TCP port connection"
msgstr "Подключение к TCP-порту"
msgid "Telegram bot API token."
msgstr "API токен Telegram бота."
msgid "Telegram message will be sent when connected or disconnected from the Internet."
msgstr "Сообщение в Telegram будет отправлено при подключении или отключении от Интернет."
msgid "The type of record requested in the DNS query (if the service supports it)."
msgstr "Тип записи запрашиваемой в DNS-запросе (если сервис поддерживает)."
@@ -517,6 +550,9 @@ msgstr "Таймаут между остановкой и запуском се
msgid "Timeout between stopping and starting a ModemManger interface when restarting."
msgstr "Таймаут между остановкой и запуском интерфейса ModemManger при перезапуске."
msgid "To support HTTP services you need to install curl."
msgstr "Для поддержки HTTP сервисов необходимо установить curl."
msgid "Type a time string"
msgstr "Введите строку времени"
@@ -557,12 +593,20 @@ msgstr ""
msgid "When email will be sent"
msgstr "Когда будет отправлено сообщение"
msgid "When message will be sent"
msgstr "Когда будет отправлено сообщение"
msgid "Windows: 32 bytes"
msgstr "Windows: 32 байта"
msgid "Write messages to the system log."
msgstr "Записывать сообщения в системный журнал."
msgid ""
"You need to register a new %sTelegram bot%s. Then get the bot's API token and paste it into the <code>Bot token</code> field. After that, open a chat with the bot, write something (in the Telegram app) and you will be able to get the chat ID using the <code>ID</code> button."
msgstr ""
"Необходимо зарегистрировать новый %sTelegram бот%s. Затем получить API токен бота и вставить его в поле <code>Токен бота</code>. После этого, откройте чат с ботом, напишите что-нибудь (в приложении Telegram) и можно будет получить ID чата с помощью кнопки <code>ID</code>."
msgid "after connection"
msgstr "после подключения"

View File

@@ -44,6 +44,15 @@ msgstr ""
msgid "Blink"
msgstr ""
msgid "Bot API token is missing!"
msgstr ""
msgid "Bot token"
msgstr ""
msgid "Chat ID"
msgstr ""
msgid "Check type"
msgstr ""
@@ -68,6 +77,9 @@ msgstr ""
msgid "Contents have been saved."
msgstr ""
msgid "Curl is not available..."
msgstr ""
msgid "Dead interval"
msgstr ""
@@ -101,9 +113,6 @@ msgstr ""
msgid "DNS query type"
msgstr ""
msgid "DNS provider"
msgstr ""
msgid "Edit"
msgstr ""
@@ -140,6 +149,9 @@ msgstr ""
msgid "Enabled"
msgstr ""
msgid "Error"
msgstr ""
msgid "Expecting:"
msgstr ""
@@ -181,12 +193,18 @@ msgstr ""
msgid "Huge: 1492 bytes"
msgstr ""
msgid "ID of the Telegram chat to which messages will be sent."
msgstr ""
msgid "ICMP-echo request (ping)"
msgstr ""
msgid "ICMP packet data size"
msgstr ""
msgid "Incorrect bot token?"
msgstr ""
msgid "Instances"
msgstr ""
@@ -289,6 +307,9 @@ msgstr ""
msgid "No <abbr title=\"Light Emitting Diode\">LED</abbr>s available..."
msgstr ""
msgid "No messages available. Write something to the bot and try again."
msgstr ""
msgid "Not scheduled"
msgstr ""
@@ -345,6 +366,9 @@ msgstr ""
msgid "Polling interval"
msgstr ""
msgid "Provider"
msgstr ""
msgid "Public IP"
msgstr ""
@@ -360,6 +384,9 @@ msgstr ""
msgid "Recipient"
msgstr ""
msgid "Request chat ID from bot API"
msgstr ""
msgid "Restart"
msgstr ""
@@ -435,7 +462,7 @@ msgstr ""
msgid "Service configuration"
msgstr ""
msgid "Service for determining the public IP address through DNS."
msgid "Service for determining the public IP address."
msgstr ""
msgid "Service: detector always runs as a system service."
@@ -477,6 +504,12 @@ msgstr ""
msgid "TCP port connection"
msgstr ""
msgid "Telegram bot API token."
msgstr ""
msgid "Telegram message will be sent when connected or disconnected from the Internet."
msgstr ""
msgid "The type of record requested in the DNS query (if the service supports it)."
msgstr ""
@@ -489,6 +522,9 @@ msgstr ""
msgid "Timeout between stopping and starting a ModemManger interface when restarting."
msgstr ""
msgid "To support HTTP services you need to install curl."
msgstr ""
msgid "Type a time string"
msgstr ""
@@ -525,12 +561,19 @@ msgstr ""
msgid "When email will be sent"
msgstr ""
msgid "When message will be sent"
msgstr ""
msgid "Windows: 32 bytes"
msgstr ""
msgid "Write messages to the system log."
msgstr ""
msgid ""
"You need to register a new %sTelegram bot%s. Then get the bot's API token and paste it into the <code>Bot token</code> field. After that, open a chat with the bot, write something (in the Telegram app) and you will be able to get the chat ID using the <code>ID</code> button."
msgstr ""
msgid "after connection"
msgstr ""

View File

@@ -4,6 +4,7 @@ local appName = "internet-detector"
local appExec = "/usr/bin/internet-detector"
local mailsendExec = "/usr/bin/mailsend"
local modemManagerInit = "/etc/init.d/modemmanager"
local curlExec = "/usr/bin/curl"
local InternetDetector = require(appName .. ".main")
local uci = require("uci")
@@ -37,6 +38,16 @@ local function init()
else
lines[#lines + 1] = '"email_mod":false'
end
if prequire(appName .. ".modules.mod_telegram") then
lines[#lines + 1] = '"telegram":true'
else
lines[#lines + 1] = '"telegram":false'
end
if unistd.access(curlExec, "x") then
lines[#lines + 1] = '"curl_exec":true'
else
lines[#lines + 1] = '"curl_exec":false'
end
return string.format("{%s}", table.concat(lines, ","))
end

BIN
screenshots/06.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 166 KiB