Files
dotfiles/config/quickshell/Services/IpService.qml
2025-10-25 12:14:47 +02:00

208 lines
5.8 KiB
QML

import QtQuick
import Quickshell
import Quickshell.Io
import qs.Services
import qs.Utils
pragma Singleton
Singleton {
property alias ip: cacheFileAdapter.ip
readonly property string cacheFilePath: CacheService.ipCacheFile
readonly property string aliasFilePath: Qt.resolvedUrl("../Assets/Config/IpAliases.json")
property string countryCode: "N/A"
property string alias: ""
property real fetchInterval: 120 // in s
property real fetchTimeout: 10 // in s
readonly property string ipURL: "https://api.uyanide.com/ip"
readonly property string geoURL: "https://api.ipinfo.io/lite/"
property string geoURLToken: ""
function fetchIP() {
curl.fetch(ipURL, function(success, data) {
if (success) {
try {
const response = JSON.parse(data);
if (response && response.ip) {
let newIP = response.ip;
Logger.log("IpService", "Fetched IP: " + newIP);
if (newIP !== ip) {
ip = newIP;
countryCode = "N/A";
fetchGeoInfo(true); // Fetch geo info only if IP has changed
}
} else {
Logger.error("IpService", "IP response does not contain 'ip' field");
}
} catch (e) {
Logger.error("IpService", "Failed to parse IP response: " + e);
}
} else {
Logger.error("IpService", "Failed to fetch IP");
}
});
}
function fetchGeoInfo(notify) {
if (!ip || ip === "N/A") {
countryCode = "N/A";
return ;
}
let url = geoURL + ip;
if (geoURLToken)
url += "?token=" + geoURLToken;
cacheFileAdapter.geoInfo = null;
curl.fetch(url, function(success, data) {
if (success) {
try {
const response = JSON.parse(data);
if (response && (response.country_code || response.country)) {
let newCountryCode = response.country_code || response.country;
Logger.log("IpService", "Fetched country code: " + newCountryCode);
countryCode = newCountryCode;
} else {
Logger.error("IpService", "Geo response does not contain 'country_code' field");
}
cacheFileAdapter.geoInfo = response;
} catch (e) {
Logger.error("IpService", "Failed to parse geo response: " + e);
}
} else {
Logger.error("IpService", "Failed to fetch geo info");
}
SendNotification.show("New IP", `IP: ${ip}\nCountry: ${countryCode}${alias ? `\nAlias: ${alias}` : ""}`);
cacheFile.writeAdapter();
});
}
function refresh() {
fetchTimer.stop();
ip = "N/A";
fetchIP();
fetchTimer.start();
}
function updateAlias() {
if (!ip || ip === "N/A") {
alias = "";
return ;
}
alias = "";
for (let i = 0; i < aliasFileAdapter.aliases.length; i++) {
let entry = aliasFileAdapter.aliases[i];
if (entry.ip === ip) {
alias = entry.alias;
Logger.log("IpService", "Found alias for IP " + ip + ": " + alias);
break;
}
}
}
Component.onCompleted: () => {
return updateAlias();
}
onIpChanged: () => {
return updateAlias();
}
NetworkFetch {
id: curl
}
Process {
id: ipMonitor
command: ["ip", "monitor", "address", "route"]
running: true
stdout: SplitParser {
splitMarker: "\n"
onRead: {
ipMonitorDebounce.restart();
}
}
}
Timer {
id: ipMonitorDebounce
interval: 1000
repeat: false
running: false
onTriggered: {
fetchIP();
}
}
Timer {
id: fetchTimer
interval: fetchInterval * 1000
repeat: true
running: true
onTriggered: {
fetchTimer.stop();
fetchIP();
fetchTimer.start();
}
}
FileView {
id: tokenFile
path: Qt.resolvedUrl("../Assets/Config/GeoInfoToken.txt")
onLoaded: {
geoURLToken = tokenFile.text();
if (!geoURLToken)
Logger.warn("IpService", "No token found for geoIP service, assuming none is required");
if (geoURLToken[geoURLToken.length - 1] === "\n")
geoURLToken = geoURLToken.slice(0, -1);
fetchIP();
fetchTimer.start();
}
}
FileView {
id: cacheFile
path: cacheFilePath
watchChanges: false
onLoaded: {
Logger.log("IpService", "Loaded IP from cache file: " + cacheFileAdapter.ip);
if (cacheFileAdapter.geoInfo) {
countryCode = cacheFileAdapter.geoInfo.country_code || cacheFileAdapter.country || "N/A";
Logger.log("IpService", "Loaded country code from cache file: " + countryCode);
}
}
JsonAdapter {
id: cacheFileAdapter
property string ip: "N/A"
property var geoInfo: null
}
}
FileView {
id: aliasFile
path: aliasFilePath
watchChanges: true
onLoaded: {
Logger.log("IpService", "Loaded IP aliases from file, total aliases: " + aliasFileAdapter.aliases.length);
}
JsonAdapter {
id: aliasFileAdapter
property var aliases: []
}
}
}