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.** **OpenWrt >= 21.02.**
**Dependences:** lua, luaposix, libuci-lua. **Dependences:** lua, luaposix, libuci-lua.
**Recommended:** curl.
## Installation notes: ## Installation notes:
opkg update 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 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.5.2-r1_all.ipk opkg install /tmp/internet-detector_1.6.0-r1_all.ipk
rm /tmp/internet-detector_1.5.2-r1_all.ipk rm /tmp/internet-detector_1.6.0-r1_all.ipk
service internet-detector start service internet-detector start
service internet-detector enable 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 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.5.2-r1_all.ipk opkg install /tmp/luci-app-internet-detector_1.6.0-r1_all.ipk
rm /tmp/luci-app-internet-detector_1.5.2-r1_all.ipk rm /tmp/luci-app-internet-detector_1.6.0-r1_all.ipk
service rpcd restart service rpcd restart
i18n-ru: 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 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.5.2-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.5.2-r1_all.ipk rm /tmp/luci-i18n-internet-detector-ru_1.6.0-r1_all.ipk
## Screenshots: ## Screenshots:
@@ -44,9 +45,9 @@ i18n-ru:
**Dependences:** modemmanager. **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 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.5.2-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.5.2-r1_all.ipk rm /tmp/internet-detector-mod-modem-restart_1.6.0-r1_all.ipk
service internet-detector restart service internet-detector restart
![](https://github.com/gSpotx2f/luci-app-internet-detector/blob/master/screenshots/04.jpg) ![](https://github.com/gSpotx2f/luci-app-internet-detector/blob/master/screenshots/04.jpg)
@@ -55,9 +56,20 @@ i18n-ru:
**Dependences:** mailsend. **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 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.5.2-r1_all.ipk opkg install /tmp/internet-detector-mod-email_1.6.0-r1_all.ipk
rm /tmp/internet-detector-mod-email_1.5.2-r1_all.ipk rm /tmp/internet-detector-mod-email_1.6.0-r1_all.ipk
service internet-detector restart service internet-detector restart
![](https://github.com/gSpotx2f/luci-app-internet-detector/blob/master/screenshots/05.jpg) ![](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 include $(TOPDIR)/rules.mk
PKG_NAME:=internet-detector-mod-email PKG_NAME:=internet-detector-mod-email
PKG_VERSION:=1.5.2 PKG_VERSION:=1.6.0
PKG_RELEASE:=1 PKG_RELEASE:=1
PKG_MAINTAINER:=gSpot <https://github.com/gSpotx2f/luci-app-internet-detector> PKG_MAINTAINER:=gSpot <https://github.com/gSpotx2f/luci-app-internet-detector>

View File

@@ -86,7 +86,7 @@ function Module:init(t)
self._enabled = true self._enabled = true
else else
self._enabled = false 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 end
if (not self.mailRecipient or if (not self.mailRecipient or
@@ -162,6 +162,7 @@ function Module:run(currentStatus, lastStatus, timeDiff, timeNow, inetChecked)
if not self._enabled then if not self._enabled then
return return
end end
if currentStatus == 1 then if currentStatus == 1 then
self._aliveCounter = 0 self._aliveCounter = 0
self._msgSentConnect = false self._msgSentConnect = false

View File

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

View File

@@ -72,7 +72,7 @@ function Module:init(t)
self._enabled = true self._enabled = true
else else
self._enabled = false self._enabled = false
self.syslog("warning", string.format( self.syslog("err", string.format(
"%s: modemmanager service is not available", self.name)) "%s: modemmanager service is not available", self.name))
end end
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 include $(TOPDIR)/rules.mk
PKG_NAME:=internet-detector PKG_NAME:=internet-detector
PKG_VERSION:=1.5.2 PKG_VERSION:=1.6.0
PKG_RELEASE:=1 PKG_RELEASE:=1
PKG_MAINTAINER:=gSpot <https://github.com/gSpotx2f/luci-app-internet-detector> 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_mode '0'
option mod_email_alive_period '0' option mod_email_alive_period '0'
option mod_email_mail_security 'tls' 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_enabled '0'
option mod_user_scripts_alive_period '0' option mod_user_scripts_alive_period '0'
option mod_user_scripts_up_script_attempts '1' option mod_user_scripts_up_script_attempts '1'

View File

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

View File

@@ -5,7 +5,7 @@
include $(TOPDIR)/rules.mk include $(TOPDIR)/rules.mk
PKG_NAME:=luci-app-internet-detector PKG_NAME:=luci-app-internet-detector
PKG_VERSION:=1.5.2 PKG_VERSION:=1.6.0
PKG_RELEASE:=1 PKG_RELEASE:=1
LUCI_TITLE:=LuCI support for internet-detector LUCI_TITLE:=LuCI support for internet-detector
LUCI_DEPENDS:=+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({ return view.extend({
appName : 'internet-detector', appName : 'internet-detector',
configDir : '/etc/internet-detector', configDir : '/etc/internet-detector',
@@ -149,10 +176,13 @@ return view.extend({
ledsPath : '/sys/class/leds', ledsPath : '/sys/class/leds',
ledsPerInstance : 3, ledsPerInstance : 3,
leds : [], leds : [],
tgUpdatesURLPattern : 'https://api.telegram.org/bot%s/getUpdates',
mm : false, mm : false,
mmInit : false, mmInit : false,
email : false, email : false,
emailExec : false, emailExec : false,
telegram : false,
curlExec : false,
modRegularScriptNextRun: {}, modRegularScriptNextRun: {},
callInitStatus: rpc.declare({ 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({ CBITimeInput: form.Value.extend({
__name__ : 'CBI.TimeInput', __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({ CBIBlockInetStatus: form.Value.extend({
__name__ : 'CBI.BlockInetStatus', __name__ : 'CBI.BlockInetStatus',
@@ -542,6 +648,12 @@ return view.extend({
if(data[3].email_exec) { if(data[3].email_exec) {
this.emailExec = true; 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'); this.currentAppMode = uci.get(this.appName, 'config', 'mode');
@@ -756,6 +868,9 @@ return view.extend({
if(this.email) { if(this.email) {
s.tab('email', _('Email notification')); s.tab('email', _('Email notification'));
}; };
if(this.telegram) {
s.tab('telegram', _('Telegram notification'));
};
s.tab('user_scripts', _('User scripts')); s.tab('user_scripts', _('User scripts'));
s.tab('regular_script', _('Regular script')); s.tab('regular_script', _('Regular script'));
}; };
@@ -1081,16 +1196,27 @@ return view.extend({
// provider // provider
o = s.taboption('public_ip', form.ListValue, o = s.taboption('public_ip', form.ListValue,
'mod_public_ip_provider', _('DNS provider'), 'mod_public_ip_provider', _('Provider'),
_('Service for determining the public IP address through DNS.') _('Service for determining the public IP address.') + '<br />' +
((this.curlExec) ? '' :
_('To support HTTP services you need to install curl.'))
); );
o.modalonly = true; o.modalonly = true;
o.value('opendns1'); o.value('opendns1', 'opendns1 (DNS)');
o.value('opendns2'); o.value('opendns2', 'opendns2 (DNS)');
o.value('opendns3'); o.value('opendns3', 'opendns3 (DNS)');
o.value('opendns4'); o.value('opendns4', 'opendns4 (DNS)');
o.value('akamai'); o.value('google', 'google (DNS)');
o.value('google'); 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'; o.default = 'opendns1';
// ipv6 // ipv6
@@ -1102,6 +1228,12 @@ return view.extend({
o.value('0', 'A (IPv4)'); o.value('0', 'A (IPv4)');
o.value('1', 'AAAA (IPv6)'); o.value('1', 'AAAA (IPv6)');
o.default = '0'; 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 // interval
o = s.taboption('public_ip', form.ListValue, 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 // User scripts
o = s.taboption('user_scripts', form.DummyValue, '_dummy'); o = s.taboption('user_scripts', form.DummyValue, '_dummy');

View File

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

View File

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

View File

@@ -4,6 +4,7 @@ local appName = "internet-detector"
local appExec = "/usr/bin/internet-detector" local appExec = "/usr/bin/internet-detector"
local mailsendExec = "/usr/bin/mailsend" local mailsendExec = "/usr/bin/mailsend"
local modemManagerInit = "/etc/init.d/modemmanager" local modemManagerInit = "/etc/init.d/modemmanager"
local curlExec = "/usr/bin/curl"
local InternetDetector = require(appName .. ".main") local InternetDetector = require(appName .. ".main")
local uci = require("uci") local uci = require("uci")
@@ -37,6 +38,16 @@ local function init()
else else
lines[#lines + 1] = '"email_mod":false' lines[#lines + 1] = '"email_mod":false'
end 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, ",")) return string.format("{%s}", table.concat(lines, ","))
end end

BIN
screenshots/06.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 166 KiB