Merge pull request #114 from ShadowOwlCode/main

Add domain support in IP Address
This commit is contained in:
Whispering Wind
2025-03-21 00:45:16 +03:30
committed by GitHub
5 changed files with 100 additions and 74 deletions

View File

@ -16,7 +16,7 @@ get_singbox_domain_and_port() {
get_normalsub_domain_and_port() { get_normalsub_domain_and_port() {
if [ -f "$NORMALSUB_ENV" ]; then if [ -f "$NORMALSUB_ENV" ]; then
local domain port local domain port subpath
domain=$(grep -E '^HYSTERIA_DOMAIN=' "$NORMALSUB_ENV" | cut -d'=' -f2) domain=$(grep -E '^HYSTERIA_DOMAIN=' "$NORMALSUB_ENV" | cut -d'=' -f2)
port=$(grep -E '^HYSTERIA_PORT=' "$NORMALSUB_ENV" | cut -d'=' -f2) port=$(grep -E '^HYSTERIA_PORT=' "$NORMALSUB_ENV" | cut -d'=' -f2)
subpath=$(grep -E '^SUBPATH=' "$NORMALSUB_ENV" | cut -d'=' -f2) subpath=$(grep -E '^SUBPATH=' "$NORMALSUB_ENV" | cut -d'=' -f2)
@ -81,7 +81,11 @@ show_uri() {
local uri_base="hy2://$username%3A$authpassword@$ip:$port" local uri_base="hy2://$username%3A$authpassword@$ip:$port"
if [ "$ip_version" -eq 6 ]; then if [ "$ip_version" -eq 6 ]; then
if [[ "$ip" =~ ^[0-9a-fA-F:]+$ ]]; then
uri_base="hy2://$username%3A$authpassword@[$ip]:$port" uri_base="hy2://$username%3A$authpassword@[$ip]:$port"
else
uri_base="hy2://$username%3A$authpassword@$ip:$port"
fi
fi fi
local params="" local params=""

View File

@ -1,11 +1,24 @@
from pydantic import BaseModel from pydantic import BaseModel, field_validator, ValidationInfo
from ipaddress import IPv4Address, IPv6Address from ipaddress import IPv4Address, IPv6Address, ip_address
import socket
class StatusResponse(BaseModel): class StatusResponse(BaseModel):
ipv4: IPv4Address | None = None ipv4: str | None = None
ipv6: IPv6Address | None = None ipv6: str | None = None
@field_validator('ipv4', 'ipv6', mode='before')
def check_ip_or_domain(cls, v: str, info: ValidationInfo):
if v is None:
return v
try:
ip_address(v)
return v
except ValueError:
try:
socket.getaddrinfo(v, None)
return v
except socket.gaierror:
raise ValueError(f"'{v}' is not a valid IPv4 or IPv6 address or domain name")
class EditInputBody(StatusResponse): class EditInputBody(StatusResponse):
pass pass

View File

@ -264,14 +264,18 @@
if (!port) return false; if (!port) return false;
return /^[0-9]+$/.test(port) && parseInt(port) > 0 && parseInt(port) <= 65535; return /^[0-9]+$/.test(port) && parseInt(port) > 0 && parseInt(port) <= 65535;
} }
function isValidIP(ip, version) { function isValidIPorDomain(input) {
if (!ip) return true; if (!input) return true;
const ipV4Regex = /^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/;
const ipV6Regex = /^(([0-9a-fA-F]{1,4}:){7,7}([0-9a-fA-F]{1,4}|:)|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:))$/;
const domainRegex = /^(?!\-)(?:[a-zA-Z\d\-]{0,62}[a-zA-Z\d]\.){1,126}(?!\d+)[a-zA-Z\d]{1,63}$/;
const lowerInput = input.toLowerCase();
if (ipV4Regex.test(input)) return true;
if (ipV6Regex.test(input)) return true;
if (domainRegex.test(lowerInput) && !lowerInput.startsWith("http://") && !lowerInput.startsWith("https://")) return true;
if (version === 4) {
return /^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/.test(ip);
} else if (version === 6) {
return /^(([0-9a-fA-F]{1,4}:){7,7}([0-9a-fA-F]{1,4}|:)|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:))$/.test(ip);
}
return false; return false;
} }
@ -352,15 +356,8 @@
} else { } else {
input.removeClass('is-invalid'); input.removeClass('is-invalid');
} }
} else if (id === 'ipv4') { } else if (id === 'ipv4' || id === 'ipv6') { // Apply isValidIPorDomain for both IPv4 and IPv6
if (!isValidIP(input.val(), 4)) { if (!isValidIPorDomain(input.val())) {
input.addClass('is-invalid');
isValid = false;
} else {
input.removeClass('is-invalid');
}
} else if (id === 'ipv6') {
if (!isValidIP(input.val(), 6)) {
input.addClass('is-invalid'); input.addClass('is-invalid');
isValid = false; isValid = false;
} else { } else {
@ -447,15 +444,15 @@
$("#ipv4").val(data.ipv4 || ""); $("#ipv4").val(data.ipv4 || "");
$("#ipv6").val(data.ipv6 || ""); $("#ipv6").val(data.ipv6 || "");
$("#ipv4").attr("placeholder", "Enter IPv4"); $("#ipv4").attr("placeholder", "Enter IPv4 or Domain");
$("#ipv6").attr("placeholder", "Enter IPv6"); $("#ipv6").attr("placeholder", "Enter IPv6 or Domain");
}, },
error: function () { error: function () {
console.error("Failed to fetch IP addresses."); console.error("Failed to fetch IP addresses.");
$("#ipv4").attr("placeholder", "Enter IPv4"); $("#ipv4").attr("placeholder", "Enter IPv4 or Domain");
$("#ipv6").attr("placeholder", "Enter IPv6"); $("#ipv6").attr("placeholder", "Enter IPv6 or Domain");
} }
}); });
@ -695,21 +692,14 @@
$(this).addClass('is-invalid'); $(this).addClass('is-invalid');
} }
}); });
$('#ipv4').on('input', function () { $('#ipv4, #ipv6').on('input', function () { // Apply to both ipv4 and ipv6
if (isValidIP($(this).val(), 4)) { if (isValidIPorDomain($(this).val())) {
$(this).removeClass('is-invalid'); $(this).removeClass('is-invalid');
} else { } else {
$(this).addClass('is-invalid'); $(this).addClass('is-invalid');
} }
}); });
$('#ipv6').on('input', function () {
if (isValidIP($(this).val(), 6)) {
$(this).removeClass('is-invalid');
} else {
$(this).addClass('is-invalid');
}
});
$('#telegram_api_token, #telegram_admin_id').on('input', function () { $('#telegram_api_token, #telegram_admin_id').on('input', function () {
if ($(this).val().trim() !== "") { if ($(this).val().trim() !== "") {
$(this).removeClass('is-invalid'); $(this).removeClass('is-invalid');

View File

@ -679,8 +679,7 @@
// QR Code Modal // QR Code Modal
$("#qrcodeModal").on("show.bs.modal", function (event) { $("#qrcodeModal").on("show.bs.modal", function (event) {
const button = $(event.relatedTarget); const button = $(event.relatedTarget);
const username = button.data("username"); const configContainer = $(`#userConfigs-${button.data("username")}`);
const configContainer = $(`#userConfigs-${username}`);
const qrcodesContainer = $("#qrcodesContainer"); const qrcodesContainer = $("#qrcodesContainer");
qrcodesContainer.empty(); qrcodesContainer.empty();
@ -688,12 +687,28 @@
const configLink = $(this).data("link"); const configLink = $(this).data("link");
const configType = $(this).find(".config-type").text().replace(":", ""); const configType = $(this).find(".config-type").text().replace(":", "");
// Create a card for each QR code let displayType = configType;
const hashMatch = configLink.match(/#(.+)$/);
if (hashMatch && hashMatch[1]) {
const hashValue = hashMatch[1];
if (hashValue.includes("IPv4") || hashValue.includes("IPv6")) {
displayType = hashValue;
}
} else if (configLink.includes("ipv4") || configLink.includes("IPv4")) {
displayType = "IPv4";
} else if (configLink.includes("ipv6") || configLink.includes("IPv6")) {
displayType = "IPv6";
}
const qrCodeId = `qrcode-${displayType}-${Math.random().toString(36).substring(2, 10)}`;
const card = $(` const card = $(`
<div class="card d-inline-block mx-2 my-2" style="width: 180px;"> <div class="card d-inline-block my-2">
<div class="card-body"> <div class="card-body">
<div id="qrcode-${configType}" class="mx-auto cursor-pointer"></div> <div id="${qrCodeId}" class="mx-auto cursor-pointer"></div>
<div class="config-type-text mt-2 text-center">${configType}</div> <br>
<div class="config-type-text mt-2 text-center">${displayType}</div>
</div> </div>
</div> </div>
`); `);
@ -701,9 +716,10 @@
qrcodesContainer.append(card); qrcodesContainer.append(card);
const qrCodeStyling = new QRCodeStyling({ const qrCodeStyling = new QRCodeStyling({
width: 150, width: 180,
height: 150, height: 180,
data: configLink, data: configLink,
margin: 5,
dotsOptions: { dotsOptions: {
color: "#212121", color: "#212121",
type: "square" type: "square"
@ -720,15 +736,14 @@
} }
}); });
qrCodeStyling.append(document.getElementById(`qrcode-${configType}`)); qrCodeStyling.append(document.getElementById(qrCodeId));
// Add click to copy functionality to the card
card.on("click", function () { card.on("click", function () {
navigator.clipboard.writeText(configLink) navigator.clipboard.writeText(configLink)
.then(() => { .then(() => {
Swal.fire({ Swal.fire({
icon: "success", icon: "success",
title: configType + " link copied!", title: displayType + " link copied!",
showConfirmButton: false, showConfirmButton: false,
timer: 1500, timer: 1500,
}); });
@ -745,12 +760,10 @@
}); });
}); });
// Prevent modal from closing when clicking inside
$("#qrcodeModal .modal-content").on("click", function (e) { $("#qrcodeModal .modal-content").on("click", function (e) {
e.stopPropagation(); e.stopPropagation();
}); });
// Clear the QR code when the modal is hidden
$("#qrcodeModal").on("hidden.bs.modal", function () { $("#qrcodeModal").on("hidden.bs.modal", function () {
$("#qrcodesContainer").empty(); $("#qrcodesContainer").empty();
}); });
@ -759,7 +772,6 @@
$("#qrcodeModal").modal("hide"); $("#qrcodeModal").modal("hide");
}); });
// Search Functionality
function filterUsers() { function filterUsers() {
const searchText = $("#searchInput").val().toLowerCase(); const searchText = $("#searchInput").val().toLowerCase();

35
menu.sh
View File

@ -323,35 +323,42 @@ hysteria2_change_sni_handler() {
edit_ips() { edit_ips() {
while true; do while true; do
echo "======================================" echo "======================================"
echo " IP Address Manager " echo " IP/Domain Address Manager "
echo "======================================" echo "======================================"
echo "1. Change IP4" echo "1. Change IPv4 or Domain"
echo "2. Change IP6" echo "2. Change IPv6 or Domain"
echo "0. Back" echo "0. Back"
echo "======================================" echo "======================================"
read -p "Enter your choice [1-3]: " choice read -p "Enter your choice [0-2]: " choice
case $choice in case $choice in
1) 1)
read -p "Enter the new IPv4 address: " new_ip4 read -p "Enter the new IPv4 address or domain: " new_ip4_or_domain
if [[ $new_ip4 =~ ^([0-9]{1,3}\.){3}[0-9]{1,3}$ ]]; then if [[ $new_ip4_or_domain =~ ^([0-9]{1,3}\.){3}[0-9]{1,3}$ ]]; then
if [[ $(echo "$new_ip4" | awk -F. '{for (i=1;i<=NF;i++) if ($i>255) exit 1}') ]]; then if [[ $(echo "$new_ip4_or_domain" | awk -F. '{for (i=1;i<=NF;i++) if ($i>255) exit 1}') ]]; then
echo "Error: Invalid IPv4 address. Values must be between 0 and 255." echo "Error: Invalid IPv4 address. Values must be between 0 and 255."
else else
python3 "$CLI_PATH" ip-address --edit -4 "$new_ip4" python3 "$CLI_PATH" ip-address --edit -4 "$new_ip4_or_domain"
echo "IPv4 address has been updated to $new_ip4_or_domain."
fi fi
elif [[ $new_ip4_or_domain =~ ^[a-zA-Z0-9.-]+$ ]] && [[ ! $new_ip4_or_domain =~ [/:] ]]; then
python3 "$CLI_PATH" ip-address --edit -4 "$new_ip4_or_domain"
echo "Domain has been updated to $new_ip4_or_domain."
else else
echo "Error: Invalid IPv4 address format." echo "Error: Invalid IPv4 or domain format."
fi fi
break break
;; ;;
2) 2)
read -p "Enter the new IPv6 address: " new_ip6 read -p "Enter the new IPv6 address or domain: " new_ip6_or_domain
if [[ $new_ip6 =~ ^(([0-9a-fA-F]{1,4}:){7}([0-9a-fA-F]{1,4}|:)|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:))$ ]]; then if [[ $new_ip6_or_domain =~ ^(([0-9a-fA-F]{1,4}:){7}([0-9a-fA-F]{1,4}|:)|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:))$ ]]; then
python3 "$CLI_PATH" ip-address --edit -6 "$new_ip6" python3 "$CLI_PATH" ip-address --edit -6 "$new_ip6_or_domain"
echo "IPv6 address has been updated to $new_ip6." echo "IPv6 address has been updated to $new_ip6_or_domain."
elif [[ $new_ip6_or_domain =~ ^[a-zA-Z0-9.-]+$ ]] && [[ ! $new_ip6_or_domain =~ [/:] ]]; then
python3 "$CLI_PATH" ip-address --edit -6 "$new_ip6_or_domain"
echo "Domain has been updated to $new_ip6_or_domain."
else else
echo "Error: Invalid IPv6 address format." echo "Error: Invalid IPv6 or domain format."
fi fi
break break
;; ;;