Merge pull request #114 from ShadowOwlCode/main
Add domain support in IP Address
This commit is contained in:
@ -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=""
|
||||||
|
|||||||
@ -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
|
||||||
@ -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');
|
||||||
|
|||||||
@ -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
35
menu.sh
@ -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
|
||||||
;;
|
;;
|
||||||
|
|||||||
Reference in New Issue
Block a user