diff --git a/4/m_geomaxlite.cpp b/4/m_geomaxlite.cpp
new file mode 100644
index 00000000..5ca13483
--- /dev/null
+++ b/4/m_geomaxlite.cpp
@@ -0,0 +1,146 @@
+/*
+ * InspIRCd -- Internet Relay Chat Daemon
+ *
+ * Copyright (C) 2024 reverse
+ *
+ * This file contains a third-party module for InspIRCd. You can
+ * redistribute it and/or modify it under the terms of the GNU General Public
+ * License as published by the Free Software Foundation, version 2.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+ * details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+/// $ModAuthor: reverse
+/// $ModDesc: Adds city and country information to WHOIS using a local MaxMind GeoLite2 database with user mode +y.
+/// $ModConfig:
+/// $ModDepends: core 4
+
+/// $CompilerFlags: find_compiler_flags("libmaxminddb")
+/// $LinkerFlags: find_linker_flags("libmaxminddb")
+
+/// $PackageInfo: require_system("alpine") libmaxminddb-dev pkgconf
+/// $PackageInfo: require_system("arch") pkgconf libmaxminddb
+/// $PackageInfo: require_system("darwin") libmaxminddb pkg-config
+/// $PackageInfo: require_system("debian~") libmaxminddb-dev pkg-config
+/// $PackageInfo: require_system("rhel~") pkg-config libmaxminddb-devel
+
+#include "inspircd.h"
+#include "modules/whois.h"
+#include "extension.h"
+#include
+
+class ModuleGeoMaxLite final
+ : public Module
+ , public Whois::EventListener
+{
+private:
+ // MaxMind DB handle
+ MMDB_s mmdb;
+
+ // Path to the MaxMind database file
+ std::string dbpath;
+
+ // Extension item to store a user's city/country info
+ StringExtItem country_item;
+
+ // +y user mode for enabling GeoMaxLite lookups
+ SimpleUserMode geomaxlite_mode;
+
+public:
+ ModuleGeoMaxLite()
+ : Module(VF_OPTCOMMON, "Adds city and country information to WHOIS using a local MaxMind GeoLite2 database.")
+ , Whois::EventListener(this)
+ , country_item(this, "geomaxlite-country", ExtensionType::USER, true)
+ , geomaxlite_mode(this, "geomaxlite", 'y', false)
+ {
+ }
+
+ void ReadConfig(ConfigStatus& status) override
+ {
+ // Read the block from the config
+ auto& tag = ServerInstance->Config->ConfValue("geomaxlite");
+ dbpath = ServerInstance->Config->Paths.PrependConfig(
+ tag->getString("dbpath", "data/GeoLite2-City.mmdb"));
+
+ int status_open = MMDB_open(dbpath.c_str(), MMDB_MODE_MMAP, &mmdb);
+ if (status_open != MMDB_SUCCESS)
+ {
+ // If the database can't be opened, throw a module exception
+ std::string error_msg = "GeoMaxLite: Failed to open GeoLite2 database: "
+ + std::string(MMDB_strerror(status_open));
+ throw ModuleException(this, error_msg.c_str());
+ }
+ }
+
+ void OnWhois(Whois::Context& whois) override
+ {
+ User* target = whois.GetTarget();
+
+ // Only display city/country data if the target user has +y (geomaxlite) set
+ if (!target->IsModeSet(geomaxlite_mode))
+ return;
+
+ const std::string* info = country_item.Get(target);
+ if (info && !info->empty())
+ {
+ whois.SendLine(RPL_WHOISSPECIAL, "is connecting from " + *info);
+ }
+ else
+ {
+ whois.SendLine(RPL_WHOISSPECIAL, "City: Unknown, Country: Unknown");
+ }
+ }
+
+ void OnChangeRemoteAddress(LocalUser* user) override
+ {
+ // If the address is not an IP, unset any stored geo info
+ if (!user->client_sa.is_ip())
+ {
+ country_item.Unset(user);
+ return;
+ }
+
+ int gai_error = 0;
+ const struct sockaddr* addr = &user->client_sa.sa;
+ MMDB_lookup_result_s result = MMDB_lookup_sockaddr(&mmdb, addr, &gai_error);
+
+ // Unset if we got a lookup error or didn't find anything
+ if (gai_error != 0 || !result.found_entry)
+ {
+ country_item.Unset(user);
+ return;
+ }
+
+ // Fetch city and country strings (in English)
+ MMDB_entry_data_s city_data = {};
+ MMDB_entry_data_s country_data = {};
+
+ int status_city = MMDB_get_value(&result.entry, &city_data, "city", "names", "en", nullptr);
+ int status_country = MMDB_get_value(&result.entry, &country_data, "country", "names", "en", nullptr);
+
+ std::string city = (status_city == MMDB_SUCCESS && city_data.has_data)
+ ? std::string(city_data.utf8_string, city_data.data_size)
+ : "Unknown";
+
+ std::string country = (status_country == MMDB_SUCCESS && country_data.has_data)
+ ? std::string(country_data.utf8_string, country_data.data_size)
+ : "Unknown";
+
+ // Store
+ country_item.Set(user, "City: " + city + ", Country: " + country);
+ }
+
+ ~ModuleGeoMaxLite() override
+ {
+ // Close the MaxMind DB on unload
+ MMDB_close(&mmdb);
+ }
+};
+
+MODULE_INIT(ModuleGeoMaxLite)
diff --git a/4/m_whoisgeolite.cpp b/4/m_whoisgeolite.cpp
deleted file mode 100644
index 789bb61a..00000000
--- a/4/m_whoisgeolite.cpp
+++ /dev/null
@@ -1,116 +0,0 @@
-/*
- * InspIRCd -- Internet Relay Chat Daemon
- *
- * Copyright (C) 2024 reverse
- *
- * This file contains a third-party module for InspIRCd. You can
- * redistribute it and/or modify it under the terms of the GNU General Public
- * License as published by the Free Software Foundation, version 2.
- *
- * This program is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
- * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
- * details.
-
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see .
- */
-
-/// $ModAuthor: reverse
-/// $ModDesc: Adds city and country information to WHOIS using the MaxMind database.
-/// $ModConfig:
-/// $ModDepends: core 4
-
-
-/// $CompilerFlags: find_compiler_flags("libmaxminddb")
-/// $LinkerFlags: find_linker_flags("libmaxminddb")
-
-/// $PackageInfo: require_system("alpine") libmaxminddb-dev pkgconf
-/// $PackageInfo: require_system("arch") pkgconf libmaxminddb
-/// $PackageInfo: require_system("darwin") libmaxminddb pkg-config
-/// $PackageInfo: require_system("debian~") libmaxminddb-dev pkg-config
-/// $PackageInfo: require_system("rhel~") pkg-config libmaxminddb-devel
-
-#include "inspircd.h"
-#include "modules/whois.h"
-#include "extension.h"
-#include
-
-class ModuleWhoisGeoLite final : public Module, public Whois::EventListener
-{
-private:
- MMDB_s mmdb; // MaxMind database object
- std::string dbpath; // Path to the GeoLite2 database
- StringExtItem country_item; // Extension item for storing city and country info
-
-public:
- ModuleWhoisGeoLite()
- : Module(VF_OPTCOMMON, "Adds city and country information to WHOIS using the MaxMind database.")
- , Whois::EventListener(this)
- , country_item(this, "geo-lite-country", ExtensionType::USER, true) // Sync Servers
- {
- }
-
- void ReadConfig(ConfigStatus& status) override
- {
- auto& tag = ServerInstance->Config->ConfValue("geolite");
- dbpath = ServerInstance->Config->Paths.PrependConfig(tag->getString("dbpath", "data/GeoLite2-City.mmdb"));
-
- int status_open = MMDB_open(dbpath.c_str(), MMDB_MODE_MMAP, &mmdb);
- if (status_open != MMDB_SUCCESS) {
- std::string error_msg = "GeoLite2: Failed to open GeoLite2 database: " + std::string(MMDB_strerror(status_open));
- throw ModuleException(this, error_msg.c_str());
- }
- }
-
- void OnWhois(Whois::Context& whois) override
- {
- User* source = whois.GetSource();
- User* target = whois.GetTarget();
-
- if (!source->IsOper())
- return;
-
- const std::string* info = country_item.Get(target);
- if (info && !info->empty()) {
- whois.SendLine(RPL_WHOISSPECIAL, "is connecting from " + *info);
- } else {
- whois.SendLine(RPL_WHOISSPECIAL, "City: Unknown, Country: Unknown");
- }
- }
-
- void OnChangeRemoteAddress(LocalUser* user) override
- {
- if (!user->client_sa.is_ip()) {
- country_item.Unset(user);
- return;
- }
-
- int gai_error = 0;
- const struct sockaddr* addr = &user->client_sa.sa;
- MMDB_lookup_result_s result = MMDB_lookup_sockaddr(&mmdb, addr, &gai_error);
-
- if (gai_error != 0 || !result.found_entry) {
- country_item.Unset(user);
- return;
- }
-
- MMDB_entry_data_s city_data = {};
- MMDB_entry_data_s country_data = {};
- int status_city = MMDB_get_value(&result.entry, &city_data, "city", "names", "en", nullptr);
- int status_country = MMDB_get_value(&result.entry, &country_data, "country", "names", "en", nullptr);
-
- std::string city = (status_city == MMDB_SUCCESS && city_data.has_data) ? std::string(city_data.utf8_string, city_data.data_size) : "Unknown";
- std::string country = (status_country == MMDB_SUCCESS && country_data.has_data) ? std::string(country_data.utf8_string, country_data.data_size) : "Unknown";
-
- country_item.Set(user, "City: " + city + ", Country: " + country);
- }
-
- ~ModuleWhoisGeoLite() override
- {
- MMDB_close(&mmdb);
- }
-};
-
-MODULE_INIT(ModuleWhoisGeoLite)
-