mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2025-08-23 11:26:52 +00:00
Compare commits
15 commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
24a3411465 | ||
![]() |
2198e7a28f | ||
![]() |
6b23b416a7 | ||
![]() |
16f53ce4c2 | ||
![]() |
27445b30e9 | ||
![]() |
3d0212c21d | ||
![]() |
978755960f | ||
![]() |
9b51e9a5c5 | ||
![]() |
6879a8fbcb | ||
![]() |
7258841491 | ||
![]() |
23dd80fbb0 | ||
![]() |
6556884c7f | ||
![]() |
d5c532c64f | ||
![]() |
ad5f774a1e | ||
![]() |
aa285914fa |
59 changed files with 528 additions and 74214 deletions
2
.github/FUNDING.yml
vendored
2
.github/FUNDING.yml
vendored
|
@ -1,6 +1,6 @@
|
|||
# These are supported funding model platforms
|
||||
|
||||
github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
|
||||
github: MHSanaei
|
||||
patreon: # Replace with a single Patreon username
|
||||
open_collective: # Replace with a single Open Collective username
|
||||
ko_fi: # Replace with a single Ko-fi username
|
||||
|
|
2
.github/workflows/docker.yml
vendored
2
.github/workflows/docker.yml
vendored
|
@ -10,7 +10,7 @@ jobs:
|
|||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v5
|
||||
with:
|
||||
submodules: true
|
||||
|
||||
|
|
2
.github/workflows/release.yml
vendored
2
.github/workflows/release.yml
vendored
|
@ -35,7 +35,7 @@ jobs:
|
|||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v5
|
||||
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@v5
|
||||
|
|
4
.gitignore
vendored
4
.gitignore
vendored
|
@ -29,9 +29,9 @@ main
|
|||
.DS_Store
|
||||
Thumbs.db
|
||||
|
||||
# Ignore Go specific files
|
||||
# Ignore Go build files
|
||||
*.exe
|
||||
*.exe~
|
||||
x-ui.db
|
||||
|
||||
# Ignore Docker specific files
|
||||
docker-compose.override.yml
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
# ========================================================
|
||||
# Stage: Builder
|
||||
# ========================================================
|
||||
FROM golang:1.24-alpine AS builder
|
||||
FROM golang:1.25-alpine AS builder
|
||||
WORKDIR /app
|
||||
ARG TARGETARCH
|
||||
|
||||
|
|
|
@ -48,7 +48,7 @@ bash <(curl -Ls https://raw.githubusercontent.com/mhsanaei/3x-ui/master/install.
|
|||
</p>
|
||||
|
||||
- USDT (TRC20): `TXncxkvhkDWGts487Pjqq1qT9JmwRUz8CC`
|
||||
- MATIC (polygon): `0x41C9548675D044c6Bfb425786C765bc37427256A`
|
||||
- POL (polygon): `0x41C9548675D044c6Bfb425786C765bc37427256A`
|
||||
- LTC (Litecoin): `ltc1q2ach7x6d2zq0n4l0t4zl7d7xe2s6fs7a3vspwv`
|
||||
|
||||
## النجوم عبر الزمن
|
||||
|
|
|
@ -48,7 +48,7 @@ Para documentación completa, visita la [Wiki del proyecto](https://github.com/M
|
|||
</p>
|
||||
|
||||
- USDT (TRC20): `TXncxkvhkDWGts487Pjqq1qT9JmwRUz8CC`
|
||||
- MATIC (polygon): `0x41C9548675D044c6Bfb425786C765bc37427256A`
|
||||
- POL (polygon): `0x41C9548675D044c6Bfb425786C765bc37427256A`
|
||||
- LTC (Litecoin): `ltc1q2ach7x6d2zq0n4l0t4zl7d7xe2s6fs7a3vspwv`
|
||||
|
||||
## Estrellas a lo Largo del Tiempo
|
||||
|
|
|
@ -48,7 +48,7 @@ bash <(curl -Ls https://raw.githubusercontent.com/mhsanaei/3x-ui/master/install.
|
|||
</p>
|
||||
|
||||
- USDT (TRC20): `TXncxkvhkDWGts487Pjqq1qT9JmwRUz8CC`
|
||||
- MATIC (polygon): `0x41C9548675D044c6Bfb425786C765bc37427256A`
|
||||
- POL (polygon): `0x41C9548675D044c6Bfb425786C765bc37427256A`
|
||||
- LTC (Litecoin): `ltc1q2ach7x6d2zq0n4l0t4zl7d7xe2s6fs7a3vspwv`
|
||||
|
||||
## ستارهها در طول زمان
|
||||
|
|
|
@ -48,7 +48,7 @@ For full documentation, please visit the [project Wiki](https://github.com/MHSan
|
|||
</p>
|
||||
|
||||
- USDT (TRC20): `TXncxkvhkDWGts487Pjqq1qT9JmwRUz8CC`
|
||||
- MATIC (polygon): `0x41C9548675D044c6Bfb425786C765bc37427256A`
|
||||
- POL (polygon): `0x41C9548675D044c6Bfb425786C765bc37427256A`
|
||||
- LTC (Litecoin): `ltc1q2ach7x6d2zq0n4l0t4zl7d7xe2s6fs7a3vspwv`
|
||||
|
||||
## Stargazers over Time
|
||||
|
|
|
@ -48,7 +48,7 @@ bash <(curl -Ls https://raw.githubusercontent.com/mhsanaei/3x-ui/master/install.
|
|||
</p>
|
||||
|
||||
- USDT (TRC20): `TXncxkvhkDWGts487Pjqq1qT9JmwRUz8CC`
|
||||
- MATIC (polygon): `0x41C9548675D044c6Bfb425786C765bc37427256A`
|
||||
- POL (polygon): `0x41C9548675D044c6Bfb425786C765bc37427256A`
|
||||
- LTC (Litecoin): `ltc1q2ach7x6d2zq0n4l0t4zl7d7xe2s6fs7a3vspwv`
|
||||
|
||||
## Звезды с течением времени
|
||||
|
|
|
@ -48,7 +48,7 @@ bash <(curl -Ls https://raw.githubusercontent.com/mhsanaei/3x-ui/master/install.
|
|||
</p>
|
||||
|
||||
- USDT (TRC20): `TXncxkvhkDWGts487Pjqq1qT9JmwRUz8CC`
|
||||
- MATIC (polygon): `0x41C9548675D044c6Bfb425786C765bc37427256A`
|
||||
- POL (polygon): `0x41C9548675D044c6Bfb425786C765bc37427256A`
|
||||
- LTC (Litecoin): `ltc1q2ach7x6d2zq0n4l0t4zl7d7xe2s6fs7a3vspwv`
|
||||
|
||||
## 随时间变化的星标数
|
||||
|
|
|
@ -3,7 +3,10 @@ package config
|
|||
import (
|
||||
_ "embed"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strings"
|
||||
)
|
||||
|
||||
|
@ -54,12 +57,32 @@ func GetBinFolderPath() string {
|
|||
return binFolderPath
|
||||
}
|
||||
|
||||
func getBaseDir() string {
|
||||
exePath, err := os.Executable()
|
||||
if err != nil {
|
||||
return "."
|
||||
}
|
||||
exeDir := filepath.Dir(exePath)
|
||||
exeDirLower := strings.ToLower(filepath.ToSlash(exeDir))
|
||||
if strings.Contains(exeDirLower, "/appdata/local/temp/") || strings.Contains(exeDirLower, "/go-build") {
|
||||
wd, err := os.Getwd()
|
||||
if err != nil {
|
||||
return "."
|
||||
}
|
||||
return wd
|
||||
}
|
||||
return exeDir
|
||||
}
|
||||
|
||||
func GetDBFolderPath() string {
|
||||
dbFolderPath := os.Getenv("XUI_DB_FOLDER")
|
||||
if dbFolderPath == "" {
|
||||
dbFolderPath = "/etc/x-ui"
|
||||
}
|
||||
if dbFolderPath != "" {
|
||||
return dbFolderPath
|
||||
}
|
||||
if runtime.GOOS == "windows" {
|
||||
return getBaseDir()
|
||||
}
|
||||
return "/etc/x-ui"
|
||||
}
|
||||
|
||||
func GetDBPath() string {
|
||||
|
@ -68,8 +91,54 @@ func GetDBPath() string {
|
|||
|
||||
func GetLogFolder() string {
|
||||
logFolderPath := os.Getenv("XUI_LOG_FOLDER")
|
||||
if logFolderPath == "" {
|
||||
logFolderPath = "/var/log"
|
||||
}
|
||||
if logFolderPath != "" {
|
||||
return logFolderPath
|
||||
}
|
||||
if runtime.GOOS == "windows" {
|
||||
return getBaseDir()
|
||||
}
|
||||
return "/var/log"
|
||||
}
|
||||
|
||||
func copyFile(src, dst string) error {
|
||||
in, err := os.Open(src)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer in.Close()
|
||||
|
||||
out, err := os.Create(dst)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer out.Close()
|
||||
|
||||
_, err = io.Copy(out, in)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return out.Sync()
|
||||
}
|
||||
|
||||
func init() {
|
||||
if runtime.GOOS != "windows" {
|
||||
return
|
||||
}
|
||||
if os.Getenv("XUI_DB_FOLDER") != "" {
|
||||
return
|
||||
}
|
||||
oldDBFolder := "/etc/x-ui"
|
||||
oldDBPath := fmt.Sprintf("%s/%s.db", oldDBFolder, GetName())
|
||||
newDBFolder := GetDBFolderPath()
|
||||
newDBPath := fmt.Sprintf("%s/%s.db", newDBFolder, GetName())
|
||||
_, err := os.Stat(newDBPath)
|
||||
if err == nil {
|
||||
return // new exists
|
||||
}
|
||||
_, err = os.Stat(oldDBPath)
|
||||
if os.IsNotExist(err) {
|
||||
return // old does not exist
|
||||
}
|
||||
_ = copyFile(oldDBPath, newDBPath) // ignore error
|
||||
}
|
||||
|
|
8
go.mod
8
go.mod
|
@ -1,6 +1,6 @@
|
|||
module x-ui
|
||||
|
||||
go 1.24.5
|
||||
go 1.25.0
|
||||
|
||||
require (
|
||||
github.com/gin-contrib/gzip v1.2.3
|
||||
|
@ -15,7 +15,7 @@ require (
|
|||
github.com/pelletier/go-toml/v2 v2.2.4
|
||||
github.com/robfig/cron/v3 v3.0.1
|
||||
github.com/shirou/gopsutil/v4 v4.25.7
|
||||
github.com/valyala/fasthttp v1.64.0
|
||||
github.com/valyala/fasthttp v1.65.0
|
||||
github.com/xlzd/gotp v0.1.0
|
||||
github.com/xtls/xray-core v1.250803.0
|
||||
go.uber.org/atomic v1.11.0
|
||||
|
@ -56,7 +56,7 @@ require (
|
|||
github.com/leodido/go-urn v1.4.0 // indirect
|
||||
github.com/lufia/plan9stats v0.0.0-20250317134145-8bc96cf8fc35 // indirect
|
||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||
github.com/mattn/go-sqlite3 v1.14.30 // indirect
|
||||
github.com/mattn/go-sqlite3 v1.14.32 // indirect
|
||||
github.com/miekg/dns v1.1.68 // indirect
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||
|
@ -92,7 +92,7 @@ require (
|
|||
golang.org/x/tools v0.36.0 // indirect
|
||||
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 // indirect
|
||||
golang.zx2c4.com/wireguard v0.0.0-20250521234502-f333402bd9cb // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250804133106-a7a43d27e69b // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250811230008-5f3141c8851a // indirect
|
||||
google.golang.org/protobuf v1.36.7 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
gvisor.dev/gvisor v0.0.0-20250503011706-39ed1f5ac29c // indirect
|
||||
|
|
12
go.sum
12
go.sum
|
@ -95,8 +95,8 @@ github.com/lufia/plan9stats v0.0.0-20250317134145-8bc96cf8fc35 h1:PpXWgLPs+Fqr32
|
|||
github.com/lufia/plan9stats v0.0.0-20250317134145-8bc96cf8fc35/go.mod h1:autxFIvghDt3jPTLoqZ9OZ7s9qTGNAWmYCjVFWPX/zg=
|
||||
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||
github.com/mattn/go-sqlite3 v1.14.30 h1:bVreufq3EAIG1Quvws73du3/QgdeZ3myglJlrzSYYCY=
|
||||
github.com/mattn/go-sqlite3 v1.14.30/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
|
||||
github.com/mattn/go-sqlite3 v1.14.32 h1:JD12Ag3oLy1zQA+BNn74xRgaBbdhbNIDYvQUEuuErjs=
|
||||
github.com/mattn/go-sqlite3 v1.14.32/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
|
||||
github.com/miekg/dns v1.1.68 h1:jsSRkNozw7G/mnmXULynzMNIsgY2dHC8LO6U6Ij2JEA=
|
||||
github.com/miekg/dns v1.1.68/go.mod h1:fujopn7TB3Pu3JM69XaawiU0wqjpL9/8xGop5UrTPps=
|
||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
|
@ -162,8 +162,8 @@ github.com/v2fly/ss-bloomring v0.0.0-20210312155135-28617310f63e h1:5QefA066A1tF
|
|||
github.com/v2fly/ss-bloomring v0.0.0-20210312155135-28617310f63e/go.mod h1:5t19P9LBIrNamL6AcMQOncg/r10y3Pc01AbHeMhwlpU=
|
||||
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
|
||||
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
|
||||
github.com/valyala/fasthttp v1.64.0 h1:QBygLLQmiAyiXuRhthf0tuRkqAFcrC42dckN2S+N3og=
|
||||
github.com/valyala/fasthttp v1.64.0/go.mod h1:dGmFxwkWXSK0NbOSJuF7AMVzU+lkHz0wQVvVITv2UQA=
|
||||
github.com/valyala/fasthttp v1.65.0 h1:j/u3uzFEGFfRxw79iYzJN+TteTJwbYkru9uDp3d0Yf8=
|
||||
github.com/valyala/fasthttp v1.65.0/go.mod h1:P/93/YkKPMsKSnATEeELUCkG8a7Y+k99uxNHVbKINr4=
|
||||
github.com/valyala/fastjson v1.6.4 h1:uAUNq9Z6ymTgGhcm0UynUAB6tlbakBrz6CQFax3BXVQ=
|
||||
github.com/valyala/fastjson v1.6.4/go.mod h1:CLCAqky6SMuOcxStkYQvblddUtoRxhYMGLrsQns1aXY=
|
||||
github.com/vishvananda/netlink v1.3.1 h1:3AEMt62VKqz90r0tmNhog0r/PpWKmrEShJU0wJW6bV0=
|
||||
|
@ -226,8 +226,8 @@ golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 h1:B82qJJgjvYKsXS9jeu
|
|||
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2/go.mod h1:deeaetjYA+DHMHg+sMSMI58GrEteJUUzzw7en6TJQcI=
|
||||
golang.zx2c4.com/wireguard v0.0.0-20250521234502-f333402bd9cb h1:whnFRlWMcXI9d+ZbWg+4sHnLp52d5yiIPUxMBSt4X9A=
|
||||
golang.zx2c4.com/wireguard v0.0.0-20250521234502-f333402bd9cb/go.mod h1:rpwXGsirqLqN2L0JDJQlwOboGHmptD5ZD6T2VmcqhTw=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250804133106-a7a43d27e69b h1:zPKJod4w6F1+nRGDI9ubnXYhU9NSWoFAijkHkUXeTK8=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250804133106-a7a43d27e69b/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250811230008-5f3141c8851a h1:tPE/Kp+x9dMSwUm/uM0JKK0IfdiJkwAbSMSeZBXXJXc=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250811230008-5f3141c8851a/go.mod h1:gw1tLEfykwDz2ET4a12jcXt4couGAm7IwsVaTy0Sflo=
|
||||
google.golang.org/grpc v1.74.2 h1:WoosgB65DlWVC9FqI82dGsZhWFNBSLjQ84bjROOpMu4=
|
||||
google.golang.org/grpc v1.74.2/go.mod h1:CtQ+BGjaAIXHs/5YS3i473GqwBBa1zGQNevxdeBEXrM=
|
||||
google.golang.org/protobuf v1.36.7 h1:IgrO7UwFQGJdRNXH/sQux4R1Dj1WAKcLElzeeRaXV2A=
|
||||
|
|
19
install.sh
19
install.sh
|
@ -7,7 +7,6 @@ yellow='\033[0;33m'
|
|||
plain='\033[0m'
|
||||
|
||||
cur_dir=$(pwd)
|
||||
show_ip_service_lists=("https://api.ipify.org" "https://4.ident.me")
|
||||
|
||||
# check root
|
||||
[[ $EUID -ne 0 ]] && echo -e "${red}Fatal error: ${plain} Please run this script with root privilege \n " && exit 1
|
||||
|
@ -58,7 +57,7 @@ install_base() {
|
|||
zypper refresh && zypper -q install -y wget curl tar timezone
|
||||
;;
|
||||
*)
|
||||
apt-get update && apt install -y -q wget curl tar tzdata
|
||||
apt-get update && apt-get install -y -q wget curl tar tzdata
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
@ -73,10 +72,18 @@ config_after_install() {
|
|||
local existing_hasDefaultCredential=$(/usr/local/x-ui/x-ui setting -show true | grep -Eo 'hasDefaultCredential: .+' | awk '{print $2}')
|
||||
local existing_webBasePath=$(/usr/local/x-ui/x-ui setting -show true | grep -Eo 'webBasePath: .+' | awk '{print $2}')
|
||||
local existing_port=$(/usr/local/x-ui/x-ui setting -show true | grep -Eo 'port: .+' | awk '{print $2}')
|
||||
|
||||
for ip_service_addr in "${show_ip_service_lists[@]}"; do
|
||||
local server_ip=$(curl -s --max-time 3 ${ip_service_addr} 2>/dev/null)
|
||||
if [ -n "${server_ip}" ]; then
|
||||
local URL_lists=(
|
||||
"https://api4.ipify.org"
|
||||
"https://ipv4.icanhazip.com"
|
||||
"https://v4.api.ipinfo.io/ip"
|
||||
"https://ipv4.myexternalip.com/raw"
|
||||
"https://4.ident.me"
|
||||
"https://check-host.net/ip"
|
||||
)
|
||||
local server_ip=""
|
||||
for ip_address in "${URL_lists[@]}"; do
|
||||
server_ip=$(curl -s --max-time 3 "${ip_address}" 2>/dev/null | tr -d '[:space:]')
|
||||
if [[ -n "${server_ip}" ]]; then
|
||||
break
|
||||
fi
|
||||
done
|
||||
|
|
|
@ -209,9 +209,10 @@ func (s *SubJsonService) streamData(stream string) map[string]any {
|
|||
var streamSettings map[string]any
|
||||
json.Unmarshal([]byte(stream), &streamSettings)
|
||||
security, _ := streamSettings["security"].(string)
|
||||
if security == "tls" {
|
||||
switch security {
|
||||
case "tls":
|
||||
streamSettings["tlsSettings"] = s.tlsData(streamSettings["tlsSettings"].(map[string]any))
|
||||
} else if security == "reality" {
|
||||
case "reality":
|
||||
streamSettings["realitySettings"] = s.realityData(streamSettings["realitySettings"].(map[string]any))
|
||||
}
|
||||
delete(streamSettings, "sockopt")
|
||||
|
|
File diff suppressed because one or more lines are too long
|
@ -1,7 +0,0 @@
|
|||
@import "../lib/style/index.less";
|
||||
@import "../lib/style/components.less";
|
||||
|
||||
@green-6: #008771;
|
||||
@primary-color: @green-6;
|
||||
@border-radius-base: 1rem;
|
||||
@progress-remaining-color: #EDEDED;
|
File diff suppressed because one or more lines are too long
4
web/assets/axios/axios.min.js
vendored
4
web/assets/axios/axios.min.js
vendored
File diff suppressed because one or more lines are too long
|
@ -1710,7 +1710,7 @@ class Inbound extends XrayCommonClass {
|
|||
|
||||
toJson() {
|
||||
let streamSettings;
|
||||
if (this.canEnableStream()) {
|
||||
if (this.canEnableStream() || this.stream?.sockopt) {
|
||||
streamSettings = this.stream.toJson();
|
||||
}
|
||||
return {
|
||||
|
|
|
@ -919,12 +919,14 @@ Outbound.FreedomSettings.Fragment = class extends CommonClass {
|
|||
constructor(
|
||||
packets = '1-3',
|
||||
length = '',
|
||||
interval = ''
|
||||
interval = '',
|
||||
maxSplit = ''
|
||||
) {
|
||||
super();
|
||||
this.packets = packets;
|
||||
this.length = length;
|
||||
this.interval = interval;
|
||||
this.maxSplit = maxSplit;
|
||||
}
|
||||
|
||||
static fromJson(json = {}) {
|
||||
|
@ -932,6 +934,7 @@ Outbound.FreedomSettings.Fragment = class extends CommonClass {
|
|||
json.packets,
|
||||
json.length,
|
||||
json.interval,
|
||||
json.maxSplit
|
||||
);
|
||||
}
|
||||
};
|
||||
|
@ -940,12 +943,14 @@ Outbound.FreedomSettings.Noise = class extends CommonClass {
|
|||
constructor(
|
||||
type = 'rand',
|
||||
packet = '10-20',
|
||||
delay = '10-16'
|
||||
delay = '10-16',
|
||||
applyTo = 'ip'
|
||||
) {
|
||||
super();
|
||||
this.type = type;
|
||||
this.packet = packet;
|
||||
this.delay = delay;
|
||||
this.applyTo = applyTo;
|
||||
}
|
||||
|
||||
static fromJson(json = {}) {
|
||||
|
@ -953,6 +958,7 @@ Outbound.FreedomSettings.Noise = class extends CommonClass {
|
|||
json.type,
|
||||
json.packet,
|
||||
json.delay,
|
||||
json.applyTo
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -961,6 +967,7 @@ Outbound.FreedomSettings.Noise = class extends CommonClass {
|
|||
type: this.type,
|
||||
packet: this.packet,
|
||||
delay: this.delay,
|
||||
applyTo: this.applyTo
|
||||
};
|
||||
}
|
||||
};
|
||||
|
@ -988,7 +995,7 @@ Outbound.DNSSettings = class extends CommonClass {
|
|||
network = 'udp',
|
||||
address = '',
|
||||
port = 53,
|
||||
nonIPQuery = 'drop',
|
||||
nonIPQuery = 'reject',
|
||||
blockTypes = []
|
||||
) {
|
||||
super();
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -1,5 +0,0 @@
|
|||
if (process.env.NODE_ENV === 'production') {
|
||||
module.exports = require('./vue.common.prod.js')
|
||||
} else {
|
||||
module.exports = require('./vue.common.dev.js')
|
||||
}
|
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large
Load diff
6
web/assets/vue/vue.esm.browser.min.js
vendored
6
web/assets/vue/vue.esm.browser.min.js
vendored
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large
Load diff
11932
web/assets/vue/vue.js
11932
web/assets/vue/vue.js
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
|
@ -1,5 +0,0 @@
|
|||
if (process.env.NODE_ENV === 'production') {
|
||||
module.exports = require('./vue.runtime.common.prod.js')
|
||||
} else {
|
||||
module.exports = require('./vue.runtime.common.dev.js')
|
||||
}
|
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
6
web/assets/vue/vue.runtime.min.js
vendored
6
web/assets/vue/vue.runtime.min.js
vendored
File diff suppressed because one or more lines are too long
|
@ -1,76 +0,0 @@
|
|||
import Vue from './vue.runtime.common.js'
|
||||
export default Vue
|
||||
|
||||
// this should be kept in sync with src/v3/index.ts
|
||||
export const {
|
||||
version,
|
||||
|
||||
// refs
|
||||
ref,
|
||||
shallowRef,
|
||||
isRef,
|
||||
toRef,
|
||||
toRefs,
|
||||
unref,
|
||||
proxyRefs,
|
||||
customRef,
|
||||
triggerRef,
|
||||
computed,
|
||||
|
||||
// reactive
|
||||
reactive,
|
||||
isReactive,
|
||||
isReadonly,
|
||||
isShallow,
|
||||
isProxy,
|
||||
shallowReactive,
|
||||
markRaw,
|
||||
toRaw,
|
||||
readonly,
|
||||
shallowReadonly,
|
||||
|
||||
// watch
|
||||
watch,
|
||||
watchEffect,
|
||||
watchPostEffect,
|
||||
watchSyncEffect,
|
||||
|
||||
// effectScope
|
||||
effectScope,
|
||||
onScopeDispose,
|
||||
getCurrentScope,
|
||||
|
||||
// provide / inject
|
||||
provide,
|
||||
inject,
|
||||
|
||||
// lifecycle
|
||||
onBeforeMount,
|
||||
onMounted,
|
||||
onBeforeUpdate,
|
||||
onUpdated,
|
||||
onBeforeUnmount,
|
||||
onUnmounted,
|
||||
onErrorCaptured,
|
||||
onActivated,
|
||||
onDeactivated,
|
||||
onServerPrefetch,
|
||||
onRenderTracked,
|
||||
onRenderTriggered,
|
||||
|
||||
// v2 only
|
||||
set,
|
||||
del,
|
||||
|
||||
// v3 compat
|
||||
h,
|
||||
getCurrentInstance,
|
||||
useSlots,
|
||||
useAttrs,
|
||||
mergeDefaults,
|
||||
nextTick,
|
||||
useCssModule,
|
||||
useCssVars,
|
||||
defineComponent,
|
||||
defineAsyncComponent
|
||||
} = Vue
|
|
@ -2,10 +2,10 @@ package entity
|
|||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"math"
|
||||
"net"
|
||||
"strings"
|
||||
"time"
|
||||
"math"
|
||||
|
||||
"x-ui/util/common"
|
||||
)
|
||||
|
|
|
@ -42,6 +42,9 @@
|
|||
<a-form-item label='Interval'>
|
||||
<a-input v-model.trim="outbound.settings.fragment.interval"></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item label='Max Split'>
|
||||
<a-input v-model.trim="outbound.settings.fragment.maxSplit"></a-input>
|
||||
</a-form-item>
|
||||
</template>
|
||||
|
||||
<!-- Switch for Noises -->
|
||||
|
@ -75,6 +78,11 @@
|
|||
<a-form-item label='Delay'>
|
||||
<a-input v-model.trim="noise.delay"></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item label='Apply To'>
|
||||
<a-select v-model="noise.applyTo" :dropdown-class-name="themeSwitcher.currentTheme">
|
||||
<a-select-option v-for="s in ['ip','ipv4','ipv6']" :value="s">[[ s ]]</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</template>
|
||||
</template>
|
||||
|
@ -97,7 +105,7 @@
|
|||
</a-form-item>
|
||||
<a-form-item label='non-IP queries'>
|
||||
<a-select v-model="outbound.settings.nonIPQuery" :dropdown-class-name="themeSwitcher.currentTheme">
|
||||
<a-select-option v-for="s in ['drop','skip']" :value="s">[[ s ]]</a-select-option>
|
||||
<a-select-option v-for="s in ['reject','drop','skip']" :value="s">[[ s ]]</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
<a-form-item v-if="outbound.settings.nonIPQuery === 'skip'" label='Block Types' >
|
||||
|
|
|
@ -1119,7 +1119,11 @@
|
|||
protocol: inbound.protocol,
|
||||
settings: inbound.settings.toString(),
|
||||
};
|
||||
if (inbound.canEnableStream()) data.streamSettings = inbound.stream.toString();
|
||||
if (inbound.canEnableStream()){
|
||||
data.streamSettings = inbound.stream.toString();
|
||||
} else if (inbound.stream?.sockopt) {
|
||||
data.streamSettings = JSON.stringify({ sockopt: inbound.stream.sockopt.toJson() }, null, 2);
|
||||
}
|
||||
data.sniffing = inbound.sniffing.toString();
|
||||
data.allocate = inbound.allocate.toString();
|
||||
|
||||
|
@ -1139,7 +1143,11 @@
|
|||
protocol: inbound.protocol,
|
||||
settings: inbound.settings.toString(),
|
||||
};
|
||||
if (inbound.canEnableStream()) data.streamSettings = inbound.stream.toString();
|
||||
if (inbound.canEnableStream()){
|
||||
data.streamSettings = inbound.stream.toString();
|
||||
} else if (inbound.stream?.sockopt) {
|
||||
data.streamSettings = JSON.stringify({ sockopt: inbound.stream.sockopt.toJson() }, null, 2);
|
||||
}
|
||||
data.sniffing = inbound.sniffing.toString();
|
||||
data.allocate = inbound.allocate.toString();
|
||||
|
||||
|
|
|
@ -207,7 +207,7 @@
|
|||
settings: {
|
||||
domainStrategy: "AsIs",
|
||||
noises: [
|
||||
{ type: "rand", packet: "10-20", delay: "10-16" },
|
||||
{ type: "rand", packet: "10-20", delay: "10-16", applyTo: "ip" },
|
||||
],
|
||||
},
|
||||
},
|
||||
|
@ -397,7 +397,7 @@
|
|||
}
|
||||
},
|
||||
addNoise() {
|
||||
const newNoise = { type: "rand", packet: "10-20", delay: "10-16" };
|
||||
const newNoise = { type: "rand", packet: "10-20", delay: "10-16", applyTo: "ip" };
|
||||
this.noisesArray = [...this.noisesArray, newNoise];
|
||||
},
|
||||
removeNoise(index) {
|
||||
|
@ -420,6 +420,11 @@
|
|||
updatedNoises[index] = { ...updatedNoises[index], delay: value };
|
||||
this.noisesArray = updatedNoises;
|
||||
},
|
||||
updateNoiseApplyTo(index, value) {
|
||||
const updatedNoises = [...this.noisesArray];
|
||||
updatedNoises[index] = { ...updatedNoises[index], applyTo: value };
|
||||
this.noisesArray = updatedNoises;
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
fragment: {
|
||||
|
|
|
@ -90,6 +90,18 @@
|
|||
placeholder="10-20"></a-input>
|
||||
</template>
|
||||
</a-setting-list-item>
|
||||
<a-setting-list-item paddings="small">
|
||||
<template #title>ApplyTo</template>
|
||||
<template #control>
|
||||
<a-select :value="noise.applyTo" :style="{ width: '100%' }"
|
||||
:dropdown-class-name="themeSwitcher.currentTheme"
|
||||
@change="(value) => updateNoiseApplyTo(index, value)">
|
||||
<a-select-option :value="p" :label="p" v-for="p in ['ip', 'ipv4', 'ipv6']" :key="p">
|
||||
<span>[[ p ]]</span>
|
||||
</a-select-option>
|
||||
</a-select>
|
||||
</template>
|
||||
</a-setting-list-item>
|
||||
<a-space direction="horizontal" :style="{ padding: '10px 20px' }">
|
||||
<a-button v-if="noisesArray.length > 1" type="danger"
|
||||
@click="removeNoise(index)">Remove</a-button>
|
||||
|
|
|
@ -11,7 +11,6 @@ import (
|
|||
"sort"
|
||||
"time"
|
||||
|
||||
"slices"
|
||||
"x-ui/database"
|
||||
"x-ui/database/model"
|
||||
"x-ui/logger"
|
||||
|
@ -58,21 +57,21 @@ func (j *CheckClientIpJob) Run() {
|
|||
func (j *CheckClientIpJob) clearAccessLog() {
|
||||
logAccessP, err := os.OpenFile(xray.GetAccessPersistentLogPath(), os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0o644)
|
||||
j.checkError(err)
|
||||
defer logAccessP.Close()
|
||||
|
||||
accessLogPath, err := xray.GetAccessLogPath()
|
||||
j.checkError(err)
|
||||
|
||||
file, err := os.Open(accessLogPath)
|
||||
j.checkError(err)
|
||||
defer file.Close()
|
||||
|
||||
_, err = io.Copy(logAccessP, file)
|
||||
j.checkError(err)
|
||||
|
||||
logAccessP.Close()
|
||||
file.Close()
|
||||
|
||||
err = os.Truncate(accessLogPath, 0)
|
||||
j.checkError(err)
|
||||
|
||||
j.lastClear = time.Now().Unix()
|
||||
}
|
||||
|
||||
|
@ -193,10 +192,6 @@ func (j *CheckClientIpJob) checkError(e error) {
|
|||
}
|
||||
}
|
||||
|
||||
func (j *CheckClientIpJob) contains(s []string, str string) bool {
|
||||
return slices.Contains(s, str)
|
||||
}
|
||||
|
||||
func (j *CheckClientIpJob) getInboundClientIps(clientEmail string) (*model.InboundClientIps, error) {
|
||||
db := database.GetDB()
|
||||
InboundClientIps := &model.InboundClientIps{}
|
||||
|
|
|
@ -177,15 +177,16 @@ func (s *InboundService) AddInbound(inbound *model.Inbound) (*model.Inbound, boo
|
|||
|
||||
// Secure client ID
|
||||
for _, client := range clients {
|
||||
if inbound.Protocol == "trojan" {
|
||||
switch inbound.Protocol {
|
||||
case "trojan":
|
||||
if client.Password == "" {
|
||||
return inbound, false, common.NewError("empty client ID")
|
||||
}
|
||||
} else if inbound.Protocol == "shadowsocks" {
|
||||
case "shadowsocks":
|
||||
if client.Email == "" {
|
||||
return inbound, false, common.NewError("empty client ID")
|
||||
}
|
||||
} else {
|
||||
default:
|
||||
if client.ID == "" {
|
||||
return inbound, false, common.NewError("empty client ID")
|
||||
}
|
||||
|
@ -436,15 +437,16 @@ func (s *InboundService) AddInboundClient(data *model.Inbound) (bool, error) {
|
|||
|
||||
// Secure client ID
|
||||
for _, client := range clients {
|
||||
if oldInbound.Protocol == "trojan" {
|
||||
switch oldInbound.Protocol {
|
||||
case "trojan":
|
||||
if client.Password == "" {
|
||||
return false, common.NewError("empty client ID")
|
||||
}
|
||||
} else if oldInbound.Protocol == "shadowsocks" {
|
||||
case "shadowsocks":
|
||||
if client.Email == "" {
|
||||
return false, common.NewError("empty client ID")
|
||||
}
|
||||
} else {
|
||||
default:
|
||||
if client.ID == "" {
|
||||
return false, common.NewError("empty client ID")
|
||||
}
|
||||
|
@ -631,13 +633,14 @@ func (s *InboundService) UpdateInboundClient(data *model.Inbound, clientId strin
|
|||
clientIndex := -1
|
||||
for index, oldClient := range oldClients {
|
||||
oldClientId := ""
|
||||
if oldInbound.Protocol == "trojan" {
|
||||
switch oldInbound.Protocol {
|
||||
case "trojan":
|
||||
oldClientId = oldClient.Password
|
||||
newClientId = clients[0].Password
|
||||
} else if oldInbound.Protocol == "shadowsocks" {
|
||||
case "shadowsocks":
|
||||
oldClientId = oldClient.Email
|
||||
newClientId = clients[0].Email
|
||||
} else {
|
||||
default:
|
||||
oldClientId = oldClient.ID
|
||||
newClientId = clients[0].ID
|
||||
}
|
||||
|
@ -1244,11 +1247,12 @@ func (s *InboundService) SetClientTelegramUserID(trafficId int, tgId int64) (boo
|
|||
|
||||
for _, oldClient := range oldClients {
|
||||
if oldClient.Email == clientEmail {
|
||||
if inbound.Protocol == "trojan" {
|
||||
switch inbound.Protocol {
|
||||
case "trojan":
|
||||
clientId = oldClient.Password
|
||||
} else if inbound.Protocol == "shadowsocks" {
|
||||
case "shadowsocks":
|
||||
clientId = oldClient.Email
|
||||
} else {
|
||||
default:
|
||||
clientId = oldClient.ID
|
||||
}
|
||||
break
|
||||
|
@ -1328,11 +1332,12 @@ func (s *InboundService) ToggleClientEnableByEmail(clientEmail string) (bool, bo
|
|||
|
||||
for _, oldClient := range oldClients {
|
||||
if oldClient.Email == clientEmail {
|
||||
if inbound.Protocol == "trojan" {
|
||||
switch inbound.Protocol {
|
||||
case "trojan":
|
||||
clientId = oldClient.Password
|
||||
} else if inbound.Protocol == "shadowsocks" {
|
||||
case "shadowsocks":
|
||||
clientId = oldClient.Email
|
||||
} else {
|
||||
default:
|
||||
clientId = oldClient.ID
|
||||
}
|
||||
clientOldEnabled = oldClient.Enable
|
||||
|
@ -1391,11 +1396,12 @@ func (s *InboundService) ResetClientIpLimitByEmail(clientEmail string, count int
|
|||
|
||||
for _, oldClient := range oldClients {
|
||||
if oldClient.Email == clientEmail {
|
||||
if inbound.Protocol == "trojan" {
|
||||
switch inbound.Protocol {
|
||||
case "trojan":
|
||||
clientId = oldClient.Password
|
||||
} else if inbound.Protocol == "shadowsocks" {
|
||||
case "shadowsocks":
|
||||
clientId = oldClient.Email
|
||||
} else {
|
||||
default:
|
||||
clientId = oldClient.ID
|
||||
}
|
||||
break
|
||||
|
@ -1448,11 +1454,12 @@ func (s *InboundService) ResetClientExpiryTimeByEmail(clientEmail string, expiry
|
|||
|
||||
for _, oldClient := range oldClients {
|
||||
if oldClient.Email == clientEmail {
|
||||
if inbound.Protocol == "trojan" {
|
||||
switch inbound.Protocol {
|
||||
case "trojan":
|
||||
clientId = oldClient.Password
|
||||
} else if inbound.Protocol == "shadowsocks" {
|
||||
case "shadowsocks":
|
||||
clientId = oldClient.Email
|
||||
} else {
|
||||
default:
|
||||
clientId = oldClient.ID
|
||||
}
|
||||
break
|
||||
|
@ -1508,11 +1515,12 @@ func (s *InboundService) ResetClientTrafficLimitByEmail(clientEmail string, tota
|
|||
|
||||
for _, oldClient := range oldClients {
|
||||
if oldClient.Email == clientEmail {
|
||||
if inbound.Protocol == "trojan" {
|
||||
switch inbound.Protocol {
|
||||
case "trojan":
|
||||
clientId = oldClient.Password
|
||||
} else if inbound.Protocol == "shadowsocks" {
|
||||
case "shadowsocks":
|
||||
clientId = oldClient.Email
|
||||
} else {
|
||||
default:
|
||||
clientId = oldClient.ID
|
||||
}
|
||||
break
|
||||
|
|
|
@ -235,8 +235,21 @@ func (s *ServerService) GetStatus(lastStatus *Status) *Status {
|
|||
}
|
||||
|
||||
// IP fetching with caching
|
||||
showIp4ServiceLists := []string{"https://api.ipify.org", "https://4.ident.me"}
|
||||
showIp6ServiceLists := []string{"https://api6.ipify.org", "https://6.ident.me"}
|
||||
showIp4ServiceLists := []string{
|
||||
"https://api4.ipify.org",
|
||||
"https://ipv4.icanhazip.com",
|
||||
"https://v4.api.ipinfo.io/ip",
|
||||
"https://ipv4.myexternalip.com/raw",
|
||||
"https://4.ident.me",
|
||||
"https://check-host.net/ip",
|
||||
}
|
||||
showIp6ServiceLists := []string{
|
||||
"https://api6.ipify.org",
|
||||
"https://ipv6.icanhazip.com",
|
||||
"https://v6.api.ipinfo.io/ip",
|
||||
"https://ipv6.myexternalip.com/raw",
|
||||
"https://6.ident.me",
|
||||
}
|
||||
|
||||
if s.cachedIPv4 == "" {
|
||||
for _, ip4Service := range showIp4ServiceLists {
|
||||
|
|
|
@ -40,7 +40,6 @@ var (
|
|||
isRunning bool
|
||||
hostname string
|
||||
hashStorage *global.HashStorage
|
||||
handler *th.Handler
|
||||
|
||||
// clients data to adding new client
|
||||
receiver_inbound_ID int
|
||||
|
@ -641,13 +640,14 @@ func (t *Tgbot) answerCallback(callbackQuery *telego.CallbackQuery, isAdmin bool
|
|||
if len(dataArray) == 4 {
|
||||
num, err := strconv.Atoi(dataArray[3])
|
||||
if err == nil {
|
||||
if num == -2 {
|
||||
switch num {
|
||||
case -2:
|
||||
inputNumber = 0
|
||||
} else if num == -1 {
|
||||
case -1:
|
||||
if inputNumber > 0 {
|
||||
inputNumber = (inputNumber / 10)
|
||||
}
|
||||
} else {
|
||||
default:
|
||||
inputNumber = (inputNumber * 10) + num
|
||||
}
|
||||
}
|
||||
|
@ -704,6 +704,10 @@ func (t *Tgbot) answerCallback(callbackQuery *telego.CallbackQuery, isAdmin bool
|
|||
return
|
||||
}
|
||||
message_text, err := t.BuildInboundClientDataMessage(inbound.Remark, inbound.Protocol)
|
||||
if err != nil {
|
||||
t.sendCallbackAnswerTgBot(callbackQuery.ID, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
t.addClient(callbackQuery.Message.GetChat().ID, message_text, messageId)
|
||||
t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.answers.successfulOperation"))
|
||||
|
@ -715,13 +719,14 @@ func (t *Tgbot) answerCallback(callbackQuery *telego.CallbackQuery, isAdmin bool
|
|||
if len(dataArray) == 3 {
|
||||
num, err := strconv.Atoi(dataArray[2])
|
||||
if err == nil {
|
||||
if num == -2 {
|
||||
switch num {
|
||||
case -2:
|
||||
inputNumber = 0
|
||||
} else if num == -1 {
|
||||
case -1:
|
||||
if inputNumber > 0 {
|
||||
inputNumber = (inputNumber / 10)
|
||||
}
|
||||
} else {
|
||||
default:
|
||||
inputNumber = (inputNumber * 10) + num
|
||||
}
|
||||
}
|
||||
|
@ -844,13 +849,14 @@ func (t *Tgbot) answerCallback(callbackQuery *telego.CallbackQuery, isAdmin bool
|
|||
if len(dataArray) == 4 {
|
||||
num, err := strconv.Atoi(dataArray[3])
|
||||
if err == nil {
|
||||
if num == -2 {
|
||||
switch num {
|
||||
case -2:
|
||||
inputNumber = 0
|
||||
} else if num == -1 {
|
||||
case -1:
|
||||
if inputNumber > 0 {
|
||||
inputNumber = (inputNumber / 10)
|
||||
}
|
||||
} else {
|
||||
default:
|
||||
inputNumber = (inputNumber * 10) + num
|
||||
}
|
||||
}
|
||||
|
@ -919,6 +925,10 @@ func (t *Tgbot) answerCallback(callbackQuery *telego.CallbackQuery, isAdmin bool
|
|||
return
|
||||
}
|
||||
message_text, err := t.BuildInboundClientDataMessage(inbound.Remark, inbound.Protocol)
|
||||
if err != nil {
|
||||
t.sendCallbackAnswerTgBot(callbackQuery.ID, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
t.addClient(callbackQuery.Message.GetChat().ID, message_text, messageId)
|
||||
t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.answers.successfulOperation"))
|
||||
|
@ -930,13 +940,14 @@ func (t *Tgbot) answerCallback(callbackQuery *telego.CallbackQuery, isAdmin bool
|
|||
if len(dataArray) == 3 {
|
||||
num, err := strconv.Atoi(dataArray[2])
|
||||
if err == nil {
|
||||
if num == -2 {
|
||||
switch num {
|
||||
case -2:
|
||||
inputNumber = 0
|
||||
} else if num == -1 {
|
||||
case -1:
|
||||
if inputNumber > 0 {
|
||||
inputNumber = (inputNumber / 10)
|
||||
}
|
||||
} else {
|
||||
default:
|
||||
inputNumber = (inputNumber * 10) + num
|
||||
}
|
||||
}
|
||||
|
@ -1035,13 +1046,14 @@ func (t *Tgbot) answerCallback(callbackQuery *telego.CallbackQuery, isAdmin bool
|
|||
if len(dataArray) == 4 {
|
||||
num, err := strconv.Atoi(dataArray[3])
|
||||
if err == nil {
|
||||
if num == -2 {
|
||||
switch num {
|
||||
case -2:
|
||||
inputNumber = 0
|
||||
} else if num == -1 {
|
||||
case -1:
|
||||
if inputNumber > 0 {
|
||||
inputNumber = (inputNumber / 10)
|
||||
}
|
||||
} else {
|
||||
default:
|
||||
inputNumber = (inputNumber * 10) + num
|
||||
}
|
||||
}
|
||||
|
@ -1101,6 +1113,10 @@ func (t *Tgbot) answerCallback(callbackQuery *telego.CallbackQuery, isAdmin bool
|
|||
return
|
||||
}
|
||||
message_text, err := t.BuildInboundClientDataMessage(inbound.Remark, inbound.Protocol)
|
||||
if err != nil {
|
||||
t.sendCallbackAnswerTgBot(callbackQuery.ID, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
t.addClient(callbackQuery.Message.GetChat().ID, message_text, messageId)
|
||||
t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.answers.successfulOperation"))
|
||||
|
@ -1112,13 +1128,14 @@ func (t *Tgbot) answerCallback(callbackQuery *telego.CallbackQuery, isAdmin bool
|
|||
if len(dataArray) == 3 {
|
||||
num, err := strconv.Atoi(dataArray[2])
|
||||
if err == nil {
|
||||
if num == -2 {
|
||||
switch num {
|
||||
case -2:
|
||||
inputNumber = 0
|
||||
} else if num == -1 {
|
||||
case -1:
|
||||
if inputNumber > 0 {
|
||||
inputNumber = (inputNumber / 10)
|
||||
}
|
||||
} else {
|
||||
default:
|
||||
inputNumber = (inputNumber * 10) + num
|
||||
}
|
||||
}
|
||||
|
@ -1288,6 +1305,10 @@ func (t *Tgbot) answerCallback(callbackQuery *telego.CallbackQuery, isAdmin bool
|
|||
}
|
||||
|
||||
message_text, err := t.BuildInboundClientDataMessage(inbound.Remark, inbound.Protocol)
|
||||
if err != nil {
|
||||
t.sendCallbackAnswerTgBot(callbackQuery.ID, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
t.addClient(callbackQuery.Message.GetChat().ID, message_text)
|
||||
}
|
||||
|
@ -1524,6 +1545,10 @@ func (t *Tgbot) answerCallback(callbackQuery *telego.CallbackQuery, isAdmin bool
|
|||
return
|
||||
}
|
||||
message_text, err := t.BuildInboundClientDataMessage(inbound.Remark, inbound.Protocol)
|
||||
if err != nil {
|
||||
t.sendCallbackAnswerTgBot(callbackQuery.ID, err.Error())
|
||||
return
|
||||
}
|
||||
t.addClient(chatId, message_text, messageId)
|
||||
t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.answers.canceled", "Email=="+client_Email))
|
||||
case "add_client_default_ip_limit":
|
||||
|
@ -1534,6 +1559,10 @@ func (t *Tgbot) answerCallback(callbackQuery *telego.CallbackQuery, isAdmin bool
|
|||
return
|
||||
}
|
||||
message_text, err := t.BuildInboundClientDataMessage(inbound.Remark, inbound.Protocol)
|
||||
if err != nil {
|
||||
t.sendCallbackAnswerTgBot(callbackQuery.ID, err.Error())
|
||||
return
|
||||
}
|
||||
t.addClient(chatId, message_text, messageId)
|
||||
t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.answers.canceled", "Email=="+client_Email))
|
||||
case "add_client_submit_disable":
|
||||
|
@ -1598,6 +1627,10 @@ func (t *Tgbot) answerCallback(callbackQuery *telego.CallbackQuery, isAdmin bool
|
|||
return
|
||||
}
|
||||
valid_emails, extra_emails, err := t.inboundService.FilterAndSortClientEmails(emails)
|
||||
if err != nil {
|
||||
t.SendMsgToTgbot(chatId, t.I18nBot("tgbot.answers.errorOperation"), tu.ReplyKeyboardRemove())
|
||||
return
|
||||
}
|
||||
|
||||
for _, valid_emails := range valid_emails {
|
||||
traffic, err := t.inboundService.GetClientTrafficByEmail(valid_emails)
|
||||
|
@ -1760,6 +1793,10 @@ func (t *Tgbot) SubmitAddClient() (bool, error) {
|
|||
}
|
||||
|
||||
jsonString, err := t.BuildJSONForProtocol(inbound.Protocol)
|
||||
if err != nil {
|
||||
logger.Warning("BuildJSONForProtocol run failed:", err)
|
||||
return false, errors.New("failed to build JSON for protocol")
|
||||
}
|
||||
|
||||
newInbound := &model.Inbound{
|
||||
Id: receiver_inbound_ID,
|
||||
|
@ -2008,10 +2045,11 @@ func (t *Tgbot) UserLoginNotify(username string, password string, ip string, tim
|
|||
}
|
||||
|
||||
msg := ""
|
||||
if status == LoginSuccess {
|
||||
switch status {
|
||||
case LoginSuccess:
|
||||
msg += t.I18nBot("tgbot.messages.loginSuccess")
|
||||
msg += t.I18nBot("tgbot.messages.hostname", "Hostname=="+hostname)
|
||||
} else if status == LoginFail {
|
||||
case LoginFail:
|
||||
msg += t.I18nBot("tgbot.messages.loginFailed")
|
||||
msg += t.I18nBot("tgbot.messages.hostname", "Hostname=="+hostname)
|
||||
msg += t.I18nBot("tgbot.messages.password", "Password=="+password)
|
||||
|
@ -2171,6 +2209,22 @@ func (t *Tgbot) clientInfoMsg(
|
|||
expiryTime = t.I18nBot("tgbot.unlimited")
|
||||
} else if diff > 172800 || !traffic.Enable {
|
||||
expiryTime = time.Unix((traffic.ExpiryTime / 1000), 0).Format("2006-01-02 15:04:05")
|
||||
if diff > 0 {
|
||||
days := diff / 86400
|
||||
hours := (diff % 86400) / 3600
|
||||
minutes := (diff % 3600) / 60
|
||||
remainingTime := ""
|
||||
if days > 0 {
|
||||
remainingTime += fmt.Sprintf("%d %s ", days, t.I18nBot("tgbot.days"))
|
||||
}
|
||||
if hours > 0 {
|
||||
remainingTime += fmt.Sprintf("%d %s ", hours, t.I18nBot("tgbot.hours"))
|
||||
}
|
||||
if minutes > 0 {
|
||||
remainingTime += fmt.Sprintf("%d %s", minutes, t.I18nBot("tgbot.minutes"))
|
||||
}
|
||||
expiryTime += fmt.Sprintf(" (%s)", remainingTime)
|
||||
}
|
||||
} else if traffic.ExpiryTime < 0 {
|
||||
expiryTime = fmt.Sprintf("%d %s", traffic.ExpiryTime/-86400000, t.I18nBot("tgbot.days"))
|
||||
flag = true
|
||||
|
|
|
@ -561,24 +561,25 @@
|
|||
"resetOutboundTrafficError" = "خطأ في إعادة تعيين حركات المرور الصادرة"
|
||||
|
||||
[tgbot]
|
||||
"keyboardClosed" = "❌ الكيبورد المخصص اتقفلت!"
|
||||
"noResult" = "❗ مفيش نتيجة!"
|
||||
"noQuery" = "❌ مش لاقي السؤال! استخدم الأمر تاني!"
|
||||
"wentWrong" = "❌ حصل خطأ!"
|
||||
"noIpRecord" = "❗ مفيش سجل IP!"
|
||||
"noInbounds" = "❗ مفيش إدخال متواجد!"
|
||||
"unlimited" = "♾ غير محدود (إعادة ضبط)"
|
||||
"add" = "أضف"
|
||||
"keyboardClosed" = "❌ لوحة المفاتيح مغلقة!"
|
||||
"noResult" = "❗ لا يوجد نتائج!"
|
||||
"noQuery" = "❌ لم يتم العثور على الاستعلام! يرجى استخدام الأمر مرة أخرى!"
|
||||
"wentWrong" = "❌ حدث خطأ ما!"
|
||||
"noIpRecord" = "❗ لا يوجد سجل IP!"
|
||||
"noInbounds" = "❗ لم يتم العثور على أي وارد!"
|
||||
"unlimited" = "♾ غير محدود (إعادة تعيين)"
|
||||
"add" = "إضافة"
|
||||
"month" = "شهر"
|
||||
"months" = "شهور"
|
||||
"months" = "أشهر"
|
||||
"day" = "يوم"
|
||||
"days" = "أيام"
|
||||
"hours" = "ساعات"
|
||||
"unknown" = "مش معروف"
|
||||
"inbounds" = "الإدخالات"
|
||||
"minutes" = "دقائق"
|
||||
"unknown" = "غير معروف"
|
||||
"inbounds" = "الواردات"
|
||||
"clients" = "العملاء"
|
||||
"offline" = "🔴 أوفلاين"
|
||||
"online" = "🟢 أونلاين"
|
||||
"offline" = "🔴 غير متصل"
|
||||
"online" = "🟢 متصل"
|
||||
|
||||
[tgbot.commands]
|
||||
"unknown" = "❗ أمر مش معروف."
|
||||
|
|
|
@ -574,6 +574,7 @@
|
|||
"day" = "Day"
|
||||
"days" = "Days"
|
||||
"hours" = "Hours"
|
||||
"minutes" = "Minutes"
|
||||
"unknown" = "Unknown"
|
||||
"inbounds" = "Inbounds"
|
||||
"clients" = "Clients"
|
||||
|
|
|
@ -561,23 +561,24 @@
|
|||
"resetOutboundTrafficError" = "Error al reiniciar el tráfico saliente"
|
||||
|
||||
[tgbot]
|
||||
"keyboardClosed" = "❌ ¡Teclado personalizado cerrado!"
|
||||
"noResult" = "❗ ¡Sin resultados!"
|
||||
"noQuery" = "❌ ¡Consulta no encontrada! ¡Por favor utiliza el comando nuevamente!"
|
||||
"keyboardClosed" = "❌ Teclado cerrado!"
|
||||
"noResult" = "❗ ¡No hay resultados!"
|
||||
"noQuery" = "❌ ¡Consulta no encontrada! ¡Por favor, use el comando de nuevo!"
|
||||
"wentWrong" = "❌ ¡Algo salió mal!"
|
||||
"noIpRecord" = "❗ ¡Sin Registro de IP!"
|
||||
"noIpRecord" = "❗ ¡No hay registro de IP!"
|
||||
"noInbounds" = "❗ ¡No se encontraron entradas!"
|
||||
"unlimited" = "♾ Ilimitado"
|
||||
"add" = "Agregar"
|
||||
"unlimited" = "♾ Ilimitado (Restablecer)"
|
||||
"add" = "Añadir"
|
||||
"month" = "Mes"
|
||||
"months" = "Meses"
|
||||
"day" = "Día"
|
||||
"days" = "Días"
|
||||
"hours" = "Horas"
|
||||
"minutes" = "Minutos"
|
||||
"unknown" = "Desconocido"
|
||||
"inbounds" = "Entradas"
|
||||
"clients" = "Clientes"
|
||||
"offline" = "🔴 Sin conexión"
|
||||
"offline" = "🔴 Desconectado"
|
||||
"online" = "🟢 En línea"
|
||||
|
||||
[tgbot.commands]
|
||||
|
|
|
@ -561,22 +561,23 @@
|
|||
"resetOutboundTrafficError" = "خطا در بازنشانی ترافیک خروجی"
|
||||
|
||||
[tgbot]
|
||||
"keyboardClosed" = "❌ کیبورد سفارشی بسته شد!"
|
||||
"noResult" = "❗ نتیجهای یافت نشد!"
|
||||
"noQuery" = "❌ کوئری یافت نشد! لطفاً دستور را مجدداً استفاده کنید!"
|
||||
"wentWrong" = "❌ مشکلی رخ داده است!"
|
||||
"noIpRecord" = "❗ رکورد IP یافت نشد!"
|
||||
"keyboardClosed" = "❌ صفحه کلید بسته شد!"
|
||||
"noResult" = "❗ نتیجه ای یافت نشد!"
|
||||
"noQuery" = "❌ درخواست یافت نشد! لطفا دوباره تلاش کنید!"
|
||||
"wentWrong" = "❌ مشکلی پیش آمد!"
|
||||
"noIpRecord" = "❗ رکورد آی پی وجود ندارد!"
|
||||
"noInbounds" = "❗ هیچ ورودی یافت نشد!"
|
||||
"unlimited" = "♾ - نامحدود(ریست)"
|
||||
"add" = "اضافه کردن"
|
||||
"unlimited" = "♾ نامحدود(ریست)"
|
||||
"add" = "افزودن"
|
||||
"month" = "ماه"
|
||||
"months" = "ماه"
|
||||
"months" = "ماه"
|
||||
"day" = "روز"
|
||||
"days" = "روز"
|
||||
"hours" = "ساعت"
|
||||
"hours" = "ساعت"
|
||||
"minutes" = "دقیقه"
|
||||
"unknown" = "نامشخص"
|
||||
"inbounds" = "ورودیها"
|
||||
"clients" = "کلاینتها"
|
||||
"inbounds" = "ورودی ها"
|
||||
"clients" = "کاربران"
|
||||
"offline" = "🔴 آفلاین"
|
||||
"online" = "🟢 آنلاین"
|
||||
|
||||
|
|
|
@ -561,21 +561,22 @@
|
|||
"resetOutboundTrafficError" = "Gagal mereset lalu lintas keluar"
|
||||
|
||||
[tgbot]
|
||||
"keyboardClosed" = "❌ Papan ketik kustom ditutup!"
|
||||
"keyboardClosed" = "❌ Keyboard ditutup!"
|
||||
"noResult" = "❗ Tidak ada hasil!"
|
||||
"noQuery" = "❌ Permintaan tidak ditemukan! Harap gunakan perintah lagi!"
|
||||
"wentWrong" = "❌ Ada yang salah!"
|
||||
"noQuery" = "❌ Kueri tidak ditemukan! Silakan gunakan perintah lagi!"
|
||||
"wentWrong" = "❌ Terjadi kesalahan!"
|
||||
"noIpRecord" = "❗ Tidak ada Catatan IP!"
|
||||
"noInbounds" = "❗ Tidak ada masuk ditemukan!"
|
||||
"unlimited" = "♾ Tak terbatas"
|
||||
"noInbounds" = "❗ Tidak ada inbound yang ditemukan!"
|
||||
"unlimited" = "♾ Tidak terbatas (Reset)"
|
||||
"add" = "Tambah"
|
||||
"month" = "Bulan"
|
||||
"months" = "Bulan"
|
||||
"day" = "Hari"
|
||||
"days" = "Hari"
|
||||
"hours" = "Jam"
|
||||
"minutes" = "Menit"
|
||||
"unknown" = "Tidak diketahui"
|
||||
"inbounds" = "Masuk"
|
||||
"inbounds" = "Inbound"
|
||||
"clients" = "Klien"
|
||||
"offline" = "🔴 Offline"
|
||||
"online" = "🟢 Online"
|
||||
|
|
|
@ -561,21 +561,22 @@
|
|||
"resetOutboundTrafficError" = "送信トラフィックのリセットエラー"
|
||||
|
||||
[tgbot]
|
||||
"keyboardClosed" = "❌ カスタムキーボードが閉じられました!"
|
||||
"keyboardClosed" = "❌ キーボードを閉じました!"
|
||||
"noResult" = "❗ 結果がありません!"
|
||||
"noQuery" = "❌ クエリが見つかりませんでした!もう一度コマンドを使用してください!"
|
||||
"wentWrong" = "❌ 問題が発生しました!"
|
||||
"noIpRecord" = "❗ IP記録がありません!"
|
||||
"noInbounds" = "❗ インバウンド接続が見つかりません!"
|
||||
"unlimited" = "♾ 無制限"
|
||||
"noQuery" = "❌ クエリが見つかりません!コマンドを再利用してください!"
|
||||
"wentWrong" = "❌ 何かがうまくいかなかった!"
|
||||
"noIpRecord" = "❗ IPレコードがありません!"
|
||||
"noInbounds" = "❗ インバウンドが見つかりません!"
|
||||
"unlimited" = "♾ 無制限(リセット)"
|
||||
"add" = "追加"
|
||||
"month" = "月"
|
||||
"months" = "月"
|
||||
"months" = "ヶ月"
|
||||
"day" = "日"
|
||||
"days" = "日"
|
||||
"days" = "日間"
|
||||
"hours" = "時間"
|
||||
"minutes" = "分"
|
||||
"unknown" = "不明"
|
||||
"inbounds" = "インバウンド接続"
|
||||
"inbounds" = "インバウンド"
|
||||
"clients" = "クライアント"
|
||||
"offline" = "🔴 オフライン"
|
||||
"online" = "🟢 オンライン"
|
||||
|
|
|
@ -561,21 +561,22 @@
|
|||
"resetOutboundTrafficError" = "Erro ao redefinir tráfego de saída"
|
||||
|
||||
[tgbot]
|
||||
"keyboardClosed" = "❌ Teclado personalizado fechado!"
|
||||
"keyboardClosed" = "❌ Teclado fechado!"
|
||||
"noResult" = "❗ Nenhum resultado!"
|
||||
"noQuery" = "❌ Consulta não encontrada! Por favor, use o comando novamente!"
|
||||
"wentWrong" = "❌ Algo deu errado!"
|
||||
"noIpRecord" = "❗ Nenhum registro de IP!"
|
||||
"noInbounds" = "❗ Nenhuma entrada encontrada!"
|
||||
"unlimited" = "♾ Ilimitado (Reiniciar)"
|
||||
"noInbounds" = "❗ Nenhum inbound encontrado!"
|
||||
"unlimited" = "♾ Ilimitado (Reset)"
|
||||
"add" = "Adicionar"
|
||||
"month" = "Mês"
|
||||
"months" = "Meses"
|
||||
"day" = "Dia"
|
||||
"days" = "Dias"
|
||||
"hours" = "Horas"
|
||||
"minutes" = "Minutos"
|
||||
"unknown" = "Desconhecido"
|
||||
"inbounds" = "Entradas"
|
||||
"inbounds" = "Inbounds"
|
||||
"clients" = "Clientes"
|
||||
"offline" = "🔴 Offline"
|
||||
"online" = "🟢 Online"
|
||||
|
|
|
@ -574,6 +574,7 @@
|
|||
"day" = "День"
|
||||
"days" = "Дней"
|
||||
"hours" = "Часов"
|
||||
"minutes" = "Минуты"
|
||||
"unknown" = "Неизвестно"
|
||||
"inbounds" = "Инбаунды"
|
||||
"clients" = "Клиенты"
|
||||
|
|
|
@ -561,22 +561,23 @@
|
|||
"resetOutboundTrafficError" = "Giden trafik sıfırlanırken hata"
|
||||
|
||||
[tgbot]
|
||||
"keyboardClosed" = "❌ Özel klavye kapalı!"
|
||||
"keyboardClosed" = "❌ Klavye kapatıldı!"
|
||||
"noResult" = "❗ Sonuç yok!"
|
||||
"noQuery" = "❌ Sorgu bulunamadı! Lütfen komutu tekrar kullanın!"
|
||||
"wentWrong" = "❌ Bir şeyler yanlış gitti!"
|
||||
"noIpRecord" = "❗ IP Kaydı yok!"
|
||||
"noInbounds" = "❗ Gelen bulunamadı!"
|
||||
"unlimited" = "♾ Sınırsız(Sıfırla)"
|
||||
"noIpRecord" = "❗ IP Kaydı Yok!"
|
||||
"noInbounds" = "❗ Gelen bağlantı bulunamadı!"
|
||||
"unlimited" = "♾ Sınırsız (Sıfırla)"
|
||||
"add" = "Ekle"
|
||||
"month" = "Ay"
|
||||
"months" = "Aylar"
|
||||
"day" = "Gün"
|
||||
"days" = "Günler"
|
||||
"hours" = "Saatler"
|
||||
"unknown" = "Bilinmiyor"
|
||||
"minutes" = "Dakika"
|
||||
"unknown" = "Bilinmeyen"
|
||||
"inbounds" = "Gelenler"
|
||||
"clients" = "Müşteriler"
|
||||
"clients" = "İstemciler"
|
||||
"offline" = "🔴 Çevrimdışı"
|
||||
"online" = "🟢 Çevrimiçi"
|
||||
|
||||
|
|
|
@ -561,19 +561,20 @@
|
|||
"resetOutboundTrafficError" = "Помилка скидання вихідного трафіку"
|
||||
|
||||
[tgbot]
|
||||
"keyboardClosed" = "❌ Спеціальна клавіатура закрита!"
|
||||
"keyboardClosed" = "❌ Клавіатуру закрито!"
|
||||
"noResult" = "❗ Немає результату!"
|
||||
"noQuery" = "❌ Запит не знайдено! Скористайтеся командою ще раз!"
|
||||
"noQuery" = "❌ Запит не знайдено! Будь ласка, використовуйте команду ще раз!"
|
||||
"wentWrong" = "❌ Щось пішло не так!"
|
||||
"noIpRecord" = "❗ Немає IP-запису!"
|
||||
"noInbounds" = "❗ Вхідних не знайдено!"
|
||||
"unlimited" = "♾ Необмежений (скинути)"
|
||||
"noIpRecord" = "❗ Немає запису IP!"
|
||||
"noInbounds" = "❗ Вхідні не знайдені!"
|
||||
"unlimited" = "♾ Необмежено (Скинути)"
|
||||
"add" = "Додати"
|
||||
"month" = "Місяць"
|
||||
"months" = "Місяці"
|
||||
"day" = "День"
|
||||
"days" = "Дні"
|
||||
"hours" = "Годинник"
|
||||
"hours" = "Години"
|
||||
"minutes" = "Хвилини"
|
||||
"unknown" = "Невідомо"
|
||||
"inbounds" = "Вхідні"
|
||||
"clients" = "Клієнти"
|
||||
|
|
|
@ -561,22 +561,23 @@
|
|||
"resetOutboundTrafficError" = "Lỗi khi đặt lại lưu lượng truy cập đi"
|
||||
|
||||
[tgbot]
|
||||
"keyboardClosed" = "❌ Bàn phím tùy chỉnh đã đóng!"
|
||||
"keyboardClosed" = "❌ Bàn phím đã đóng!"
|
||||
"noResult" = "❗ Không có kết quả!"
|
||||
"noQuery" = "❌ Không tìm thấy truy vấn! Vui lòng sử dụng lệnh lại!"
|
||||
"noQuery" = "❌ Không tìm thấy truy vấn! Vui lòng sử dụng lại lệnh!"
|
||||
"wentWrong" = "❌ Đã xảy ra lỗi!"
|
||||
"noIpRecord" = "❗ Không có bản ghi IP!"
|
||||
"noInbounds" = "❗ Không tìm thấy inbound!"
|
||||
"unlimited" = "♾ Không giới hạn"
|
||||
"unlimited" = "♾ Không giới hạn (Đặt lại)"
|
||||
"add" = "Thêm"
|
||||
"month" = "Tháng"
|
||||
"months" = "Tháng"
|
||||
"day" = "Ngày"
|
||||
"days" = "Ngày"
|
||||
"hours" = "Giờ"
|
||||
"unknown" = "Không rõ"
|
||||
"inbounds" = "Vào"
|
||||
"clients" = "Các người dùng"
|
||||
"minutes" = "Phút"
|
||||
"unknown" = "Không xác định"
|
||||
"inbounds" = "Inbound"
|
||||
"clients" = "Client"
|
||||
"offline" = "🔴 Ngoại tuyến"
|
||||
"online" = "🟢 Trực tuyến"
|
||||
|
||||
|
|
|
@ -563,19 +563,20 @@
|
|||
[tgbot]
|
||||
"keyboardClosed" = "❌ 自定义键盘已关闭!"
|
||||
"noResult" = "❗ 没有结果!"
|
||||
"noQuery" = "❌ 未找到查询!请重新使用命令!"
|
||||
"noQuery" = "❌ 未找到查询!请再次使用该命令!"
|
||||
"wentWrong" = "❌ 出了点问题!"
|
||||
"noIpRecord" = "❗ 没有 IP 记录!"
|
||||
"noInbounds" = "❗ 没有找到入站连接!"
|
||||
"unlimited" = "♾ 无限制"
|
||||
"noIpRecord" = "❗ 没有IP记录!"
|
||||
"noInbounds" = "❗ 未找到入站!"
|
||||
"unlimited" = "♾ 无限(重置)"
|
||||
"add" = "添加"
|
||||
"month" = "月"
|
||||
"months" = "月"
|
||||
"day" = "天"
|
||||
"days" = "天"
|
||||
"hours" = "小时"
|
||||
"minutes" = "分钟"
|
||||
"unknown" = "未知"
|
||||
"inbounds" = "入站连接"
|
||||
"inbounds" = "入站"
|
||||
"clients" = "客户端"
|
||||
"offline" = "🔴 离线"
|
||||
"online" = "🟢 在线"
|
||||
|
|
|
@ -563,22 +563,23 @@
|
|||
[tgbot]
|
||||
"keyboardClosed" = "❌ 自定義鍵盤已關閉!"
|
||||
"noResult" = "❗ 沒有結果!"
|
||||
"noQuery" = "❌ 未找到查詢!請重新使用命令!"
|
||||
"noQuery" = "❌ 未找到查詢!請再次使用該命令!"
|
||||
"wentWrong" = "❌ 出了點問題!"
|
||||
"noIpRecord" = "❗ 沒有 IP 記錄!"
|
||||
"noInbounds" = "❗ 沒有找到入站連線!"
|
||||
"unlimited" = "♾ 無限制"
|
||||
"add" = "新增"
|
||||
"noIpRecord" = "❗ 沒有IP記錄!"
|
||||
"noInbounds" = "❗ 未找到入站!"
|
||||
"unlimited" = "♾ 無限(重置)"
|
||||
"add" = "添加"
|
||||
"month" = "月"
|
||||
"months" = "月"
|
||||
"day" = "天"
|
||||
"days" = "天"
|
||||
"hours" = "小時"
|
||||
"minutes" = "分鐘"
|
||||
"unknown" = "未知"
|
||||
"inbounds" = "入站連線"
|
||||
"inbounds" = "入站"
|
||||
"clients" = "客戶端"
|
||||
"offline" = "🔴 離線"
|
||||
"online" = "🟢 線上"
|
||||
"online" = "🟢 在線"
|
||||
|
||||
[tgbot.commands]
|
||||
"unknown" = "❗ 未知命令"
|
||||
|
|
249
x-ui.sh
249
x-ui.sh
|
@ -398,37 +398,6 @@ show_log() {
|
|||
esac
|
||||
}
|
||||
|
||||
show_banlog() {
|
||||
local system_log="/var/log/fail2ban.log"
|
||||
|
||||
echo -e "${green}Checking ban logs...${plain}\n"
|
||||
|
||||
if ! systemctl is-active --quiet fail2ban; then
|
||||
echo -e "${red}Fail2ban service is not running!${plain}\n"
|
||||
return 1
|
||||
fi
|
||||
|
||||
if [[ -f "$system_log" ]]; then
|
||||
echo -e "${green}Recent system ban activities from fail2ban.log:${plain}"
|
||||
grep "3x-ipl" "$system_log" | grep -E "Ban|Unban" | tail -n 10 || echo -e "${yellow}No recent system ban activities found${plain}"
|
||||
echo ""
|
||||
fi
|
||||
|
||||
if [[ -f "${iplimit_banned_log_path}" ]]; then
|
||||
echo -e "${green}3X-IPL ban log entries:${plain}"
|
||||
if [[ -s "${iplimit_banned_log_path}" ]]; then
|
||||
grep -v "INIT" "${iplimit_banned_log_path}" | tail -n 10 || echo -e "${yellow}No ban entries found${plain}"
|
||||
else
|
||||
echo -e "${yellow}Ban log file is empty${plain}"
|
||||
fi
|
||||
else
|
||||
echo -e "${red}Ban log file not found at: ${iplimit_banned_log_path}${plain}"
|
||||
fi
|
||||
|
||||
echo -e "\n${green}Current jail status:${plain}"
|
||||
fail2ban-client status 3x-ipl || echo -e "${yellow}Unable to get jail status${plain}"
|
||||
}
|
||||
|
||||
bbr_menu() {
|
||||
echo -e "${green}\t1.${plain} Enable BBR"
|
||||
echo -e "${green}\t2.${plain} Disable BBR"
|
||||
|
@ -1005,7 +974,7 @@ ssl_cert_issue() {
|
|||
# install socat second
|
||||
case "${release}" in
|
||||
ubuntu | debian | armbian)
|
||||
apt update && apt install socat -y
|
||||
apt-get update && apt-get install socat -y
|
||||
;;
|
||||
centos | rhel | almalinux | rocky | ol)
|
||||
yum -y update && yum -y install socat
|
||||
|
@ -1330,81 +1299,7 @@ run_speedtest() {
|
|||
speedtest
|
||||
}
|
||||
|
||||
create_iplimit_jails() {
|
||||
# Use default bantime if not passed => 30 minutes
|
||||
local bantime="${1:-30}"
|
||||
|
||||
# Uncomment 'allowipv6 = auto' in fail2ban.conf
|
||||
sed -i 's/#allowipv6 = auto/allowipv6 = auto/g' /etc/fail2ban/fail2ban.conf
|
||||
|
||||
# On Debian 12+ fail2ban's default backend should be changed to systemd
|
||||
if [[ "${release}" == "debian" && ${os_version} -ge 12 ]]; then
|
||||
sed -i '0,/action =/s/backend = auto/backend = systemd/' /etc/fail2ban/jail.conf
|
||||
fi
|
||||
|
||||
cat << EOF > /etc/fail2ban/jail.d/3x-ipl.conf
|
||||
[3x-ipl]
|
||||
enabled=true
|
||||
backend=auto
|
||||
filter=3x-ipl
|
||||
action=3x-ipl
|
||||
logpath=${iplimit_log_path}
|
||||
maxretry=2
|
||||
findtime=32
|
||||
bantime=${bantime}m
|
||||
EOF
|
||||
|
||||
cat << EOF > /etc/fail2ban/filter.d/3x-ipl.conf
|
||||
[Definition]
|
||||
datepattern = ^%%Y/%%m/%%d %%H:%%M:%%S
|
||||
failregex = \[LIMIT_IP\]\s*Email\s*=\s*<F-USER>.+</F-USER>\s*\|\|\s*SRC\s*=\s*<ADDR>
|
||||
ignoreregex =
|
||||
EOF
|
||||
|
||||
cat << EOF > /etc/fail2ban/action.d/3x-ipl.conf
|
||||
[INCLUDES]
|
||||
before = iptables-allports.conf
|
||||
|
||||
[Definition]
|
||||
actionstart = <iptables> -N f2b-<name>
|
||||
<iptables> -A f2b-<name> -j <returntype>
|
||||
<iptables> -I <chain> -p <protocol> -j f2b-<name>
|
||||
|
||||
actionstop = <iptables> -D <chain> -p <protocol> -j f2b-<name>
|
||||
<actionflush>
|
||||
<iptables> -X f2b-<name>
|
||||
|
||||
actioncheck = <iptables> -n -L <chain> | grep -q 'f2b-<name>[ \t]'
|
||||
|
||||
actionban = <iptables> -I f2b-<name> 1 -s <ip> -j <blocktype>
|
||||
echo "\$(date +"%%Y/%%m/%%d %%H:%%M:%%S") BAN [Email] = <F-USER> [IP] = <ip> banned for <bantime> seconds." >> ${iplimit_banned_log_path}
|
||||
|
||||
actionunban = <iptables> -D f2b-<name> -s <ip> -j <blocktype>
|
||||
echo "\$(date +"%%Y/%%m/%%d %%H:%%M:%%S") UNBAN [Email] = <F-USER> [IP] = <ip> unbanned." >> ${iplimit_banned_log_path}
|
||||
|
||||
[Init]
|
||||
name = default
|
||||
protocol = tcp
|
||||
chain = INPUT
|
||||
EOF
|
||||
|
||||
echo -e "${green}Ip Limit jail files created with a bantime of ${bantime} minutes.${plain}"
|
||||
}
|
||||
|
||||
iplimit_remove_conflicts() {
|
||||
local jail_files=(
|
||||
/etc/fail2ban/jail.conf
|
||||
/etc/fail2ban/jail.local
|
||||
)
|
||||
|
||||
for file in "${jail_files[@]}"; do
|
||||
# Check for [3x-ipl] config in jail file then remove it
|
||||
if test -f "${file}" && grep -qw '3x-ipl' ${file}; then
|
||||
sed -i "/\[3x-ipl\]/,/^$/d" ${file}
|
||||
echo -e "${yellow}Removing conflicts of [3x-ipl] in jail (${file})!${plain}\n"
|
||||
fi
|
||||
done
|
||||
}
|
||||
|
||||
ip_validation() {
|
||||
ipv6_regex="^(([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}|:)|fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|::(ffff(:0{1,4}){0,1}:){0,1}((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]))$"
|
||||
|
@ -1514,14 +1409,22 @@ install_iplimit() {
|
|||
# Check the OS and install necessary packages
|
||||
case "${release}" in
|
||||
ubuntu)
|
||||
apt-get update
|
||||
if [[ "${os_version}" -ge 24 ]]; then
|
||||
apt update && apt install python3-pip -y
|
||||
apt-get install python3-pip -y
|
||||
python3 -m pip install pyasynchat --break-system-packages
|
||||
fi
|
||||
apt update && apt install fail2ban -y
|
||||
apt-get install fail2ban -y
|
||||
;;
|
||||
debian | armbian)
|
||||
apt update && apt install fail2ban -y
|
||||
debian)
|
||||
apt-get update
|
||||
if [ "$os_version" -ge 12 ]; then
|
||||
apt-get install -y python3-systemd
|
||||
fi
|
||||
apt-get install -y fail2ban
|
||||
;;
|
||||
armbian)
|
||||
apt-get update && apt-get install fail2ban -y
|
||||
;;
|
||||
centos | rhel | almalinux | rocky | ol)
|
||||
yum update -y && yum install epel-release -y
|
||||
|
@ -1632,11 +1535,129 @@ remove_iplimit() {
|
|||
esac
|
||||
}
|
||||
|
||||
SSH_port_forwarding() {
|
||||
local server_ip=$(curl -s --max-time 3 https://api.ipify.org)
|
||||
if [ -z "$server_ip" ]; then
|
||||
server_ip=$(curl -s --max-time 3 https://4.ident.me)
|
||||
show_banlog() {
|
||||
local system_log="/var/log/fail2ban.log"
|
||||
|
||||
echo -e "${green}Checking ban logs...${plain}\n"
|
||||
|
||||
if ! systemctl is-active --quiet fail2ban; then
|
||||
echo -e "${red}Fail2ban service is not running!${plain}\n"
|
||||
return 1
|
||||
fi
|
||||
|
||||
if [[ -f "$system_log" ]]; then
|
||||
echo -e "${green}Recent system ban activities from fail2ban.log:${plain}"
|
||||
grep "3x-ipl" "$system_log" | grep -E "Ban|Unban" | tail -n 10 || echo -e "${yellow}No recent system ban activities found${plain}"
|
||||
echo ""
|
||||
fi
|
||||
|
||||
if [[ -f "${iplimit_banned_log_path}" ]]; then
|
||||
echo -e "${green}3X-IPL ban log entries:${plain}"
|
||||
if [[ -s "${iplimit_banned_log_path}" ]]; then
|
||||
grep -v "INIT" "${iplimit_banned_log_path}" | tail -n 10 || echo -e "${yellow}No ban entries found${plain}"
|
||||
else
|
||||
echo -e "${yellow}Ban log file is empty${plain}"
|
||||
fi
|
||||
else
|
||||
echo -e "${red}Ban log file not found at: ${iplimit_banned_log_path}${plain}"
|
||||
fi
|
||||
|
||||
echo -e "\n${green}Current jail status:${plain}"
|
||||
fail2ban-client status 3x-ipl || echo -e "${yellow}Unable to get jail status${plain}"
|
||||
}
|
||||
|
||||
create_iplimit_jails() {
|
||||
# Use default bantime if not passed => 30 minutes
|
||||
local bantime="${1:-30}"
|
||||
|
||||
# Uncomment 'allowipv6 = auto' in fail2ban.conf
|
||||
sed -i 's/#allowipv6 = auto/allowipv6 = auto/g' /etc/fail2ban/fail2ban.conf
|
||||
|
||||
# On Debian 12+ fail2ban's default backend should be changed to systemd
|
||||
if [[ "${release}" == "debian" && ${os_version} -ge 12 ]]; then
|
||||
sed -i '0,/action =/s/backend = auto/backend = systemd/' /etc/fail2ban/jail.conf
|
||||
fi
|
||||
|
||||
cat << EOF > /etc/fail2ban/jail.d/3x-ipl.conf
|
||||
[3x-ipl]
|
||||
enabled=true
|
||||
backend=auto
|
||||
filter=3x-ipl
|
||||
action=3x-ipl
|
||||
logpath=${iplimit_log_path}
|
||||
maxretry=2
|
||||
findtime=32
|
||||
bantime=${bantime}m
|
||||
EOF
|
||||
|
||||
cat << EOF > /etc/fail2ban/filter.d/3x-ipl.conf
|
||||
[Definition]
|
||||
datepattern = ^%%Y/%%m/%%d %%H:%%M:%%S
|
||||
failregex = \[LIMIT_IP\]\s*Email\s*=\s*<F-USER>.+</F-USER>\s*\|\|\s*SRC\s*=\s*<ADDR>
|
||||
ignoreregex =
|
||||
EOF
|
||||
|
||||
cat << EOF > /etc/fail2ban/action.d/3x-ipl.conf
|
||||
[INCLUDES]
|
||||
before = iptables-allports.conf
|
||||
|
||||
[Definition]
|
||||
actionstart = <iptables> -N f2b-<name>
|
||||
<iptables> -A f2b-<name> -j <returntype>
|
||||
<iptables> -I <chain> -p <protocol> -j f2b-<name>
|
||||
|
||||
actionstop = <iptables> -D <chain> -p <protocol> -j f2b-<name>
|
||||
<actionflush>
|
||||
<iptables> -X f2b-<name>
|
||||
|
||||
actioncheck = <iptables> -n -L <chain> | grep -q 'f2b-<name>[ \t]'
|
||||
|
||||
actionban = <iptables> -I f2b-<name> 1 -s <ip> -j <blocktype>
|
||||
echo "\$(date +"%%Y/%%m/%%d %%H:%%M:%%S") BAN [Email] = <F-USER> [IP] = <ip> banned for <bantime> seconds." >> ${iplimit_banned_log_path}
|
||||
|
||||
actionunban = <iptables> -D f2b-<name> -s <ip> -j <blocktype>
|
||||
echo "\$(date +"%%Y/%%m/%%d %%H:%%M:%%S") UNBAN [Email] = <F-USER> [IP] = <ip> unbanned." >> ${iplimit_banned_log_path}
|
||||
|
||||
[Init]
|
||||
name = default
|
||||
protocol = tcp
|
||||
chain = INPUT
|
||||
EOF
|
||||
|
||||
echo -e "${green}Ip Limit jail files created with a bantime of ${bantime} minutes.${plain}"
|
||||
}
|
||||
|
||||
iplimit_remove_conflicts() {
|
||||
local jail_files=(
|
||||
/etc/fail2ban/jail.conf
|
||||
/etc/fail2ban/jail.local
|
||||
)
|
||||
|
||||
for file in "${jail_files[@]}"; do
|
||||
# Check for [3x-ipl] config in jail file then remove it
|
||||
if test -f "${file}" && grep -qw '3x-ipl' ${file}; then
|
||||
sed -i "/\[3x-ipl\]/,/^$/d" ${file}
|
||||
echo -e "${yellow}Removing conflicts of [3x-ipl] in jail (${file})!${plain}\n"
|
||||
fi
|
||||
done
|
||||
}
|
||||
|
||||
SSH_port_forwarding() {
|
||||
local URL_lists=(
|
||||
"https://api4.ipify.org"
|
||||
"https://ipv4.icanhazip.com"
|
||||
"https://v4.api.ipinfo.io/ip"
|
||||
"https://ipv4.myexternalip.com/raw"
|
||||
"https://4.ident.me"
|
||||
"https://check-host.net/ip"
|
||||
)
|
||||
local server_ip=""
|
||||
for ip_address in "${URL_lists[@]}"; do
|
||||
server_ip=$(curl -s --max-time 3 "${ip_address}" 2>/dev/null | tr -d '[:space:]')
|
||||
if [[ -n "${server_ip}" ]]; then
|
||||
break
|
||||
fi
|
||||
done
|
||||
local existing_webBasePath=$(/usr/local/x-ui/x-ui setting -show true | grep -Eo 'webBasePath: .+' | awk '{print $2}')
|
||||
local existing_port=$(/usr/local/x-ui/x-ui setting -show true | grep -Eo 'port: .+' | awk '{print $2}')
|
||||
local existing_listenIP=$(/usr/local/x-ui/x-ui setting -getListen true | grep -Eo 'listenIP: .+' | awk '{print $2}')
|
||||
|
|
Loading…
Reference in a new issue