Compare commits
No commits in common. "main" and "v1.1.3" have entirely different histories.
14
.github/FUNDING.yml
vendored
|
@ -1,14 +0,0 @@
|
||||||
# These are supported funding model platforms
|
|
||||||
|
|
||||||
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
|
|
||||||
tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
|
|
||||||
community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
|
|
||||||
liberapay: # Replace with a single Liberapay username
|
|
||||||
issuehunt: # Replace with a single IssueHunt username
|
|
||||||
lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry
|
|
||||||
polar: # Replace with a single Polar username
|
|
||||||
buy_me_a_coffee: mhsanaei
|
|
||||||
custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']
|
|
77
.github/ISSUE_TEMPLATE/bug_report.yaml
vendored
|
@ -1,77 +0,0 @@
|
||||||
name: Bug report
|
|
||||||
description: Create a report to help us improve
|
|
||||||
title: "Bug report"
|
|
||||||
labels: ["bug"]
|
|
||||||
|
|
||||||
body:
|
|
||||||
- type: markdown
|
|
||||||
attributes:
|
|
||||||
value: |
|
|
||||||
Thank you for reporting a bug! Please fill out the following information.
|
|
||||||
|
|
||||||
- type: textarea
|
|
||||||
id: what-happened
|
|
||||||
attributes:
|
|
||||||
label: Describe the bug
|
|
||||||
description: A clear and concise description of what the bug is.
|
|
||||||
placeholder: My problem is...
|
|
||||||
validations:
|
|
||||||
required: true
|
|
||||||
|
|
||||||
- type: textarea
|
|
||||||
id: how-repeat-problem
|
|
||||||
attributes:
|
|
||||||
label: How to repeat the problem?
|
|
||||||
description: Sequence of actions that allow you to reproduce the bug
|
|
||||||
placeholder: |
|
|
||||||
1. Open `Inbounds` page
|
|
||||||
2. ...
|
|
||||||
validations:
|
|
||||||
required: true
|
|
||||||
|
|
||||||
- type: textarea
|
|
||||||
id: expected-action
|
|
||||||
attributes:
|
|
||||||
label: Expected action
|
|
||||||
description: What's going to happen
|
|
||||||
placeholder: Must be...
|
|
||||||
validations:
|
|
||||||
required: false
|
|
||||||
|
|
||||||
- type: textarea
|
|
||||||
id: received-action
|
|
||||||
attributes:
|
|
||||||
label: Received action
|
|
||||||
description: What's really happening
|
|
||||||
placeholder: It's actually happening...
|
|
||||||
validations:
|
|
||||||
required: false
|
|
||||||
|
|
||||||
- type: input
|
|
||||||
id: xui-version
|
|
||||||
attributes:
|
|
||||||
label: 3x-ui Version
|
|
||||||
description: Which version of 3x-ui are you using?
|
|
||||||
placeholder: 2.X.X
|
|
||||||
validations:
|
|
||||||
required: true
|
|
||||||
|
|
||||||
- type: input
|
|
||||||
id: xray-version
|
|
||||||
attributes:
|
|
||||||
label: Xray-core Version
|
|
||||||
description: Which version of Xray-core are you using?
|
|
||||||
placeholder: 2.X.X
|
|
||||||
validations:
|
|
||||||
required: false
|
|
||||||
|
|
||||||
- type: checkboxes
|
|
||||||
id: checklist
|
|
||||||
attributes:
|
|
||||||
label: Checklist
|
|
||||||
description: Please check all the checkboxes
|
|
||||||
options:
|
|
||||||
- label: This bug report is written entirely in English.
|
|
||||||
required: true
|
|
||||||
- label: This bug report is new and no one has reported it before me.
|
|
||||||
required: true
|
|
56
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
Normal file
|
@ -0,0 +1,56 @@
|
||||||
|
name: Issue Report
|
||||||
|
description: "Create a report to help us improve."
|
||||||
|
body:
|
||||||
|
- type: checkboxes
|
||||||
|
id: terms
|
||||||
|
attributes:
|
||||||
|
label: Welcome
|
||||||
|
options:
|
||||||
|
- label: Yes, I'm using the latest major release. Only such installations are supported.
|
||||||
|
required: true
|
||||||
|
- label: Yes, I'm using the supported system. Only such systems are supported.
|
||||||
|
required: true
|
||||||
|
- label: Yes, I have read all WIKI document,nothing can help me in my problem.
|
||||||
|
required: true
|
||||||
|
- label: Yes, I've searched similar issues on GitHub and didn't find any.
|
||||||
|
required: true
|
||||||
|
- label: Yes, I've included all information below (version, config, log, etc).
|
||||||
|
required: true
|
||||||
|
|
||||||
|
- type: textarea
|
||||||
|
id: problem
|
||||||
|
attributes:
|
||||||
|
label: Description of the problem,screencshot would be good
|
||||||
|
placeholder: Your problem description
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
|
||||||
|
- type: textarea
|
||||||
|
id: version
|
||||||
|
attributes:
|
||||||
|
label: Version of 3x-ui
|
||||||
|
value: |-
|
||||||
|
<details>
|
||||||
|
|
||||||
|
```console
|
||||||
|
# Paste here
|
||||||
|
```
|
||||||
|
|
||||||
|
</details>
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
|
||||||
|
- type: textarea
|
||||||
|
id: log
|
||||||
|
attributes:
|
||||||
|
label: x-ui log reports or xray log
|
||||||
|
value: |-
|
||||||
|
<details>
|
||||||
|
|
||||||
|
```console
|
||||||
|
# paste log here
|
||||||
|
```
|
||||||
|
|
||||||
|
</details>
|
||||||
|
validations:
|
||||||
|
required: true
|
20
.github/ISSUE_TEMPLATE/feature_request.md
vendored
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
---
|
||||||
|
name: Feature request
|
||||||
|
about: Suggest an idea for this project
|
||||||
|
title: ''
|
||||||
|
labels: enhancement
|
||||||
|
assignees: ''
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Is your feature request related to a problem? Please describe.**
|
||||||
|
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
|
||||||
|
|
||||||
|
**Describe the solution you'd like**
|
||||||
|
A clear and concise description of what you want to happen.
|
||||||
|
|
||||||
|
**Describe alternatives you've considered**
|
||||||
|
A clear and concise description of any alternative solutions or features you've considered.
|
||||||
|
|
||||||
|
**Additional context**
|
||||||
|
Add any other context or screenshots about the feature request here.
|
39
.github/ISSUE_TEMPLATE/feature_request.yaml
vendored
|
@ -1,39 +0,0 @@
|
||||||
name: Feature request
|
|
||||||
description: Suggest an idea for this project
|
|
||||||
title: "Feature request"
|
|
||||||
labels: ["enhancement"]
|
|
||||||
|
|
||||||
body:
|
|
||||||
- type: textarea
|
|
||||||
id: is-related-problem
|
|
||||||
attributes:
|
|
||||||
label: Is your feature request related to a problem?
|
|
||||||
description: A clear and concise description of what the problem is.
|
|
||||||
placeholder: I'm always frustrated when...
|
|
||||||
validations:
|
|
||||||
required: true
|
|
||||||
|
|
||||||
- type: textarea
|
|
||||||
id: solution
|
|
||||||
attributes:
|
|
||||||
label: Describe the solution you'd like
|
|
||||||
description: A clear and concise description of what you want to happen.
|
|
||||||
validations:
|
|
||||||
required: true
|
|
||||||
|
|
||||||
- type: textarea
|
|
||||||
id: alternatives
|
|
||||||
attributes:
|
|
||||||
label: Describe alternatives you've considered
|
|
||||||
description: A clear and concise description of any alternative solutions or features you've considered.
|
|
||||||
validations:
|
|
||||||
required: false
|
|
||||||
|
|
||||||
- type: checkboxes
|
|
||||||
id: checklist
|
|
||||||
attributes:
|
|
||||||
label: Checklist
|
|
||||||
description: Please check all the checkboxes
|
|
||||||
options:
|
|
||||||
- label: This feature report is written entirely in English.
|
|
||||||
required: true
|
|
10
.github/ISSUE_TEMPLATE/question-.md
vendored
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
---
|
||||||
|
name: 'Question '
|
||||||
|
about: Describe this issue template's purpose here.
|
||||||
|
title: ''
|
||||||
|
labels: question
|
||||||
|
assignees: ''
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
|
22
.github/ISSUE_TEMPLATE/question.yaml
vendored
|
@ -1,22 +0,0 @@
|
||||||
name: Question
|
|
||||||
description: Describe this issue template's purpose here.
|
|
||||||
title: "Question"
|
|
||||||
labels: ["question"]
|
|
||||||
|
|
||||||
body:
|
|
||||||
- type: textarea
|
|
||||||
id: question
|
|
||||||
attributes:
|
|
||||||
label: Question
|
|
||||||
placeholder: I have a question, ..., how can I solve it?
|
|
||||||
validations:
|
|
||||||
required: true
|
|
||||||
|
|
||||||
- type: checkboxes
|
|
||||||
id: checklist
|
|
||||||
attributes:
|
|
||||||
label: Checklist
|
|
||||||
description: Please check all the checkboxes
|
|
||||||
options:
|
|
||||||
- label: This question is written entirely in English.
|
|
||||||
required: true
|
|
10
.github/dependabot.yml
vendored
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
version: 2
|
||||||
|
updates:
|
||||||
|
- package-ecosystem: "gomod" # See documentation for possible values
|
||||||
|
directory: "/" # Location of package manifests
|
||||||
|
schedule:
|
||||||
|
interval: "daily"
|
||||||
|
- package-ecosystem: "github-actions"
|
||||||
|
directory: "/"
|
||||||
|
schedule:
|
||||||
|
interval: "daily"
|
20
.github/pull_request_template.yml
vendored
|
@ -1,20 +0,0 @@
|
||||||
## What is the pull request?
|
|
||||||
|
|
||||||
<!-- Briefly describe the changes introduced by this pull request -->
|
|
||||||
|
|
||||||
## Which part of the application is affected by the change?
|
|
||||||
|
|
||||||
- [ ] Frontend
|
|
||||||
- [ ] Backend
|
|
||||||
|
|
||||||
## Type of Changes
|
|
||||||
|
|
||||||
- [ ] Bug fix
|
|
||||||
- [ ] New feature
|
|
||||||
- [ ] Refactoring
|
|
||||||
- [ ] Other
|
|
||||||
|
|
||||||
## Screenshots
|
|
||||||
|
|
||||||
<!-- Add screenshots to illustrate the changes -->
|
|
||||||
<!-- Remove this section if it is not applicable. -->
|
|
57
.github/workflows/docker.yml
vendored
|
@ -1,57 +0,0 @@
|
||||||
name: Release 3X-UI for Docker
|
|
||||||
on:
|
|
||||||
workflow_dispatch:
|
|
||||||
push:
|
|
||||||
tags:
|
|
||||||
- "v*.*.*"
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
build:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v5
|
|
||||||
with:
|
|
||||||
submodules: true
|
|
||||||
|
|
||||||
- name: Docker meta
|
|
||||||
id: meta
|
|
||||||
uses: docker/metadata-action@v5
|
|
||||||
with:
|
|
||||||
images: |
|
|
||||||
hsanaeii/3x-ui
|
|
||||||
ghcr.io/mhsanaei/3x-ui
|
|
||||||
tags: |
|
|
||||||
type=ref,event=branch
|
|
||||||
type=ref,event=tag
|
|
||||||
type=pep440,pattern={{version}}
|
|
||||||
|
|
||||||
- name: Set up QEMU
|
|
||||||
uses: docker/setup-qemu-action@v3
|
|
||||||
|
|
||||||
- name: Set up Docker Buildx
|
|
||||||
uses: docker/setup-buildx-action@v3
|
|
||||||
with:
|
|
||||||
install: true
|
|
||||||
|
|
||||||
- name: Login to Docker Hub
|
|
||||||
uses: docker/login-action@v3
|
|
||||||
with:
|
|
||||||
username: ${{ secrets.DOCKER_HUB_USERNAME }}
|
|
||||||
password: ${{ secrets.DOCKER_HUB_TOKEN }}
|
|
||||||
|
|
||||||
- name: Login to GHCR
|
|
||||||
uses: docker/login-action@v3
|
|
||||||
with:
|
|
||||||
registry: ghcr.io
|
|
||||||
username: ${{ github.repository_owner }}
|
|
||||||
password: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
|
|
||||||
- name: Build and push Docker image
|
|
||||||
uses: docker/build-push-action@v6
|
|
||||||
with:
|
|
||||||
context: .
|
|
||||||
push: true
|
|
||||||
platforms: linux/amd64, linux/arm64/v8, linux/arm/v7, linux/arm/v6, linux/386
|
|
||||||
tags: ${{ steps.meta.outputs.tags }}
|
|
||||||
labels: ${{ steps.meta.outputs.labels }}
|
|
165
.github/workflows/release.yml
vendored
|
@ -1,144 +1,43 @@
|
||||||
name: Release 3X-UI
|
name: Release 3X-ui
|
||||||
|
|
||||||
on:
|
on:
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
release:
|
|
||||||
types: [published]
|
|
||||||
push:
|
|
||||||
branches:
|
|
||||||
- main
|
|
||||||
paths:
|
|
||||||
- '.github/workflows/release.yml'
|
|
||||||
- '**.js'
|
|
||||||
- '**.css'
|
|
||||||
- '**.html'
|
|
||||||
- '**.sh'
|
|
||||||
- '**.go'
|
|
||||||
- 'go.mod'
|
|
||||||
- 'go.sum'
|
|
||||||
- 'x-ui.service'
|
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
linuxamd64build:
|
||||||
permissions:
|
name: build x-ui amd64 version
|
||||||
contents: write
|
runs-on: ubuntu-20.04
|
||||||
strategy:
|
|
||||||
matrix:
|
|
||||||
platform:
|
|
||||||
- amd64
|
|
||||||
- arm64
|
|
||||||
- armv7
|
|
||||||
- armv6
|
|
||||||
- 386
|
|
||||||
- armv5
|
|
||||||
- s390x
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- uses: actions/checkout@v3.5.0
|
||||||
uses: actions/checkout@v5
|
- name: Set up Go
|
||||||
|
uses: actions/setup-go@v4.0.0
|
||||||
- name: Setup Go
|
|
||||||
uses: actions/setup-go@v5
|
|
||||||
with:
|
with:
|
||||||
go-version-file: go.mod
|
go-version: 'stable'
|
||||||
check-latest: true
|
- name: build linux amd64 version
|
||||||
|
|
||||||
- name: Build 3X-UI
|
|
||||||
run: |
|
run: |
|
||||||
export CGO_ENABLED=1
|
CGO_ENABLED=1 GOOS=linux GOARCH=amd64 go build -o xui-release -v main.go
|
||||||
export GOOS=linux
|
|
||||||
export GOARCH=${{ matrix.platform }}
|
|
||||||
# Use Bootlin prebuilt cross-toolchains (musl 1.2.5 in stable series)
|
|
||||||
case "${{ matrix.platform }}" in
|
|
||||||
amd64) BOOTLIN_ARCH="x86-64" ;;
|
|
||||||
arm64) BOOTLIN_ARCH="aarch64" ;;
|
|
||||||
armv7) BOOTLIN_ARCH="armv7-eabihf"; export GOARCH=arm GOARM=7 ;;
|
|
||||||
armv6) BOOTLIN_ARCH="armv6-eabihf"; export GOARCH=arm GOARM=6 ;;
|
|
||||||
armv5) BOOTLIN_ARCH="armv5-eabi"; export GOARCH=arm GOARM=5 ;;
|
|
||||||
386) BOOTLIN_ARCH="x86-i686" ;;
|
|
||||||
s390x) BOOTLIN_ARCH="s390x-z13" ;;
|
|
||||||
esac
|
|
||||||
echo "Resolving Bootlin musl toolchain for arch=$BOOTLIN_ARCH (platform=${{ matrix.platform }})"
|
|
||||||
TARBALL_BASE="https://toolchains.bootlin.com/downloads/releases/toolchains/$BOOTLIN_ARCH/tarballs/"
|
|
||||||
TARBALL_URL=$(curl -fsSL "$TARBALL_BASE" | grep -oE "${BOOTLIN_ARCH}--musl--stable-[^\"]+\\.tar\\.xz" | sort -r | head -n1)
|
|
||||||
[ -z "$TARBALL_URL" ] && { echo "Failed to locate Bootlin musl toolchain for arch=$BOOTLIN_ARCH" >&2; exit 1; }
|
|
||||||
echo "Downloading: $TARBALL_URL"
|
|
||||||
cd /tmp
|
|
||||||
curl -fL -sS -o "$(basename "$TARBALL_URL")" "$TARBALL_BASE/$TARBALL_URL"
|
|
||||||
tar -xf "$(basename "$TARBALL_URL")"
|
|
||||||
TOOLCHAIN_DIR=$(find . -maxdepth 1 -type d -name "${BOOTLIN_ARCH}--musl--stable-*" | head -n1)
|
|
||||||
export PATH="$(realpath "$TOOLCHAIN_DIR")/bin:$PATH"
|
|
||||||
export CC=$(realpath "$(find "$TOOLCHAIN_DIR/bin" -name '*-gcc.br_real' -type f -executable | head -n1)")
|
|
||||||
[ -z "$CC" ] && { echo "No gcc.br_real found in $TOOLCHAIN_DIR/bin" >&2; exit 1; }
|
|
||||||
cd -
|
|
||||||
go build -ldflags "-w -s -linkmode external -extldflags '-static'" -o xui-release -v main.go
|
|
||||||
file xui-release
|
|
||||||
ldd xui-release || echo "Static binary confirmed"
|
|
||||||
|
|
||||||
mkdir x-ui
|
mkdir x-ui
|
||||||
cp xui-release x-ui/
|
cp xui-release x-ui/xui-release
|
||||||
cp x-ui.service x-ui/
|
cp x-ui.service x-ui/x-ui.service
|
||||||
cp x-ui.sh x-ui/
|
cp x-ui.sh x-ui/x-ui.sh
|
||||||
mv x-ui/xui-release x-ui/x-ui
|
cd x-ui
|
||||||
mkdir x-ui/bin
|
mv xui-release x-ui
|
||||||
cd x-ui/bin
|
mkdir bin
|
||||||
|
cd bin
|
||||||
# Download dependencies
|
wget https://github.com/mhsanaei/Xray-core/releases/latest/download/Xray-linux-64.zip
|
||||||
Xray_URL="https://github.com/XTLS/Xray-core/releases/download/v25.8.3/"
|
unzip Xray-linux-64.zip
|
||||||
if [ "${{ matrix.platform }}" == "amd64" ]; then
|
rm -f Xray-linux-64.zip geoip.dat geosite.dat
|
||||||
wget -q ${Xray_URL}Xray-linux-64.zip
|
wget https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geoip.dat
|
||||||
unzip Xray-linux-64.zip
|
wget https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geosite.dat
|
||||||
rm -f Xray-linux-64.zip
|
mv xray xray-linux-amd64
|
||||||
elif [ "${{ matrix.platform }}" == "arm64" ]; then
|
cd ..
|
||||||
wget -q ${Xray_URL}Xray-linux-arm64-v8a.zip
|
cd ..
|
||||||
unzip Xray-linux-arm64-v8a.zip
|
- name: package
|
||||||
rm -f Xray-linux-arm64-v8a.zip
|
run: tar -zcvf x-ui-linux-amd64.tar.gz x-ui
|
||||||
elif [ "${{ matrix.platform }}" == "armv7" ]; then
|
- name: upload
|
||||||
wget -q ${Xray_URL}Xray-linux-arm32-v7a.zip
|
uses: svenstaro/upload-release-action@2.5.0
|
||||||
unzip Xray-linux-arm32-v7a.zip
|
|
||||||
rm -f Xray-linux-arm32-v7a.zip
|
|
||||||
elif [ "${{ matrix.platform }}" == "armv6" ]; then
|
|
||||||
wget -q ${Xray_URL}Xray-linux-arm32-v6.zip
|
|
||||||
unzip Xray-linux-arm32-v6.zip
|
|
||||||
rm -f Xray-linux-arm32-v6.zip
|
|
||||||
elif [ "${{ matrix.platform }}" == "386" ]; then
|
|
||||||
wget -q ${Xray_URL}Xray-linux-32.zip
|
|
||||||
unzip Xray-linux-32.zip
|
|
||||||
rm -f Xray-linux-32.zip
|
|
||||||
elif [ "${{ matrix.platform }}" == "armv5" ]; then
|
|
||||||
wget -q ${Xray_URL}Xray-linux-arm32-v5.zip
|
|
||||||
unzip Xray-linux-arm32-v5.zip
|
|
||||||
rm -f Xray-linux-arm32-v5.zip
|
|
||||||
elif [ "${{ matrix.platform }}" == "s390x" ]; then
|
|
||||||
wget -q ${Xray_URL}Xray-linux-s390x.zip
|
|
||||||
unzip Xray-linux-s390x.zip
|
|
||||||
rm -f Xray-linux-s390x.zip
|
|
||||||
fi
|
|
||||||
rm -f geoip.dat geosite.dat
|
|
||||||
wget -q https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geoip.dat
|
|
||||||
wget -q https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geosite.dat
|
|
||||||
wget -q -O geoip_IR.dat https://github.com/chocolate4u/Iran-v2ray-rules/releases/latest/download/geoip.dat
|
|
||||||
wget -q -O geosite_IR.dat https://github.com/chocolate4u/Iran-v2ray-rules/releases/latest/download/geosite.dat
|
|
||||||
wget -q -O geoip_RU.dat https://github.com/runetfreedom/russia-v2ray-rules-dat/releases/latest/download/geoip.dat
|
|
||||||
wget -q -O geosite_RU.dat https://github.com/runetfreedom/russia-v2ray-rules-dat/releases/latest/download/geosite.dat
|
|
||||||
mv xray xray-linux-${{ matrix.platform }}
|
|
||||||
cd ../..
|
|
||||||
|
|
||||||
- name: Package
|
|
||||||
run: tar -zcvf x-ui-linux-${{ matrix.platform }}.tar.gz x-ui
|
|
||||||
|
|
||||||
- name: Upload files to Artifacts
|
|
||||||
uses: actions/upload-artifact@v4
|
|
||||||
with:
|
|
||||||
name: x-ui-linux-${{ matrix.platform }}
|
|
||||||
path: ./x-ui-linux-${{ matrix.platform }}.tar.gz
|
|
||||||
|
|
||||||
- name: Upload files to GH release
|
|
||||||
uses: svenstaro/upload-release-action@v2
|
|
||||||
if: github.event_name == 'release' && github.event.action == 'published'
|
|
||||||
with:
|
with:
|
||||||
repo_token: ${{ secrets.GITHUB_TOKEN }}
|
repo_token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
tag: ${{ github.ref }}
|
tag: ${{ github.ref }}
|
||||||
file: x-ui-linux-${{ matrix.platform }}.tar.gz
|
file: x-ui-linux-amd64.tar.gz
|
||||||
asset_name: x-ui-linux-${{ matrix.platform }}.tar.gz
|
asset_name: x-ui-linux-amd64.tar.gz
|
||||||
prerelease: true
|
prerelease: true
|
||||||
|
overwrite: true
|
||||||
|
|
46
.gitignore
vendored
|
@ -1,40 +1,12 @@
|
||||||
# Ignore editor and IDE settings
|
.idea
|
||||||
.idea/
|
tmp
|
||||||
.vscode/
|
|
||||||
.cache/
|
|
||||||
.sync*
|
|
||||||
|
|
||||||
# Ignore log files
|
|
||||||
*.log
|
|
||||||
|
|
||||||
# Ignore temporary files
|
|
||||||
tmp/
|
|
||||||
*.tar.gz
|
|
||||||
|
|
||||||
# Ignore build and distribution directories
|
|
||||||
backup/
|
|
||||||
bin/
|
bin/
|
||||||
dist/
|
dist/
|
||||||
release/
|
x-ui-*.tar.gz
|
||||||
node_modules/
|
|
||||||
|
|
||||||
# Ignore compiled binaries
|
|
||||||
main
|
|
||||||
|
|
||||||
# Ignore script and executable files
|
|
||||||
/release.sh
|
|
||||||
/x-ui
|
/x-ui
|
||||||
|
/release.sh
|
||||||
# Ignore OS specific files
|
.sync*
|
||||||
.DS_Store
|
main
|
||||||
Thumbs.db
|
release/
|
||||||
|
access.log
|
||||||
# Ignore Go build files
|
.cache
|
||||||
*.exe
|
|
||||||
x-ui.db
|
|
||||||
|
|
||||||
# Ignore Docker specific files
|
|
||||||
docker-compose.override.yml
|
|
||||||
|
|
||||||
# Ignore .env (Environment Variables) file
|
|
||||||
.env
|
|
||||||
|
|
|
@ -1,7 +0,0 @@
|
||||||
#!/bin/sh
|
|
||||||
|
|
||||||
# Start fail2ban
|
|
||||||
[ $XUI_ENABLE_FAIL2BAN == "true" ] && fail2ban-client -x start
|
|
||||||
|
|
||||||
# Run x-ui
|
|
||||||
exec /app/x-ui
|
|
|
@ -1,40 +0,0 @@
|
||||||
#!/bin/sh
|
|
||||||
case $1 in
|
|
||||||
amd64)
|
|
||||||
ARCH="64"
|
|
||||||
FNAME="amd64"
|
|
||||||
;;
|
|
||||||
i386)
|
|
||||||
ARCH="32"
|
|
||||||
FNAME="i386"
|
|
||||||
;;
|
|
||||||
armv8 | arm64 | aarch64)
|
|
||||||
ARCH="arm64-v8a"
|
|
||||||
FNAME="arm64"
|
|
||||||
;;
|
|
||||||
armv7 | arm | arm32)
|
|
||||||
ARCH="arm32-v7a"
|
|
||||||
FNAME="arm32"
|
|
||||||
;;
|
|
||||||
armv6)
|
|
||||||
ARCH="arm32-v6"
|
|
||||||
FNAME="armv6"
|
|
||||||
;;
|
|
||||||
*)
|
|
||||||
ARCH="64"
|
|
||||||
FNAME="amd64"
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
mkdir -p build/bin
|
|
||||||
cd build/bin
|
|
||||||
wget -q "https://github.com/XTLS/Xray-core/releases/download/v25.8.3/Xray-linux-${ARCH}.zip"
|
|
||||||
unzip "Xray-linux-${ARCH}.zip"
|
|
||||||
rm -f "Xray-linux-${ARCH}.zip" geoip.dat geosite.dat
|
|
||||||
mv xray "xray-linux-${FNAME}"
|
|
||||||
wget -q https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geoip.dat
|
|
||||||
wget -q https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geosite.dat
|
|
||||||
wget -q -O geoip_IR.dat https://github.com/chocolate4u/Iran-v2ray-rules/releases/latest/download/geoip.dat
|
|
||||||
wget -q -O geosite_IR.dat https://github.com/chocolate4u/Iran-v2ray-rules/releases/latest/download/geosite.dat
|
|
||||||
wget -q -O geoip_RU.dat https://github.com/runetfreedom/russia-v2ray-rules-dat/releases/latest/download/geoip.dat
|
|
||||||
wget -q -O geosite_RU.dat https://github.com/runetfreedom/russia-v2ray-rules-dat/releases/latest/download/geosite.dat
|
|
||||||
cd ../../
|
|
54
Dockerfile
|
@ -1,54 +0,0 @@
|
||||||
# ========================================================
|
|
||||||
# Stage: Builder
|
|
||||||
# ========================================================
|
|
||||||
FROM golang:1.25-alpine AS builder
|
|
||||||
WORKDIR /app
|
|
||||||
ARG TARGETARCH
|
|
||||||
|
|
||||||
RUN apk --no-cache --update add \
|
|
||||||
build-base \
|
|
||||||
gcc \
|
|
||||||
wget \
|
|
||||||
unzip
|
|
||||||
|
|
||||||
COPY . .
|
|
||||||
|
|
||||||
ENV CGO_ENABLED=1
|
|
||||||
ENV CGO_CFLAGS="-D_LARGEFILE64_SOURCE"
|
|
||||||
RUN go build -ldflags "-w -s" -o build/x-ui main.go
|
|
||||||
RUN ./DockerInit.sh "$TARGETARCH"
|
|
||||||
|
|
||||||
# ========================================================
|
|
||||||
# Stage: Final Image of 3x-ui
|
|
||||||
# ========================================================
|
|
||||||
FROM alpine
|
|
||||||
ENV TZ=Asia/Tehran
|
|
||||||
WORKDIR /app
|
|
||||||
|
|
||||||
RUN apk add --no-cache --update \
|
|
||||||
ca-certificates \
|
|
||||||
tzdata \
|
|
||||||
fail2ban \
|
|
||||||
bash
|
|
||||||
|
|
||||||
COPY --from=builder /app/build/ /app/
|
|
||||||
COPY --from=builder /app/DockerEntrypoint.sh /app/
|
|
||||||
COPY --from=builder /app/x-ui.sh /usr/bin/x-ui
|
|
||||||
|
|
||||||
|
|
||||||
# Configure fail2ban
|
|
||||||
RUN rm -f /etc/fail2ban/jail.d/alpine-ssh.conf \
|
|
||||||
&& cp /etc/fail2ban/jail.conf /etc/fail2ban/jail.local \
|
|
||||||
&& sed -i "s/^\[ssh\]$/&\nenabled = false/" /etc/fail2ban/jail.local \
|
|
||||||
&& sed -i "s/^\[sshd\]$/&\nenabled = false/" /etc/fail2ban/jail.local \
|
|
||||||
&& sed -i "s/#allowipv6 = auto/allowipv6 = auto/g" /etc/fail2ban/fail2ban.conf
|
|
||||||
|
|
||||||
RUN chmod +x \
|
|
||||||
/app/DockerEntrypoint.sh \
|
|
||||||
/app/x-ui \
|
|
||||||
/usr/bin/x-ui
|
|
||||||
|
|
||||||
ENV XUI_ENABLE_FAIL2BAN="true"
|
|
||||||
VOLUME [ "/etc/x-ui" ]
|
|
||||||
CMD [ "./x-ui" ]
|
|
||||||
ENTRYPOINT [ "/app/DockerEntrypoint.sh" ]
|
|
|
@ -1,56 +0,0 @@
|
||||||
[English](/README.md) | [فارسی](/README.fa_IR.md) | [العربية](/README.ar_EG.md) | [中文](/README.zh_CN.md) | [Español](/README.es_ES.md) | [Русский](/README.ru_RU.md)
|
|
||||||
|
|
||||||
<p align="center">
|
|
||||||
<picture>
|
|
||||||
<source media="(prefers-color-scheme: dark)" srcset="./media/3x-ui-dark.png">
|
|
||||||
<img alt="3x-ui" src="./media/3x-ui-light.png">
|
|
||||||
</picture>
|
|
||||||
</p>
|
|
||||||
|
|
||||||
[](https://github.com/MHSanaei/3x-ui/releases)
|
|
||||||
[](https://github.com/MHSanaei/3x-ui/actions)
|
|
||||||
[](#)
|
|
||||||
[](https://github.com/MHSanaei/3x-ui/releases/latest)
|
|
||||||
[](https://www.gnu.org/licenses/gpl-3.0.en.html)
|
|
||||||
|
|
||||||
**3X-UI** — لوحة تحكم متقدمة مفتوحة المصدر تعتمد على الويب مصممة لإدارة خادم Xray-core. توفر واجهة سهلة الاستخدام لتكوين ومراقبة بروتوكولات VPN والوكيل المختلفة.
|
|
||||||
|
|
||||||
> [!IMPORTANT]
|
|
||||||
> هذا المشروع مخصص للاستخدام الشخصي والاتصال فقط، يرجى عدم استخدامه لأغراض غير قانونية، يرجى عدم استخدامه في بيئة الإنتاج.
|
|
||||||
|
|
||||||
كمشروع محسن من مشروع X-UI الأصلي، يوفر 3X-UI استقرارًا محسنًا ودعمًا أوسع للبروتوكولات وميزات إضافية.
|
|
||||||
|
|
||||||
## البدء السريع
|
|
||||||
|
|
||||||
```
|
|
||||||
bash <(curl -Ls https://raw.githubusercontent.com/mhsanaei/3x-ui/master/install.sh)
|
|
||||||
```
|
|
||||||
|
|
||||||
للحصول على الوثائق الكاملة، يرجى زيارة [ويكي المشروع](https://github.com/MHSanaei/3x-ui/wiki).
|
|
||||||
|
|
||||||
## شكر خاص إلى
|
|
||||||
|
|
||||||
- [alireza0](https://github.com/alireza0/)
|
|
||||||
|
|
||||||
## الاعتراف
|
|
||||||
|
|
||||||
- [Iran v2ray rules](https://github.com/chocolate4u/Iran-v2ray-rules) (الترخيص: **GPL-3.0**): _قواعد توجيه v2ray/xray و v2ray/xray-clients المحسنة مع النطاقات الإيرانية المدمجة وتركيز على الأمان وحظر الإعلانات._
|
|
||||||
- [Russia v2ray rules](https://github.com/runetfreedom/russia-v2ray-rules-dat) (الترخيص: **GPL-3.0**): _يحتوي هذا المستودع على قواعد توجيه V2Ray محدثة تلقائيًا بناءً على بيانات النطاقات والعناوين المحظورة في روسيا._
|
|
||||||
|
|
||||||
## دعم المشروع
|
|
||||||
|
|
||||||
**إذا كان هذا المشروع مفيدًا لك، فقد ترغب في إعطائه**:star2:
|
|
||||||
|
|
||||||
<p align="left">
|
|
||||||
<a href="https://buymeacoffee.com/mhsanaei" target="_blank">
|
|
||||||
<img src="./media/buymeacoffe.png" alt="Image">
|
|
||||||
</a>
|
|
||||||
</p>
|
|
||||||
|
|
||||||
- USDT (TRC20): `TXncxkvhkDWGts487Pjqq1qT9JmwRUz8CC`
|
|
||||||
- POL (polygon): `0x41C9548675D044c6Bfb425786C765bc37427256A`
|
|
||||||
- LTC (Litecoin): `ltc1q2ach7x6d2zq0n4l0t4zl7d7xe2s6fs7a3vspwv`
|
|
||||||
|
|
||||||
## النجوم عبر الزمن
|
|
||||||
|
|
||||||
[](https://starchart.cc/MHSanaei/3x-ui)
|
|
|
@ -1,56 +0,0 @@
|
||||||
[English](/README.md) | [فارسی](/README.fa_IR.md) | [العربية](/README.ar_EG.md) | [中文](/README.zh_CN.md) | [Español](/README.es_ES.md) | [Русский](/README.ru_RU.md)
|
|
||||||
|
|
||||||
<p align="center">
|
|
||||||
<picture>
|
|
||||||
<source media="(prefers-color-scheme: dark)" srcset="./media/3x-ui-dark.png">
|
|
||||||
<img alt="3x-ui" src="./media/3x-ui-light.png">
|
|
||||||
</picture>
|
|
||||||
</p>
|
|
||||||
|
|
||||||
[](https://github.com/MHSanaei/3x-ui/releases)
|
|
||||||
[](https://github.com/MHSanaei/3x-ui/actions)
|
|
||||||
[](#)
|
|
||||||
[](https://github.com/MHSanaei/3x-ui/releases/latest)
|
|
||||||
[](https://www.gnu.org/licenses/gpl-3.0.en.html)
|
|
||||||
|
|
||||||
**3X-UI** — panel de control avanzado basado en web de código abierto diseñado para gestionar el servidor Xray-core. Ofrece una interfaz fácil de usar para configurar y monitorear varios protocolos VPN y proxy.
|
|
||||||
|
|
||||||
> [!IMPORTANT]
|
|
||||||
> Este proyecto es solo para uso personal y comunicación, por favor no lo use para fines ilegales, por favor no lo use en un entorno de producción.
|
|
||||||
|
|
||||||
Como una versión mejorada del proyecto X-UI original, 3X-UI proporciona mayor estabilidad, soporte más amplio de protocolos y características adicionales.
|
|
||||||
|
|
||||||
## Inicio Rápido
|
|
||||||
|
|
||||||
```
|
|
||||||
bash <(curl -Ls https://raw.githubusercontent.com/mhsanaei/3x-ui/master/install.sh)
|
|
||||||
```
|
|
||||||
|
|
||||||
Para documentación completa, visita la [Wiki del proyecto](https://github.com/MHSanaei/3x-ui/wiki).
|
|
||||||
|
|
||||||
## Un Agradecimiento Especial a
|
|
||||||
|
|
||||||
- [alireza0](https://github.com/alireza0/)
|
|
||||||
|
|
||||||
## Reconocimientos
|
|
||||||
|
|
||||||
- [Iran v2ray rules](https://github.com/chocolate4u/Iran-v2ray-rules) (Licencia: **GPL-3.0**): _Reglas de enrutamiento mejoradas para v2ray/xray y v2ray/xray-clients con dominios iraníes incorporados y un enfoque en seguridad y bloqueo de anuncios._
|
|
||||||
- [Russia v2ray rules](https://github.com/runetfreedom/russia-v2ray-rules-dat) (Licencia: **GPL-3.0**): _Este repositorio contiene reglas de enrutamiento V2Ray actualizadas automáticamente basadas en datos de dominios y direcciones bloqueadas en Rusia._
|
|
||||||
|
|
||||||
## Apoyar el Proyecto
|
|
||||||
|
|
||||||
**Si este proyecto te es útil, puedes darle una**:star2:
|
|
||||||
|
|
||||||
<p align="left">
|
|
||||||
<a href="https://buymeacoffee.com/mhsanaei" target="_blank">
|
|
||||||
<img src="./media/buymeacoffe.png" alt="Image">
|
|
||||||
</a>
|
|
||||||
</p>
|
|
||||||
|
|
||||||
- USDT (TRC20): `TXncxkvhkDWGts487Pjqq1qT9JmwRUz8CC`
|
|
||||||
- POL (polygon): `0x41C9548675D044c6Bfb425786C765bc37427256A`
|
|
||||||
- LTC (Litecoin): `ltc1q2ach7x6d2zq0n4l0t4zl7d7xe2s6fs7a3vspwv`
|
|
||||||
|
|
||||||
## Estrellas a lo Largo del Tiempo
|
|
||||||
|
|
||||||
[](https://starchart.cc/MHSanaei/3x-ui)
|
|
|
@ -1,56 +0,0 @@
|
||||||
[English](/README.md) | [فارسی](/README.fa_IR.md) | [العربية](/README.ar_EG.md) | [中文](/README.zh_CN.md) | [Español](/README.es_ES.md) | [Русский](/README.ru_RU.md)
|
|
||||||
|
|
||||||
<p align="center">
|
|
||||||
<picture>
|
|
||||||
<source media="(prefers-color-scheme: dark)" srcset="./media/3x-ui-dark.png">
|
|
||||||
<img alt="3x-ui" src="./media/3x-ui-light.png">
|
|
||||||
</picture>
|
|
||||||
</p>
|
|
||||||
|
|
||||||
[](https://github.com/MHSanaei/3x-ui/releases)
|
|
||||||
[](https://github.com/MHSanaei/3x-ui/actions)
|
|
||||||
[](#)
|
|
||||||
[](https://github.com/MHSanaei/3x-ui/releases/latest)
|
|
||||||
[](https://www.gnu.org/licenses/gpl-3.0.en.html)
|
|
||||||
|
|
||||||
**3X-UI** — یک پنل کنترل پیشرفته مبتنی بر وب با کد باز که برای مدیریت سرور Xray-core طراحی شده است. این پنل یک رابط کاربری آسان برای پیکربندی و نظارت بر پروتکلهای مختلف VPN و پراکسی ارائه میدهد.
|
|
||||||
|
|
||||||
> [!IMPORTANT]
|
|
||||||
> این پروژه فقط برای استفاده شخصی و ارتباطات است، لطفاً از آن برای اهداف غیرقانونی استفاده نکنید، لطفاً از آن در محیط تولید استفاده نکنید.
|
|
||||||
|
|
||||||
به عنوان یک نسخه بهبود یافته از پروژه اصلی X-UI، 3X-UI پایداری بهتر، پشتیبانی گستردهتر از پروتکلها و ویژگیهای اضافی را ارائه میدهد.
|
|
||||||
|
|
||||||
## شروع سریع
|
|
||||||
|
|
||||||
```
|
|
||||||
bash <(curl -Ls https://raw.githubusercontent.com/mhsanaei/3x-ui/master/install.sh)
|
|
||||||
```
|
|
||||||
|
|
||||||
برای مستندات کامل، لطفاً به [ویکی پروژه](https://github.com/MHSanaei/3x-ui/wiki) مراجعه کنید.
|
|
||||||
|
|
||||||
## تشکر ویژه از
|
|
||||||
|
|
||||||
- [alireza0](https://github.com/alireza0/)
|
|
||||||
|
|
||||||
## قدردانی
|
|
||||||
|
|
||||||
- [Iran v2ray rules](https://github.com/chocolate4u/Iran-v2ray-rules) (مجوز: **GPL-3.0**): _قوانین مسیریابی بهبود یافته v2ray/xray و v2ray/xray-clients با دامنههای ایرانی داخلی و تمرکز بر امنیت و مسدود کردن تبلیغات._
|
|
||||||
- [Russia v2ray rules](https://github.com/runetfreedom/russia-v2ray-rules-dat) (مجوز: **GPL-3.0**): _این مخزن شامل قوانین مسیریابی V2Ray بهروزرسانی شده خودکار بر اساس دادههای دامنهها و آدرسهای مسدود شده در روسیه است._
|
|
||||||
|
|
||||||
## پشتیبانی از پروژه
|
|
||||||
|
|
||||||
**اگر این پروژه برای شما مفید است، میتوانید به آن یک**:star2: بدهید
|
|
||||||
|
|
||||||
<p align="left">
|
|
||||||
<a href="https://buymeacoffee.com/mhsanaei" target="_blank">
|
|
||||||
<img src="./media/buymeacoffe.png" alt="Image">
|
|
||||||
</a>
|
|
||||||
</p>
|
|
||||||
|
|
||||||
- USDT (TRC20): `TXncxkvhkDWGts487Pjqq1qT9JmwRUz8CC`
|
|
||||||
- POL (polygon): `0x41C9548675D044c6Bfb425786C765bc37427256A`
|
|
||||||
- LTC (Litecoin): `ltc1q2ach7x6d2zq0n4l0t4zl7d7xe2s6fs7a3vspwv`
|
|
||||||
|
|
||||||
## ستارهها در طول زمان
|
|
||||||
|
|
||||||
[](https://starchart.cc/MHSanaei/3x-ui)
|
|
141
README.md
|
@ -1,56 +1,117 @@
|
||||||
[English](/README.md) | [فارسی](/README.fa_IR.md) | [العربية](/README.ar_EG.md) | [中文](/README.zh_CN.md) | [Español](/README.es_ES.md) | [Русский](/README.ru_RU.md)
|
# 3x-ui
|
||||||
|

|
||||||
|

|
||||||
|

|
||||||
|

|
||||||
|
[](https://www.gnu.org/licenses/gpl-3.0.en.html)
|
||||||
|
|
||||||
<p align="center">
|
|
||||||
<picture>
|
|
||||||
<source media="(prefers-color-scheme: dark)" srcset="./media/3x-ui-dark.png">
|
|
||||||
<img alt="3x-ui" src="./media/3x-ui-light.png">
|
|
||||||
</picture>
|
|
||||||
</p>
|
|
||||||
|
|
||||||
[](https://github.com/MHSanaei/3x-ui/releases)
|
> **Disclaimer: This project is only for personal learning and communication, please do not use it for illegal purposes, please do not use it in a production environment**
|
||||||
[](https://github.com/MHSanaei/3x-ui/actions)
|
|
||||||
[](#)
|
|
||||||
[](https://github.com/MHSanaei/3x-ui/releases/latest)
|
|
||||||
[](https://www.gnu.org/licenses/gpl-3.0.en.html)
|
|
||||||
|
|
||||||
**3X-UI** — advanced, open-source web-based control panel designed for managing Xray-core server. It offers a user-friendly interface for configuring and monitoring various VPN and proxy protocols.
|
xray panel supporting multi-protocol, **Multi-lang (English,Farsi,Chinese)**
|
||||||
|
|
||||||
> [!IMPORTANT]
|
# Install & Upgrade
|
||||||
> This project is only for personal using, please do not use it for illegal purposes, please do not use it in a production environment.
|
|
||||||
|
|
||||||
As an enhanced fork of the original X-UI project, 3X-UI provides improved stability, broader protocol support, and additional features.
|
```
|
||||||
|
|
||||||
## Quick Start
|
|
||||||
|
|
||||||
```bash
|
|
||||||
bash <(curl -Ls https://raw.githubusercontent.com/mhsanaei/3x-ui/master/install.sh)
|
bash <(curl -Ls https://raw.githubusercontent.com/mhsanaei/3x-ui/master/install.sh)
|
||||||
```
|
```
|
||||||
|
## Install custom version
|
||||||
|
To install your desired version you can add the version to the end of install command. Example for ver `v1.0.9`:
|
||||||
|
```
|
||||||
|
bash <(curl -Ls https://raw.githubusercontent.com/mhsanaei/3x-ui/master/install.sh) v1.0.9
|
||||||
|
```
|
||||||
|
# SSL
|
||||||
|
```
|
||||||
|
apt-get install certbot -y
|
||||||
|
certbot certonly --standalone --agree-tos --register-unsafely-without-email -d yourdomain.com
|
||||||
|
certbot renew --dry-run
|
||||||
|
```
|
||||||
|
|
||||||
For full documentation, please visit the [project Wiki](https://github.com/MHSanaei/3x-ui/wiki).
|
**If you think this project is helpful to you, you may wish to give a** :star2:
|
||||||
|
|
||||||
## A Special Thanks to
|
# Default settings
|
||||||
|
|
||||||
|
- Port: 2053
|
||||||
|
- username and password will be generated randomly if you skip to modify your own security(x-ui "7")
|
||||||
|
- database path: /etc/x-ui/x-ui.db
|
||||||
|
|
||||||
|
before you set ssl on settings
|
||||||
|
- http:// ip or domain:2053/xui
|
||||||
|
|
||||||
|
After you set ssl on settings
|
||||||
|
- https://yourdomain:2053/xui
|
||||||
|
|
||||||
|
# Enable Traffic For Users:
|
||||||
|
|
||||||
|
**copy and paste to xray Configuration :** (you don't need to do this if you have a fresh install)
|
||||||
|
- [for enable traffic](https://raw.githubusercontent.com/mhsanaei/3x-ui/main/media/for%20enable%20traffic.txt)
|
||||||
|
- [for enable traffic+block all iran ip address](https://raw.githubusercontent.com/mhsanaei/3x-ui/main/media/for%20enable%20traffic%2Bblock%20all%20iran%20ip.txt)
|
||||||
|
|
||||||
|
# Features
|
||||||
|
|
||||||
|
- System Status Monitoring
|
||||||
|
- Search within all inbounds and clients
|
||||||
|
- Support Dark/Light theme UI
|
||||||
|
- Support multi-user multi-protocol, web page visualization operation
|
||||||
|
- Supported protocols: vmess, vless, trojan, shadowsocks, dokodemo-door, socks, http
|
||||||
|
- Support for configuring more transport configurations
|
||||||
|
- Traffic statistics, limit traffic, limit expiration time
|
||||||
|
- Customizable xray configuration templates
|
||||||
|
- Support https access panel (self-provided domain name + ssl certificate)
|
||||||
|
- Support one-click SSL certificate application and automatic renewal
|
||||||
|
- For more advanced configuration items, please refer to the panel
|
||||||
|
|
||||||
|
# Tg robot use
|
||||||
|
|
||||||
|
X-UI supports daily traffic notification, panel login reminder and other functions through the Tg robot. To use the Tg robot, you need to apply for the specific application tutorial. You can refer to the [blog](https://coderfan.net/how-to-use-telegram-bot-to-alarm-you-when-someone-login-into-your-vps.html)
|
||||||
|
Set the robot-related parameters in the panel background, including:
|
||||||
|
|
||||||
|
- Tg robot Token
|
||||||
|
- Tg robot ChatId
|
||||||
|
- Tg robot cycle runtime, in crontab syntax
|
||||||
|
- Tg robot Expiration threshold
|
||||||
|
- Tg robot Traffic threshold
|
||||||
|
- Tg robot Enable send backup in cycle runtime
|
||||||
|
- Tg robot Enable CPU usage alarm threshold
|
||||||
|
|
||||||
|
Reference syntax:
|
||||||
|
|
||||||
|
- @hourly // hourly notification
|
||||||
|
- @daily // Daily notification (00:00 in the morning)
|
||||||
|
- @every 8h // notify every 8 hours
|
||||||
|
|
||||||
|
# Telegram Bot Features
|
||||||
|
|
||||||
|
- Report periodic
|
||||||
|
- Login notification
|
||||||
|
- CPU threshold notification
|
||||||
|
- Threshold for Expiration time and Traffic to report in advance
|
||||||
|
- Support client report if client's telegram username is added to the end of `email` like 'test123@telegram_username'
|
||||||
|
- Support telegram traffic report searched with UID (VMESS/VLESS) or Password (TROJAN) - anonymously
|
||||||
|
- Menu based bot
|
||||||
|
- Search client by email ( only admin )
|
||||||
|
- Check all inbounds
|
||||||
|
- Check server status
|
||||||
|
- Check Exhausted users
|
||||||
|
- Receive backup by request and in periodic reports
|
||||||
|
|
||||||
|
# A Special Thanks To
|
||||||
- [alireza0](https://github.com/alireza0/)
|
- [alireza0](https://github.com/alireza0/)
|
||||||
|
- [HexaSoftwareTech](https://github.com/HexaSoftwareTech/)
|
||||||
|
|
||||||
## Acknowledgment
|
# Suggestion System
|
||||||
|
- Ubuntu 20.04+
|
||||||
|
- Debian 10+
|
||||||
|
- CentOS 8+
|
||||||
|
- Fedora 36+
|
||||||
|
|
||||||
- [Iran v2ray rules](https://github.com/chocolate4u/Iran-v2ray-rules) (License: **GPL-3.0**): _Enhanced v2ray/xray and v2ray/xray-clients routing rules with built-in Iranian domains and a focus on security and adblocking._
|
# Pictures
|
||||||
- [Russia v2ray rules](https://github.com/runetfreedom/russia-v2ray-rules-dat) (License: **GPL-3.0**): _This repository contains automatically updated V2Ray routing rules based on data on blocked domains and addresses in Russia._
|
|
||||||
|
|
||||||
## Support project
|

|
||||||
|

|
||||||
|

|
||||||
|

|
||||||
|
|
||||||
**If this project is helpful to you, you may wish to give it a**:star2:
|
## Stargazers over time
|
||||||
|
|
||||||
<p align="left">
|
[](https://starchart.cc/MHSanaei/3x-ui)
|
||||||
<a href="https://buymeacoffee.com/mhsanaei" target="_blank">
|
|
||||||
<img src="./media/buymeacoffe.png" alt="Image">
|
|
||||||
</a>
|
|
||||||
</p>
|
|
||||||
|
|
||||||
- USDT (TRC20): `TXncxkvhkDWGts487Pjqq1qT9JmwRUz8CC`
|
|
||||||
- POL (polygon): `0x41C9548675D044c6Bfb425786C765bc37427256A`
|
|
||||||
- LTC (Litecoin): `ltc1q2ach7x6d2zq0n4l0t4zl7d7xe2s6fs7a3vspwv`
|
|
||||||
|
|
||||||
## Stargazers over Time
|
|
||||||
|
|
||||||
[](https://starchart.cc/MHSanaei/3x-ui)
|
|
||||||
|
|
|
@ -1,56 +0,0 @@
|
||||||
[English](/README.md) | [فارسی](/README.fa_IR.md) | [العربية](/README.ar_EG.md) | [中文](/README.zh_CN.md) | [Español](/README.es_ES.md) | [Русский](/README.ru_RU.md)
|
|
||||||
|
|
||||||
<p align="center">
|
|
||||||
<picture>
|
|
||||||
<source media="(prefers-color-scheme: dark)" srcset="./media/3x-ui-dark.png">
|
|
||||||
<img alt="3x-ui" src="./media/3x-ui-light.png">
|
|
||||||
</picture>
|
|
||||||
</p>
|
|
||||||
|
|
||||||
[](https://github.com/MHSanaei/3x-ui/releases)
|
|
||||||
[](https://github.com/MHSanaei/3x-ui/actions)
|
|
||||||
[](#)
|
|
||||||
[](https://github.com/MHSanaei/3x-ui/releases/latest)
|
|
||||||
[](https://www.gnu.org/licenses/gpl-3.0.en.html)
|
|
||||||
|
|
||||||
**3X-UI** — продвинутая панель управления с открытым исходным кодом на основе веб-интерфейса, разработанная для управления сервером Xray-core. Предоставляет удобный интерфейс для настройки и мониторинга различных VPN и прокси-протоколов.
|
|
||||||
|
|
||||||
> [!IMPORTANT]
|
|
||||||
> Этот проект предназначен только для личного использования, пожалуйста, не используйте его в незаконных целях и в производственной среде.
|
|
||||||
|
|
||||||
Как улучшенная версия оригинального проекта X-UI, 3X-UI обеспечивает повышенную стабильность, более широкую поддержку протоколов и дополнительные функции.
|
|
||||||
|
|
||||||
## Быстрый старт
|
|
||||||
|
|
||||||
```
|
|
||||||
bash <(curl -Ls https://raw.githubusercontent.com/mhsanaei/3x-ui/master/install.sh)
|
|
||||||
```
|
|
||||||
|
|
||||||
Полную документацию смотрите в [вики проекта](https://github.com/MHSanaei/3x-ui/wiki).
|
|
||||||
|
|
||||||
## Особая благодарность
|
|
||||||
|
|
||||||
- [alireza0](https://github.com/alireza0/)
|
|
||||||
|
|
||||||
## Благодарности
|
|
||||||
|
|
||||||
- [Iran v2ray rules](https://github.com/chocolate4u/Iran-v2ray-rules) (Лицензия: **GPL-3.0**): _Улучшенные правила маршрутизации для v2ray/xray и v2ray/xray-clients со встроенными иранскими доменами и фокусом на безопасность и блокировку рекламы._
|
|
||||||
- [Russia v2ray rules](https://github.com/runetfreedom/russia-v2ray-rules-dat) (Лицензия: **GPL-3.0**): _Этот репозиторий содержит автоматически обновляемые правила маршрутизации V2Ray на основе данных о заблокированных доменах и адресах в России._
|
|
||||||
|
|
||||||
## Поддержка проекта
|
|
||||||
|
|
||||||
**Если этот проект полезен для вас, вы можете поставить ему**:star2:
|
|
||||||
|
|
||||||
<p align="left">
|
|
||||||
<a href="https://buymeacoffee.com/mhsanaei" target="_blank">
|
|
||||||
<img src="./media/buymeacoffe.png" alt="Image">
|
|
||||||
</a>
|
|
||||||
</p>
|
|
||||||
|
|
||||||
- USDT (TRC20): `TXncxkvhkDWGts487Pjqq1qT9JmwRUz8CC`
|
|
||||||
- POL (polygon): `0x41C9548675D044c6Bfb425786C765bc37427256A`
|
|
||||||
- LTC (Litecoin): `ltc1q2ach7x6d2zq0n4l0t4zl7d7xe2s6fs7a3vspwv`
|
|
||||||
|
|
||||||
## Звезды с течением времени
|
|
||||||
|
|
||||||
[](https://starchart.cc/MHSanaei/3x-ui)
|
|
|
@ -1,56 +0,0 @@
|
||||||
[English](/README.md) | [فارسی](/README.fa_IR.md) | [العربية](/README.ar_EG.md) | [中文](/README.zh_CN.md) | [Español](/README.es_ES.md) | [Русский](/README.ru_RU.md)
|
|
||||||
|
|
||||||
<p align="center">
|
|
||||||
<picture>
|
|
||||||
<source media="(prefers-color-scheme: dark)" srcset="./media/3x-ui-dark.png">
|
|
||||||
<img alt="3x-ui" src="./media/3x-ui-light.png">
|
|
||||||
</picture>
|
|
||||||
</p>
|
|
||||||
|
|
||||||
[](https://github.com/MHSanaei/3x-ui/releases)
|
|
||||||
[](https://github.com/MHSanaei/3x-ui/actions)
|
|
||||||
[](#)
|
|
||||||
[](https://github.com/MHSanaei/3x-ui/releases/latest)
|
|
||||||
[](https://www.gnu.org/licenses/gpl-3.0.en.html)
|
|
||||||
|
|
||||||
**3X-UI** — 一个基于网页的高级开源控制面板,专为管理 Xray-core 服务器而设计。它提供了用户友好的界面,用于配置和监控各种 VPN 和代理协议。
|
|
||||||
|
|
||||||
> [!IMPORTANT]
|
|
||||||
> 本项目仅用于个人使用和通信,请勿将其用于非法目的,请勿在生产环境中使用。
|
|
||||||
|
|
||||||
作为原始 X-UI 项目的增强版本,3X-UI 提供了更好的稳定性、更广泛的协议支持和额外的功能。
|
|
||||||
|
|
||||||
## 快速开始
|
|
||||||
|
|
||||||
```
|
|
||||||
bash <(curl -Ls https://raw.githubusercontent.com/mhsanaei/3x-ui/master/install.sh)
|
|
||||||
```
|
|
||||||
|
|
||||||
完整文档请参阅 [项目Wiki](https://github.com/MHSanaei/3x-ui/wiki)。
|
|
||||||
|
|
||||||
## 特别感谢
|
|
||||||
|
|
||||||
- [alireza0](https://github.com/alireza0/)
|
|
||||||
|
|
||||||
## 致谢
|
|
||||||
|
|
||||||
- [Iran v2ray rules](https://github.com/chocolate4u/Iran-v2ray-rules) (许可证: **GPL-3.0**): _增强的 v2ray/xray 和 v2ray/xray-clients 路由规则,内置伊朗域名,专注于安全性和广告拦截。_
|
|
||||||
- [Russia v2ray rules](https://github.com/runetfreedom/russia-v2ray-rules-dat) (许可证: **GPL-3.0**): _此仓库包含基于俄罗斯被阻止域名和地址数据自动更新的 V2Ray 路由规则。_
|
|
||||||
|
|
||||||
## 支持项目
|
|
||||||
|
|
||||||
**如果这个项目对您有帮助,您可以给它一个**:star2:
|
|
||||||
|
|
||||||
<p align="left">
|
|
||||||
<a href="https://buymeacoffee.com/mhsanaei" target="_blank">
|
|
||||||
<img src="./media/buymeacoffe.png" alt="Image">
|
|
||||||
</a>
|
|
||||||
</p>
|
|
||||||
|
|
||||||
- USDT (TRC20): `TXncxkvhkDWGts487Pjqq1qT9JmwRUz8CC`
|
|
||||||
- POL (polygon): `0x41C9548675D044c6Bfb425786C765bc37427256A`
|
|
||||||
- LTC (Litecoin): `ltc1q2ach7x6d2zq0n4l0t4zl7d7xe2s6fs7a3vspwv`
|
|
||||||
|
|
||||||
## 随时间变化的星标数
|
|
||||||
|
|
||||||
[](https://starchart.cc/MHSanaei/3x-ui)
|
|
104
config/config.go
|
@ -3,10 +3,7 @@ package config
|
||||||
import (
|
import (
|
||||||
_ "embed"
|
_ "embed"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
|
||||||
"runtime"
|
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -19,11 +16,10 @@ var name string
|
||||||
type LogLevel string
|
type LogLevel string
|
||||||
|
|
||||||
const (
|
const (
|
||||||
Debug LogLevel = "debug"
|
Debug LogLevel = "debug"
|
||||||
Info LogLevel = "info"
|
Info LogLevel = "info"
|
||||||
Notice LogLevel = "notice"
|
Warn LogLevel = "warn"
|
||||||
Warn LogLevel = "warn"
|
Error LogLevel = "error"
|
||||||
Error LogLevel = "error"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func GetVersion() string {
|
func GetVersion() string {
|
||||||
|
@ -49,96 +45,6 @@ func IsDebug() bool {
|
||||||
return os.Getenv("XUI_DEBUG") == "true"
|
return os.Getenv("XUI_DEBUG") == "true"
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetBinFolderPath() string {
|
|
||||||
binFolderPath := os.Getenv("XUI_BIN_FOLDER")
|
|
||||||
if binFolderPath == "" {
|
|
||||||
binFolderPath = "bin"
|
|
||||||
}
|
|
||||||
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 != "" {
|
|
||||||
return dbFolderPath
|
|
||||||
}
|
|
||||||
if runtime.GOOS == "windows" {
|
|
||||||
return getBaseDir()
|
|
||||||
}
|
|
||||||
return "/etc/x-ui"
|
|
||||||
}
|
|
||||||
|
|
||||||
func GetDBPath() string {
|
func GetDBPath() string {
|
||||||
return fmt.Sprintf("%s/%s.db", GetDBFolderPath(), GetName())
|
return fmt.Sprintf("/etc/%s/%s.db", GetName(), GetName())
|
||||||
}
|
|
||||||
|
|
||||||
func GetLogFolder() string {
|
|
||||||
logFolderPath := os.Getenv("XUI_LOG_FOLDER")
|
|
||||||
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
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
2.6.6
|
1.1.3
|
||||||
|
|
154
database/db.go
|
@ -1,17 +1,11 @@
|
||||||
package database
|
package database
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
|
||||||
"io"
|
|
||||||
"io/fs"
|
"io/fs"
|
||||||
"log"
|
|
||||||
"os"
|
"os"
|
||||||
"path"
|
"path"
|
||||||
"slices"
|
|
||||||
|
|
||||||
"x-ui/config"
|
"x-ui/config"
|
||||||
"x-ui/database/model"
|
"x-ui/database/model"
|
||||||
"x-ui/util/crypto"
|
|
||||||
"x-ui/xray"
|
"x-ui/xray"
|
||||||
|
|
||||||
"gorm.io/driver/sqlite"
|
"gorm.io/driver/sqlite"
|
||||||
|
@ -21,101 +15,43 @@ import (
|
||||||
|
|
||||||
var db *gorm.DB
|
var db *gorm.DB
|
||||||
|
|
||||||
const (
|
|
||||||
defaultUsername = "admin"
|
|
||||||
defaultPassword = "admin"
|
|
||||||
)
|
|
||||||
|
|
||||||
func initModels() error {
|
|
||||||
models := []any{
|
|
||||||
&model.User{},
|
|
||||||
&model.Inbound{},
|
|
||||||
&model.OutboundTraffics{},
|
|
||||||
&model.Setting{},
|
|
||||||
&model.InboundClientIps{},
|
|
||||||
&xray.ClientTraffic{},
|
|
||||||
&model.HistoryOfSeeders{},
|
|
||||||
}
|
|
||||||
for _, model := range models {
|
|
||||||
if err := db.AutoMigrate(model); err != nil {
|
|
||||||
log.Printf("Error auto migrating model: %v", err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func initUser() error {
|
func initUser() error {
|
||||||
empty, err := isTableEmpty("users")
|
err := db.AutoMigrate(&model.User{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("Error checking if users table is empty: %v", err)
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if empty {
|
var count int64
|
||||||
hashedPassword, err := crypto.HashPasswordAsBcrypt(defaultPassword)
|
err = db.Model(&model.User{}).Count(&count).Error
|
||||||
|
if err != nil {
|
||||||
if err != nil {
|
return err
|
||||||
log.Printf("Error hashing default password: %v", err)
|
}
|
||||||
return err
|
if count == 0 {
|
||||||
}
|
|
||||||
|
|
||||||
user := &model.User{
|
user := &model.User{
|
||||||
Username: defaultUsername,
|
Username: "admin",
|
||||||
Password: hashedPassword,
|
Password: "admin",
|
||||||
}
|
}
|
||||||
return db.Create(user).Error
|
return db.Create(user).Error
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func runSeeders(isUsersEmpty bool) error {
|
func initInbound() error {
|
||||||
empty, err := isTableEmpty("history_of_seeders")
|
return db.AutoMigrate(&model.Inbound{})
|
||||||
if err != nil {
|
|
||||||
log.Printf("Error checking if users table is empty: %v", err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if empty && isUsersEmpty {
|
|
||||||
hashSeeder := &model.HistoryOfSeeders{
|
|
||||||
SeederName: "UserPasswordHash",
|
|
||||||
}
|
|
||||||
return db.Create(hashSeeder).Error
|
|
||||||
} else {
|
|
||||||
var seedersHistory []string
|
|
||||||
db.Model(&model.HistoryOfSeeders{}).Pluck("seeder_name", &seedersHistory)
|
|
||||||
|
|
||||||
if !slices.Contains(seedersHistory, "UserPasswordHash") && !isUsersEmpty {
|
|
||||||
var users []model.User
|
|
||||||
db.Find(&users)
|
|
||||||
|
|
||||||
for _, user := range users {
|
|
||||||
hashedPassword, err := crypto.HashPasswordAsBcrypt(user.Password)
|
|
||||||
if err != nil {
|
|
||||||
log.Printf("Error hashing password for user '%s': %v", user.Username, err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
db.Model(&user).Update("password", hashedPassword)
|
|
||||||
}
|
|
||||||
|
|
||||||
hashSeeder := &model.HistoryOfSeeders{
|
|
||||||
SeederName: "UserPasswordHash",
|
|
||||||
}
|
|
||||||
return db.Create(hashSeeder).Error
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func isTableEmpty(tableName string) (bool, error) {
|
func initSetting() error {
|
||||||
var count int64
|
return db.AutoMigrate(&model.Setting{})
|
||||||
err := db.Table(tableName).Count(&count).Error
|
}
|
||||||
return count == 0, err
|
func initInboundClientIps() error {
|
||||||
|
return db.AutoMigrate(&model.InboundClientIps{})
|
||||||
|
}
|
||||||
|
func initClientTraffic() error {
|
||||||
|
return db.AutoMigrate(&xray.ClientTraffic{})
|
||||||
}
|
}
|
||||||
|
|
||||||
func InitDB(dbPath string) error {
|
func InitDB(dbPath string) error {
|
||||||
dir := path.Dir(dbPath)
|
dir := path.Dir(dbPath)
|
||||||
err := os.MkdirAll(dir, fs.ModePerm)
|
err := os.MkdirAll(dir, fs.ModeDir)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -136,26 +72,27 @@ func InitDB(dbPath string) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := initModels(); err != nil {
|
err = initUser()
|
||||||
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
err = initInbound()
|
||||||
isUsersEmpty, err := isTableEmpty("users")
|
if err != nil {
|
||||||
|
|
||||||
if err := initUser(); err != nil {
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return runSeeders(isUsersEmpty)
|
err = initSetting()
|
||||||
}
|
if err != nil {
|
||||||
|
return err
|
||||||
func CloseDB() error {
|
|
||||||
if db != nil {
|
|
||||||
sqlDB, err := db.DB()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return sqlDB.Close()
|
|
||||||
}
|
}
|
||||||
|
err = initInboundClientIps()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = initClientTraffic()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -166,22 +103,3 @@ func GetDB() *gorm.DB {
|
||||||
func IsNotFound(err error) bool {
|
func IsNotFound(err error) bool {
|
||||||
return err == gorm.ErrRecordNotFound
|
return err == gorm.ErrRecordNotFound
|
||||||
}
|
}
|
||||||
|
|
||||||
func IsSQLiteDB(file io.ReaderAt) (bool, error) {
|
|
||||||
signature := []byte("SQLite format 3\x00")
|
|
||||||
buf := make([]byte, len(signature))
|
|
||||||
_, err := file.ReadAt(buf, 0)
|
|
||||||
if err != nil {
|
|
||||||
return false, err
|
|
||||||
}
|
|
||||||
return bytes.Equal(buf, signature), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func Checkpoint() error {
|
|
||||||
// Update WAL
|
|
||||||
err := db.Exec("PRAGMA wal_checkpoint;").Error
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
|
@ -2,7 +2,6 @@ package model
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"x-ui/util/json_util"
|
"x-ui/util/json_util"
|
||||||
"x-ui/xray"
|
"x-ui/xray"
|
||||||
)
|
)
|
||||||
|
@ -10,14 +9,12 @@ import (
|
||||||
type Protocol string
|
type Protocol string
|
||||||
|
|
||||||
const (
|
const (
|
||||||
VMESS Protocol = "vmess"
|
VMess Protocol = "vmess"
|
||||||
VLESS Protocol = "vless"
|
VLESS Protocol = "vless"
|
||||||
DOKODEMO Protocol = "dokodemo-door"
|
Dokodemo Protocol = "Dokodemo-door"
|
||||||
HTTP Protocol = "http"
|
Http Protocol = "http"
|
||||||
Trojan Protocol = "trojan"
|
Trojan Protocol = "trojan"
|
||||||
Shadowsocks Protocol = "shadowsocks"
|
Shadowsocks Protocol = "shadowsocks"
|
||||||
Socks Protocol = "socks"
|
|
||||||
WireGuard Protocol = "wireguard"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type User struct {
|
type User struct {
|
||||||
|
@ -39,32 +36,17 @@ type Inbound struct {
|
||||||
|
|
||||||
// config part
|
// config part
|
||||||
Listen string `json:"listen" form:"listen"`
|
Listen string `json:"listen" form:"listen"`
|
||||||
Port int `json:"port" form:"port"`
|
Port int `json:"port" form:"port" gorm:"unique"`
|
||||||
Protocol Protocol `json:"protocol" form:"protocol"`
|
Protocol Protocol `json:"protocol" form:"protocol"`
|
||||||
Settings string `json:"settings" form:"settings"`
|
Settings string `json:"settings" form:"settings"`
|
||||||
StreamSettings string `json:"streamSettings" form:"streamSettings"`
|
StreamSettings string `json:"streamSettings" form:"streamSettings"`
|
||||||
Tag string `json:"tag" form:"tag" gorm:"unique"`
|
Tag string `json:"tag" form:"tag" gorm:"unique"`
|
||||||
Sniffing string `json:"sniffing" form:"sniffing"`
|
Sniffing string `json:"sniffing" form:"sniffing"`
|
||||||
Allocate string `json:"allocate" form:"allocate"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type OutboundTraffics struct {
|
|
||||||
Id int `json:"id" form:"id" gorm:"primaryKey;autoIncrement"`
|
|
||||||
Tag string `json:"tag" form:"tag" gorm:"unique"`
|
|
||||||
Up int64 `json:"up" form:"up" gorm:"default:0"`
|
|
||||||
Down int64 `json:"down" form:"down" gorm:"default:0"`
|
|
||||||
Total int64 `json:"total" form:"total" gorm:"default:0"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type InboundClientIps struct {
|
type InboundClientIps struct {
|
||||||
Id int `json:"id" gorm:"primaryKey;autoIncrement"`
|
Id int `json:"id" gorm:"primaryKey;autoIncrement"`
|
||||||
ClientEmail string `json:"clientEmail" form:"clientEmail" gorm:"unique"`
|
ClientEmail string `json:"clientEmail" form:"clientEmail" gorm:"unique"`
|
||||||
Ips string `json:"ips" form:"ips"`
|
Ips string `json:"ips" form:"ips"`
|
||||||
}
|
|
||||||
|
|
||||||
type HistoryOfSeeders struct {
|
|
||||||
Id int `json:"id" gorm:"primaryKey;autoIncrement"`
|
|
||||||
SeederName string `json:"seederName"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (i *Inbound) GenXrayInboundConfig() *xray.InboundConfig {
|
func (i *Inbound) GenXrayInboundConfig() *xray.InboundConfig {
|
||||||
|
@ -80,7 +62,6 @@ func (i *Inbound) GenXrayInboundConfig() *xray.InboundConfig {
|
||||||
StreamSettings: json_util.RawMessage(i.StreamSettings),
|
StreamSettings: json_util.RawMessage(i.StreamSettings),
|
||||||
Tag: i.Tag,
|
Tag: i.Tag,
|
||||||
Sniffing: json_util.RawMessage(i.Sniffing),
|
Sniffing: json_util.RawMessage(i.Sniffing),
|
||||||
Allocate: json_util.RawMessage(i.Allocate),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -92,16 +73,11 @@ type Setting struct {
|
||||||
|
|
||||||
type Client struct {
|
type Client struct {
|
||||||
ID string `json:"id"`
|
ID string `json:"id"`
|
||||||
Security string `json:"security"`
|
|
||||||
Password string `json:"password"`
|
Password string `json:"password"`
|
||||||
Flow string `json:"flow"`
|
Flow string `json:"flow"`
|
||||||
|
AlterIds uint16 `json:"alterId"`
|
||||||
Email string `json:"email"`
|
Email string `json:"email"`
|
||||||
LimitIP int `json:"limitIp"`
|
LimitIP int `json:"limitIp"`
|
||||||
TotalGB int64 `json:"totalGB" form:"totalGB"`
|
TotalGB int64 `json:"totalGB" form:"totalGB"`
|
||||||
ExpiryTime int64 `json:"expiryTime" form:"expiryTime"`
|
ExpiryTime int64 `json:"expiryTime" form:"expiryTime"`
|
||||||
Enable bool `json:"enable" form:"enable"`
|
|
||||||
TgID int64 `json:"tgId" form:"tgId"`
|
|
||||||
SubID string `json:"subId" form:"subId"`
|
|
||||||
Comment string `json:"comment" form:"comment"`
|
|
||||||
Reset int `json:"reset" form:"reset"`
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,16 +0,0 @@
|
||||||
services:
|
|
||||||
3xui:
|
|
||||||
build:
|
|
||||||
context: .
|
|
||||||
dockerfile: ./Dockerfile
|
|
||||||
container_name: 3xui_app
|
|
||||||
# hostname: yourhostname <- optional
|
|
||||||
volumes:
|
|
||||||
- $PWD/db/:/etc/x-ui/
|
|
||||||
- $PWD/cert/:/root/cert/
|
|
||||||
environment:
|
|
||||||
XRAY_VMESS_AEAD_FORCED: "false"
|
|
||||||
XUI_ENABLE_FAIL2BAN: "true"
|
|
||||||
tty: true
|
|
||||||
network_mode: host
|
|
||||||
restart: unless-stopped
|
|
124
go.mod
|
@ -1,100 +1,62 @@
|
||||||
module x-ui
|
module x-ui
|
||||||
|
|
||||||
go 1.25.0
|
go 1.20
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/gin-contrib/gzip v1.2.3
|
github.com/Workiva/go-datastructures v1.0.53
|
||||||
github.com/gin-contrib/sessions v1.0.4
|
github.com/gin-contrib/sessions v0.0.4
|
||||||
github.com/gin-gonic/gin v1.10.1
|
github.com/gin-gonic/gin v1.9.0
|
||||||
github.com/goccy/go-json v0.10.5
|
github.com/go-cmd/cmd v1.4.1
|
||||||
github.com/google/uuid v1.6.0
|
github.com/go-telegram-bot-api/telegram-bot-api/v5 v5.5.1
|
||||||
github.com/joho/godotenv v1.5.1
|
github.com/nicksnyder/go-i18n/v2 v2.2.1
|
||||||
github.com/mymmrac/telego v1.2.0
|
|
||||||
github.com/nicksnyder/go-i18n/v2 v2.6.0
|
|
||||||
github.com/op/go-logging v0.0.0-20160315200505-970db520ece7
|
github.com/op/go-logging v0.0.0-20160315200505-970db520ece7
|
||||||
github.com/pelletier/go-toml/v2 v2.2.4
|
github.com/pelletier/go-toml/v2 v2.0.7
|
||||||
github.com/robfig/cron/v3 v3.0.1
|
github.com/robfig/cron/v3 v3.0.1
|
||||||
github.com/shirou/gopsutil/v4 v4.25.7
|
github.com/shirou/gopsutil/v3 v3.23.2
|
||||||
github.com/valyala/fasthttp v1.65.0
|
github.com/xtls/xray-core v1.8.0
|
||||||
github.com/xlzd/gotp v0.1.0
|
go.uber.org/atomic v1.10.0
|
||||||
github.com/xtls/xray-core v1.250803.0
|
golang.org/x/text v0.8.0
|
||||||
go.uber.org/atomic v1.11.0
|
google.golang.org/grpc v1.54.0
|
||||||
golang.org/x/crypto v0.41.0
|
gorm.io/driver/sqlite v1.4.4
|
||||||
golang.org/x/text v0.28.0
|
gorm.io/gorm v1.24.6
|
||||||
google.golang.org/grpc v1.74.2
|
|
||||||
gorm.io/driver/sqlite v1.6.0
|
|
||||||
gorm.io/gorm v1.30.1
|
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/andybalholm/brotli v1.2.0 // indirect
|
github.com/BurntSushi/toml v1.2.1 // indirect
|
||||||
github.com/bytedance/sonic v1.14.0 // indirect
|
github.com/bytedance/sonic v1.8.2 // indirect
|
||||||
github.com/bytedance/sonic/loader v0.3.0 // indirect
|
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 // indirect
|
||||||
github.com/cloudflare/circl v1.6.1 // indirect
|
github.com/gin-contrib/sse v0.1.0 // indirect
|
||||||
github.com/cloudwego/base64x v0.1.6 // indirect
|
github.com/go-ole/go-ole v1.2.6 // indirect
|
||||||
github.com/dgryski/go-metro v0.0.0-20250106013310-edb8663e5e33 // indirect
|
|
||||||
github.com/ebitengine/purego v0.8.4 // indirect
|
|
||||||
github.com/gabriel-vasile/mimetype v1.4.9 // indirect
|
|
||||||
github.com/gin-contrib/sse v1.1.0 // indirect
|
|
||||||
github.com/go-ole/go-ole v1.3.0 // indirect
|
|
||||||
github.com/go-playground/locales v0.14.1 // indirect
|
github.com/go-playground/locales v0.14.1 // indirect
|
||||||
github.com/go-playground/universal-translator v0.18.1 // indirect
|
github.com/go-playground/universal-translator v0.18.1 // indirect
|
||||||
github.com/go-playground/validator/v10 v10.27.0 // indirect
|
github.com/go-playground/validator/v10 v10.11.2 // indirect
|
||||||
github.com/google/btree v1.1.3 // indirect
|
github.com/goccy/go-json v0.10.0 // indirect
|
||||||
github.com/gorilla/context v1.1.2 // indirect
|
github.com/golang/protobuf v1.5.3 // indirect
|
||||||
github.com/gorilla/securecookie v1.1.2 // indirect
|
github.com/gorilla/context v1.1.1 // indirect
|
||||||
github.com/gorilla/sessions v1.4.0 // indirect
|
github.com/gorilla/securecookie v1.1.1 // indirect
|
||||||
github.com/gorilla/websocket v1.5.3 // indirect
|
github.com/gorilla/sessions v1.2.1 // indirect
|
||||||
github.com/grbit/go-json v0.11.0 // indirect
|
|
||||||
github.com/jinzhu/inflection v1.0.0 // indirect
|
github.com/jinzhu/inflection v1.0.0 // indirect
|
||||||
github.com/jinzhu/now v1.1.5 // indirect
|
github.com/jinzhu/now v1.1.5 // indirect
|
||||||
github.com/json-iterator/go v1.1.12 // indirect
|
github.com/json-iterator/go v1.1.12 // indirect
|
||||||
github.com/juju/ratelimit v1.0.2 // indirect
|
github.com/klauspost/cpuid/v2 v2.2.4 // indirect
|
||||||
github.com/klauspost/compress v1.18.0 // indirect
|
github.com/leodido/go-urn v1.2.1 // indirect
|
||||||
github.com/klauspost/cpuid/v2 v2.3.0 // indirect
|
github.com/lufia/plan9stats v0.0.0-20230110061619-bbe2e5e100de // indirect
|
||||||
github.com/kr/text v0.2.0 // indirect
|
github.com/mattn/go-isatty v0.0.17 // indirect
|
||||||
github.com/leodido/go-urn v1.4.0 // indirect
|
github.com/mattn/go-sqlite3 v1.14.16 // 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.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/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||||
github.com/pires/go-proxyproto v0.8.1 // indirect
|
github.com/pires/go-proxyproto v0.6.2 // indirect
|
||||||
github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 // indirect
|
github.com/power-devops/perfstat v0.0.0-20221212215047-62379fc7944b // indirect
|
||||||
github.com/quic-go/qpack v0.5.1 // indirect
|
github.com/tklauser/go-sysconf v0.3.11 // indirect
|
||||||
github.com/quic-go/quic-go v0.54.0 // indirect
|
github.com/tklauser/numcpus v0.6.0 // indirect
|
||||||
github.com/refraction-networking/utls v1.8.0 // indirect
|
|
||||||
github.com/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3 // indirect
|
|
||||||
github.com/rogpeppe/go-internal v1.14.1 // indirect
|
|
||||||
github.com/sagernet/sing v0.7.5 // indirect
|
|
||||||
github.com/sagernet/sing-shadowsocks v0.2.8 // indirect
|
|
||||||
github.com/seiflotfy/cuckoofilter v0.0.0-20240715131351-a2f2c23f1771 // indirect
|
|
||||||
github.com/tklauser/go-sysconf v0.3.15 // indirect
|
|
||||||
github.com/tklauser/numcpus v0.10.0 // indirect
|
|
||||||
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
|
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
|
||||||
github.com/ugorji/go/codec v1.3.0 // indirect
|
github.com/ugorji/go/codec v1.2.10 // indirect
|
||||||
github.com/v2fly/ss-bloomring v0.0.0-20210312155135-28617310f63e // indirect
|
github.com/yusufpapurcu/wmi v1.2.2 // indirect
|
||||||
github.com/valyala/bytebufferpool v1.0.0 // indirect
|
golang.org/x/arch v0.2.0 // indirect
|
||||||
github.com/valyala/fastjson v1.6.4 // indirect
|
golang.org/x/crypto v0.7.0 // indirect
|
||||||
github.com/vishvananda/netlink v1.3.1 // indirect
|
golang.org/x/net v0.8.0 // indirect
|
||||||
github.com/vishvananda/netns v0.0.5 // indirect
|
golang.org/x/sys v0.6.0 // indirect
|
||||||
github.com/xtls/reality v0.0.0-20250727231020-de3bb4d08f5a // indirect
|
google.golang.org/genproto v0.0.0-20230306155012-7f2fa6fef1f4 // indirect
|
||||||
github.com/yusufpapurcu/wmi v1.2.4 // indirect
|
google.golang.org/protobuf v1.29.1 // indirect
|
||||||
go.uber.org/mock v0.5.2 // indirect
|
|
||||||
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba // indirect
|
|
||||||
golang.org/x/arch v0.20.0 // indirect
|
|
||||||
golang.org/x/mod v0.27.0 // indirect
|
|
||||||
golang.org/x/net v0.43.0 // indirect
|
|
||||||
golang.org/x/sync v0.16.0 // indirect
|
|
||||||
golang.org/x/sys v0.35.0 // indirect
|
|
||||||
golang.org/x/time v0.12.0 // indirect
|
|
||||||
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-20250811230008-5f3141c8851a // indirect
|
|
||||||
google.golang.org/protobuf v1.36.7 // indirect
|
|
||||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||||
gvisor.dev/gvisor v0.0.0-20250503011706-39ed1f5ac29c // indirect
|
|
||||||
lukechampine.com/blake3 v1.4.1 // indirect
|
|
||||||
)
|
)
|
||||||
|
|
407
go.sum
|
@ -1,251 +1,260 @@
|
||||||
github.com/BurntSushi/toml v1.5.0 h1:W5quZX/G/csjUnuI8SUYlsHs9M38FC7znL0lIO+DvMg=
|
github.com/BurntSushi/toml v1.0.0/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
|
||||||
github.com/BurntSushi/toml v1.5.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho=
|
github.com/BurntSushi/toml v1.2.1 h1:9F2/+DoOYIOksmaJFPw1tGFy1eDnIJXg+UHjuD8lTak=
|
||||||
github.com/andybalholm/brotli v1.2.0 h1:ukwgCxwYrmACq68yiUqwIWnGY0cTPox/M94sVwToPjQ=
|
github.com/BurntSushi/toml v1.2.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
|
||||||
github.com/andybalholm/brotli v1.2.0/go.mod h1:rzTDkvFWvIrjDXZHkuS16NPggd91W3kUSvPlQ1pLaKY=
|
github.com/Workiva/go-datastructures v1.0.53 h1:J6Y/52yX10Xc5JjXmGtWoSSxs3mZnGSaq37xZZh7Yig=
|
||||||
github.com/bytedance/sonic v1.14.0 h1:/OfKt8HFw0kh2rj8N0F6C/qPGRESq0BbaNZgcNXXzQQ=
|
github.com/Workiva/go-datastructures v1.0.53/go.mod h1:1yZL+zfsztete+ePzZz/Zb1/t5BnDuE2Ya2MMGhzP6A=
|
||||||
github.com/bytedance/sonic v1.14.0/go.mod h1:WoEbx8WTcFJfzCe0hbmyTGrfjt8PzNEBdxlNUO24NhA=
|
github.com/andybalholm/brotli v1.0.5 h1:8uQZIdzKmjc/iuPu7O2ioW48L81FgatrcpfFmiq/cCs=
|
||||||
github.com/bytedance/sonic/loader v0.3.0 h1:dskwH8edlzNMctoruo8FPTJDF3vLtDT0sXZwvZJyqeA=
|
github.com/antonlindstrom/pgstore v0.0.0-20200229204646-b08ebf1105e0/go.mod h1:2Ti6VUHVxpC0VSmTZzEvpzysnaGAfGBOoMIz5ykPyyw=
|
||||||
github.com/bytedance/sonic/loader v0.3.0/go.mod h1:N8A3vUdtUebEY2/VQC0MyhYeKUFosQU6FxH2JmUe6VI=
|
github.com/boj/redistore v0.0.0-20180917114910-cd5dcc76aeff/go.mod h1:+RTT1BOk5P97fT2CiHkbFQwkK3mjsFAP6zCYV2aXtjw=
|
||||||
github.com/cloudflare/circl v1.6.1 h1:zqIqSPIndyBh1bjLVVDHMPpVKqp8Su/V+6MeDzzQBQ0=
|
github.com/bradfitz/gomemcache v0.0.0-20190913173617-a41fca850d0b/go.mod h1:H0wQNHz2YrLsuXOZozoeDmnHXkNCRmMW0gwFWDfEZDA=
|
||||||
github.com/cloudflare/circl v1.6.1/go.mod h1:uddAzsPgqdMAYatqJ0lsjX1oECcQLIlRpzZh3pJrofs=
|
github.com/bradleypeabody/gorilla-sessions-memcache v0.0.0-20181103040241-659414f458e1/go.mod h1:dkChI7Tbtx7H1Tj7TqGSZMOeGpMP5gLHtjroHd4agiI=
|
||||||
github.com/cloudwego/base64x v0.1.6 h1:t11wG9AECkCDk5fMSoxmufanudBtJ+/HemLstXDLI2M=
|
github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM=
|
||||||
github.com/cloudwego/base64x v0.1.6/go.mod h1:OFcloc187FXDaYHvrNIjxSe8ncn0OOM8gEHfghB2IPU=
|
github.com/bytedance/sonic v1.8.2 h1:Eq1oE3xWIBE3tj2ZtJFK1rDAx7+uA4bRytozVhXMHKY=
|
||||||
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
github.com/bytedance/sonic v1.8.2/go.mod h1:i736AoUSYt75HyZLoJW9ERYxcy6eaN6h4BZXU064P/U=
|
||||||
|
github.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06/go.mod h1:DH46F32mSOjUmXrMHnKwZdA8wcEefY7UVqBKYGjpdQY=
|
||||||
|
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 h1:qSGYFH7+jGhDF8vLC+iwCD4WpbV1EBDSzWkJODFLams=
|
||||||
|
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311/go.mod h1:b583jCggY9gE99b6G5LEC39OIiVsWj+R97kbl5odCEk=
|
||||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/dgryski/go-metro v0.0.0-20200812162917-85c65e2d0165/go.mod h1:c9O8+fpSOX1DM8cPNSkX/qsBWdkD4yd2dpciOWQjpBw=
|
github.com/dgryski/go-metro v0.0.0-20211217172704-adc40b04c140 h1:y7y0Oa6UawqTFPCDw9JG6pdKt4F9pAhHv0B7FMGaGD0=
|
||||||
github.com/dgryski/go-metro v0.0.0-20250106013310-edb8663e5e33 h1:ucRHb6/lvW/+mTEIGbvhcYU3S8+uSNkuMjx/qZFfhtM=
|
github.com/francoispqt/gojay v1.2.13 h1:d2m3sFjloqoIUQU3TsHBgj6qg/BVGlTBeHDUmyJnXKk=
|
||||||
github.com/dgryski/go-metro v0.0.0-20250106013310-edb8663e5e33/go.mod h1:c9O8+fpSOX1DM8cPNSkX/qsBWdkD4yd2dpciOWQjpBw=
|
|
||||||
github.com/ebitengine/purego v0.8.4 h1:CF7LEKg5FFOsASUj0+QwaXf8Ht6TlFxg09+S9wz0omw=
|
|
||||||
github.com/ebitengine/purego v0.8.4/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ=
|
|
||||||
github.com/gabriel-vasile/mimetype v1.4.9 h1:5k+WDwEsD9eTLL8Tz3L0VnmVh9QxGjRmjBvAG7U/oYY=
|
|
||||||
github.com/gabriel-vasile/mimetype v1.4.9/go.mod h1:WnSQhFKJuBlRyLiKohA/2DtIlPFAbguNaG7QCHcyGok=
|
|
||||||
github.com/ghodss/yaml v1.0.1-0.20220118164431-d8423dcdf344 h1:Arcl6UOIS/kgO2nW3A65HN+7CMjSDP/gofXL4CZt1V4=
|
github.com/ghodss/yaml v1.0.1-0.20220118164431-d8423dcdf344 h1:Arcl6UOIS/kgO2nW3A65HN+7CMjSDP/gofXL4CZt1V4=
|
||||||
github.com/ghodss/yaml v1.0.1-0.20220118164431-d8423dcdf344/go.mod h1:GIjDIg/heH5DOkXY3YJ/wNhfHsQHoXGjl8G8amsYQ1I=
|
github.com/gin-contrib/sessions v0.0.4 h1:gq4fNa1Zmp564iHP5G6EBuktilEos8VKhe2sza1KMgo=
|
||||||
github.com/gin-contrib/gzip v1.2.3 h1:dAhT722RuEG330ce2agAs75z7yB+NKvX/ZM1r8w0u2U=
|
github.com/gin-contrib/sessions v0.0.4/go.mod h1:pQ3sIyviBBGcxgyR8mkeJuXbeV3h3NYmhJADQTq5+Vo=
|
||||||
github.com/gin-contrib/gzip v1.2.3/go.mod h1:ad72i4Bzmaypk8M762gNXa2wkxxjbz0icRNnuLJ9a/c=
|
github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
|
||||||
github.com/gin-contrib/sessions v1.0.4 h1:ha6CNdpYiTOK/hTp05miJLbpTSNfOnFg5Jm2kbcqy8U=
|
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
|
||||||
github.com/gin-contrib/sessions v1.0.4/go.mod h1:ccmkrb2z6iU2osiAHZG3x3J4suJK+OU27oqzlWOqQgs=
|
github.com/gin-gonic/gin v1.7.4/go.mod h1:jD2toBW3GZUr5UMcdrwQA10I7RuaFOl/SGeDjXkfUtY=
|
||||||
github.com/gin-contrib/sse v1.1.0 h1:n0w2GMuUpWDVp7qSpvze6fAu9iRxJY4Hmj6AmBOU05w=
|
github.com/gin-gonic/gin v1.9.0 h1:OjyFBKICoexlu99ctXNR2gg+c5pKrKMuyjgARg9qeY8=
|
||||||
github.com/gin-contrib/sse v1.1.0/go.mod h1:hxRZ5gVpWMT7Z0B0gSNYqqsSCNIJMjzvm6fqCz9vjwM=
|
github.com/gin-gonic/gin v1.9.0/go.mod h1:W1Me9+hsUSyj3CePGrd1/QrKJMSJ1Tu/0hFEH89961k=
|
||||||
github.com/gin-gonic/gin v1.10.1 h1:T0ujvqyCSqRopADpgPgiTT63DUQVSfojyME59Ei63pQ=
|
github.com/globalsign/mgo v0.0.0-20181015135952-eeefdecb41b8/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q=
|
||||||
github.com/gin-gonic/gin v1.10.1/go.mod h1:4PMNQiOhvDRa013RKVbsiNwoyezlm2rm0uX/T7kzp5Y=
|
github.com/go-cmd/cmd v1.4.1 h1:JUcEIE84v8DSy02XTZpUDeGKExk2oW3DA10hTjbQwmc=
|
||||||
github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
|
github.com/go-cmd/cmd v1.4.1/go.mod h1:tbBenttXtZU4c5djS1o7PWL5pd2xAr5sIqH1kGdNiRc=
|
||||||
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY=
|
||||||
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
|
|
||||||
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
|
|
||||||
github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
|
github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
|
||||||
github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE=
|
github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
|
||||||
github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78=
|
|
||||||
github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
|
github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
|
||||||
github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
|
github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8=
|
||||||
github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
|
github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
|
||||||
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
|
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
|
||||||
|
github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA=
|
||||||
github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
|
github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
|
||||||
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
|
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
|
||||||
github.com/go-playground/validator/v10 v10.27.0 h1:w8+XrWVMhGkxOaaowyKH35gFydVHOvC0/uWoy2Fzwn4=
|
github.com/go-playground/validator/v10 v10.4.1/go.mod h1:nlOn6nFhuKACm19sB/8EGNn9GlaMV7XkbRSipzJ0Ii4=
|
||||||
github.com/go-playground/validator/v10 v10.27.0/go.mod h1:I5QpIEbmr8On7W0TktmJAumgzX4CA1XNl4ZmDuVHKKo=
|
github.com/go-playground/validator/v10 v10.11.2 h1:q3SHpufmypg+erIExEKUmsgmhDTyhcJ38oeKGACXohU=
|
||||||
github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4=
|
github.com/go-playground/validator/v10 v10.11.2/go.mod h1:NieE624vt4SCTJtD87arVLvdmjPAeV8BQlHtMnw9D7s=
|
||||||
github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
|
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0 h1:p104kn46Q8WdvHunIJ9dAyjPVtrBPhSr3KT2yUst43I=
|
||||||
github.com/golang/mock v1.7.0-rc.1 h1:YojYx61/OLFsiv6Rw1Z96LpldJIy31o+UHmwAUMJ6/U=
|
github.com/go-telegram-bot-api/telegram-bot-api/v5 v5.5.1 h1:wG8n/XJQ07TmjbITcGiUaOtXxdrINDz1b0J1w0SzqDc=
|
||||||
github.com/golang/mock v1.7.0-rc.1/go.mod h1:s42URUywIqd+OcERslBJvOjepvNymP31m3q8d/GkuRs=
|
github.com/go-telegram-bot-api/telegram-bot-api/v5 v5.5.1/go.mod h1:A2S0CWkNylc2phvKXWBBdD3K0iGnDBGbzRpISP2zBl8=
|
||||||
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
|
github.com/go-test/deep v1.0.7 h1:/VSMRlnY/JSyqxQUzQLKVMAskpY/NZKFA5j2P+0pP2M=
|
||||||
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
|
github.com/goccy/go-json v0.10.0 h1:mXKd9Qw4NuzShiRlOXKews24ufknHO7gx30lsDyokKA=
|
||||||
github.com/google/btree v1.1.3 h1:CVpQJjYgC4VbzxeGVHfvZrv1ctoYCAI8vbl07Fcxlyg=
|
github.com/goccy/go-json v0.10.0/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
|
||||||
github.com/google/btree v1.1.3/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4=
|
github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc=
|
||||||
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
|
||||||
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
|
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
|
||||||
|
github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
|
||||||
|
github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
|
||||||
|
github.com/gomodule/redigo v2.0.0+incompatible/go.mod h1:B4C85qUVwatsJoIUNIfCRsp7qO0iAmpGFZ4EELWSbC4=
|
||||||
|
github.com/google/btree v1.1.2 h1:xf4v41cLI2Z6FxbKm+8Bu+m8ifhj15JuZ9sa0jZCMUU=
|
||||||
|
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
|
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
|
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
|
||||||
|
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||||
github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0=
|
github.com/google/pprof v0.0.0-20230228050547-1710fef4ab10 h1:CqYfpuYIjnlNxM3msdyPRKabhXZWbKjf3Q8BWROFBso=
|
||||||
github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
github.com/gorilla/context v1.1.1 h1:AWwleXJkX/nhcU9bZSnZoi3h/qGYqQAGhq6zZe/aQW8=
|
||||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg=
|
||||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
github.com/gorilla/securecookie v1.1.1 h1:miw7JPhV+b/lAHSXz4qd/nN9jRiAFV5FwjeKyCS8BvQ=
|
||||||
github.com/gorilla/context v1.1.2 h1:WRkNAv2uoa03QNIc1A6u4O7DAGMUVoopZhkiXWA2V1o=
|
github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4=
|
||||||
github.com/gorilla/context v1.1.2/go.mod h1:KDPwT9i/MeWHiLl90fuTgrt4/wPcv75vFAZLaOOcbxM=
|
github.com/gorilla/sessions v1.1.1/go.mod h1:8KCfur6+4Mqcc6S0FEfKuN15Vl5MgXW92AE8ovaJD0w=
|
||||||
github.com/gorilla/securecookie v1.1.2 h1:YCIWL56dvtr73r6715mJs5ZvhtnY73hBvEF8kXD8ePA=
|
github.com/gorilla/sessions v1.2.0/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM=
|
||||||
github.com/gorilla/securecookie v1.1.2/go.mod h1:NfCASbcHqRSY+3a8tlWJwsQap2VX5pwzwo4h3eOamfo=
|
github.com/gorilla/sessions v1.2.1 h1:DHd3rPN5lE3Ts3D8rKkQ8x/0kqfeNmBAaiSi+o7FsgI=
|
||||||
github.com/gorilla/sessions v1.4.0 h1:kpIYOp/oi6MG/p5PgxApU8srsSw9tuFbt46Lt7auzqQ=
|
github.com/gorilla/sessions v1.2.1/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM=
|
||||||
github.com/gorilla/sessions v1.4.0/go.mod h1:FLWm50oby91+hl7p/wRxDth9bWSuk0qVL2emc7lT5ik=
|
github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc=
|
||||||
github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg=
|
|
||||||
github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
|
||||||
github.com/grbit/go-json v0.11.0 h1:bAbyMdYrYl/OjYsSqLH99N2DyQ291mHy726Mx+sYrnc=
|
|
||||||
github.com/grbit/go-json v0.11.0/go.mod h1:IYpHsdybQ386+6g3VE6AXQ3uTGa5mquBme5/ZWmtzek=
|
|
||||||
github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
|
github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
|
||||||
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
|
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
|
||||||
|
github.com/jinzhu/now v1.1.4/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
|
||||||
github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ=
|
github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ=
|
||||||
github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
|
github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
|
||||||
github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
|
github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
||||||
github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
|
|
||||||
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
|
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
|
||||||
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
|
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
|
||||||
github.com/juju/ratelimit v1.0.2 h1:sRxmtRiajbvrcLQT7S+JbqU0ntsb9W2yhSdNN8tWfaI=
|
github.com/kidstuff/mongostore v0.0.0-20181113001930-e650cd85ee4b/go.mod h1:g2nVr8KZVXJSS97Jo8pJ0jgq29P6H7dG0oplUA86MQw=
|
||||||
github.com/juju/ratelimit v1.0.2/go.mod h1:qapgC/Gy+xNh9UxzV13HGGl/6UXNN+ct+vwSgWNm/qk=
|
github.com/klauspost/compress v1.16.0 h1:iULayQNOReoYUe+1qtKOqw9CwJv3aNQu8ivo7lw1HU4=
|
||||||
github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo=
|
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
|
||||||
github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ=
|
github.com/klauspost/cpuid/v2 v2.2.4 h1:acbojRNwl3o09bUq+yDCtZFc1aiwaAAxtcn8YkZXnvk=
|
||||||
github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y=
|
github.com/klauspost/cpuid/v2 v2.2.4/go.mod h1:RVVoqg1df56z8g3pUjL/3lE5UfnlrJX8tyFgg4nqhuY=
|
||||||
github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0=
|
github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
|
||||||
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
|
||||||
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
|
||||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII=
|
||||||
github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
|
github.com/leodido/go-urn v1.2.1 h1:BqpAaACuzVSgi/VLzGZIobT2z4v53pjosyNd9Yv6n/w=
|
||||||
github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
|
github.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ictyFfxY=
|
||||||
github.com/lufia/plan9stats v0.0.0-20250317134145-8bc96cf8fc35 h1:PpXWgLPs+Fqr325bN2FD2ISlRRztXibcX6e8f5FR5Dc=
|
github.com/lib/pq v1.10.3/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
|
||||||
github.com/lufia/plan9stats v0.0.0-20250317134145-8bc96cf8fc35/go.mod h1:autxFIvghDt3jPTLoqZ9OZ7s9qTGNAWmYCjVFWPX/zg=
|
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I=
|
||||||
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
github.com/lufia/plan9stats v0.0.0-20230110061619-bbe2e5e100de h1:V53FWzU6KAZVi1tPp5UIsMoUWJ2/PNwYIDXnu7QuBCE=
|
||||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
github.com/lufia/plan9stats v0.0.0-20230110061619-bbe2e5e100de/go.mod h1:JKx41uQRwqlTZabZc+kILPrO/3jlKnQ2Z8b7YiVw5cE=
|
||||||
github.com/mattn/go-sqlite3 v1.14.32 h1:JD12Ag3oLy1zQA+BNn74xRgaBbdhbNIDYvQUEuuErjs=
|
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
|
||||||
github.com/mattn/go-sqlite3 v1.14.32/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
|
github.com/mattn/go-isatty v0.0.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPng=
|
||||||
github.com/miekg/dns v1.1.68 h1:jsSRkNozw7G/mnmXULynzMNIsgY2dHC8LO6U6Ij2JEA=
|
github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
||||||
github.com/miekg/dns v1.1.68/go.mod h1:fujopn7TB3Pu3JM69XaawiU0wqjpL9/8xGop5UrTPps=
|
github.com/mattn/go-sqlite3 v1.14.15/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
|
||||||
|
github.com/mattn/go-sqlite3 v1.14.16 h1:yOQRA0RpS5PFz/oikGwBEqvAWhWg5ufRz4ETLjwpU1Y=
|
||||||
|
github.com/mattn/go-sqlite3 v1.14.16/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
|
||||||
|
github.com/memcachier/mc v2.0.1+incompatible/go.mod h1:7bkvFE61leUBvXz+yxsOnGBQSZpBSPIMUQSmmSHvuXc=
|
||||||
|
github.com/miekg/dns v1.1.51 h1:0+Xg7vObnhrz/4ZCZcZh7zPXlmU0aveS2HDBd0m0qSo=
|
||||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
|
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
|
||||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||||
|
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
||||||
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
|
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
|
||||||
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
|
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
|
||||||
github.com/mymmrac/telego v1.2.0 h1:CHmR9eiugpTiF/ttppmK89E6mcu9d4DmNQS0tMpUs6M=
|
github.com/nicksnyder/go-i18n/v2 v2.2.1 h1:aOzRCdwsJuoExfZhoiXHy4bjruwCMdt5otbYojM/PaA=
|
||||||
github.com/mymmrac/telego v1.2.0/go.mod h1:OiCm4QjqB/ZY2E4VAmkVH8EeLLhM4QuFuO1KOCuvGoM=
|
github.com/nicksnyder/go-i18n/v2 v2.2.1/go.mod h1:fF2++lPHlo+/kPaj3nB0uxtPwzlPm+BlgwGX7MkeGj0=
|
||||||
github.com/nicksnyder/go-i18n/v2 v2.6.0 h1:C/m2NNWNiTB6SK4Ao8df5EWm3JETSTIGNXBpMJTxzxQ=
|
github.com/onsi/ginkgo/v2 v2.9.0 h1:Tugw2BKlNHTMfG+CheOITkYvk4LAh6MFOvikhGVnhE8=
|
||||||
github.com/nicksnyder/go-i18n/v2 v2.6.0/go.mod h1:88sRqr0C6OPyJn0/KRNaEz1uWorjxIKP7rUUcvycecE=
|
|
||||||
github.com/op/go-logging v0.0.0-20160315200505-970db520ece7 h1:lDH9UUVJtmYCjyT0CI4q8xvlXPxeZ0gYCVvWbmPlp88=
|
github.com/op/go-logging v0.0.0-20160315200505-970db520ece7 h1:lDH9UUVJtmYCjyT0CI4q8xvlXPxeZ0gYCVvWbmPlp88=
|
||||||
github.com/op/go-logging v0.0.0-20160315200505-970db520ece7/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk=
|
github.com/op/go-logging v0.0.0-20160315200505-970db520ece7/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk=
|
||||||
github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8=
|
github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8=
|
||||||
github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c=
|
github.com/pelletier/go-toml/v2 v2.0.7 h1:muncTPStnKRos5dpVKULv2FVd4bMOhNePj9CjgDb8Us=
|
||||||
github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4=
|
github.com/pelletier/go-toml/v2 v2.0.7/go.mod h1:eumQOmlWiOPt5WriQQqoM5y18pDHwha2N+QD+EUNTek=
|
||||||
github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY=
|
github.com/philhofer/fwd v1.1.1/go.mod h1:gk3iGcWd9+svBvR0sR+KPcfE+RNWozjowpeBVG3ZVNU=
|
||||||
github.com/pires/go-proxyproto v0.8.1 h1:9KEixbdJfhrbtjpz/ZwCdWDD2Xem0NZ38qMYaASJgp0=
|
github.com/pires/go-proxyproto v0.6.2 h1:KAZ7UteSOt6urjme6ZldyFm4wDe/z0ZUP0Yv0Dos0d8=
|
||||||
github.com/pires/go-proxyproto v0.8.1/go.mod h1:ZKAAyp3cgy5Y5Mo4n9AlScrkCZwUy0g3Jf+slqQVcuU=
|
github.com/pires/go-proxyproto v0.6.2/go.mod h1:Odh9VFOZJCf9G8cLW5o435Xf1J95Jw9Gw5rnCjcwzAY=
|
||||||
|
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 h1:o4JXh1EVt9k/+g42oCprj/FisM4qX9L3sZB3upGN2ZU=
|
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE=
|
||||||
github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE=
|
github.com/power-devops/perfstat v0.0.0-20221212215047-62379fc7944b h1:0LFwY6Q3gMACTjAbMZBjXAqTOzOwFaj2Ld6cjeQ7Rig=
|
||||||
github.com/quic-go/qpack v0.5.1 h1:giqksBPnT/HDtZ6VhtFKgoLOWmlyo9Ei6u9PqzIMbhI=
|
github.com/power-devops/perfstat v0.0.0-20221212215047-62379fc7944b/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE=
|
||||||
github.com/quic-go/qpack v0.5.1/go.mod h1:+PC4XFrEskIVkcLzpEkbLqq1uCoxPhQuvK5rH1ZgaEg=
|
github.com/quasoft/memstore v0.0.0-20191010062613-2bce066d2b0b/go.mod h1:wTPjTepVu7uJBYgZ0SdWHQlIas582j6cn2jgk4DDdlg=
|
||||||
github.com/quic-go/quic-go v0.54.0 h1:6s1YB9QotYI6Ospeiguknbp2Znb/jZYjZLRXn9kMQBg=
|
github.com/quic-go/qtls-go1-19 v0.2.1 h1:aJcKNMkH5ASEJB9FXNeZCyTEIHU1J7MmHyz1Q1TSG1A=
|
||||||
github.com/quic-go/quic-go v0.54.0/go.mod h1:e68ZEaCdyviluZmy44P6Iey98v/Wfz6HCjQEm+l8zTY=
|
github.com/quic-go/qtls-go1-20 v0.1.1 h1:KbChDlg82d3IHqaj2bn6GfKRj84Per2VGf5XV3wSwQk=
|
||||||
github.com/refraction-networking/utls v1.8.0 h1:L38krhiTAyj9EeiQQa2sg+hYb4qwLCqdMcpZrRfbONE=
|
github.com/quic-go/quic-go v0.33.0 h1:ItNoTDN/Fm/zBlq769lLJc8ECe9gYaW40veHCCco7y0=
|
||||||
github.com/refraction-networking/utls v1.8.0/go.mod h1:jkSOEkLqn+S/jtpEHPOsVv/4V4EVnelwbMQl4vCWXAM=
|
github.com/refraction-networking/utls v1.2.3-0.20230308205431-4f1df6c200db h1:ULRv/GPW5KYDafE0FACN2no+HTCyQLUtfyOIeyp3GNc=
|
||||||
github.com/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3 h1:f/FNXud6gA3MNr8meMVVGxhp+QBTqY91tM8HjEuMjGg=
|
github.com/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3 h1:f/FNXud6gA3MNr8meMVVGxhp+QBTqY91tM8HjEuMjGg=
|
||||||
github.com/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3/go.mod h1:HgjTstvQsPGkxUsCd2KWxErBblirPizecHcpD3ffK+s=
|
|
||||||
github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs=
|
github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs=
|
||||||
github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro=
|
github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro=
|
||||||
github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
|
github.com/rogpeppe/go-internal v1.8.0 h1:FCbCCtXNOY3UtUuHUYaghJg4y7Fd14rXifAYUAtL9R8=
|
||||||
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
|
github.com/sagernet/sing v0.1.7 h1:g4vjr3q8SUlBZSx97Emz5OBfSMBxxW5Q8C2PfdoSo08=
|
||||||
github.com/sagernet/sing v0.7.5 h1:gNMwZCLPqR+4e0g6dwi0sSsrvOmoMjpZgqxKsuJZatc=
|
github.com/sagernet/sing-shadowsocks v0.1.1 h1:uFK2rlVeD/b1xhDwSMbUI2goWc6fOKxp+ZeKHZq6C9Q=
|
||||||
github.com/sagernet/sing v0.7.5/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak=
|
github.com/sagernet/wireguard-go v0.0.0-20221116151939-c99467f53f2c h1:vK2wyt9aWYHHvNLWniwijBu/n4pySypiKRhN32u/JGo=
|
||||||
github.com/sagernet/sing-shadowsocks v0.2.8 h1:PURj5PRoAkqeHh2ZW205RWzN9E9RtKCVCzByXruQWfE=
|
github.com/seiflotfy/cuckoofilter v0.0.0-20220411075957-e3b120b3f5fb h1:XfLJSPIOUX+osiMraVgIrMR27uMXnRJWGm1+GL8/63U=
|
||||||
github.com/sagernet/sing-shadowsocks v0.2.8/go.mod h1:lo7TWEMDcN5/h5B8S0ew+r78ZODn6SwVaFhvB6H+PTI=
|
github.com/shirou/gopsutil/v3 v3.23.2 h1:PAWSuiAszn7IhPMBtXsbSCafej7PqUOvY6YywlQUExU=
|
||||||
github.com/seiflotfy/cuckoofilter v0.0.0-20240715131351-a2f2c23f1771 h1:emzAzMZ1L9iaKCTxdy3Em8Wv4ChIAGnfiz18Cda70g4=
|
github.com/shirou/gopsutil/v3 v3.23.2/go.mod h1:gv0aQw33GLo3pG8SiWKiQrbDzbRY1K80RyZJ7V4Th1M=
|
||||||
github.com/seiflotfy/cuckoofilter v0.0.0-20240715131351-a2f2c23f1771/go.mod h1:bR6DqgcAl1zTcOX8/pE2Qkj9XO00eCNqmKb7lXP8EAg=
|
|
||||||
github.com/shirou/gopsutil/v4 v4.25.7 h1:bNb2JuqKuAu3tRlPv5piSmBZyMfecwQ+t/ILq+1JqVM=
|
|
||||||
github.com/shirou/gopsutil/v4 v4.25.7/go.mod h1:XV/egmwJtd3ZQjBpJVY5kndsiOO4IRqy9TQnmm6VP7U=
|
|
||||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||||
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||||
|
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||||
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
|
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||||
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||||
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
|
github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8=
|
||||||
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||||
github.com/tklauser/go-sysconf v0.3.15 h1:VE89k0criAymJ/Os65CSn1IXaol+1wrsFHEB8Ol49K4=
|
github.com/tinylib/msgp v1.1.5/go.mod h1:eQsjooMTnV42mHu917E26IogZ2930nFyBQdofk10Udg=
|
||||||
github.com/tklauser/go-sysconf v0.3.15/go.mod h1:Dmjwr6tYFIseJw7a3dRLJfsHAMXZ3nEnL/aZY+0IuI4=
|
github.com/tklauser/go-sysconf v0.3.11 h1:89WgdJhk5SNwJfu+GKyYveZ4IaJ7xAkecBo+KdJV0CM=
|
||||||
github.com/tklauser/numcpus v0.10.0 h1:18njr6LDBk1zuna922MgdjQuJFjrdppsZG60sHGfjso=
|
github.com/tklauser/go-sysconf v0.3.11/go.mod h1:GqXfhXY3kiPa0nAXPDIQIWzJbMCB7AmcWpGR8lSZfqI=
|
||||||
github.com/tklauser/numcpus v0.10.0/go.mod h1:BiTKazU708GQTYF4mB+cmlpT2Is1gLk7XVuEeem8LsQ=
|
github.com/tklauser/numcpus v0.6.0 h1:kebhY2Qt+3U6RNK7UqpYNA+tJ23IBEGKkB7JQBfDYms=
|
||||||
|
github.com/tklauser/numcpus v0.6.0/go.mod h1:FEZLMke0lhOUG6w2JadTzp0a+Nl8PF/GFkQ5UVIcaL4=
|
||||||
|
github.com/ttacon/chalk v0.0.0-20160626202418-22c06c80ed31/go.mod h1:onvgF043R+lC5RZ8IT9rBXDaEDnpnw/Cl+HFiw+v/7Q=
|
||||||
github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
|
github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
|
||||||
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
|
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
|
||||||
github.com/ugorji/go/codec v1.3.0 h1:Qd2W2sQawAfG8XSvzwhBeoGq71zXOC/Q1E9y/wUcsUA=
|
github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw=
|
||||||
github.com/ugorji/go/codec v1.3.0/go.mod h1:pRBVtBSKl77K30Bv8R2P+cLSGaTtex6fsA2Wjqmfxj4=
|
github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY=
|
||||||
|
github.com/ugorji/go/codec v1.2.10 h1:eimT6Lsr+2lzmSZxPhLFoOWFmQqwk0fllJJ5hEbTXtQ=
|
||||||
|
github.com/ugorji/go/codec v1.2.10/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
|
||||||
github.com/v2fly/ss-bloomring v0.0.0-20210312155135-28617310f63e h1:5QefA066A1tF8gHIiADmOVOV5LS43gt3ONnlEl3xkwI=
|
github.com/v2fly/ss-bloomring v0.0.0-20210312155135-28617310f63e h1:5QefA066A1tF8gHIiADmOVOV5LS43gt3ONnlEl3xkwI=
|
||||||
github.com/v2fly/ss-bloomring v0.0.0-20210312155135-28617310f63e/go.mod h1:5t19P9LBIrNamL6AcMQOncg/r10y3Pc01AbHeMhwlpU=
|
github.com/xtls/reality v0.0.0-20230309125256-0d0713b108c8 h1:LLtLxEe3S0Ko+ckqt4t29RLskpNdOZfgjZCC2/Byr50=
|
||||||
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
|
github.com/xtls/xray-core v1.8.0 h1:/OD0sDv6YIBqvE+cVfnqlKrtbMs0Fm9IP5BR5d8Eu4k=
|
||||||
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
|
github.com/xtls/xray-core v1.8.0/go.mod h1:i9KWgbLyxg/NT+3+g4nE74Zp3DgTCP3X04YkSfsJeDI=
|
||||||
github.com/valyala/fasthttp v1.65.0 h1:j/u3uzFEGFfRxw79iYzJN+TteTJwbYkru9uDp3d0Yf8=
|
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||||
github.com/valyala/fasthttp v1.65.0/go.mod h1:P/93/YkKPMsKSnATEeELUCkG8a7Y+k99uxNHVbKINr4=
|
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||||
github.com/valyala/fastjson v1.6.4 h1:uAUNq9Z6ymTgGhcm0UynUAB6tlbakBrz6CQFax3BXVQ=
|
github.com/yusufpapurcu/wmi v1.2.2 h1:KBNDSne4vP5mbSWnJbO+51IMOXJB67QiYCSBrubbPRg=
|
||||||
github.com/valyala/fastjson v1.6.4/go.mod h1:CLCAqky6SMuOcxStkYQvblddUtoRxhYMGLrsQns1aXY=
|
github.com/yusufpapurcu/wmi v1.2.2/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
|
||||||
github.com/vishvananda/netlink v1.3.1 h1:3AEMt62VKqz90r0tmNhog0r/PpWKmrEShJU0wJW6bV0=
|
go.starlark.net v0.0.0-20230302034142-4b1e35fe2254 h1:Ss6D3hLXTM0KobyBYEAygXzFfGcjnmfEJOBgSbemCtg=
|
||||||
github.com/vishvananda/netlink v1.3.1/go.mod h1:ARtKouGSTGchR8aMwmkzC0qiNPrrWO5JS/XMVl45+b4=
|
go.uber.org/atomic v1.10.0 h1:9qC72Qh0+3MqyJbAn8YU5xVq1frD8bn3JtD2oXtafVQ=
|
||||||
github.com/vishvananda/netns v0.0.5 h1:DfiHV+j8bA32MFM7bfEunvT8IAqQ/NzSJHtcmW5zdEY=
|
go.uber.org/atomic v1.10.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0=
|
||||||
github.com/vishvananda/netns v0.0.5/go.mod h1:SpkAiCQRtJ6TvvxPnOSyH3BMl6unz3xZlaprSwhNNJM=
|
golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
|
||||||
github.com/xlzd/gotp v0.1.0 h1:37blvlKCh38s+fkem+fFh7sMnceltoIEBYTVXyoa5Po=
|
golang.org/x/arch v0.2.0 h1:W1sUEHXiJTfjaFJ5SLo0N6lZn+0eO5gWD1MFeTGqQEY=
|
||||||
github.com/xlzd/gotp v0.1.0/go.mod h1:ndLJ3JKzi3xLmUProq4LLxCuECL93dG9WASNLpHz8qg=
|
golang.org/x/arch v0.2.0/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
|
||||||
github.com/xtls/reality v0.0.0-20250727231020-de3bb4d08f5a h1:Fs8Pc0JAc/LDOf9Q4DzKrk+Ujf4ILlyvfvDVZcmOZ2o=
|
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||||
github.com/xtls/reality v0.0.0-20250727231020-de3bb4d08f5a/go.mod h1:XxvnCCgBee4WWE0bc4E+a7wbk8gkJ/rS0vNVNtC5qp0=
|
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||||
github.com/xtls/xray-core v1.250803.0 h1:sYdRC243UsujnePINH4IfM4MfHE4lj2p4wZFAfeE2GI=
|
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||||
github.com/xtls/xray-core v1.250803.0/go.mod h1:z2vn2o30flYEgpSz1iEhdZP1I46UZ3+gXINZyohH3yE=
|
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||||
github.com/xyproto/randomstring v1.0.5 h1:YtlWPoRdgMu3NZtP45drfy1GKoojuR7hmRcnhZqKjWU=
|
golang.org/x/crypto v0.7.0 h1:AvwMYaRytfdeVt3u6mLaxYtErKYjxA2OXjJ1HHq6t3A=
|
||||||
github.com/xyproto/randomstring v1.0.5/go.mod h1:rgmS5DeNXLivK7YprL0pY+lTuhNQW3iGxZ18UQApw/E=
|
golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU=
|
||||||
github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0=
|
golang.org/x/exp v0.0.0-20230307190834-24139beb5833 h1:SChBja7BCQewoTAU7IgvucQKMIXrEpFxNMs0spT3/5s=
|
||||||
github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
|
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||||
go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA=
|
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||||
go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A=
|
golang.org/x/mod v0.9.0 h1:KENHtAZL2y3NLMYZeHY9DW8HW8V+kQyJsY/V9JlKvCs=
|
||||||
go.opentelemetry.io/otel v1.36.0 h1:UumtzIklRBY6cI/lllNZlALOF5nNIzJVb16APdvgTXg=
|
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||||
go.opentelemetry.io/otel v1.36.0/go.mod h1:/TcFMXYjyRNh8khOAO9ybYkqaDBb/70aVwkNML4pP8E=
|
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
go.opentelemetry.io/otel/metric v1.36.0 h1:MoWPKVhQvJ+eeXWHFBOPoBOi20jh6Iq2CcCREuTYufE=
|
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||||
go.opentelemetry.io/otel/metric v1.36.0/go.mod h1:zC7Ks+yeyJt4xig9DEw9kuUFe5C3zLbVjV2PzT6qzbs=
|
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||||
go.opentelemetry.io/otel/sdk v1.36.0 h1:b6SYIuLRs88ztox4EyrvRti80uXIFy+Sqzoh9kFULbs=
|
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||||
go.opentelemetry.io/otel/sdk v1.36.0/go.mod h1:+lC+mTgD+MUWfjJubi2vvXWcVxyr9rmlshZni72pXeY=
|
golang.org/x/net v0.8.0 h1:Zrh2ngAOFYneWTAIAPethzeaQLuHwhuBkuV6ZiRnUaQ=
|
||||||
go.opentelemetry.io/otel/sdk/metric v1.36.0 h1:r0ntwwGosWGaa0CrSt8cuNuTcccMXERFwHX4dThiPis=
|
golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc=
|
||||||
go.opentelemetry.io/otel/sdk/metric v1.36.0/go.mod h1:qTNOhFDfKRwX0yXOqJYegL5WRaW376QbB7P4Pb0qva4=
|
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
go.opentelemetry.io/otel/trace v1.36.0 h1:ahxWNuqZjpdiFAyrIoQ4GIiAIhxAunQR6MUoKrsNd4w=
|
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
go.opentelemetry.io/otel/trace v1.36.0/go.mod h1:gQ+OnDZzrybY4k4seLzPAWNwVBBVlF2szhehOBB/tGA=
|
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE=
|
golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o=
|
||||||
go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0=
|
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
go.uber.org/mock v0.5.2 h1:LbtPTcP8A5k9WPXj54PPPbjcI4Y6lhyOZXn+VS7wNko=
|
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
go.uber.org/mock v0.5.2/go.mod h1:wLlUxC2vVTPTaE3UD51E0BGOAElKrILxhVSDYQLld5o=
|
|
||||||
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba h1:0b9z3AuHCjxk0x/opv64kcgZLBseWJUpBw5I82+2U4M=
|
|
||||||
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba/go.mod h1:PLyyIXexvUFg3Owu6p/WfdlivPbZJsZdgWZlrGope/Y=
|
|
||||||
golang.org/x/arch v0.20.0 h1:dx1zTU0MAE98U+TQ8BLl7XsJbgze2WnNKF/8tGp/Q6c=
|
|
||||||
golang.org/x/arch v0.20.0/go.mod h1:bdwinDaKcfZUGpH09BB7ZmOfhalA8lQdzl62l8gGWsk=
|
|
||||||
golang.org/x/crypto v0.41.0 h1:WKYxWedPGCTVVl5+WHSSrOBT0O8lx32+zxmHxijgXp4=
|
|
||||||
golang.org/x/crypto v0.41.0/go.mod h1:pO5AFd7FA68rFak7rOAGVuygIISepHftHnr8dr6+sUc=
|
|
||||||
golang.org/x/mod v0.27.0 h1:kb+q2PyFnEADO2IEF935ehFUXlWiNjJWtRNgBLSfbxQ=
|
|
||||||
golang.org/x/mod v0.27.0/go.mod h1:rWI627Fq0DEoudcK+MBkNkCe0EetEaDSwJJkCcjpazc=
|
|
||||||
golang.org/x/net v0.43.0 h1:lat02VYK2j4aLzMzecihNvTlJNQUq316m2Mr9rnM6YE=
|
|
||||||
golang.org/x/net v0.43.0/go.mod h1:vhO1fvI4dGsIjh73sWfUVjj3N7CA9WkKJNQm2svM6Jg=
|
|
||||||
golang.org/x/sync v0.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw=
|
|
||||||
golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
|
|
||||||
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.6.0 h1:MVltZSvRTcU2ljQOhs94SXPftV6DCNnZViHeQps87pQ=
|
||||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||||
golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI=
|
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||||
golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
golang.org/x/text v0.28.0 h1:rhazDwis8INMIwQ4tpjLDzUhx6RlXqZNPEM0huQojng=
|
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||||
golang.org/x/text v0.28.0/go.mod h1:U8nCwOR8jO/marOQ0QbDiOngZVEBB7MAiitBuMjXiNU=
|
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
golang.org/x/time v0.12.0 h1:ScB/8o8olJvc+CQPWrK3fPZNfh7qgwCrY0zJmoEQLSE=
|
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||||
golang.org/x/time v0.12.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg=
|
golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||||
golang.org/x/tools v0.36.0 h1:kWS0uv/zsvHEle1LbV5LE8QujrxB3wfQyxHfhOk0Qkg=
|
golang.org/x/text v0.8.0 h1:57P1ETyNKtuIjB4SRd15iJxuhj8Gc416Y78H3qgMh68=
|
||||||
golang.org/x/tools v0.36.0/go.mod h1:WBDiHKJK8YgLHlcQPYQzNCkUxUypCaa5ZegCVutKm+s=
|
golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
||||||
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 h1:B82qJJgjvYKsXS9jeunTOisW56dUokqW/FOteYJJ/yg=
|
golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4=
|
||||||
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2/go.mod h1:deeaetjYA+DHMHg+sMSMI58GrEteJUUzzw7en6TJQcI=
|
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
golang.zx2c4.com/wireguard v0.0.0-20250521234502-f333402bd9cb h1:whnFRlWMcXI9d+ZbWg+4sHnLp52d5yiIPUxMBSt4X9A=
|
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||||
golang.zx2c4.com/wireguard v0.0.0-20250521234502-f333402bd9cb/go.mod h1:rpwXGsirqLqN2L0JDJQlwOboGHmptD5ZD6T2VmcqhTw=
|
golang.org/x/tools v0.0.0-20201022035929-9cf592e881e9/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250811230008-5f3141c8851a h1:tPE/Kp+x9dMSwUm/uM0JKK0IfdiJkwAbSMSeZBXXJXc=
|
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250811230008-5f3141c8851a/go.mod h1:gw1tLEfykwDz2ET4a12jcXt4couGAm7IwsVaTy0Sflo=
|
golang.org/x/tools v0.7.0 h1:W4OVu8VVOaIO0yzWMNdepAulS7YfoS3Zabrm8DOXXU4=
|
||||||
google.golang.org/grpc v1.74.2 h1:WoosgB65DlWVC9FqI82dGsZhWFNBSLjQ84bjROOpMu4=
|
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
google.golang.org/grpc v1.74.2/go.mod h1:CtQ+BGjaAIXHs/5YS3i473GqwBBa1zGQNevxdeBEXrM=
|
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
google.golang.org/protobuf v1.36.7 h1:IgrO7UwFQGJdRNXH/sQux4R1Dj1WAKcLElzeeRaXV2A=
|
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
google.golang.org/protobuf v1.36.7/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY=
|
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
|
google.golang.org/genproto v0.0.0-20230306155012-7f2fa6fef1f4 h1:DdoeryqhaXp1LtT/emMP1BRJPHHKFi5akj/nbx/zNTA=
|
||||||
|
google.golang.org/genproto v0.0.0-20230306155012-7f2fa6fef1f4/go.mod h1:NWraEVixdDnqcqQ30jipen1STv2r/n24Wb7twVTGR4s=
|
||||||
|
google.golang.org/grpc v1.54.0 h1:EhTqbhiYeixwWQtAEZAxmV9MGqcjEU2mFx52xCzNyag=
|
||||||
|
google.golang.org/grpc v1.54.0/go.mod h1:PUSEXI6iWghWaB6lXM4knEgpJNu2qUcKfDtNci3EC2g=
|
||||||
|
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
||||||
|
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
||||||
|
google.golang.org/protobuf v1.29.1 h1:7QBf+IK2gx70Ap/hDsOmam3GE0v9HicjfEdAxE62UoM=
|
||||||
|
google.golang.org/protobuf v1.29.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
|
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
|
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
||||||
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
|
||||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
gopkg.in/yaml.v3 v3.0.0-20200605160147-a5ece683394c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
|
||||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
gorm.io/driver/sqlite v1.6.0 h1:WHRRrIiulaPiPFmDcod6prc4l2VGVWHz80KspNsxSfQ=
|
gorm.io/driver/sqlite v1.4.4 h1:gIufGoR0dQzjkyqDyYSCvsYR6fba1Gw5YKDqKeChxFc=
|
||||||
gorm.io/driver/sqlite v1.6.0/go.mod h1:AO9V1qIQddBESngQUKWL9yoH93HIeA1X6V633rBwyT8=
|
gorm.io/driver/sqlite v1.4.4/go.mod h1:0Aq3iPO+v9ZKbcdiz8gLWRw5VOPcBOPUQJFLq5e2ecI=
|
||||||
gorm.io/gorm v1.30.1 h1:lSHg33jJTBxs2mgJRfRZeLDG+WZaHYCk3Wtfl6Ngzo4=
|
gorm.io/gorm v1.24.0/go.mod h1:DVrVomtaYTbqs7gB/x2uVvqnXzv0nqjB396B8cG4dBA=
|
||||||
gorm.io/gorm v1.30.1/go.mod h1:8Z33v652h4//uMA76KjeDH8mJXPm1QNCYrMeatR0DOE=
|
gorm.io/gorm v1.24.6 h1:wy98aq9oFEetsc4CAbKD2SoBCdMzsbSIvSUUFJuHi5s=
|
||||||
gvisor.dev/gvisor v0.0.0-20250503011706-39ed1f5ac29c h1:m/r7OM+Y2Ty1sgBQ7Qb27VgIMBW8ZZhT4gLnUyDIhzI=
|
gorm.io/gorm v1.24.6/go.mod h1:L4uxeKpfBml98NYqVqwAdmV1a2nBtAec/cf3fpucW/k=
|
||||||
gvisor.dev/gvisor v0.0.0-20250503011706-39ed1f5ac29c/go.mod h1:3r5CMtNQMKIvBlrmM9xWUNamjKBYPOWyXOjmg5Kts3g=
|
gvisor.dev/gvisor v0.0.0-20220901235040-6ca97ef2ce1c h1:m5lcgWnL3OElQNVyp3qcncItJ2c0sQlSGjYK2+nJTA4=
|
||||||
lukechampine.com/blake3 v1.4.1 h1:I3Smz7gso8w4/TunLKec6K2fn+kyKtDxr/xcQEN84Wg=
|
lukechampine.com/blake3 v1.1.7 h1:GgRMhmdsuK8+ii6UZFDL8Nb+VyMwadAgcJyfYHxG6n0=
|
||||||
lukechampine.com/blake3 v1.4.1/go.mod h1:QFosUxmjB8mnrWFSNwKmvxHpfY72bmD2tQ0kBMM3kwo=
|
rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=
|
||||||
|
|
284
install.sh
|
@ -2,14 +2,13 @@
|
||||||
|
|
||||||
red='\033[0;31m'
|
red='\033[0;31m'
|
||||||
green='\033[0;32m'
|
green='\033[0;32m'
|
||||||
blue='\033[0;34m'
|
|
||||||
yellow='\033[0;33m'
|
yellow='\033[0;33m'
|
||||||
plain='\033[0m'
|
plain='\033[0m'
|
||||||
|
|
||||||
cur_dir=$(pwd)
|
cur_dir=$(pwd)
|
||||||
|
|
||||||
# check root
|
# check root
|
||||||
[[ $EUID -ne 0 ]] && echo -e "${red}Fatal error: ${plain} Please run this script with root privilege \n " && exit 1
|
[[ $EUID -ne 0 ]] && echo -e "${red}Fatal error:${plain} Please run this script with root privilege \n " && exit 1
|
||||||
|
|
||||||
# Check OS and set release variable
|
# Check OS and set release variable
|
||||||
if [[ -f /etc/os-release ]]; then
|
if [[ -f /etc/os-release ]]; then
|
||||||
|
@ -24,207 +23,156 @@ else
|
||||||
fi
|
fi
|
||||||
echo "The OS release is: $release"
|
echo "The OS release is: $release"
|
||||||
|
|
||||||
arch() {
|
arch=$(arch)
|
||||||
case "$(uname -m)" in
|
|
||||||
x86_64 | x64 | amd64) echo 'amd64' ;;
|
|
||||||
i*86 | x86) echo '386' ;;
|
|
||||||
armv8* | armv8 | arm64 | aarch64) echo 'arm64' ;;
|
|
||||||
armv7* | armv7 | arm) echo 'armv7' ;;
|
|
||||||
armv6* | armv6) echo 'armv6' ;;
|
|
||||||
armv5* | armv5) echo 'armv5' ;;
|
|
||||||
s390x) echo 's390x' ;;
|
|
||||||
*) echo -e "${green}Unsupported CPU architecture! ${plain}" && rm -f install.sh && exit 1 ;;
|
|
||||||
esac
|
|
||||||
}
|
|
||||||
|
|
||||||
echo "Arch: $(arch)"
|
if [[ $arch == "x86_64" || $arch == "x64" || $arch == "amd64" ]]; then
|
||||||
|
arch="amd64"
|
||||||
|
elif [[ $arch == "aarch64" || $arch == "arm64" ]]; then
|
||||||
|
arch="arm64"
|
||||||
|
else
|
||||||
|
arch="amd64"
|
||||||
|
echo -e "${red} Failed to check system arch, will use default arch: ${arch}${plain}"
|
||||||
|
fi
|
||||||
|
|
||||||
install_base() {
|
echo "arch: ${arch}"
|
||||||
case "${release}" in
|
|
||||||
ubuntu | debian | armbian)
|
|
||||||
apt-get update && apt-get install -y -q wget curl tar tzdata
|
|
||||||
;;
|
|
||||||
centos | rhel | almalinux | rocky | ol)
|
|
||||||
yum -y update && yum install -y -q wget curl tar tzdata
|
|
||||||
;;
|
|
||||||
fedora | amzn | virtuozzo)
|
|
||||||
dnf -y update && dnf install -y -q wget curl tar tzdata
|
|
||||||
;;
|
|
||||||
arch | manjaro | parch)
|
|
||||||
pacman -Syu && pacman -Syu --noconfirm wget curl tar tzdata
|
|
||||||
;;
|
|
||||||
opensuse-tumbleweed)
|
|
||||||
zypper refresh && zypper -q install -y wget curl tar timezone
|
|
||||||
;;
|
|
||||||
*)
|
|
||||||
apt-get update && apt-get install -y -q wget curl tar tzdata
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
}
|
|
||||||
|
|
||||||
gen_random_string() {
|
if [ $(getconf WORD_BIT) != '32' ] && [ $(getconf LONG_BIT) != '64' ]; then
|
||||||
local length="$1"
|
echo "x-ui dosen't support 32-bit(x86) system, please use 64 bit operating system(x86_64) instead, if there is something wrong, please get in touch with me!"
|
||||||
local random_string=$(LC_ALL=C tr -dc 'a-zA-Z0-9' </dev/urandom | fold -w "$length" | head -n 1)
|
exit -1
|
||||||
echo "$random_string"
|
fi
|
||||||
}
|
|
||||||
|
|
||||||
config_after_install() {
|
os_version=""
|
||||||
local existing_hasDefaultCredential=$(/usr/local/x-ui/x-ui setting -show true | grep -Eo 'hasDefaultCredential: .+' | awk '{print $2}')
|
os_version=$(grep -i version_id /etc/os-release | cut -d \" -f2 | cut -d . -f1)
|
||||||
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 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
|
|
||||||
|
|
||||||
if [[ ${#existing_webBasePath} -lt 4 ]]; then
|
if [[ "${release}" == "centos" ]]; then
|
||||||
if [[ "$existing_hasDefaultCredential" == "true" ]]; then
|
if [[ ${os_version} -lt 8 ]]; then
|
||||||
local config_webBasePath=$(gen_random_string 18)
|
echo -e "${red} Please use CentOS 8 or higher ${plain}\n" && exit 1
|
||||||
local config_username=$(gen_random_string 10)
|
fi
|
||||||
local config_password=$(gen_random_string 10)
|
elif [[ "${release}" == "ubuntu" ]]; then
|
||||||
|
if [[ ${os_version} -lt 20 ]]; then
|
||||||
read -rp "Would you like to customize the Panel Port settings? (If not, a random port will be applied) [y/n]: " config_confirm
|
echo -e "${red}please use Ubuntu 20 or higher version!${plain}\n" && exit 1
|
||||||
if [[ "${config_confirm}" == "y" || "${config_confirm}" == "Y" ]]; then
|
|
||||||
read -rp "Please set up the panel port: " config_port
|
|
||||||
echo -e "${yellow}Your Panel Port is: ${config_port}${plain}"
|
|
||||||
else
|
|
||||||
local config_port=$(shuf -i 1024-62000 -n 1)
|
|
||||||
echo -e "${yellow}Generated random port: ${config_port}${plain}"
|
|
||||||
fi
|
|
||||||
|
|
||||||
/usr/local/x-ui/x-ui setting -username "${config_username}" -password "${config_password}" -port "${config_port}" -webBasePath "${config_webBasePath}"
|
|
||||||
echo -e "This is a fresh installation, generating random login info for security concerns:"
|
|
||||||
echo -e "###############################################"
|
|
||||||
echo -e "${green}Username: ${config_username}${plain}"
|
|
||||||
echo -e "${green}Password: ${config_password}${plain}"
|
|
||||||
echo -e "${green}Port: ${config_port}${plain}"
|
|
||||||
echo -e "${green}WebBasePath: ${config_webBasePath}${plain}"
|
|
||||||
echo -e "${green}Access URL: http://${server_ip}:${config_port}/${config_webBasePath}${plain}"
|
|
||||||
echo -e "###############################################"
|
|
||||||
else
|
|
||||||
local config_webBasePath=$(gen_random_string 18)
|
|
||||||
echo -e "${yellow}WebBasePath is missing or too short. Generating a new one...${plain}"
|
|
||||||
/usr/local/x-ui/x-ui setting -webBasePath "${config_webBasePath}"
|
|
||||||
echo -e "${green}New WebBasePath: ${config_webBasePath}${plain}"
|
|
||||||
echo -e "${green}Access URL: http://${server_ip}:${existing_port}/${config_webBasePath}${plain}"
|
|
||||||
fi
|
|
||||||
else
|
|
||||||
if [[ "$existing_hasDefaultCredential" == "true" ]]; then
|
|
||||||
local config_username=$(gen_random_string 10)
|
|
||||||
local config_password=$(gen_random_string 10)
|
|
||||||
|
|
||||||
echo -e "${yellow}Default credentials detected. Security update required...${plain}"
|
|
||||||
/usr/local/x-ui/x-ui setting -username "${config_username}" -password "${config_password}"
|
|
||||||
echo -e "Generated new random login credentials:"
|
|
||||||
echo -e "###############################################"
|
|
||||||
echo -e "${green}Username: ${config_username}${plain}"
|
|
||||||
echo -e "${green}Password: ${config_password}${plain}"
|
|
||||||
echo -e "###############################################"
|
|
||||||
else
|
|
||||||
echo -e "${green}Username, Password, and WebBasePath are properly set. Exiting...${plain}"
|
|
||||||
fi
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
/usr/local/x-ui/x-ui migrate
|
elif [[ "${release}" == "fedora" ]]; then
|
||||||
|
if [[ ${os_version} -lt 36 ]]; then
|
||||||
|
echo -e "${red}please use Fedora 36 or higher version!${plain}\n" && exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
elif [[ "${release}" == "debian" ]]; then
|
||||||
|
if [[ ${os_version} -lt 10 ]]; then
|
||||||
|
echo -e "${red} Please use Debian 10 or higher ${plain}\n" && exit 1
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
echo -e "${red}Failed to check the OS version, please contact the author!${plain}" && exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
|
||||||
|
install_base() {
|
||||||
|
if [[ "${release}" == "centos" ]]; then
|
||||||
|
yum install wget curl tar -y
|
||||||
|
else
|
||||||
|
apt install wget curl tar -y
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
#This function will be called when user installed x-ui out of sercurity
|
||||||
|
config_after_install() {
|
||||||
|
echo -e "${yellow}Install/update finished! For security it's recommended to modify panel settings ${plain}"
|
||||||
|
read -p "Do you want to continue with the modification [y/n]? ": config_confirm
|
||||||
|
if [[ x"${config_confirm}" == x"y" || x"${config_confirm}" == x"Y" ]]; then
|
||||||
|
read -p "Please set up your username:" config_account
|
||||||
|
echo -e "${yellow}Your username will be:${config_account}${plain}"
|
||||||
|
read -p "Please set up your password:" config_password
|
||||||
|
echo -e "${yellow}Your password will be:${config_password}${plain}"
|
||||||
|
read -p "Please set up the panel port:" config_port
|
||||||
|
echo -e "${yellow}Your panel port is:${config_port}${plain}"
|
||||||
|
echo -e "${yellow}Initializing, please wait...${plain}"
|
||||||
|
/usr/local/x-ui/x-ui setting -username ${config_account} -password ${config_password}
|
||||||
|
echo -e "${yellow}Account name and password set successfully!${plain}"
|
||||||
|
/usr/local/x-ui/x-ui setting -port ${config_port}
|
||||||
|
echo -e "${yellow}Panel port set successfully!${plain}"
|
||||||
|
else
|
||||||
|
echo -e "${red}cancel...${plain}"
|
||||||
|
if [[ ! -f "/etc/x-ui/x-ui.db" ]]; then
|
||||||
|
local usernameTemp=$(head -c 6 /dev/urandom | base64)
|
||||||
|
local passwordTemp=$(head -c 6 /dev/urandom | base64)
|
||||||
|
/usr/local/x-ui/x-ui setting -username ${usernameTemp} -password ${passwordTemp}
|
||||||
|
echo -e "this is a fresh installation,will generate random login info for security concerns:"
|
||||||
|
echo -e "###############################################"
|
||||||
|
echo -e "${green}username:${usernameTemp}${plain}"
|
||||||
|
echo -e "${green}password:${passwordTemp}${plain}"
|
||||||
|
echo -e "###############################################"
|
||||||
|
echo -e "${red}if you forgot your login info,you can type x-ui and then type 7 to check after installation${plain}"
|
||||||
|
else
|
||||||
|
echo -e "${red} this is your upgrade,will keep old settings,if you forgot your login info,you can type x-ui and then type 7 to check${plain}"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
install_x-ui() {
|
install_x-ui() {
|
||||||
|
systemctl stop x-ui
|
||||||
cd /usr/local/
|
cd /usr/local/
|
||||||
|
|
||||||
# Download resources
|
|
||||||
if [ $# == 0 ]; then
|
if [ $# == 0 ]; then
|
||||||
tag_version=$(curl -Ls "https://api.github.com/repos/MHSanaei/3x-ui/releases/latest" | grep '"tag_name":' | sed -E 's/.*"([^"]+)".*/\1/')
|
last_version=$(curl -Ls "https://api.github.com/repos/MHSanaei/3x-ui/releases/latest" | grep '"tag_name":' | sed -E 's/.*"([^"]+)".*/\1/')
|
||||||
if [[ ! -n "$tag_version" ]]; then
|
if [[ ! -n "$last_version" ]]; then
|
||||||
echo -e "${red}Failed to fetch x-ui version, it may be due to GitHub API restrictions, please try it later${plain}"
|
echo -e "${red}Failed to fetch x-ui version, it maybe due to Github API restrictions, please try it later${plain}"
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
echo -e "Got x-ui latest version: ${tag_version}, beginning the installation..."
|
echo -e "Got x-ui latest version: ${last_version}, beginning the installation..."
|
||||||
wget -N -O /usr/local/x-ui-linux-$(arch).tar.gz https://github.com/MHSanaei/3x-ui/releases/download/${tag_version}/x-ui-linux-$(arch).tar.gz
|
wget -N --no-check-certificate -O /usr/local/x-ui-linux-${arch}.tar.gz https://github.com/MHSanaei/3x-ui/releases/download/${last_version}/x-ui-linux-${arch}.tar.gz
|
||||||
if [[ $? -ne 0 ]]; then
|
if [[ $? -ne 0 ]]; then
|
||||||
echo -e "${red}Downloading x-ui failed, please be sure that your server can access GitHub ${plain}"
|
echo -e "${red}Downloading x-ui failed, please be sure that your server can access Github ${plain}"
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
else
|
else
|
||||||
tag_version=$1
|
last_version=$1
|
||||||
tag_version_numeric=${tag_version#v}
|
url="https://github.com/MHSanaei/3x-ui/releases/download/${last_version}/x-ui-linux-${arch}.tar.gz"
|
||||||
min_version="2.3.5"
|
echo -e "Begining to install x-ui $1"
|
||||||
|
wget -N --no-check-certificate -O /usr/local/x-ui-linux-${arch}.tar.gz ${url}
|
||||||
if [[ "$(printf '%s\n' "$min_version" "$tag_version_numeric" | sort -V | head -n1)" != "$min_version" ]]; then
|
|
||||||
echo -e "${red}Please use a newer version (at least v2.3.5). Exiting installation.${plain}"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
url="https://github.com/MHSanaei/3x-ui/releases/download/${tag_version}/x-ui-linux-$(arch).tar.gz"
|
|
||||||
echo -e "Beginning to install x-ui $1"
|
|
||||||
wget -N -O /usr/local/x-ui-linux-$(arch).tar.gz ${url}
|
|
||||||
if [[ $? -ne 0 ]]; then
|
if [[ $? -ne 0 ]]; then
|
||||||
echo -e "${red}Download x-ui $1 failed, please check if the version exists ${plain}"
|
echo -e "${red}Download x-ui $1 failed,please check the version exists${plain}"
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
wget -O /usr/bin/x-ui-temp https://raw.githubusercontent.com/MHSanaei/3x-ui/main/x-ui.sh
|
|
||||||
|
|
||||||
# Stop x-ui service and remove old resources
|
|
||||||
if [[ -e /usr/local/x-ui/ ]]; then
|
if [[ -e /usr/local/x-ui/ ]]; then
|
||||||
systemctl stop x-ui
|
|
||||||
rm /usr/local/x-ui/ -rf
|
rm /usr/local/x-ui/ -rf
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Extract resources and set permissions
|
tar zxvf x-ui-linux-${arch}.tar.gz
|
||||||
tar zxvf x-ui-linux-$(arch).tar.gz
|
rm x-ui-linux-${arch}.tar.gz -f
|
||||||
rm x-ui-linux-$(arch).tar.gz -f
|
|
||||||
|
|
||||||
cd x-ui
|
cd x-ui
|
||||||
chmod +x x-ui
|
chmod +x x-ui bin/xray-linux-${arch}
|
||||||
chmod +x x-ui.sh
|
cp -f x-ui.service /etc/systemd/system/
|
||||||
|
wget --no-check-certificate -O /usr/bin/x-ui https://raw.githubusercontent.com/MHSanaei/3x-ui/main/x-ui.sh
|
||||||
# Check the system's architecture and rename the file accordingly
|
chmod +x /usr/local/x-ui/x-ui.sh
|
||||||
if [[ $(arch) == "armv5" || $(arch) == "armv6" || $(arch) == "armv7" ]]; then
|
|
||||||
mv bin/xray-linux-$(arch) bin/xray-linux-arm
|
|
||||||
chmod +x bin/xray-linux-arm
|
|
||||||
fi
|
|
||||||
chmod +x x-ui bin/xray-linux-$(arch)
|
|
||||||
|
|
||||||
# Update x-ui cli and se set permission
|
|
||||||
mv -f /usr/bin/x-ui-temp /usr/bin/x-ui
|
|
||||||
chmod +x /usr/bin/x-ui
|
chmod +x /usr/bin/x-ui
|
||||||
config_after_install
|
config_after_install
|
||||||
|
#echo -e "If it is a new installation, the default web port is ${green}2053${plain}, The username and password are ${green}admin${plain} by default"
|
||||||
cp -f x-ui.service /etc/systemd/system/
|
#echo -e "Please make sure that this port is not occupied by other procedures,${yellow} And make sure that port 2053 has been released${plain}"
|
||||||
|
# echo -e "If you want to modify the 2053 to other ports and enter the x-ui command to modify it, you must also ensure that the port you modify is also released"
|
||||||
|
#echo -e ""
|
||||||
|
#echo -e "If it is updated panel, access the panel in your previous way"
|
||||||
|
#echo -e ""
|
||||||
systemctl daemon-reload
|
systemctl daemon-reload
|
||||||
systemctl enable x-ui
|
systemctl enable x-ui
|
||||||
systemctl start x-ui
|
systemctl start x-ui
|
||||||
echo -e "${green}x-ui ${tag_version}${plain} installation finished, it is running now..."
|
echo -e "${green}x-ui ${last_version}${plain} installation finished, it is running now..."
|
||||||
echo -e ""
|
echo -e ""
|
||||||
echo -e "┌───────────────────────────────────────────────────────┐
|
echo -e "x-ui control menu usages: "
|
||||||
│ ${blue}x-ui control menu usages (subcommands):${plain} │
|
echo -e "----------------------------------------------"
|
||||||
│ │
|
echo -e "x-ui - Enter Admin menu"
|
||||||
│ ${blue}x-ui${plain} - Admin Management Script │
|
echo -e "x-ui start - Start x-ui"
|
||||||
│ ${blue}x-ui start${plain} - Start │
|
echo -e "x-ui stop - Stop x-ui"
|
||||||
│ ${blue}x-ui stop${plain} - Stop │
|
echo -e "x-ui restart - Restart x-ui"
|
||||||
│ ${blue}x-ui restart${plain} - Restart │
|
echo -e "x-ui status - Show x-ui status"
|
||||||
│ ${blue}x-ui status${plain} - Current Status │
|
echo -e "x-ui enable - Enable x-ui on system startup"
|
||||||
│ ${blue}x-ui settings${plain} - Current Settings │
|
echo -e "x-ui disable - Disable x-ui on system startup"
|
||||||
│ ${blue}x-ui enable${plain} - Enable Autostart on OS Startup │
|
echo -e "x-ui log - Check x-ui logs"
|
||||||
│ ${blue}x-ui disable${plain} - Disable Autostart on OS Startup │
|
echo -e "x-ui update - Update x-ui"
|
||||||
│ ${blue}x-ui log${plain} - Check logs │
|
echo -e "x-ui install - Install x-ui"
|
||||||
│ ${blue}x-ui banlog${plain} - Check Fail2ban ban logs │
|
echo -e "x-ui uninstall - Uninstall x-ui"
|
||||||
│ ${blue}x-ui update${plain} - Update │
|
echo -e "----------------------------------------------"
|
||||||
│ ${blue}x-ui legacy${plain} - legacy version │
|
|
||||||
│ ${blue}x-ui install${plain} - Install │
|
|
||||||
│ ${blue}x-ui uninstall${plain} - Uninstall │
|
|
||||||
└───────────────────────────────────────────────────────┘"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
echo -e "${green}Running...${plain}"
|
echo -e "${green}Running...${plain}"
|
||||||
|
|
100
logger/logger.go
|
@ -1,128 +1,58 @@
|
||||||
package logger
|
package logger
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/op/go-logging"
|
"github.com/op/go-logging"
|
||||||
|
"os"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var logger *logging.Logger
|
||||||
logger *logging.Logger
|
|
||||||
logBuffer []struct {
|
|
||||||
time string
|
|
||||||
level logging.Level
|
|
||||||
log string
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
InitLogger(logging.INFO)
|
InitLogger(logging.INFO)
|
||||||
}
|
}
|
||||||
|
|
||||||
func InitLogger(level logging.Level) {
|
func InitLogger(level logging.Level) {
|
||||||
|
format := logging.MustStringFormatter(
|
||||||
|
`%{time:2006/01/02 15:04:05} %{level} - %{message}`,
|
||||||
|
)
|
||||||
newLogger := logging.MustGetLogger("x-ui")
|
newLogger := logging.MustGetLogger("x-ui")
|
||||||
var err error
|
backend := logging.NewLogBackend(os.Stderr, "", 0)
|
||||||
var backend logging.Backend
|
|
||||||
var format logging.Formatter
|
|
||||||
ppid := os.Getppid()
|
|
||||||
|
|
||||||
backend, err = logging.NewSyslogBackend("")
|
|
||||||
if err != nil {
|
|
||||||
println(err)
|
|
||||||
backend = logging.NewLogBackend(os.Stderr, "", 0)
|
|
||||||
}
|
|
||||||
if ppid > 0 && err != nil {
|
|
||||||
format = logging.MustStringFormatter(`%{time:2006/01/02 15:04:05} %{level} - %{message}`)
|
|
||||||
} else {
|
|
||||||
format = logging.MustStringFormatter(`%{level} - %{message}`)
|
|
||||||
}
|
|
||||||
|
|
||||||
backendFormatter := logging.NewBackendFormatter(backend, format)
|
backendFormatter := logging.NewBackendFormatter(backend, format)
|
||||||
backendLeveled := logging.AddModuleLevel(backendFormatter)
|
backendLeveled := logging.AddModuleLevel(backendFormatter)
|
||||||
backendLeveled.SetLevel(level, "x-ui")
|
backendLeveled.SetLevel(level, "")
|
||||||
newLogger.SetBackend(backendLeveled)
|
newLogger.SetBackend(backendLeveled)
|
||||||
|
|
||||||
logger = newLogger
|
logger = newLogger
|
||||||
}
|
}
|
||||||
|
|
||||||
func Debug(args ...any) {
|
func Debug(args ...interface{}) {
|
||||||
logger.Debug(args...)
|
logger.Debug(args...)
|
||||||
addToBuffer("DEBUG", fmt.Sprint(args...))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func Debugf(format string, args ...any) {
|
func Debugf(format string, args ...interface{}) {
|
||||||
logger.Debugf(format, args...)
|
logger.Debugf(format, args...)
|
||||||
addToBuffer("DEBUG", fmt.Sprintf(format, args...))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func Info(args ...any) {
|
func Info(args ...interface{}) {
|
||||||
logger.Info(args...)
|
logger.Info(args...)
|
||||||
addToBuffer("INFO", fmt.Sprint(args...))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func Infof(format string, args ...any) {
|
func Infof(format string, args ...interface{}) {
|
||||||
logger.Infof(format, args...)
|
logger.Infof(format, args...)
|
||||||
addToBuffer("INFO", fmt.Sprintf(format, args...))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func Notice(args ...any) {
|
func Warning(args ...interface{}) {
|
||||||
logger.Notice(args...)
|
|
||||||
addToBuffer("NOTICE", fmt.Sprint(args...))
|
|
||||||
}
|
|
||||||
|
|
||||||
func Noticef(format string, args ...any) {
|
|
||||||
logger.Noticef(format, args...)
|
|
||||||
addToBuffer("NOTICE", fmt.Sprintf(format, args...))
|
|
||||||
}
|
|
||||||
|
|
||||||
func Warning(args ...any) {
|
|
||||||
logger.Warning(args...)
|
logger.Warning(args...)
|
||||||
addToBuffer("WARNING", fmt.Sprint(args...))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func Warningf(format string, args ...any) {
|
func Warningf(format string, args ...interface{}) {
|
||||||
logger.Warningf(format, args...)
|
logger.Warningf(format, args...)
|
||||||
addToBuffer("WARNING", fmt.Sprintf(format, args...))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func Error(args ...any) {
|
func Error(args ...interface{}) {
|
||||||
logger.Error(args...)
|
logger.Error(args...)
|
||||||
addToBuffer("ERROR", fmt.Sprint(args...))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func Errorf(format string, args ...any) {
|
func Errorf(format string, args ...interface{}) {
|
||||||
logger.Errorf(format, args...)
|
logger.Errorf(format, args...)
|
||||||
addToBuffer("ERROR", fmt.Sprintf(format, args...))
|
|
||||||
}
|
|
||||||
|
|
||||||
func addToBuffer(level string, newLog string) {
|
|
||||||
t := time.Now()
|
|
||||||
if len(logBuffer) >= 10240 {
|
|
||||||
logBuffer = logBuffer[1:]
|
|
||||||
}
|
|
||||||
|
|
||||||
logLevel, _ := logging.LogLevel(level)
|
|
||||||
logBuffer = append(logBuffer, struct {
|
|
||||||
time string
|
|
||||||
level logging.Level
|
|
||||||
log string
|
|
||||||
}{
|
|
||||||
time: t.Format("2006/01/02 15:04:05"),
|
|
||||||
level: logLevel,
|
|
||||||
log: newLog,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func GetLogs(c int, level string) []string {
|
|
||||||
var output []string
|
|
||||||
logLevel, _ := logging.LogLevel(level)
|
|
||||||
|
|
||||||
for i := len(logBuffer) - 1; i >= 0 && len(output) <= c; i-- {
|
|
||||||
if logBuffer[i].level <= logLevel {
|
|
||||||
output = append(output, fmt.Sprintf("%s %s - %s", logBuffer[i].time, logBuffer[i].level, logBuffer[i].log))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return output
|
|
||||||
}
|
}
|
||||||
|
|
326
main.go
|
@ -8,104 +8,69 @@ import (
|
||||||
"os/signal"
|
"os/signal"
|
||||||
"syscall"
|
"syscall"
|
||||||
_ "unsafe"
|
_ "unsafe"
|
||||||
|
|
||||||
"x-ui/config"
|
"x-ui/config"
|
||||||
"x-ui/database"
|
"x-ui/database"
|
||||||
"x-ui/logger"
|
"x-ui/logger"
|
||||||
"x-ui/sub"
|
"x-ui/v2ui"
|
||||||
"x-ui/util/crypto"
|
|
||||||
"x-ui/web"
|
"x-ui/web"
|
||||||
"x-ui/web/global"
|
"x-ui/web/global"
|
||||||
"x-ui/web/service"
|
"x-ui/web/service"
|
||||||
|
|
||||||
"github.com/joho/godotenv"
|
|
||||||
"github.com/op/go-logging"
|
"github.com/op/go-logging"
|
||||||
)
|
)
|
||||||
|
|
||||||
func runWebServer() {
|
func runWebServer() {
|
||||||
log.Printf("Starting %v %v", config.GetName(), config.GetVersion())
|
log.Printf("%v %v", config.GetName(), config.GetVersion())
|
||||||
|
|
||||||
switch config.GetLogLevel() {
|
switch config.GetLogLevel() {
|
||||||
case config.Debug:
|
case config.Debug:
|
||||||
logger.InitLogger(logging.DEBUG)
|
logger.InitLogger(logging.DEBUG)
|
||||||
case config.Info:
|
case config.Info:
|
||||||
logger.InitLogger(logging.INFO)
|
logger.InitLogger(logging.INFO)
|
||||||
case config.Notice:
|
|
||||||
logger.InitLogger(logging.NOTICE)
|
|
||||||
case config.Warn:
|
case config.Warn:
|
||||||
logger.InitLogger(logging.WARNING)
|
logger.InitLogger(logging.WARNING)
|
||||||
case config.Error:
|
case config.Error:
|
||||||
logger.InitLogger(logging.ERROR)
|
logger.InitLogger(logging.ERROR)
|
||||||
default:
|
default:
|
||||||
log.Fatalf("Unknown log level: %v", config.GetLogLevel())
|
log.Fatal("unknown log level:", config.GetLogLevel())
|
||||||
}
|
}
|
||||||
|
|
||||||
godotenv.Load()
|
|
||||||
|
|
||||||
err := database.InitDB(config.GetDBPath())
|
err := database.InitDB(config.GetDBPath())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("Error initializing database: %v", err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
var server *web.Server
|
var server *web.Server
|
||||||
|
|
||||||
server = web.NewServer()
|
server = web.NewServer()
|
||||||
global.SetWebServer(server)
|
global.SetWebServer(server)
|
||||||
err = server.Start()
|
err = server.Start()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("Error starting web server: %v", err)
|
log.Println(err)
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
var subServer *sub.Server
|
|
||||||
subServer = sub.NewServer()
|
|
||||||
global.SetSubServer(subServer)
|
|
||||||
err = subServer.Start()
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalf("Error starting sub server: %v", err)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
sigCh := make(chan os.Signal, 1)
|
sigCh := make(chan os.Signal, 1)
|
||||||
// Trap shutdown signals
|
//信号量捕获处理
|
||||||
signal.Notify(sigCh, syscall.SIGHUP, syscall.SIGTERM)
|
signal.Notify(sigCh, syscall.SIGHUP, syscall.SIGTERM, syscall.SIGKILL)
|
||||||
for {
|
for {
|
||||||
sig := <-sigCh
|
sig := <-sigCh
|
||||||
|
|
||||||
switch sig {
|
switch sig {
|
||||||
case syscall.SIGHUP:
|
case syscall.SIGHUP:
|
||||||
logger.Info("Received SIGHUP signal. Restarting servers...")
|
|
||||||
|
|
||||||
err := server.Stop()
|
err := server.Stop()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Debug("Error stopping web server:", err)
|
logger.Warning("stop server err:", err)
|
||||||
}
|
}
|
||||||
err = subServer.Stop()
|
|
||||||
if err != nil {
|
|
||||||
logger.Debug("Error stopping sub server:", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
server = web.NewServer()
|
server = web.NewServer()
|
||||||
global.SetWebServer(server)
|
global.SetWebServer(server)
|
||||||
err = server.Start()
|
err = server.Start()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("Error restarting web server: %v", err)
|
log.Println(err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
log.Println("Web server restarted successfully.")
|
|
||||||
|
|
||||||
subServer = sub.NewServer()
|
|
||||||
global.SetSubServer(subServer)
|
|
||||||
err = subServer.Start()
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalf("Error restarting sub server: %v", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
log.Println("Sub server restarted successfully.")
|
|
||||||
|
|
||||||
default:
|
default:
|
||||||
server.Stop()
|
server.Stop()
|
||||||
subServer.Stop()
|
|
||||||
log.Println("Shutting down servers.")
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -114,16 +79,16 @@ func runWebServer() {
|
||||||
func resetSetting() {
|
func resetSetting() {
|
||||||
err := database.InitDB(config.GetDBPath())
|
err := database.InitDB(config.GetDBPath())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println("Failed to initialize database:", err)
|
fmt.Println(err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
settingService := service.SettingService{}
|
settingService := service.SettingService{}
|
||||||
err = settingService.ResetSettings()
|
err = settingService.ResetSettings()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println("Failed to reset settings:", err)
|
fmt.Println("reset setting failed:", err)
|
||||||
} else {
|
} else {
|
||||||
fmt.Println("Settings successfully reset.")
|
fmt.Println("reset setting success")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -132,73 +97,49 @@ func showSetting(show bool) {
|
||||||
settingService := service.SettingService{}
|
settingService := service.SettingService{}
|
||||||
port, err := settingService.GetPort()
|
port, err := settingService.GetPort()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println("get current port failed, error info:", err)
|
fmt.Println("get current port fialed,error info:", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
webBasePath, err := settingService.GetBasePath()
|
|
||||||
if err != nil {
|
|
||||||
fmt.Println("get webBasePath failed, error info:", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
certFile, err := settingService.GetCertFile()
|
|
||||||
if err != nil {
|
|
||||||
fmt.Println("get cert file failed, error info:", err)
|
|
||||||
}
|
|
||||||
keyFile, err := settingService.GetKeyFile()
|
|
||||||
if err != nil {
|
|
||||||
fmt.Println("get key file failed, error info:", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
userService := service.UserService{}
|
userService := service.UserService{}
|
||||||
userModel, err := userService.GetFirstUser()
|
userModel, err := userService.GetFirstUser()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println("get current user info failed, error info:", err)
|
fmt.Println("get current user info failed,error info:", err)
|
||||||
}
|
}
|
||||||
|
username := userModel.Username
|
||||||
if userModel.Username == "" || userModel.Password == "" {
|
userpasswd := userModel.Password
|
||||||
|
if (username == "") || (userpasswd == "") {
|
||||||
fmt.Println("current username or password is empty")
|
fmt.Println("current username or password is empty")
|
||||||
}
|
}
|
||||||
|
fmt.Println("current pannel settings as follows:")
|
||||||
fmt.Println("current panel settings as follows:")
|
fmt.Println("username:", username)
|
||||||
if certFile == "" || keyFile == "" {
|
fmt.Println("userpasswd:", userpasswd)
|
||||||
fmt.Println("Warning: Panel is not secure with SSL")
|
|
||||||
} else {
|
|
||||||
fmt.Println("Panel is secure with SSL")
|
|
||||||
}
|
|
||||||
|
|
||||||
hasDefaultCredential := func() bool {
|
|
||||||
return userModel.Username == "admin" && crypto.CheckPasswordHash(userModel.Password, "admin")
|
|
||||||
}()
|
|
||||||
|
|
||||||
fmt.Println("hasDefaultCredential:", hasDefaultCredential)
|
|
||||||
fmt.Println("port:", port)
|
fmt.Println("port:", port)
|
||||||
fmt.Println("webBasePath:", webBasePath)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func updateTgbotEnableSts(status bool) {
|
func updateTgbotEnableSts(status bool) {
|
||||||
settingService := service.SettingService{}
|
settingService := service.SettingService{}
|
||||||
currentTgSts, err := settingService.GetTgbotEnabled()
|
currentTgSts, err := settingService.GetTgbotenabled()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println(err)
|
fmt.Println(err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
logger.Infof("current enabletgbot status[%v],need update to status[%v]", currentTgSts, status)
|
logger.Infof("current enabletgbot status[%v],need update to status[%v]", currentTgSts, status)
|
||||||
if currentTgSts != status {
|
if currentTgSts != status {
|
||||||
err := settingService.SetTgbotEnabled(status)
|
err := settingService.SetTgbotenabled(status)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println(err)
|
fmt.Println(err)
|
||||||
return
|
return
|
||||||
} else {
|
} else {
|
||||||
logger.Infof("SetTgbotEnabled[%v] success", status)
|
logger.Infof("SetTgbotenabled[%v] success", status)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func updateTgbotSetting(tgBotToken string, tgBotChatid string, tgBotRuntime string) {
|
func updateTgbotSetting(tgBotToken string, tgBotChatid string, tgBotRuntime string) {
|
||||||
err := database.InitDB(config.GetDBPath())
|
err := database.InitDB(config.GetDBPath())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println("Error initializing database:", err)
|
fmt.Println(err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -207,159 +148,62 @@ func updateTgbotSetting(tgBotToken string, tgBotChatid string, tgBotRuntime stri
|
||||||
if tgBotToken != "" {
|
if tgBotToken != "" {
|
||||||
err := settingService.SetTgBotToken(tgBotToken)
|
err := settingService.SetTgBotToken(tgBotToken)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Printf("Error setting Telegram bot token: %v\n", err)
|
fmt.Println(err)
|
||||||
return
|
return
|
||||||
|
} else {
|
||||||
|
logger.Info("updateTgbotSetting tgBotToken success")
|
||||||
}
|
}
|
||||||
logger.Info("Successfully updated Telegram bot token.")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if tgBotRuntime != "" {
|
if tgBotRuntime != "" {
|
||||||
err := settingService.SetTgbotRuntime(tgBotRuntime)
|
err := settingService.SetTgbotRuntime(tgBotRuntime)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Printf("Error setting Telegram bot runtime: %v\n", err)
|
fmt.Println(err)
|
||||||
return
|
return
|
||||||
|
} else {
|
||||||
|
logger.Infof("updateTgbotSetting tgBotRuntime[%s] success", tgBotRuntime)
|
||||||
}
|
}
|
||||||
logger.Infof("Successfully updated Telegram bot runtime to [%s].", tgBotRuntime)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if tgBotChatid != "" {
|
if tgBotChatid != "" {
|
||||||
err := settingService.SetTgBotChatId(tgBotChatid)
|
err := settingService.SetTgBotChatId(tgBotChatid)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Printf("Error setting Telegram bot chat ID: %v\n", err)
|
fmt.Println(err)
|
||||||
return
|
return
|
||||||
}
|
|
||||||
logger.Info("Successfully updated Telegram bot chat ID.")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func updateSetting(port int, username string, password string, webBasePath string, listenIP string, resetTwoFactor bool) {
|
|
||||||
err := database.InitDB(config.GetDBPath())
|
|
||||||
if err != nil {
|
|
||||||
fmt.Println("Database initialization failed:", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
settingService := service.SettingService{}
|
|
||||||
userService := service.UserService{}
|
|
||||||
|
|
||||||
if port > 0 {
|
|
||||||
err := settingService.SetPort(port)
|
|
||||||
if err != nil {
|
|
||||||
fmt.Println("Failed to set port:", err)
|
|
||||||
} else {
|
} else {
|
||||||
fmt.Printf("Port set successfully: %v\n", port)
|
logger.Info("updateTgbotSetting tgBotChatid success")
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if username != "" || password != "" {
|
|
||||||
err := userService.UpdateFirstUser(username, password)
|
|
||||||
if err != nil {
|
|
||||||
fmt.Println("Failed to update username and password:", err)
|
|
||||||
} else {
|
|
||||||
fmt.Println("Username and password updated successfully")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if webBasePath != "" {
|
|
||||||
err := settingService.SetBasePath(webBasePath)
|
|
||||||
if err != nil {
|
|
||||||
fmt.Println("Failed to set base URI path:", err)
|
|
||||||
} else {
|
|
||||||
fmt.Println("Base URI path set successfully")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if resetTwoFactor {
|
|
||||||
err := settingService.SetTwoFactorEnable(false)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
fmt.Println("Failed to reset two-factor authentication:", err)
|
|
||||||
} else {
|
|
||||||
settingService.SetTwoFactorToken("")
|
|
||||||
fmt.Println("Two-factor authentication reset successfully")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if listenIP != "" {
|
|
||||||
err := settingService.SetListen(listenIP)
|
|
||||||
if err != nil {
|
|
||||||
fmt.Println("Failed to set listen IP:", err)
|
|
||||||
} else {
|
|
||||||
fmt.Printf("listen %v set successfully", listenIP)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func updateCert(publicKey string, privateKey string) {
|
func updateSetting(port int, username string, password string) {
|
||||||
err := database.InitDB(config.GetDBPath())
|
err := database.InitDB(config.GetDBPath())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println(err)
|
fmt.Println(err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if (privateKey != "" && publicKey != "") || (privateKey == "" && publicKey == "") {
|
settingService := service.SettingService{}
|
||||||
settingService := service.SettingService{}
|
|
||||||
err = settingService.SetCertFile(publicKey)
|
if port > 0 {
|
||||||
|
err := settingService.SetPort(port)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println("set certificate public key failed:", err)
|
fmt.Println("set port failed:", err)
|
||||||
} else {
|
} else {
|
||||||
fmt.Println("set certificate public key success")
|
fmt.Printf("set port %v success", port)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
err = settingService.SetKeyFile(privateKey)
|
if username != "" || password != "" {
|
||||||
|
userService := service.UserService{}
|
||||||
|
err := userService.UpdateFirstUser(username, password)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println("set certificate private key failed:", err)
|
fmt.Println("set username and password failed:", err)
|
||||||
} else {
|
} else {
|
||||||
fmt.Println("set certificate private key success")
|
fmt.Println("set username and password success")
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
fmt.Println("both public and private key should be entered.")
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetCertificate(getCert bool) {
|
|
||||||
if getCert {
|
|
||||||
settingService := service.SettingService{}
|
|
||||||
certFile, err := settingService.GetCertFile()
|
|
||||||
if err != nil {
|
|
||||||
fmt.Println("get cert file failed, error info:", err)
|
|
||||||
}
|
|
||||||
keyFile, err := settingService.GetKeyFile()
|
|
||||||
if err != nil {
|
|
||||||
fmt.Println("get key file failed, error info:", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
fmt.Println("cert:", certFile)
|
|
||||||
fmt.Println("key:", keyFile)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func GetListenIP(getListen bool) {
|
|
||||||
if getListen {
|
|
||||||
|
|
||||||
settingService := service.SettingService{}
|
|
||||||
ListenIP, err := settingService.GetListen()
|
|
||||||
if err != nil {
|
|
||||||
log.Printf("Failed to retrieve listen IP: %v", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
fmt.Println("listenIP:", ListenIP)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func migrateDb() {
|
|
||||||
inboundService := service.InboundService{}
|
|
||||||
|
|
||||||
err := database.InitDB(config.GetDBPath())
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
fmt.Println("Start migrating database...")
|
|
||||||
inboundService.MigrateDB()
|
|
||||||
fmt.Println("Migration done!")
|
|
||||||
}
|
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
if len(os.Args) < 2 {
|
if len(os.Args) < 2 {
|
||||||
runWebServer()
|
runWebServer()
|
||||||
|
@ -371,39 +215,29 @@ func main() {
|
||||||
|
|
||||||
runCmd := flag.NewFlagSet("run", flag.ExitOnError)
|
runCmd := flag.NewFlagSet("run", flag.ExitOnError)
|
||||||
|
|
||||||
|
v2uiCmd := flag.NewFlagSet("v2-ui", flag.ExitOnError)
|
||||||
|
var dbPath string
|
||||||
|
v2uiCmd.StringVar(&dbPath, "db", "/etc/v2-ui/v2-ui.db", "set v2-ui db file path")
|
||||||
|
|
||||||
settingCmd := flag.NewFlagSet("setting", flag.ExitOnError)
|
settingCmd := flag.NewFlagSet("setting", flag.ExitOnError)
|
||||||
var port int
|
var port int
|
||||||
var username string
|
var username string
|
||||||
var password string
|
var password string
|
||||||
var webBasePath string
|
|
||||||
var listenIP string
|
|
||||||
var getListen bool
|
|
||||||
var webCertFile string
|
|
||||||
var webKeyFile string
|
|
||||||
var tgbottoken string
|
var tgbottoken string
|
||||||
var tgbotchatid string
|
var tgbotchatid string
|
||||||
var enabletgbot bool
|
var enabletgbot bool
|
||||||
var tgbotRuntime string
|
var tgbotRuntime string
|
||||||
var reset bool
|
var reset bool
|
||||||
var show bool
|
var show bool
|
||||||
var getCert bool
|
settingCmd.BoolVar(&reset, "reset", false, "reset all settings")
|
||||||
var resetTwoFactor bool
|
settingCmd.BoolVar(&show, "show", false, "show current settings")
|
||||||
settingCmd.BoolVar(&reset, "reset", false, "Reset all settings")
|
settingCmd.IntVar(&port, "port", 0, "set panel port")
|
||||||
settingCmd.BoolVar(&show, "show", false, "Display current settings")
|
settingCmd.StringVar(&username, "username", "", "set login username")
|
||||||
settingCmd.IntVar(&port, "port", 0, "Set panel port number")
|
settingCmd.StringVar(&password, "password", "", "set login password")
|
||||||
settingCmd.StringVar(&username, "username", "", "Set login username")
|
settingCmd.StringVar(&tgbottoken, "tgbottoken", "", "set telegrame bot token")
|
||||||
settingCmd.StringVar(&password, "password", "", "Set login password")
|
settingCmd.StringVar(&tgbotRuntime, "tgbotRuntime", "", "set telegrame bot cron time")
|
||||||
settingCmd.StringVar(&webBasePath, "webBasePath", "", "Set base path for Panel")
|
settingCmd.StringVar(&tgbotchatid, "tgbotchatid", "", "set telegrame bot chat id")
|
||||||
settingCmd.StringVar(&listenIP, "listenIP", "", "set panel listenIP IP")
|
settingCmd.BoolVar(&enabletgbot, "enabletgbot", false, "enable telegram bot notify")
|
||||||
settingCmd.BoolVar(&resetTwoFactor, "resetTwoFactor", false, "Reset two-factor authentication settings")
|
|
||||||
settingCmd.BoolVar(&getListen, "getListen", false, "Display current panel listenIP IP")
|
|
||||||
settingCmd.BoolVar(&getCert, "getCert", false, "Display current certificate settings")
|
|
||||||
settingCmd.StringVar(&webCertFile, "webCert", "", "Set path to public key file for panel")
|
|
||||||
settingCmd.StringVar(&webKeyFile, "webCertKey", "", "Set path to private key file for panel")
|
|
||||||
settingCmd.StringVar(&tgbottoken, "tgbottoken", "", "Set token for Telegram bot")
|
|
||||||
settingCmd.StringVar(&tgbotRuntime, "tgbotRuntime", "", "Set cron time for Telegram bot notifications")
|
|
||||||
settingCmd.StringVar(&tgbotchatid, "tgbotchatid", "", "Set chat ID for Telegram bot notifications")
|
|
||||||
settingCmd.BoolVar(&enabletgbot, "enabletgbot", false, "Enable notifications via Telegram bot")
|
|
||||||
|
|
||||||
oldUsage := flag.Usage
|
oldUsage := flag.Usage
|
||||||
flag.Usage = func() {
|
flag.Usage = func() {
|
||||||
|
@ -411,7 +245,7 @@ func main() {
|
||||||
fmt.Println()
|
fmt.Println()
|
||||||
fmt.Println("Commands:")
|
fmt.Println("Commands:")
|
||||||
fmt.Println(" run run web panel")
|
fmt.Println(" run run web panel")
|
||||||
fmt.Println(" migrate migrate form other/old x-ui")
|
fmt.Println(" v2-ui migrate form v2-ui")
|
||||||
fmt.Println(" setting set settings")
|
fmt.Println(" setting set settings")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -429,8 +263,16 @@ func main() {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
runWebServer()
|
runWebServer()
|
||||||
case "migrate":
|
case "v2-ui":
|
||||||
migrateDb()
|
err := v2uiCmd.Parse(os.Args[2:])
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = v2ui.MigrateFromV2UI(dbPath)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println("migrate from v2-ui failed:", err)
|
||||||
|
}
|
||||||
case "setting":
|
case "setting":
|
||||||
err := settingCmd.Parse(os.Args[2:])
|
err := settingCmd.Parse(os.Args[2:])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -440,39 +282,21 @@ func main() {
|
||||||
if reset {
|
if reset {
|
||||||
resetSetting()
|
resetSetting()
|
||||||
} else {
|
} else {
|
||||||
updateSetting(port, username, password, webBasePath, listenIP, resetTwoFactor)
|
updateSetting(port, username, password)
|
||||||
}
|
}
|
||||||
if show {
|
if show {
|
||||||
showSetting(show)
|
showSetting(show)
|
||||||
}
|
}
|
||||||
if getListen {
|
|
||||||
GetListenIP(getListen)
|
|
||||||
}
|
|
||||||
if getCert {
|
|
||||||
GetCertificate(getCert)
|
|
||||||
}
|
|
||||||
if (tgbottoken != "") || (tgbotchatid != "") || (tgbotRuntime != "") {
|
if (tgbottoken != "") || (tgbotchatid != "") || (tgbotRuntime != "") {
|
||||||
updateTgbotSetting(tgbottoken, tgbotchatid, tgbotRuntime)
|
updateTgbotSetting(tgbottoken, tgbotchatid, tgbotRuntime)
|
||||||
}
|
}
|
||||||
if enabletgbot {
|
|
||||||
updateTgbotEnableSts(enabletgbot)
|
|
||||||
}
|
|
||||||
case "cert":
|
|
||||||
err := settingCmd.Parse(os.Args[2:])
|
|
||||||
if err != nil {
|
|
||||||
fmt.Println(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if reset {
|
|
||||||
updateCert("", "")
|
|
||||||
} else {
|
|
||||||
updateCert(webCertFile, webKeyFile)
|
|
||||||
}
|
|
||||||
default:
|
default:
|
||||||
fmt.Println("Invalid subcommands")
|
fmt.Println("except 'run' or 'v2-ui' or 'setting' subcommands")
|
||||||
fmt.Println()
|
fmt.Println()
|
||||||
runCmd.Usage()
|
runCmd.Usage()
|
||||||
fmt.Println()
|
fmt.Println()
|
||||||
|
v2uiCmd.Usage()
|
||||||
|
fmt.Println()
|
||||||
settingCmd.Usage()
|
settingCmd.Usage()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Before Width: | Height: | Size: 253 KiB |
Before Width: | Height: | Size: 248 KiB |
Before Width: | Height: | Size: 240 KiB |
Before Width: | Height: | Size: 240 KiB |
Before Width: | Height: | Size: 44 KiB |
Before Width: | Height: | Size: 44 KiB |
Before Width: | Height: | Size: 25 KiB |
Before Width: | Height: | Size: 25 KiB |
Before Width: | Height: | Size: 277 KiB |
Before Width: | Height: | Size: 266 KiB |
Before Width: | Height: | Size: 202 KiB |
Before Width: | Height: | Size: 206 KiB |
Before Width: | Height: | Size: 62 KiB |
Before Width: | Height: | Size: 255 KiB |
BIN
media/1.png
Normal file
After Width: | Height: | Size: 44 KiB |
BIN
media/2.png
Normal file
After Width: | Height: | Size: 25 KiB |
BIN
media/3.png
Normal file
After Width: | Height: | Size: 37 KiB |
Before Width: | Height: | Size: 226 KiB |
Before Width: | Height: | Size: 224 KiB |
BIN
media/4.png
Normal file
After Width: | Height: | Size: 42 KiB |
Before Width: | Height: | Size: 15 KiB |
Before Width: | Height: | Size: 9.8 KiB |
Before Width: | Height: | Size: 6.7 KiB |
Before Width: | Height: | Size: 544 KiB |
Before Width: | Height: | Size: 6.1 KiB |
77
media/for enable traffic+block all iran ip.txt
Normal file
|
@ -0,0 +1,77 @@
|
||||||
|
{
|
||||||
|
"log": {
|
||||||
|
"loglevel": "warning",
|
||||||
|
"access": "./access.log"
|
||||||
|
},
|
||||||
|
|
||||||
|
"api": {
|
||||||
|
"services": [
|
||||||
|
"HandlerService",
|
||||||
|
"LoggerService",
|
||||||
|
"StatsService"
|
||||||
|
],
|
||||||
|
"tag": "api"
|
||||||
|
},
|
||||||
|
"inbounds": [
|
||||||
|
{
|
||||||
|
"listen": "127.0.0.1",
|
||||||
|
"port": 62789,
|
||||||
|
"protocol": "dokodemo-door",
|
||||||
|
"settings": {
|
||||||
|
"address": "127.0.0.1"
|
||||||
|
},
|
||||||
|
"tag": "api"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"outbounds": [
|
||||||
|
{
|
||||||
|
"protocol": "freedom",
|
||||||
|
"settings": {}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"protocol": "blackhole",
|
||||||
|
"settings": {},
|
||||||
|
"tag": "blocked"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"policy": {
|
||||||
|
"levels": {
|
||||||
|
"0": {
|
||||||
|
"statsUserUplink": true,
|
||||||
|
"statsUserDownlink": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"system": {
|
||||||
|
"statsInboundDownlink": true,
|
||||||
|
"statsInboundUplink": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"routing": {
|
||||||
|
"domainStrategy": "IPIfNonMatch",
|
||||||
|
"rules": [
|
||||||
|
{
|
||||||
|
"inboundTag": [
|
||||||
|
"api"
|
||||||
|
],
|
||||||
|
"outboundTag": "api",
|
||||||
|
"type": "field"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ip": [
|
||||||
|
"geoip:private",
|
||||||
|
"geoip:ir"
|
||||||
|
],
|
||||||
|
"outboundTag": "blocked",
|
||||||
|
"type": "field"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"outboundTag": "blocked",
|
||||||
|
"protocol": [
|
||||||
|
"bittorrent"
|
||||||
|
],
|
||||||
|
"type": "field"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"stats": {}
|
||||||
|
}
|
75
media/for enable traffic.txt
Normal file
|
@ -0,0 +1,75 @@
|
||||||
|
{
|
||||||
|
"log": {
|
||||||
|
"loglevel": "warning",
|
||||||
|
"access": "./access.log"
|
||||||
|
},
|
||||||
|
|
||||||
|
"api": {
|
||||||
|
"services": [
|
||||||
|
"HandlerService",
|
||||||
|
"LoggerService",
|
||||||
|
"StatsService"
|
||||||
|
],
|
||||||
|
"tag": "api"
|
||||||
|
},
|
||||||
|
"inbounds": [
|
||||||
|
{
|
||||||
|
"listen": "127.0.0.1",
|
||||||
|
"port": 62789,
|
||||||
|
"protocol": "dokodemo-door",
|
||||||
|
"settings": {
|
||||||
|
"address": "127.0.0.1"
|
||||||
|
},
|
||||||
|
"tag": "api"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"outbounds": [
|
||||||
|
{
|
||||||
|
"protocol": "freedom",
|
||||||
|
"settings": {}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"protocol": "blackhole",
|
||||||
|
"settings": {},
|
||||||
|
"tag": "blocked"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"policy": {
|
||||||
|
"levels": {
|
||||||
|
"0": {
|
||||||
|
"statsUserUplink": true,
|
||||||
|
"statsUserDownlink": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"system": {
|
||||||
|
"statsInboundDownlink": true,
|
||||||
|
"statsInboundUplink": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"routing": {
|
||||||
|
"rules": [
|
||||||
|
{
|
||||||
|
"inboundTag": [
|
||||||
|
"api"
|
||||||
|
],
|
||||||
|
"outboundTag": "api",
|
||||||
|
"type": "field"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ip": [
|
||||||
|
"geoip:private"
|
||||||
|
],
|
||||||
|
"outboundTag": "blocked",
|
||||||
|
"type": "field"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"outboundTag": "blocked",
|
||||||
|
"protocol": [
|
||||||
|
"bittorrent"
|
||||||
|
],
|
||||||
|
"type": "field"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"stats": {}
|
||||||
|
}
|
BIN
media/newbot.png
Before Width: | Height: | Size: 455 KiB |
Before Width: | Height: | Size: 33 KiB |
BIN
media/token.png
Before Width: | Height: | Size: 18 KiB |
Before Width: | Height: | Size: 22 KiB |
|
@ -1,90 +0,0 @@
|
||||||
{
|
|
||||||
"remarks": "",
|
|
||||||
"dns": {
|
|
||||||
"tag": "dns_out",
|
|
||||||
"queryStrategy": "UseIP",
|
|
||||||
"servers": [
|
|
||||||
{
|
|
||||||
"address": "8.8.8.8",
|
|
||||||
"skipFallback": false
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"inbounds": [
|
|
||||||
{
|
|
||||||
"port": 10808,
|
|
||||||
"protocol": "socks",
|
|
||||||
"settings": {
|
|
||||||
"auth": "noauth",
|
|
||||||
"udp": true,
|
|
||||||
"userLevel": 8
|
|
||||||
},
|
|
||||||
"sniffing": {
|
|
||||||
"destOverride": [
|
|
||||||
"http",
|
|
||||||
"tls",
|
|
||||||
"quic",
|
|
||||||
"fakedns"
|
|
||||||
],
|
|
||||||
"enabled": true
|
|
||||||
},
|
|
||||||
"tag": "socks"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"port": 10809,
|
|
||||||
"protocol": "http",
|
|
||||||
"settings": {
|
|
||||||
"userLevel": 8
|
|
||||||
},
|
|
||||||
"tag": "http"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"log": {
|
|
||||||
"loglevel": "warning"
|
|
||||||
},
|
|
||||||
"outbounds": [
|
|
||||||
{
|
|
||||||
"tag": "direct",
|
|
||||||
"protocol": "freedom",
|
|
||||||
"settings": {
|
|
||||||
"domainStrategy": "AsIs",
|
|
||||||
"redirect": "",
|
|
||||||
"noises": []
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"tag": "block",
|
|
||||||
"protocol": "blackhole",
|
|
||||||
"settings": {
|
|
||||||
"response": {
|
|
||||||
"type": "http"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"policy": {
|
|
||||||
"levels": {
|
|
||||||
"8": {
|
|
||||||
"connIdle": 300,
|
|
||||||
"downlinkOnly": 1,
|
|
||||||
"handshake": 4,
|
|
||||||
"uplinkOnly": 1
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"system": {
|
|
||||||
"statsOutboundUplink": true,
|
|
||||||
"statsOutboundDownlink": true
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"routing": {
|
|
||||||
"domainStrategy": "AsIs",
|
|
||||||
"rules": [
|
|
||||||
{
|
|
||||||
"type": "field",
|
|
||||||
"network": "tcp,udp",
|
|
||||||
"outboundTag": "proxy"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"stats": {}
|
|
||||||
}
|
|
213
sub/sub.go
|
@ -1,213 +0,0 @@
|
||||||
package sub
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"crypto/tls"
|
|
||||||
"io"
|
|
||||||
"net"
|
|
||||||
"net/http"
|
|
||||||
"strconv"
|
|
||||||
|
|
||||||
"x-ui/config"
|
|
||||||
"x-ui/logger"
|
|
||||||
"x-ui/util/common"
|
|
||||||
"x-ui/web/middleware"
|
|
||||||
"x-ui/web/network"
|
|
||||||
"x-ui/web/service"
|
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Server struct {
|
|
||||||
httpServer *http.Server
|
|
||||||
listener net.Listener
|
|
||||||
|
|
||||||
sub *SUBController
|
|
||||||
settingService service.SettingService
|
|
||||||
|
|
||||||
ctx context.Context
|
|
||||||
cancel context.CancelFunc
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewServer() *Server {
|
|
||||||
ctx, cancel := context.WithCancel(context.Background())
|
|
||||||
return &Server{
|
|
||||||
ctx: ctx,
|
|
||||||
cancel: cancel,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Server) initRouter() (*gin.Engine, error) {
|
|
||||||
if config.IsDebug() {
|
|
||||||
gin.SetMode(gin.DebugMode)
|
|
||||||
} else {
|
|
||||||
gin.DefaultWriter = io.Discard
|
|
||||||
gin.DefaultErrorWriter = io.Discard
|
|
||||||
gin.SetMode(gin.ReleaseMode)
|
|
||||||
}
|
|
||||||
|
|
||||||
engine := gin.Default()
|
|
||||||
|
|
||||||
subDomain, err := s.settingService.GetSubDomain()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if subDomain != "" {
|
|
||||||
engine.Use(middleware.DomainValidatorMiddleware(subDomain))
|
|
||||||
}
|
|
||||||
|
|
||||||
LinksPath, err := s.settingService.GetSubPath()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
JsonPath, err := s.settingService.GetSubJsonPath()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
Encrypt, err := s.settingService.GetSubEncrypt()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
ShowInfo, err := s.settingService.GetSubShowInfo()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
RemarkModel, err := s.settingService.GetRemarkModel()
|
|
||||||
if err != nil {
|
|
||||||
RemarkModel = "-ieo"
|
|
||||||
}
|
|
||||||
|
|
||||||
SubUpdates, err := s.settingService.GetSubUpdates()
|
|
||||||
if err != nil {
|
|
||||||
SubUpdates = "10"
|
|
||||||
}
|
|
||||||
|
|
||||||
SubJsonFragment, err := s.settingService.GetSubJsonFragment()
|
|
||||||
if err != nil {
|
|
||||||
SubJsonFragment = ""
|
|
||||||
}
|
|
||||||
|
|
||||||
SubJsonNoises, err := s.settingService.GetSubJsonNoises()
|
|
||||||
if err != nil {
|
|
||||||
SubJsonNoises = ""
|
|
||||||
}
|
|
||||||
|
|
||||||
SubJsonMux, err := s.settingService.GetSubJsonMux()
|
|
||||||
if err != nil {
|
|
||||||
SubJsonMux = ""
|
|
||||||
}
|
|
||||||
|
|
||||||
SubJsonRules, err := s.settingService.GetSubJsonRules()
|
|
||||||
if err != nil {
|
|
||||||
SubJsonRules = ""
|
|
||||||
}
|
|
||||||
|
|
||||||
SubTitle, err := s.settingService.GetSubTitle()
|
|
||||||
if err != nil {
|
|
||||||
SubTitle = ""
|
|
||||||
}
|
|
||||||
|
|
||||||
g := engine.Group("/")
|
|
||||||
|
|
||||||
s.sub = NewSUBController(
|
|
||||||
g, LinksPath, JsonPath, Encrypt, ShowInfo, RemarkModel, SubUpdates,
|
|
||||||
SubJsonFragment, SubJsonNoises, SubJsonMux, SubJsonRules, SubTitle)
|
|
||||||
|
|
||||||
return engine, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Server) Start() (err error) {
|
|
||||||
// This is an anonymous function, no function name
|
|
||||||
defer func() {
|
|
||||||
if err != nil {
|
|
||||||
s.Stop()
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
subEnable, err := s.settingService.GetSubEnable()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if !subEnable {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
engine, err := s.initRouter()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
certFile, err := s.settingService.GetSubCertFile()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
keyFile, err := s.settingService.GetSubKeyFile()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
listen, err := s.settingService.GetSubListen()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
port, err := s.settingService.GetSubPort()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
listenAddr := net.JoinHostPort(listen, strconv.Itoa(port))
|
|
||||||
listener, err := net.Listen("tcp", listenAddr)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if certFile != "" || keyFile != "" {
|
|
||||||
cert, err := tls.LoadX509KeyPair(certFile, keyFile)
|
|
||||||
if err == nil {
|
|
||||||
c := &tls.Config{
|
|
||||||
Certificates: []tls.Certificate{cert},
|
|
||||||
}
|
|
||||||
listener = network.NewAutoHttpsListener(listener)
|
|
||||||
listener = tls.NewListener(listener, c)
|
|
||||||
logger.Info("Sub server running HTTPS on", listener.Addr())
|
|
||||||
} else {
|
|
||||||
logger.Error("Error loading certificates:", err)
|
|
||||||
logger.Info("Sub server running HTTP on", listener.Addr())
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
logger.Info("Sub server running HTTP on", listener.Addr())
|
|
||||||
}
|
|
||||||
s.listener = listener
|
|
||||||
|
|
||||||
s.httpServer = &http.Server{
|
|
||||||
Handler: engine,
|
|
||||||
}
|
|
||||||
|
|
||||||
go func() {
|
|
||||||
s.httpServer.Serve(listener)
|
|
||||||
}()
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Server) Stop() error {
|
|
||||||
s.cancel()
|
|
||||||
|
|
||||||
var err1 error
|
|
||||||
var err2 error
|
|
||||||
if s.httpServer != nil {
|
|
||||||
err1 = s.httpServer.Shutdown(s.ctx)
|
|
||||||
}
|
|
||||||
if s.listener != nil {
|
|
||||||
err2 = s.listener.Close()
|
|
||||||
}
|
|
||||||
return common.Combine(err1, err2)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Server) GetCtx() context.Context {
|
|
||||||
return s.ctx
|
|
||||||
}
|
|
|
@ -1,137 +0,0 @@
|
||||||
package sub
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/base64"
|
|
||||||
"net"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
|
||||||
)
|
|
||||||
|
|
||||||
type SUBController struct {
|
|
||||||
subTitle string
|
|
||||||
subPath string
|
|
||||||
subJsonPath string
|
|
||||||
subEncrypt bool
|
|
||||||
updateInterval string
|
|
||||||
|
|
||||||
subService *SubService
|
|
||||||
subJsonService *SubJsonService
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewSUBController(
|
|
||||||
g *gin.RouterGroup,
|
|
||||||
subPath string,
|
|
||||||
jsonPath string,
|
|
||||||
encrypt bool,
|
|
||||||
showInfo bool,
|
|
||||||
rModel string,
|
|
||||||
update string,
|
|
||||||
jsonFragment string,
|
|
||||||
jsonNoise string,
|
|
||||||
jsonMux string,
|
|
||||||
jsonRules string,
|
|
||||||
subTitle string,
|
|
||||||
) *SUBController {
|
|
||||||
sub := NewSubService(showInfo, rModel)
|
|
||||||
a := &SUBController{
|
|
||||||
subTitle: subTitle,
|
|
||||||
subPath: subPath,
|
|
||||||
subJsonPath: jsonPath,
|
|
||||||
subEncrypt: encrypt,
|
|
||||||
updateInterval: update,
|
|
||||||
|
|
||||||
subService: sub,
|
|
||||||
subJsonService: NewSubJsonService(jsonFragment, jsonNoise, jsonMux, jsonRules, sub),
|
|
||||||
}
|
|
||||||
a.initRouter(g)
|
|
||||||
return a
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *SUBController) initRouter(g *gin.RouterGroup) {
|
|
||||||
gLink := g.Group(a.subPath)
|
|
||||||
gJson := g.Group(a.subJsonPath)
|
|
||||||
|
|
||||||
gLink.GET(":subid", a.subs)
|
|
||||||
|
|
||||||
gJson.GET(":subid", a.subJsons)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *SUBController) subs(c *gin.Context) {
|
|
||||||
subId := c.Param("subid")
|
|
||||||
var host string
|
|
||||||
if h, err := getHostFromXFH(c.GetHeader("X-Forwarded-Host")); err == nil {
|
|
||||||
host = h
|
|
||||||
}
|
|
||||||
if host == "" {
|
|
||||||
host = c.GetHeader("X-Real-IP")
|
|
||||||
}
|
|
||||||
if host == "" {
|
|
||||||
var err error
|
|
||||||
host, _, err = net.SplitHostPort(c.Request.Host)
|
|
||||||
if err != nil {
|
|
||||||
host = c.Request.Host
|
|
||||||
}
|
|
||||||
}
|
|
||||||
subs, header, err := a.subService.GetSubs(subId, host)
|
|
||||||
if err != nil || len(subs) == 0 {
|
|
||||||
c.String(400, "Error!")
|
|
||||||
} else {
|
|
||||||
result := ""
|
|
||||||
for _, sub := range subs {
|
|
||||||
result += sub + "\n"
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add headers
|
|
||||||
c.Writer.Header().Set("Subscription-Userinfo", header)
|
|
||||||
c.Writer.Header().Set("Profile-Update-Interval", a.updateInterval)
|
|
||||||
c.Writer.Header().Set("Profile-Title", "base64:" + base64.StdEncoding.EncodeToString([]byte(a.subTitle)))
|
|
||||||
|
|
||||||
if a.subEncrypt {
|
|
||||||
c.String(200, base64.StdEncoding.EncodeToString([]byte(result)))
|
|
||||||
} else {
|
|
||||||
c.String(200, result)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *SUBController) subJsons(c *gin.Context) {
|
|
||||||
subId := c.Param("subid")
|
|
||||||
var host string
|
|
||||||
if h, err := getHostFromXFH(c.GetHeader("X-Forwarded-Host")); err == nil {
|
|
||||||
host = h
|
|
||||||
}
|
|
||||||
if host == "" {
|
|
||||||
host = c.GetHeader("X-Real-IP")
|
|
||||||
}
|
|
||||||
if host == "" {
|
|
||||||
var err error
|
|
||||||
host, _, err = net.SplitHostPort(c.Request.Host)
|
|
||||||
if err != nil {
|
|
||||||
host = c.Request.Host
|
|
||||||
}
|
|
||||||
}
|
|
||||||
jsonSub, header, err := a.subJsonService.GetJson(subId, host)
|
|
||||||
if err != nil || len(jsonSub) == 0 {
|
|
||||||
c.String(400, "Error!")
|
|
||||||
} else {
|
|
||||||
|
|
||||||
// Add headers
|
|
||||||
c.Writer.Header().Set("Subscription-Userinfo", header)
|
|
||||||
c.Writer.Header().Set("Profile-Update-Interval", a.updateInterval)
|
|
||||||
c.Writer.Header().Set("Profile-Title", "base64:" + base64.StdEncoding.EncodeToString([]byte(a.subTitle)))
|
|
||||||
|
|
||||||
c.String(200, jsonSub)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func getHostFromXFH(s string) (string, error) {
|
|
||||||
if strings.Contains(s, ":") {
|
|
||||||
realHost, _, err := net.SplitHostPort(s)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
return realHost, nil
|
|
||||||
}
|
|
||||||
return s, nil
|
|
||||||
}
|
|
|
@ -1,396 +0,0 @@
|
||||||
package sub
|
|
||||||
|
|
||||||
import (
|
|
||||||
_ "embed"
|
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"x-ui/database/model"
|
|
||||||
"x-ui/logger"
|
|
||||||
"x-ui/util/json_util"
|
|
||||||
"x-ui/util/random"
|
|
||||||
"x-ui/web/service"
|
|
||||||
"x-ui/xray"
|
|
||||||
)
|
|
||||||
|
|
||||||
//go:embed default.json
|
|
||||||
var defaultJson string
|
|
||||||
|
|
||||||
type SubJsonService struct {
|
|
||||||
configJson map[string]any
|
|
||||||
defaultOutbounds []json_util.RawMessage
|
|
||||||
fragment string
|
|
||||||
noises string
|
|
||||||
mux string
|
|
||||||
|
|
||||||
inboundService service.InboundService
|
|
||||||
SubService *SubService
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewSubJsonService(fragment string, noises string, mux string, rules string, subService *SubService) *SubJsonService {
|
|
||||||
var configJson map[string]any
|
|
||||||
var defaultOutbounds []json_util.RawMessage
|
|
||||||
json.Unmarshal([]byte(defaultJson), &configJson)
|
|
||||||
if outboundSlices, ok := configJson["outbounds"].([]any); ok {
|
|
||||||
for _, defaultOutbound := range outboundSlices {
|
|
||||||
jsonBytes, _ := json.Marshal(defaultOutbound)
|
|
||||||
defaultOutbounds = append(defaultOutbounds, jsonBytes)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if rules != "" {
|
|
||||||
var newRules []any
|
|
||||||
routing, _ := configJson["routing"].(map[string]any)
|
|
||||||
defaultRules, _ := routing["rules"].([]any)
|
|
||||||
json.Unmarshal([]byte(rules), &newRules)
|
|
||||||
defaultRules = append(newRules, defaultRules...)
|
|
||||||
routing["rules"] = defaultRules
|
|
||||||
configJson["routing"] = routing
|
|
||||||
}
|
|
||||||
|
|
||||||
if fragment != "" {
|
|
||||||
defaultOutbounds = append(defaultOutbounds, json_util.RawMessage(fragment))
|
|
||||||
}
|
|
||||||
|
|
||||||
if noises != "" {
|
|
||||||
defaultOutbounds = append(defaultOutbounds, json_util.RawMessage(noises))
|
|
||||||
}
|
|
||||||
|
|
||||||
return &SubJsonService{
|
|
||||||
configJson: configJson,
|
|
||||||
defaultOutbounds: defaultOutbounds,
|
|
||||||
fragment: fragment,
|
|
||||||
noises: noises,
|
|
||||||
mux: mux,
|
|
||||||
SubService: subService,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *SubJsonService) GetJson(subId string, host string) (string, string, error) {
|
|
||||||
inbounds, err := s.SubService.getInboundsBySubId(subId)
|
|
||||||
if err != nil || len(inbounds) == 0 {
|
|
||||||
return "", "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
var header string
|
|
||||||
var traffic xray.ClientTraffic
|
|
||||||
var clientTraffics []xray.ClientTraffic
|
|
||||||
var configArray []json_util.RawMessage
|
|
||||||
|
|
||||||
// Prepare Inbounds
|
|
||||||
for _, inbound := range inbounds {
|
|
||||||
clients, err := s.inboundService.GetClients(inbound)
|
|
||||||
if err != nil {
|
|
||||||
logger.Error("SubJsonService - GetClients: Unable to get clients from inbound")
|
|
||||||
}
|
|
||||||
if clients == nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if len(inbound.Listen) > 0 && inbound.Listen[0] == '@' {
|
|
||||||
listen, port, streamSettings, err := s.SubService.getFallbackMaster(inbound.Listen, inbound.StreamSettings)
|
|
||||||
if err == nil {
|
|
||||||
inbound.Listen = listen
|
|
||||||
inbound.Port = port
|
|
||||||
inbound.StreamSettings = streamSettings
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, client := range clients {
|
|
||||||
if client.Enable && client.SubID == subId {
|
|
||||||
clientTraffics = append(clientTraffics, s.SubService.getClientTraffics(inbound.ClientStats, client.Email))
|
|
||||||
newConfigs := s.getConfig(inbound, client, host)
|
|
||||||
configArray = append(configArray, newConfigs...)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(configArray) == 0 {
|
|
||||||
return "", "", nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Prepare statistics
|
|
||||||
for index, clientTraffic := range clientTraffics {
|
|
||||||
if index == 0 {
|
|
||||||
traffic.Up = clientTraffic.Up
|
|
||||||
traffic.Down = clientTraffic.Down
|
|
||||||
traffic.Total = clientTraffic.Total
|
|
||||||
if clientTraffic.ExpiryTime > 0 {
|
|
||||||
traffic.ExpiryTime = clientTraffic.ExpiryTime
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
traffic.Up += clientTraffic.Up
|
|
||||||
traffic.Down += clientTraffic.Down
|
|
||||||
if traffic.Total == 0 || clientTraffic.Total == 0 {
|
|
||||||
traffic.Total = 0
|
|
||||||
} else {
|
|
||||||
traffic.Total += clientTraffic.Total
|
|
||||||
}
|
|
||||||
if clientTraffic.ExpiryTime != traffic.ExpiryTime {
|
|
||||||
traffic.ExpiryTime = 0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Combile outbounds
|
|
||||||
var finalJson []byte
|
|
||||||
if len(configArray) == 1 {
|
|
||||||
finalJson, _ = json.MarshalIndent(configArray[0], "", " ")
|
|
||||||
} else {
|
|
||||||
finalJson, _ = json.MarshalIndent(configArray, "", " ")
|
|
||||||
}
|
|
||||||
|
|
||||||
header = fmt.Sprintf("upload=%d; download=%d; total=%d; expire=%d", traffic.Up, traffic.Down, traffic.Total, traffic.ExpiryTime/1000)
|
|
||||||
return string(finalJson), header, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *SubJsonService) getConfig(inbound *model.Inbound, client model.Client, host string) []json_util.RawMessage {
|
|
||||||
var newJsonArray []json_util.RawMessage
|
|
||||||
stream := s.streamData(inbound.StreamSettings)
|
|
||||||
|
|
||||||
externalProxies, ok := stream["externalProxy"].([]any)
|
|
||||||
if !ok || len(externalProxies) == 0 {
|
|
||||||
externalProxies = []any{
|
|
||||||
map[string]any{
|
|
||||||
"forceTls": "same",
|
|
||||||
"dest": host,
|
|
||||||
"port": float64(inbound.Port),
|
|
||||||
"remark": "",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
delete(stream, "externalProxy")
|
|
||||||
|
|
||||||
for _, ep := range externalProxies {
|
|
||||||
extPrxy := ep.(map[string]any)
|
|
||||||
inbound.Listen = extPrxy["dest"].(string)
|
|
||||||
inbound.Port = int(extPrxy["port"].(float64))
|
|
||||||
newStream := stream
|
|
||||||
switch extPrxy["forceTls"].(string) {
|
|
||||||
case "tls":
|
|
||||||
if newStream["security"] != "tls" {
|
|
||||||
newStream["security"] = "tls"
|
|
||||||
newStream["tslSettings"] = map[string]any{}
|
|
||||||
}
|
|
||||||
case "none":
|
|
||||||
if newStream["security"] != "none" {
|
|
||||||
newStream["security"] = "none"
|
|
||||||
delete(newStream, "tslSettings")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
streamSettings, _ := json.MarshalIndent(newStream, "", " ")
|
|
||||||
|
|
||||||
var newOutbounds []json_util.RawMessage
|
|
||||||
|
|
||||||
switch inbound.Protocol {
|
|
||||||
case "vmess", "vless":
|
|
||||||
newOutbounds = append(newOutbounds, s.genVnext(inbound, streamSettings, client))
|
|
||||||
case "trojan", "shadowsocks":
|
|
||||||
newOutbounds = append(newOutbounds, s.genServer(inbound, streamSettings, client))
|
|
||||||
}
|
|
||||||
|
|
||||||
newOutbounds = append(newOutbounds, s.defaultOutbounds...)
|
|
||||||
newConfigJson := make(map[string]any)
|
|
||||||
for key, value := range s.configJson {
|
|
||||||
newConfigJson[key] = value
|
|
||||||
}
|
|
||||||
newConfigJson["outbounds"] = newOutbounds
|
|
||||||
newConfigJson["remarks"] = s.SubService.genRemark(inbound, client.Email, extPrxy["remark"].(string))
|
|
||||||
|
|
||||||
newConfig, _ := json.MarshalIndent(newConfigJson, "", " ")
|
|
||||||
newJsonArray = append(newJsonArray, newConfig)
|
|
||||||
}
|
|
||||||
|
|
||||||
return newJsonArray
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *SubJsonService) streamData(stream string) map[string]any {
|
|
||||||
var streamSettings map[string]any
|
|
||||||
json.Unmarshal([]byte(stream), &streamSettings)
|
|
||||||
security, _ := streamSettings["security"].(string)
|
|
||||||
switch security {
|
|
||||||
case "tls":
|
|
||||||
streamSettings["tlsSettings"] = s.tlsData(streamSettings["tlsSettings"].(map[string]any))
|
|
||||||
case "reality":
|
|
||||||
streamSettings["realitySettings"] = s.realityData(streamSettings["realitySettings"].(map[string]any))
|
|
||||||
}
|
|
||||||
delete(streamSettings, "sockopt")
|
|
||||||
|
|
||||||
if s.fragment != "" {
|
|
||||||
streamSettings["sockopt"] = json_util.RawMessage(`{"dialerProxy": "fragment", "tcpKeepAliveIdle": 100, "tcpMptcp": true, "penetrate": true}`)
|
|
||||||
}
|
|
||||||
|
|
||||||
// remove proxy protocol
|
|
||||||
network, _ := streamSettings["network"].(string)
|
|
||||||
switch network {
|
|
||||||
case "tcp":
|
|
||||||
streamSettings["tcpSettings"] = s.removeAcceptProxy(streamSettings["tcpSettings"])
|
|
||||||
case "ws":
|
|
||||||
streamSettings["wsSettings"] = s.removeAcceptProxy(streamSettings["wsSettings"])
|
|
||||||
case "httpupgrade":
|
|
||||||
streamSettings["httpupgradeSettings"] = s.removeAcceptProxy(streamSettings["httpupgradeSettings"])
|
|
||||||
}
|
|
||||||
return streamSettings
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *SubJsonService) removeAcceptProxy(setting any) map[string]any {
|
|
||||||
netSettings, ok := setting.(map[string]any)
|
|
||||||
if ok {
|
|
||||||
delete(netSettings, "acceptProxyProtocol")
|
|
||||||
}
|
|
||||||
return netSettings
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *SubJsonService) tlsData(tData map[string]any) map[string]any {
|
|
||||||
tlsData := make(map[string]any, 1)
|
|
||||||
tlsClientSettings, _ := tData["settings"].(map[string]any)
|
|
||||||
|
|
||||||
tlsData["serverName"] = tData["serverName"]
|
|
||||||
tlsData["alpn"] = tData["alpn"]
|
|
||||||
if allowInsecure, ok := tlsClientSettings["allowInsecure"].(bool); ok {
|
|
||||||
tlsData["allowInsecure"] = allowInsecure
|
|
||||||
}
|
|
||||||
if fingerprint, ok := tlsClientSettings["fingerprint"].(string); ok {
|
|
||||||
tlsData["fingerprint"] = fingerprint
|
|
||||||
}
|
|
||||||
return tlsData
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *SubJsonService) realityData(rData map[string]any) map[string]any {
|
|
||||||
rltyData := make(map[string]any, 1)
|
|
||||||
rltyClientSettings, _ := rData["settings"].(map[string]any)
|
|
||||||
|
|
||||||
rltyData["show"] = false
|
|
||||||
rltyData["publicKey"] = rltyClientSettings["publicKey"]
|
|
||||||
rltyData["fingerprint"] = rltyClientSettings["fingerprint"]
|
|
||||||
rltyData["mldsa65Verify"] = rltyClientSettings["mldsa65Verify"]
|
|
||||||
|
|
||||||
// Set random data
|
|
||||||
rltyData["spiderX"] = "/" + random.Seq(15)
|
|
||||||
shortIds, ok := rData["shortIds"].([]any)
|
|
||||||
if ok && len(shortIds) > 0 {
|
|
||||||
rltyData["shortId"] = shortIds[random.Num(len(shortIds))].(string)
|
|
||||||
} else {
|
|
||||||
rltyData["shortId"] = ""
|
|
||||||
}
|
|
||||||
serverNames, ok := rData["serverNames"].([]any)
|
|
||||||
if ok && len(serverNames) > 0 {
|
|
||||||
rltyData["serverName"] = serverNames[random.Num(len(serverNames))].(string)
|
|
||||||
} else {
|
|
||||||
rltyData["serverName"] = ""
|
|
||||||
}
|
|
||||||
|
|
||||||
return rltyData
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *SubJsonService) genVnext(inbound *model.Inbound, streamSettings json_util.RawMessage, client model.Client) json_util.RawMessage {
|
|
||||||
outbound := Outbound{}
|
|
||||||
usersData := make([]UserVnext, 1)
|
|
||||||
|
|
||||||
usersData[0].ID = client.ID
|
|
||||||
usersData[0].Level = 8
|
|
||||||
if inbound.Protocol == model.VMESS {
|
|
||||||
usersData[0].Security = client.Security
|
|
||||||
}
|
|
||||||
if inbound.Protocol == model.VLESS {
|
|
||||||
usersData[0].Flow = client.Flow
|
|
||||||
usersData[0].Encryption = "none"
|
|
||||||
}
|
|
||||||
|
|
||||||
vnextData := make([]VnextSetting, 1)
|
|
||||||
vnextData[0] = VnextSetting{
|
|
||||||
Address: inbound.Listen,
|
|
||||||
Port: inbound.Port,
|
|
||||||
Users: usersData,
|
|
||||||
}
|
|
||||||
|
|
||||||
outbound.Protocol = string(inbound.Protocol)
|
|
||||||
outbound.Tag = "proxy"
|
|
||||||
if s.mux != "" {
|
|
||||||
outbound.Mux = json_util.RawMessage(s.mux)
|
|
||||||
}
|
|
||||||
outbound.StreamSettings = streamSettings
|
|
||||||
outbound.Settings = OutboundSettings{
|
|
||||||
Vnext: vnextData,
|
|
||||||
}
|
|
||||||
|
|
||||||
result, _ := json.MarshalIndent(outbound, "", " ")
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *SubJsonService) genServer(inbound *model.Inbound, streamSettings json_util.RawMessage, client model.Client) json_util.RawMessage {
|
|
||||||
outbound := Outbound{}
|
|
||||||
|
|
||||||
serverData := make([]ServerSetting, 1)
|
|
||||||
serverData[0] = ServerSetting{
|
|
||||||
Address: inbound.Listen,
|
|
||||||
Port: inbound.Port,
|
|
||||||
Level: 8,
|
|
||||||
Password: client.Password,
|
|
||||||
}
|
|
||||||
|
|
||||||
if inbound.Protocol == model.Shadowsocks {
|
|
||||||
var inboundSettings map[string]any
|
|
||||||
json.Unmarshal([]byte(inbound.Settings), &inboundSettings)
|
|
||||||
method, _ := inboundSettings["method"].(string)
|
|
||||||
serverData[0].Method = method
|
|
||||||
|
|
||||||
// server password in multi-user 2022 protocols
|
|
||||||
if strings.HasPrefix(method, "2022") {
|
|
||||||
if serverPassword, ok := inboundSettings["password"].(string); ok {
|
|
||||||
serverData[0].Password = fmt.Sprintf("%s:%s", serverPassword, client.Password)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
outbound.Protocol = string(inbound.Protocol)
|
|
||||||
outbound.Tag = "proxy"
|
|
||||||
if s.mux != "" {
|
|
||||||
outbound.Mux = json_util.RawMessage(s.mux)
|
|
||||||
}
|
|
||||||
outbound.StreamSettings = streamSettings
|
|
||||||
outbound.Settings = OutboundSettings{
|
|
||||||
Servers: serverData,
|
|
||||||
}
|
|
||||||
|
|
||||||
result, _ := json.MarshalIndent(outbound, "", " ")
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
type Outbound struct {
|
|
||||||
Protocol string `json:"protocol"`
|
|
||||||
Tag string `json:"tag"`
|
|
||||||
StreamSettings json_util.RawMessage `json:"streamSettings"`
|
|
||||||
Mux json_util.RawMessage `json:"mux,omitempty"`
|
|
||||||
ProxySettings map[string]any `json:"proxySettings,omitempty"`
|
|
||||||
Settings OutboundSettings `json:"settings,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type OutboundSettings struct {
|
|
||||||
Vnext []VnextSetting `json:"vnext,omitempty"`
|
|
||||||
Servers []ServerSetting `json:"servers,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type VnextSetting struct {
|
|
||||||
Address string `json:"address"`
|
|
||||||
Port int `json:"port"`
|
|
||||||
Users []UserVnext `json:"users"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type UserVnext struct {
|
|
||||||
Encryption string `json:"encryption,omitempty"`
|
|
||||||
Flow string `json:"flow,omitempty"`
|
|
||||||
ID string `json:"id"`
|
|
||||||
Security string `json:"security,omitempty"`
|
|
||||||
Level int `json:"level"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type ServerSetting struct {
|
|
||||||
Password string `json:"password"`
|
|
||||||
Level int `json:"level"`
|
|
||||||
Address string `json:"address"`
|
|
||||||
Port int `json:"port"`
|
|
||||||
Flow string `json:"flow,omitempty"`
|
|
||||||
Method string `json:"method,omitempty"`
|
|
||||||
}
|
|
|
@ -1,997 +0,0 @@
|
||||||
package sub
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/base64"
|
|
||||||
"fmt"
|
|
||||||
"net/url"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"x-ui/database"
|
|
||||||
"x-ui/database/model"
|
|
||||||
"x-ui/logger"
|
|
||||||
"x-ui/util/common"
|
|
||||||
"x-ui/util/random"
|
|
||||||
"x-ui/web/service"
|
|
||||||
"x-ui/xray"
|
|
||||||
|
|
||||||
"github.com/goccy/go-json"
|
|
||||||
)
|
|
||||||
|
|
||||||
type SubService struct {
|
|
||||||
address string
|
|
||||||
showInfo bool
|
|
||||||
remarkModel string
|
|
||||||
datepicker string
|
|
||||||
inboundService service.InboundService
|
|
||||||
settingService service.SettingService
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewSubService(showInfo bool, remarkModel string) *SubService {
|
|
||||||
return &SubService{
|
|
||||||
showInfo: showInfo,
|
|
||||||
remarkModel: remarkModel,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *SubService) GetSubs(subId string, host string) ([]string, string, error) {
|
|
||||||
s.address = host
|
|
||||||
var result []string
|
|
||||||
var header string
|
|
||||||
var traffic xray.ClientTraffic
|
|
||||||
var clientTraffics []xray.ClientTraffic
|
|
||||||
inbounds, err := s.getInboundsBySubId(subId)
|
|
||||||
if err != nil {
|
|
||||||
return nil, "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(inbounds) == 0 {
|
|
||||||
return nil, "", common.NewError("No inbounds found with ", subId)
|
|
||||||
}
|
|
||||||
|
|
||||||
s.datepicker, err = s.settingService.GetDatepicker()
|
|
||||||
if err != nil {
|
|
||||||
s.datepicker = "gregorian"
|
|
||||||
}
|
|
||||||
for _, inbound := range inbounds {
|
|
||||||
clients, err := s.inboundService.GetClients(inbound)
|
|
||||||
if err != nil {
|
|
||||||
logger.Error("SubService - GetClients: Unable to get clients from inbound")
|
|
||||||
}
|
|
||||||
if clients == nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if len(inbound.Listen) > 0 && inbound.Listen[0] == '@' {
|
|
||||||
listen, port, streamSettings, err := s.getFallbackMaster(inbound.Listen, inbound.StreamSettings)
|
|
||||||
if err == nil {
|
|
||||||
inbound.Listen = listen
|
|
||||||
inbound.Port = port
|
|
||||||
inbound.StreamSettings = streamSettings
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for _, client := range clients {
|
|
||||||
if client.Enable && client.SubID == subId {
|
|
||||||
link := s.getLink(inbound, client.Email)
|
|
||||||
result = append(result, link)
|
|
||||||
clientTraffics = append(clientTraffics, s.getClientTraffics(inbound.ClientStats, client.Email))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Prepare statistics
|
|
||||||
for index, clientTraffic := range clientTraffics {
|
|
||||||
if index == 0 {
|
|
||||||
traffic.Up = clientTraffic.Up
|
|
||||||
traffic.Down = clientTraffic.Down
|
|
||||||
traffic.Total = clientTraffic.Total
|
|
||||||
if clientTraffic.ExpiryTime > 0 {
|
|
||||||
traffic.ExpiryTime = clientTraffic.ExpiryTime
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
traffic.Up += clientTraffic.Up
|
|
||||||
traffic.Down += clientTraffic.Down
|
|
||||||
if traffic.Total == 0 || clientTraffic.Total == 0 {
|
|
||||||
traffic.Total = 0
|
|
||||||
} else {
|
|
||||||
traffic.Total += clientTraffic.Total
|
|
||||||
}
|
|
||||||
if clientTraffic.ExpiryTime != traffic.ExpiryTime {
|
|
||||||
traffic.ExpiryTime = 0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
header = fmt.Sprintf("upload=%d; download=%d; total=%d; expire=%d", traffic.Up, traffic.Down, traffic.Total, traffic.ExpiryTime/1000)
|
|
||||||
return result, header, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *SubService) getInboundsBySubId(subId string) ([]*model.Inbound, error) {
|
|
||||||
db := database.GetDB()
|
|
||||||
var inbounds []*model.Inbound
|
|
||||||
err := db.Model(model.Inbound{}).Preload("ClientStats").Where(`id in (
|
|
||||||
SELECT DISTINCT inbounds.id
|
|
||||||
FROM inbounds,
|
|
||||||
JSON_EACH(JSON_EXTRACT(inbounds.settings, '$.clients')) AS client
|
|
||||||
WHERE
|
|
||||||
protocol in ('vmess','vless','trojan','shadowsocks')
|
|
||||||
AND JSON_EXTRACT(client.value, '$.subId') = ? AND enable = ?
|
|
||||||
)`, subId, true).Find(&inbounds).Error
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return inbounds, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *SubService) getClientTraffics(traffics []xray.ClientTraffic, email string) xray.ClientTraffic {
|
|
||||||
for _, traffic := range traffics {
|
|
||||||
if traffic.Email == email {
|
|
||||||
return traffic
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return xray.ClientTraffic{}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *SubService) getFallbackMaster(dest string, streamSettings string) (string, int, string, error) {
|
|
||||||
db := database.GetDB()
|
|
||||||
var inbound *model.Inbound
|
|
||||||
err := db.Model(model.Inbound{}).
|
|
||||||
Where("JSON_TYPE(settings, '$.fallbacks') = 'array'").
|
|
||||||
Where("EXISTS (SELECT * FROM json_each(settings, '$.fallbacks') WHERE json_extract(value, '$.dest') = ?)", dest).
|
|
||||||
Find(&inbound).Error
|
|
||||||
if err != nil {
|
|
||||||
return "", 0, "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
var stream map[string]any
|
|
||||||
json.Unmarshal([]byte(streamSettings), &stream)
|
|
||||||
var masterStream map[string]any
|
|
||||||
json.Unmarshal([]byte(inbound.StreamSettings), &masterStream)
|
|
||||||
stream["security"] = masterStream["security"]
|
|
||||||
stream["tlsSettings"] = masterStream["tlsSettings"]
|
|
||||||
stream["externalProxy"] = masterStream["externalProxy"]
|
|
||||||
modifiedStream, _ := json.MarshalIndent(stream, "", " ")
|
|
||||||
|
|
||||||
return inbound.Listen, inbound.Port, string(modifiedStream), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *SubService) getLink(inbound *model.Inbound, email string) string {
|
|
||||||
switch inbound.Protocol {
|
|
||||||
case "vmess":
|
|
||||||
return s.genVmessLink(inbound, email)
|
|
||||||
case "vless":
|
|
||||||
return s.genVlessLink(inbound, email)
|
|
||||||
case "trojan":
|
|
||||||
return s.genTrojanLink(inbound, email)
|
|
||||||
case "shadowsocks":
|
|
||||||
return s.genShadowsocksLink(inbound, email)
|
|
||||||
}
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *SubService) genVmessLink(inbound *model.Inbound, email string) string {
|
|
||||||
if inbound.Protocol != model.VMESS {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
obj := map[string]any{
|
|
||||||
"v": "2",
|
|
||||||
"add": s.address,
|
|
||||||
"port": inbound.Port,
|
|
||||||
"type": "none",
|
|
||||||
}
|
|
||||||
var stream map[string]any
|
|
||||||
json.Unmarshal([]byte(inbound.StreamSettings), &stream)
|
|
||||||
network, _ := stream["network"].(string)
|
|
||||||
obj["net"] = network
|
|
||||||
switch network {
|
|
||||||
case "tcp":
|
|
||||||
tcp, _ := stream["tcpSettings"].(map[string]any)
|
|
||||||
header, _ := tcp["header"].(map[string]any)
|
|
||||||
typeStr, _ := header["type"].(string)
|
|
||||||
obj["type"] = typeStr
|
|
||||||
if typeStr == "http" {
|
|
||||||
request := header["request"].(map[string]any)
|
|
||||||
requestPath, _ := request["path"].([]any)
|
|
||||||
obj["path"] = requestPath[0].(string)
|
|
||||||
headers, _ := request["headers"].(map[string]any)
|
|
||||||
obj["host"] = searchHost(headers)
|
|
||||||
}
|
|
||||||
case "kcp":
|
|
||||||
kcp, _ := stream["kcpSettings"].(map[string]any)
|
|
||||||
header, _ := kcp["header"].(map[string]any)
|
|
||||||
obj["type"], _ = header["type"].(string)
|
|
||||||
obj["path"], _ = kcp["seed"].(string)
|
|
||||||
case "ws":
|
|
||||||
ws, _ := stream["wsSettings"].(map[string]any)
|
|
||||||
obj["path"] = ws["path"].(string)
|
|
||||||
if host, ok := ws["host"].(string); ok && len(host) > 0 {
|
|
||||||
obj["host"] = host
|
|
||||||
} else {
|
|
||||||
headers, _ := ws["headers"].(map[string]any)
|
|
||||||
obj["host"] = searchHost(headers)
|
|
||||||
}
|
|
||||||
case "grpc":
|
|
||||||
grpc, _ := stream["grpcSettings"].(map[string]any)
|
|
||||||
obj["path"] = grpc["serviceName"].(string)
|
|
||||||
obj["authority"] = grpc["authority"].(string)
|
|
||||||
if grpc["multiMode"].(bool) {
|
|
||||||
obj["type"] = "multi"
|
|
||||||
}
|
|
||||||
case "httpupgrade":
|
|
||||||
httpupgrade, _ := stream["httpupgradeSettings"].(map[string]any)
|
|
||||||
obj["path"] = httpupgrade["path"].(string)
|
|
||||||
if host, ok := httpupgrade["host"].(string); ok && len(host) > 0 {
|
|
||||||
obj["host"] = host
|
|
||||||
} else {
|
|
||||||
headers, _ := httpupgrade["headers"].(map[string]any)
|
|
||||||
obj["host"] = searchHost(headers)
|
|
||||||
}
|
|
||||||
case "xhttp":
|
|
||||||
xhttp, _ := stream["xhttpSettings"].(map[string]any)
|
|
||||||
obj["path"] = xhttp["path"].(string)
|
|
||||||
if host, ok := xhttp["host"].(string); ok && len(host) > 0 {
|
|
||||||
obj["host"] = host
|
|
||||||
} else {
|
|
||||||
headers, _ := xhttp["headers"].(map[string]any)
|
|
||||||
obj["host"] = searchHost(headers)
|
|
||||||
}
|
|
||||||
obj["mode"] = xhttp["mode"].(string)
|
|
||||||
}
|
|
||||||
security, _ := stream["security"].(string)
|
|
||||||
obj["tls"] = security
|
|
||||||
if security == "tls" {
|
|
||||||
tlsSetting, _ := stream["tlsSettings"].(map[string]any)
|
|
||||||
alpns, _ := tlsSetting["alpn"].([]any)
|
|
||||||
if len(alpns) > 0 {
|
|
||||||
var alpn []string
|
|
||||||
for _, a := range alpns {
|
|
||||||
alpn = append(alpn, a.(string))
|
|
||||||
}
|
|
||||||
obj["alpn"] = strings.Join(alpn, ",")
|
|
||||||
}
|
|
||||||
if sniValue, ok := searchKey(tlsSetting, "serverName"); ok {
|
|
||||||
obj["sni"], _ = sniValue.(string)
|
|
||||||
}
|
|
||||||
|
|
||||||
tlsSettings, _ := searchKey(tlsSetting, "settings")
|
|
||||||
if tlsSetting != nil {
|
|
||||||
if fpValue, ok := searchKey(tlsSettings, "fingerprint"); ok {
|
|
||||||
obj["fp"], _ = fpValue.(string)
|
|
||||||
}
|
|
||||||
if insecure, ok := searchKey(tlsSettings, "allowInsecure"); ok {
|
|
||||||
obj["allowInsecure"], _ = insecure.(bool)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
clients, _ := s.inboundService.GetClients(inbound)
|
|
||||||
clientIndex := -1
|
|
||||||
for i, client := range clients {
|
|
||||||
if client.Email == email {
|
|
||||||
clientIndex = i
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
obj["id"] = clients[clientIndex].ID
|
|
||||||
obj["scy"] = clients[clientIndex].Security
|
|
||||||
|
|
||||||
externalProxies, _ := stream["externalProxy"].([]any)
|
|
||||||
|
|
||||||
if len(externalProxies) > 0 {
|
|
||||||
links := ""
|
|
||||||
for index, externalProxy := range externalProxies {
|
|
||||||
ep, _ := externalProxy.(map[string]any)
|
|
||||||
newSecurity, _ := ep["forceTls"].(string)
|
|
||||||
newObj := map[string]any{}
|
|
||||||
for key, value := range obj {
|
|
||||||
if !(newSecurity == "none" && (key == "alpn" || key == "sni" || key == "fp" || key == "allowInsecure")) {
|
|
||||||
newObj[key] = value
|
|
||||||
}
|
|
||||||
}
|
|
||||||
newObj["ps"] = s.genRemark(inbound, email, ep["remark"].(string))
|
|
||||||
newObj["add"] = ep["dest"].(string)
|
|
||||||
newObj["port"] = int(ep["port"].(float64))
|
|
||||||
|
|
||||||
if newSecurity != "same" {
|
|
||||||
newObj["tls"] = newSecurity
|
|
||||||
}
|
|
||||||
if index > 0 {
|
|
||||||
links += "\n"
|
|
||||||
}
|
|
||||||
jsonStr, _ := json.MarshalIndent(newObj, "", " ")
|
|
||||||
links += "vmess://" + base64.StdEncoding.EncodeToString(jsonStr)
|
|
||||||
}
|
|
||||||
return links
|
|
||||||
}
|
|
||||||
|
|
||||||
obj["ps"] = s.genRemark(inbound, email, "")
|
|
||||||
|
|
||||||
jsonStr, _ := json.MarshalIndent(obj, "", " ")
|
|
||||||
return "vmess://" + base64.StdEncoding.EncodeToString(jsonStr)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *SubService) genVlessLink(inbound *model.Inbound, email string) string {
|
|
||||||
address := s.address
|
|
||||||
if inbound.Protocol != model.VLESS {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
var stream map[string]any
|
|
||||||
json.Unmarshal([]byte(inbound.StreamSettings), &stream)
|
|
||||||
clients, _ := s.inboundService.GetClients(inbound)
|
|
||||||
clientIndex := -1
|
|
||||||
for i, client := range clients {
|
|
||||||
if client.Email == email {
|
|
||||||
clientIndex = i
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
uuid := clients[clientIndex].ID
|
|
||||||
port := inbound.Port
|
|
||||||
streamNetwork := stream["network"].(string)
|
|
||||||
params := make(map[string]string)
|
|
||||||
params["type"] = streamNetwork
|
|
||||||
|
|
||||||
switch streamNetwork {
|
|
||||||
case "tcp":
|
|
||||||
tcp, _ := stream["tcpSettings"].(map[string]any)
|
|
||||||
header, _ := tcp["header"].(map[string]any)
|
|
||||||
typeStr, _ := header["type"].(string)
|
|
||||||
if typeStr == "http" {
|
|
||||||
request := header["request"].(map[string]any)
|
|
||||||
requestPath, _ := request["path"].([]any)
|
|
||||||
params["path"] = requestPath[0].(string)
|
|
||||||
headers, _ := request["headers"].(map[string]any)
|
|
||||||
params["host"] = searchHost(headers)
|
|
||||||
params["headerType"] = "http"
|
|
||||||
}
|
|
||||||
case "kcp":
|
|
||||||
kcp, _ := stream["kcpSettings"].(map[string]any)
|
|
||||||
header, _ := kcp["header"].(map[string]any)
|
|
||||||
params["headerType"] = header["type"].(string)
|
|
||||||
params["seed"] = kcp["seed"].(string)
|
|
||||||
case "ws":
|
|
||||||
ws, _ := stream["wsSettings"].(map[string]any)
|
|
||||||
params["path"] = ws["path"].(string)
|
|
||||||
if host, ok := ws["host"].(string); ok && len(host) > 0 {
|
|
||||||
params["host"] = host
|
|
||||||
} else {
|
|
||||||
headers, _ := ws["headers"].(map[string]any)
|
|
||||||
params["host"] = searchHost(headers)
|
|
||||||
}
|
|
||||||
case "grpc":
|
|
||||||
grpc, _ := stream["grpcSettings"].(map[string]any)
|
|
||||||
params["serviceName"] = grpc["serviceName"].(string)
|
|
||||||
params["authority"], _ = grpc["authority"].(string)
|
|
||||||
if grpc["multiMode"].(bool) {
|
|
||||||
params["mode"] = "multi"
|
|
||||||
}
|
|
||||||
case "httpupgrade":
|
|
||||||
httpupgrade, _ := stream["httpupgradeSettings"].(map[string]any)
|
|
||||||
params["path"] = httpupgrade["path"].(string)
|
|
||||||
if host, ok := httpupgrade["host"].(string); ok && len(host) > 0 {
|
|
||||||
params["host"] = host
|
|
||||||
} else {
|
|
||||||
headers, _ := httpupgrade["headers"].(map[string]any)
|
|
||||||
params["host"] = searchHost(headers)
|
|
||||||
}
|
|
||||||
case "xhttp":
|
|
||||||
xhttp, _ := stream["xhttpSettings"].(map[string]any)
|
|
||||||
params["path"] = xhttp["path"].(string)
|
|
||||||
if host, ok := xhttp["host"].(string); ok && len(host) > 0 {
|
|
||||||
params["host"] = host
|
|
||||||
} else {
|
|
||||||
headers, _ := xhttp["headers"].(map[string]any)
|
|
||||||
params["host"] = searchHost(headers)
|
|
||||||
}
|
|
||||||
params["mode"] = xhttp["mode"].(string)
|
|
||||||
}
|
|
||||||
security, _ := stream["security"].(string)
|
|
||||||
if security == "tls" {
|
|
||||||
params["security"] = "tls"
|
|
||||||
tlsSetting, _ := stream["tlsSettings"].(map[string]any)
|
|
||||||
alpns, _ := tlsSetting["alpn"].([]any)
|
|
||||||
var alpn []string
|
|
||||||
for _, a := range alpns {
|
|
||||||
alpn = append(alpn, a.(string))
|
|
||||||
}
|
|
||||||
if len(alpn) > 0 {
|
|
||||||
params["alpn"] = strings.Join(alpn, ",")
|
|
||||||
}
|
|
||||||
if sniValue, ok := searchKey(tlsSetting, "serverName"); ok {
|
|
||||||
params["sni"], _ = sniValue.(string)
|
|
||||||
}
|
|
||||||
|
|
||||||
tlsSettings, _ := searchKey(tlsSetting, "settings")
|
|
||||||
if tlsSetting != nil {
|
|
||||||
if fpValue, ok := searchKey(tlsSettings, "fingerprint"); ok {
|
|
||||||
params["fp"], _ = fpValue.(string)
|
|
||||||
}
|
|
||||||
if insecure, ok := searchKey(tlsSettings, "allowInsecure"); ok {
|
|
||||||
if insecure.(bool) {
|
|
||||||
params["allowInsecure"] = "1"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if streamNetwork == "tcp" && len(clients[clientIndex].Flow) > 0 {
|
|
||||||
params["flow"] = clients[clientIndex].Flow
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if security == "reality" {
|
|
||||||
params["security"] = "reality"
|
|
||||||
realitySetting, _ := stream["realitySettings"].(map[string]any)
|
|
||||||
realitySettings, _ := searchKey(realitySetting, "settings")
|
|
||||||
if realitySetting != nil {
|
|
||||||
if sniValue, ok := searchKey(realitySetting, "serverNames"); ok {
|
|
||||||
sNames, _ := sniValue.([]any)
|
|
||||||
params["sni"] = sNames[random.Num(len(sNames))].(string)
|
|
||||||
}
|
|
||||||
if pbkValue, ok := searchKey(realitySettings, "publicKey"); ok {
|
|
||||||
params["pbk"], _ = pbkValue.(string)
|
|
||||||
}
|
|
||||||
if sidValue, ok := searchKey(realitySetting, "shortIds"); ok {
|
|
||||||
shortIds, _ := sidValue.([]any)
|
|
||||||
params["sid"] = shortIds[random.Num(len(shortIds))].(string)
|
|
||||||
}
|
|
||||||
if fpValue, ok := searchKey(realitySettings, "fingerprint"); ok {
|
|
||||||
if fp, ok := fpValue.(string); ok && len(fp) > 0 {
|
|
||||||
params["fp"] = fp
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if pqvValue, ok := searchKey(realitySettings, "mldsa65Verify"); ok {
|
|
||||||
if pqv, ok := pqvValue.(string); ok && len(pqv) > 0 {
|
|
||||||
params["pqv"] = pqv
|
|
||||||
}
|
|
||||||
}
|
|
||||||
params["spx"] = "/" + random.Seq(15)
|
|
||||||
}
|
|
||||||
|
|
||||||
if streamNetwork == "tcp" && len(clients[clientIndex].Flow) > 0 {
|
|
||||||
params["flow"] = clients[clientIndex].Flow
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if security != "tls" && security != "reality" {
|
|
||||||
params["security"] = "none"
|
|
||||||
}
|
|
||||||
|
|
||||||
externalProxies, _ := stream["externalProxy"].([]any)
|
|
||||||
|
|
||||||
if len(externalProxies) > 0 {
|
|
||||||
links := ""
|
|
||||||
for index, externalProxy := range externalProxies {
|
|
||||||
ep, _ := externalProxy.(map[string]any)
|
|
||||||
newSecurity, _ := ep["forceTls"].(string)
|
|
||||||
dest, _ := ep["dest"].(string)
|
|
||||||
port := int(ep["port"].(float64))
|
|
||||||
link := fmt.Sprintf("vless://%s@%s:%d", uuid, dest, port)
|
|
||||||
|
|
||||||
if newSecurity != "same" {
|
|
||||||
params["security"] = newSecurity
|
|
||||||
} else {
|
|
||||||
params["security"] = security
|
|
||||||
}
|
|
||||||
url, _ := url.Parse(link)
|
|
||||||
q := url.Query()
|
|
||||||
|
|
||||||
for k, v := range params {
|
|
||||||
if !(newSecurity == "none" && (k == "alpn" || k == "sni" || k == "fp" || k == "allowInsecure")) {
|
|
||||||
q.Add(k, v)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set the new query values on the URL
|
|
||||||
url.RawQuery = q.Encode()
|
|
||||||
|
|
||||||
url.Fragment = s.genRemark(inbound, email, ep["remark"].(string))
|
|
||||||
|
|
||||||
if index > 0 {
|
|
||||||
links += "\n"
|
|
||||||
}
|
|
||||||
links += url.String()
|
|
||||||
}
|
|
||||||
return links
|
|
||||||
}
|
|
||||||
|
|
||||||
link := fmt.Sprintf("vless://%s@%s:%d", uuid, address, port)
|
|
||||||
url, _ := url.Parse(link)
|
|
||||||
q := url.Query()
|
|
||||||
|
|
||||||
for k, v := range params {
|
|
||||||
q.Add(k, v)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set the new query values on the URL
|
|
||||||
url.RawQuery = q.Encode()
|
|
||||||
|
|
||||||
url.Fragment = s.genRemark(inbound, email, "")
|
|
||||||
return url.String()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *SubService) genTrojanLink(inbound *model.Inbound, email string) string {
|
|
||||||
address := s.address
|
|
||||||
if inbound.Protocol != model.Trojan {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
var stream map[string]any
|
|
||||||
json.Unmarshal([]byte(inbound.StreamSettings), &stream)
|
|
||||||
clients, _ := s.inboundService.GetClients(inbound)
|
|
||||||
clientIndex := -1
|
|
||||||
for i, client := range clients {
|
|
||||||
if client.Email == email {
|
|
||||||
clientIndex = i
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
password := clients[clientIndex].Password
|
|
||||||
port := inbound.Port
|
|
||||||
streamNetwork := stream["network"].(string)
|
|
||||||
params := make(map[string]string)
|
|
||||||
params["type"] = streamNetwork
|
|
||||||
|
|
||||||
switch streamNetwork {
|
|
||||||
case "tcp":
|
|
||||||
tcp, _ := stream["tcpSettings"].(map[string]any)
|
|
||||||
header, _ := tcp["header"].(map[string]any)
|
|
||||||
typeStr, _ := header["type"].(string)
|
|
||||||
if typeStr == "http" {
|
|
||||||
request := header["request"].(map[string]any)
|
|
||||||
requestPath, _ := request["path"].([]any)
|
|
||||||
params["path"] = requestPath[0].(string)
|
|
||||||
headers, _ := request["headers"].(map[string]any)
|
|
||||||
params["host"] = searchHost(headers)
|
|
||||||
params["headerType"] = "http"
|
|
||||||
}
|
|
||||||
case "kcp":
|
|
||||||
kcp, _ := stream["kcpSettings"].(map[string]any)
|
|
||||||
header, _ := kcp["header"].(map[string]any)
|
|
||||||
params["headerType"] = header["type"].(string)
|
|
||||||
params["seed"] = kcp["seed"].(string)
|
|
||||||
case "ws":
|
|
||||||
ws, _ := stream["wsSettings"].(map[string]any)
|
|
||||||
params["path"] = ws["path"].(string)
|
|
||||||
if host, ok := ws["host"].(string); ok && len(host) > 0 {
|
|
||||||
params["host"] = host
|
|
||||||
} else {
|
|
||||||
headers, _ := ws["headers"].(map[string]any)
|
|
||||||
params["host"] = searchHost(headers)
|
|
||||||
}
|
|
||||||
case "grpc":
|
|
||||||
grpc, _ := stream["grpcSettings"].(map[string]any)
|
|
||||||
params["serviceName"] = grpc["serviceName"].(string)
|
|
||||||
params["authority"], _ = grpc["authority"].(string)
|
|
||||||
if grpc["multiMode"].(bool) {
|
|
||||||
params["mode"] = "multi"
|
|
||||||
}
|
|
||||||
case "httpupgrade":
|
|
||||||
httpupgrade, _ := stream["httpupgradeSettings"].(map[string]any)
|
|
||||||
params["path"] = httpupgrade["path"].(string)
|
|
||||||
if host, ok := httpupgrade["host"].(string); ok && len(host) > 0 {
|
|
||||||
params["host"] = host
|
|
||||||
} else {
|
|
||||||
headers, _ := httpupgrade["headers"].(map[string]any)
|
|
||||||
params["host"] = searchHost(headers)
|
|
||||||
}
|
|
||||||
case "xhttp":
|
|
||||||
xhttp, _ := stream["xhttpSettings"].(map[string]any)
|
|
||||||
params["path"] = xhttp["path"].(string)
|
|
||||||
if host, ok := xhttp["host"].(string); ok && len(host) > 0 {
|
|
||||||
params["host"] = host
|
|
||||||
} else {
|
|
||||||
headers, _ := xhttp["headers"].(map[string]any)
|
|
||||||
params["host"] = searchHost(headers)
|
|
||||||
}
|
|
||||||
params["mode"] = xhttp["mode"].(string)
|
|
||||||
}
|
|
||||||
security, _ := stream["security"].(string)
|
|
||||||
if security == "tls" {
|
|
||||||
params["security"] = "tls"
|
|
||||||
tlsSetting, _ := stream["tlsSettings"].(map[string]any)
|
|
||||||
alpns, _ := tlsSetting["alpn"].([]any)
|
|
||||||
var alpn []string
|
|
||||||
for _, a := range alpns {
|
|
||||||
alpn = append(alpn, a.(string))
|
|
||||||
}
|
|
||||||
if len(alpn) > 0 {
|
|
||||||
params["alpn"] = strings.Join(alpn, ",")
|
|
||||||
}
|
|
||||||
if sniValue, ok := searchKey(tlsSetting, "serverName"); ok {
|
|
||||||
params["sni"], _ = sniValue.(string)
|
|
||||||
}
|
|
||||||
|
|
||||||
tlsSettings, _ := searchKey(tlsSetting, "settings")
|
|
||||||
if tlsSetting != nil {
|
|
||||||
if fpValue, ok := searchKey(tlsSettings, "fingerprint"); ok {
|
|
||||||
params["fp"], _ = fpValue.(string)
|
|
||||||
}
|
|
||||||
if insecure, ok := searchKey(tlsSettings, "allowInsecure"); ok {
|
|
||||||
if insecure.(bool) {
|
|
||||||
params["allowInsecure"] = "1"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if security == "reality" {
|
|
||||||
params["security"] = "reality"
|
|
||||||
realitySetting, _ := stream["realitySettings"].(map[string]any)
|
|
||||||
realitySettings, _ := searchKey(realitySetting, "settings")
|
|
||||||
if realitySetting != nil {
|
|
||||||
if sniValue, ok := searchKey(realitySetting, "serverNames"); ok {
|
|
||||||
sNames, _ := sniValue.([]any)
|
|
||||||
params["sni"] = sNames[random.Num(len(sNames))].(string)
|
|
||||||
}
|
|
||||||
if pbkValue, ok := searchKey(realitySettings, "publicKey"); ok {
|
|
||||||
params["pbk"], _ = pbkValue.(string)
|
|
||||||
}
|
|
||||||
if sidValue, ok := searchKey(realitySetting, "shortIds"); ok {
|
|
||||||
shortIds, _ := sidValue.([]any)
|
|
||||||
params["sid"] = shortIds[random.Num(len(shortIds))].(string)
|
|
||||||
}
|
|
||||||
if fpValue, ok := searchKey(realitySettings, "fingerprint"); ok {
|
|
||||||
if fp, ok := fpValue.(string); ok && len(fp) > 0 {
|
|
||||||
params["fp"] = fp
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if pqvValue, ok := searchKey(realitySettings, "mldsa65Verify"); ok {
|
|
||||||
if pqv, ok := pqvValue.(string); ok && len(pqv) > 0 {
|
|
||||||
params["pqv"] = pqv
|
|
||||||
}
|
|
||||||
}
|
|
||||||
params["spx"] = "/" + random.Seq(15)
|
|
||||||
}
|
|
||||||
|
|
||||||
if streamNetwork == "tcp" && len(clients[clientIndex].Flow) > 0 {
|
|
||||||
params["flow"] = clients[clientIndex].Flow
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if security != "tls" && security != "reality" {
|
|
||||||
params["security"] = "none"
|
|
||||||
}
|
|
||||||
|
|
||||||
externalProxies, _ := stream["externalProxy"].([]any)
|
|
||||||
|
|
||||||
if len(externalProxies) > 0 {
|
|
||||||
links := ""
|
|
||||||
for index, externalProxy := range externalProxies {
|
|
||||||
ep, _ := externalProxy.(map[string]any)
|
|
||||||
newSecurity, _ := ep["forceTls"].(string)
|
|
||||||
dest, _ := ep["dest"].(string)
|
|
||||||
port := int(ep["port"].(float64))
|
|
||||||
link := fmt.Sprintf("trojan://%s@%s:%d", password, dest, port)
|
|
||||||
|
|
||||||
if newSecurity != "same" {
|
|
||||||
params["security"] = newSecurity
|
|
||||||
} else {
|
|
||||||
params["security"] = security
|
|
||||||
}
|
|
||||||
url, _ := url.Parse(link)
|
|
||||||
q := url.Query()
|
|
||||||
|
|
||||||
for k, v := range params {
|
|
||||||
if !(newSecurity == "none" && (k == "alpn" || k == "sni" || k == "fp" || k == "allowInsecure")) {
|
|
||||||
q.Add(k, v)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set the new query values on the URL
|
|
||||||
url.RawQuery = q.Encode()
|
|
||||||
|
|
||||||
url.Fragment = s.genRemark(inbound, email, ep["remark"].(string))
|
|
||||||
|
|
||||||
if index > 0 {
|
|
||||||
links += "\n"
|
|
||||||
}
|
|
||||||
links += url.String()
|
|
||||||
}
|
|
||||||
return links
|
|
||||||
}
|
|
||||||
|
|
||||||
link := fmt.Sprintf("trojan://%s@%s:%d", password, address, port)
|
|
||||||
|
|
||||||
url, _ := url.Parse(link)
|
|
||||||
q := url.Query()
|
|
||||||
|
|
||||||
for k, v := range params {
|
|
||||||
q.Add(k, v)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set the new query values on the URL
|
|
||||||
url.RawQuery = q.Encode()
|
|
||||||
|
|
||||||
url.Fragment = s.genRemark(inbound, email, "")
|
|
||||||
return url.String()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *SubService) genShadowsocksLink(inbound *model.Inbound, email string) string {
|
|
||||||
address := s.address
|
|
||||||
if inbound.Protocol != model.Shadowsocks {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
var stream map[string]any
|
|
||||||
json.Unmarshal([]byte(inbound.StreamSettings), &stream)
|
|
||||||
clients, _ := s.inboundService.GetClients(inbound)
|
|
||||||
|
|
||||||
var settings map[string]any
|
|
||||||
json.Unmarshal([]byte(inbound.Settings), &settings)
|
|
||||||
inboundPassword := settings["password"].(string)
|
|
||||||
method := settings["method"].(string)
|
|
||||||
clientIndex := -1
|
|
||||||
for i, client := range clients {
|
|
||||||
if client.Email == email {
|
|
||||||
clientIndex = i
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
streamNetwork := stream["network"].(string)
|
|
||||||
params := make(map[string]string)
|
|
||||||
params["type"] = streamNetwork
|
|
||||||
|
|
||||||
switch streamNetwork {
|
|
||||||
case "tcp":
|
|
||||||
tcp, _ := stream["tcpSettings"].(map[string]any)
|
|
||||||
header, _ := tcp["header"].(map[string]any)
|
|
||||||
typeStr, _ := header["type"].(string)
|
|
||||||
if typeStr == "http" {
|
|
||||||
request := header["request"].(map[string]any)
|
|
||||||
requestPath, _ := request["path"].([]any)
|
|
||||||
params["path"] = requestPath[0].(string)
|
|
||||||
headers, _ := request["headers"].(map[string]any)
|
|
||||||
params["host"] = searchHost(headers)
|
|
||||||
params["headerType"] = "http"
|
|
||||||
}
|
|
||||||
case "kcp":
|
|
||||||
kcp, _ := stream["kcpSettings"].(map[string]any)
|
|
||||||
header, _ := kcp["header"].(map[string]any)
|
|
||||||
params["headerType"] = header["type"].(string)
|
|
||||||
params["seed"] = kcp["seed"].(string)
|
|
||||||
case "ws":
|
|
||||||
ws, _ := stream["wsSettings"].(map[string]any)
|
|
||||||
params["path"] = ws["path"].(string)
|
|
||||||
if host, ok := ws["host"].(string); ok && len(host) > 0 {
|
|
||||||
params["host"] = host
|
|
||||||
} else {
|
|
||||||
headers, _ := ws["headers"].(map[string]any)
|
|
||||||
params["host"] = searchHost(headers)
|
|
||||||
}
|
|
||||||
case "grpc":
|
|
||||||
grpc, _ := stream["grpcSettings"].(map[string]any)
|
|
||||||
params["serviceName"] = grpc["serviceName"].(string)
|
|
||||||
params["authority"], _ = grpc["authority"].(string)
|
|
||||||
if grpc["multiMode"].(bool) {
|
|
||||||
params["mode"] = "multi"
|
|
||||||
}
|
|
||||||
case "httpupgrade":
|
|
||||||
httpupgrade, _ := stream["httpupgradeSettings"].(map[string]any)
|
|
||||||
params["path"] = httpupgrade["path"].(string)
|
|
||||||
if host, ok := httpupgrade["host"].(string); ok && len(host) > 0 {
|
|
||||||
params["host"] = host
|
|
||||||
} else {
|
|
||||||
headers, _ := httpupgrade["headers"].(map[string]any)
|
|
||||||
params["host"] = searchHost(headers)
|
|
||||||
}
|
|
||||||
case "xhttp":
|
|
||||||
xhttp, _ := stream["xhttpSettings"].(map[string]any)
|
|
||||||
params["path"] = xhttp["path"].(string)
|
|
||||||
if host, ok := xhttp["host"].(string); ok && len(host) > 0 {
|
|
||||||
params["host"] = host
|
|
||||||
} else {
|
|
||||||
headers, _ := xhttp["headers"].(map[string]any)
|
|
||||||
params["host"] = searchHost(headers)
|
|
||||||
}
|
|
||||||
params["mode"] = xhttp["mode"].(string)
|
|
||||||
}
|
|
||||||
|
|
||||||
security, _ := stream["security"].(string)
|
|
||||||
if security == "tls" {
|
|
||||||
params["security"] = "tls"
|
|
||||||
tlsSetting, _ := stream["tlsSettings"].(map[string]any)
|
|
||||||
alpns, _ := tlsSetting["alpn"].([]any)
|
|
||||||
var alpn []string
|
|
||||||
for _, a := range alpns {
|
|
||||||
alpn = append(alpn, a.(string))
|
|
||||||
}
|
|
||||||
if len(alpn) > 0 {
|
|
||||||
params["alpn"] = strings.Join(alpn, ",")
|
|
||||||
}
|
|
||||||
if sniValue, ok := searchKey(tlsSetting, "serverName"); ok {
|
|
||||||
params["sni"], _ = sniValue.(string)
|
|
||||||
}
|
|
||||||
|
|
||||||
tlsSettings, _ := searchKey(tlsSetting, "settings")
|
|
||||||
if tlsSetting != nil {
|
|
||||||
if fpValue, ok := searchKey(tlsSettings, "fingerprint"); ok {
|
|
||||||
params["fp"], _ = fpValue.(string)
|
|
||||||
}
|
|
||||||
if insecure, ok := searchKey(tlsSettings, "allowInsecure"); ok {
|
|
||||||
if insecure.(bool) {
|
|
||||||
params["allowInsecure"] = "1"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
encPart := fmt.Sprintf("%s:%s", method, clients[clientIndex].Password)
|
|
||||||
if method[0] == '2' {
|
|
||||||
encPart = fmt.Sprintf("%s:%s:%s", method, inboundPassword, clients[clientIndex].Password)
|
|
||||||
}
|
|
||||||
|
|
||||||
externalProxies, _ := stream["externalProxy"].([]any)
|
|
||||||
|
|
||||||
if len(externalProxies) > 0 {
|
|
||||||
links := ""
|
|
||||||
for index, externalProxy := range externalProxies {
|
|
||||||
ep, _ := externalProxy.(map[string]any)
|
|
||||||
newSecurity, _ := ep["forceTls"].(string)
|
|
||||||
dest, _ := ep["dest"].(string)
|
|
||||||
port := int(ep["port"].(float64))
|
|
||||||
link := fmt.Sprintf("ss://%s@%s:%d", base64.StdEncoding.EncodeToString([]byte(encPart)), dest, port)
|
|
||||||
|
|
||||||
if newSecurity != "same" {
|
|
||||||
params["security"] = newSecurity
|
|
||||||
} else {
|
|
||||||
params["security"] = security
|
|
||||||
}
|
|
||||||
url, _ := url.Parse(link)
|
|
||||||
q := url.Query()
|
|
||||||
|
|
||||||
for k, v := range params {
|
|
||||||
if !(newSecurity == "none" && (k == "alpn" || k == "sni" || k == "fp" || k == "allowInsecure")) {
|
|
||||||
q.Add(k, v)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set the new query values on the URL
|
|
||||||
url.RawQuery = q.Encode()
|
|
||||||
|
|
||||||
url.Fragment = s.genRemark(inbound, email, ep["remark"].(string))
|
|
||||||
|
|
||||||
if index > 0 {
|
|
||||||
links += "\n"
|
|
||||||
}
|
|
||||||
links += url.String()
|
|
||||||
}
|
|
||||||
return links
|
|
||||||
}
|
|
||||||
|
|
||||||
link := fmt.Sprintf("ss://%s@%s:%d", base64.StdEncoding.EncodeToString([]byte(encPart)), address, inbound.Port)
|
|
||||||
url, _ := url.Parse(link)
|
|
||||||
q := url.Query()
|
|
||||||
|
|
||||||
for k, v := range params {
|
|
||||||
q.Add(k, v)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set the new query values on the URL
|
|
||||||
url.RawQuery = q.Encode()
|
|
||||||
|
|
||||||
url.Fragment = s.genRemark(inbound, email, "")
|
|
||||||
return url.String()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *SubService) genRemark(inbound *model.Inbound, email string, extra string) string {
|
|
||||||
separationChar := string(s.remarkModel[0])
|
|
||||||
orderChars := s.remarkModel[1:]
|
|
||||||
orders := map[byte]string{
|
|
||||||
'i': "",
|
|
||||||
'e': "",
|
|
||||||
'o': "",
|
|
||||||
}
|
|
||||||
if len(email) > 0 {
|
|
||||||
orders['e'] = email
|
|
||||||
}
|
|
||||||
if len(inbound.Remark) > 0 {
|
|
||||||
orders['i'] = inbound.Remark
|
|
||||||
}
|
|
||||||
if len(extra) > 0 {
|
|
||||||
orders['o'] = extra
|
|
||||||
}
|
|
||||||
|
|
||||||
var remark []string
|
|
||||||
for i := 0; i < len(orderChars); i++ {
|
|
||||||
char := orderChars[i]
|
|
||||||
order, exists := orders[char]
|
|
||||||
if exists && order != "" {
|
|
||||||
remark = append(remark, order)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if s.showInfo {
|
|
||||||
statsExist := false
|
|
||||||
var stats xray.ClientTraffic
|
|
||||||
for _, clientStat := range inbound.ClientStats {
|
|
||||||
if clientStat.Email == email {
|
|
||||||
stats = clientStat
|
|
||||||
statsExist = true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get remained days
|
|
||||||
if statsExist {
|
|
||||||
if !stats.Enable {
|
|
||||||
return fmt.Sprintf("⛔️N/A%s%s", separationChar, strings.Join(remark, separationChar))
|
|
||||||
}
|
|
||||||
if vol := stats.Total - (stats.Up + stats.Down); vol > 0 {
|
|
||||||
remark = append(remark, fmt.Sprintf("%s%s", common.FormatTraffic(vol), "📊"))
|
|
||||||
}
|
|
||||||
now := time.Now().Unix()
|
|
||||||
switch exp := stats.ExpiryTime / 1000; {
|
|
||||||
case exp > 0:
|
|
||||||
remainingSeconds := exp - now
|
|
||||||
days := remainingSeconds / 86400
|
|
||||||
hours := (remainingSeconds % 86400) / 3600
|
|
||||||
minutes := (remainingSeconds % 3600) / 60
|
|
||||||
if days > 0 {
|
|
||||||
if hours > 0 {
|
|
||||||
remark = append(remark, fmt.Sprintf("%dD,%dH⏳", days, hours))
|
|
||||||
} else {
|
|
||||||
remark = append(remark, fmt.Sprintf("%dD⏳", days))
|
|
||||||
}
|
|
||||||
} else if hours > 0 {
|
|
||||||
remark = append(remark, fmt.Sprintf("%dH⏳", hours))
|
|
||||||
} else {
|
|
||||||
remark = append(remark, fmt.Sprintf("%dM⏳", minutes))
|
|
||||||
}
|
|
||||||
case exp < 0:
|
|
||||||
days := exp / -86400
|
|
||||||
hours := (exp % -86400) / 3600
|
|
||||||
minutes := (exp % -3600) / 60
|
|
||||||
if days > 0 {
|
|
||||||
if hours > 0 {
|
|
||||||
remark = append(remark, fmt.Sprintf("%dD,%dH⏳", days, hours))
|
|
||||||
} else {
|
|
||||||
remark = append(remark, fmt.Sprintf("%dD⏳", days))
|
|
||||||
}
|
|
||||||
} else if hours > 0 {
|
|
||||||
remark = append(remark, fmt.Sprintf("%dH⏳", hours))
|
|
||||||
} else {
|
|
||||||
remark = append(remark, fmt.Sprintf("%dM⏳", minutes))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return strings.Join(remark, separationChar)
|
|
||||||
}
|
|
||||||
|
|
||||||
func searchKey(data any, key string) (any, bool) {
|
|
||||||
switch val := data.(type) {
|
|
||||||
case map[string]any:
|
|
||||||
for k, v := range val {
|
|
||||||
if k == key {
|
|
||||||
return v, true
|
|
||||||
}
|
|
||||||
if result, ok := searchKey(v, key); ok {
|
|
||||||
return result, true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
case []any:
|
|
||||||
for _, v := range val {
|
|
||||||
if result, ok := searchKey(v, key); ok {
|
|
||||||
return result, true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil, false
|
|
||||||
}
|
|
||||||
|
|
||||||
func searchHost(headers any) string {
|
|
||||||
data, _ := headers.(map[string]any)
|
|
||||||
for k, v := range data {
|
|
||||||
if strings.EqualFold(k, "host") {
|
|
||||||
switch v.(type) {
|
|
||||||
case []any:
|
|
||||||
hosts, _ := v.([]any)
|
|
||||||
if len(hosts) > 0 {
|
|
||||||
return hosts[0].(string)
|
|
||||||
} else {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
case any:
|
|
||||||
return v.(string)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return ""
|
|
||||||
}
|
|
|
@ -3,21 +3,22 @@ package common
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"x-ui/logger"
|
"x-ui/logger"
|
||||||
)
|
)
|
||||||
|
|
||||||
func NewErrorf(format string, a ...any) error {
|
var CtxDone = errors.New("context done")
|
||||||
|
|
||||||
|
func NewErrorf(format string, a ...interface{}) error {
|
||||||
msg := fmt.Sprintf(format, a...)
|
msg := fmt.Sprintf(format, a...)
|
||||||
return errors.New(msg)
|
return errors.New(msg)
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewError(a ...any) error {
|
func NewError(a ...interface{}) error {
|
||||||
msg := fmt.Sprintln(a...)
|
msg := fmt.Sprintln(a...)
|
||||||
return errors.New(msg)
|
return errors.New(msg)
|
||||||
}
|
}
|
||||||
|
|
||||||
func Recover(msg string) any {
|
func Recover(msg string) interface{} {
|
||||||
panicErr := recover()
|
panicErr := recover()
|
||||||
if panicErr != nil {
|
if panicErr != nil {
|
||||||
if msg != "" {
|
if msg != "" {
|
||||||
|
|
|
@ -4,14 +4,18 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
)
|
)
|
||||||
|
|
||||||
func FormatTraffic(trafficBytes int64) string {
|
func FormatTraffic(trafficBytes int64) (size string) {
|
||||||
units := []string{"B", "KB", "MB", "GB", "TB", "PB"}
|
if trafficBytes < 1024 {
|
||||||
unitIndex := 0
|
return fmt.Sprintf("%.2fB", float64(trafficBytes)/float64(1))
|
||||||
size := float64(trafficBytes)
|
} else if trafficBytes < (1024 * 1024) {
|
||||||
|
return fmt.Sprintf("%.2fKB", float64(trafficBytes)/float64(1024))
|
||||||
for size >= 1024 && unitIndex < len(units)-1 {
|
} else if trafficBytes < (1024 * 1024 * 1024) {
|
||||||
size /= 1024
|
return fmt.Sprintf("%.2fMB", float64(trafficBytes)/float64(1024*1024))
|
||||||
unitIndex++
|
} else if trafficBytes < (1024 * 1024 * 1024 * 1024) {
|
||||||
|
return fmt.Sprintf("%.2fGB", float64(trafficBytes)/float64(1024*1024*1024))
|
||||||
|
} else if trafficBytes < (1024 * 1024 * 1024 * 1024 * 1024) {
|
||||||
|
return fmt.Sprintf("%.2fTB", float64(trafficBytes)/float64(1024*1024*1024*1024))
|
||||||
|
} else {
|
||||||
|
return fmt.Sprintf("%.2fEB", float64(trafficBytes)/float64(1024*1024*1024*1024*1024))
|
||||||
}
|
}
|
||||||
return fmt.Sprintf("%.2f%s", size, units[unitIndex])
|
|
||||||
}
|
}
|
||||||
|
|
9
util/common/stringUtil.go
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
package common
|
||||||
|
|
||||||
|
import "sort"
|
||||||
|
|
||||||
|
func IsSubString(target string, str_array []string) bool {
|
||||||
|
sort.Strings(str_array)
|
||||||
|
index := sort.SearchStrings(str_array, target)
|
||||||
|
return index < len(str_array) && str_array[index] == target
|
||||||
|
}
|
12
util/context.go
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
package util
|
||||||
|
|
||||||
|
import "context"
|
||||||
|
|
||||||
|
func IsDone(ctx context.Context) bool {
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
return true
|
||||||
|
default:
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,15 +0,0 @@
|
||||||
package crypto
|
|
||||||
|
|
||||||
import (
|
|
||||||
"golang.org/x/crypto/bcrypt"
|
|
||||||
)
|
|
||||||
|
|
||||||
func HashPasswordAsBcrypt(password string) (string, error) {
|
|
||||||
hash, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
|
|
||||||
return string(hash), err
|
|
||||||
}
|
|
||||||
|
|
||||||
func CheckPasswordHash(hash, password string) bool {
|
|
||||||
err := bcrypt.CompareHashAndPassword([]byte(hash), []byte(password))
|
|
||||||
return err == nil
|
|
||||||
}
|
|
|
@ -6,7 +6,7 @@ import (
|
||||||
|
|
||||||
type RawMessage []byte
|
type RawMessage []byte
|
||||||
|
|
||||||
// MarshalJSON: Customize json.RawMessage default behavior
|
// MarshalJSON 自定义 json.RawMessage 默认行为
|
||||||
func (m RawMessage) MarshalJSON() ([]byte, error) {
|
func (m RawMessage) MarshalJSON() ([]byte, error) {
|
||||||
if len(m) == 0 {
|
if len(m) == 0 {
|
||||||
return []byte("null"), nil
|
return []byte("null"), nil
|
||||||
|
@ -14,7 +14,7 @@ func (m RawMessage) MarshalJSON() ([]byte, error) {
|
||||||
return m, nil
|
return m, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// UnmarshalJSON: sets *m to a copy of data.
|
// UnmarshalJSON sets *m to a copy of data.
|
||||||
func (m *RawMessage) UnmarshalJSON(data []byte) error {
|
func (m *RawMessage) UnmarshalJSON(data []byte) error {
|
||||||
if m == nil {
|
if m == nil {
|
||||||
return errors.New("json.RawMessage: UnmarshalJSON on nil pointer")
|
return errors.New("json.RawMessage: UnmarshalJSON on nil pointer")
|
||||||
|
|
|
@ -2,18 +2,19 @@ package random
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"math/rand"
|
"math/rand"
|
||||||
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var numSeq [10]rune
|
||||||
numSeq [10]rune
|
var lowerSeq [26]rune
|
||||||
lowerSeq [26]rune
|
var upperSeq [26]rune
|
||||||
upperSeq [26]rune
|
var numLowerSeq [36]rune
|
||||||
numLowerSeq [36]rune
|
var numUpperSeq [36]rune
|
||||||
numUpperSeq [36]rune
|
var allSeq [62]rune
|
||||||
allSeq [62]rune
|
|
||||||
)
|
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
|
rand.Seed(time.Now().UnixNano())
|
||||||
|
|
||||||
for i := 0; i < 10; i++ {
|
for i := 0; i < 10; i++ {
|
||||||
numSeq[i] = rune('0' + i)
|
numSeq[i] = rune('0' + i)
|
||||||
}
|
}
|
||||||
|
@ -40,7 +41,3 @@ func Seq(n int) string {
|
||||||
}
|
}
|
||||||
return string(runes)
|
return string(runes)
|
||||||
}
|
}
|
||||||
|
|
||||||
func Num(n int) int {
|
|
||||||
return rand.Intn(n)
|
|
||||||
}
|
|
||||||
|
|
0
util/sys/a.s
Normal file
|
@ -4,5 +4,5 @@ import (
|
||||||
_ "unsafe"
|
_ "unsafe"
|
||||||
)
|
)
|
||||||
|
|
||||||
//go:linkname HostProc github.com/shirou/gopsutil/v4/internal/common.HostProc
|
//go:linkname HostProc github.com/shirou/gopsutil/v3/internal/common.HostProc
|
||||||
func HostProc(combineWith ...string) string
|
func HostProc(combineWith ...string) string
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
package sys
|
package sys
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/shirou/gopsutil/v4/net"
|
"github.com/shirou/gopsutil/v3/net"
|
||||||
)
|
)
|
||||||
|
|
||||||
func GetTCPCount() (int, error) {
|
func GetTCPCount() (int, error) {
|
||||||
|
|
|
@ -24,8 +24,8 @@ func getLinesNum(filename string) (int, error) {
|
||||||
|
|
||||||
var buffPosition int
|
var buffPosition int
|
||||||
for {
|
for {
|
||||||
i := bytes.IndexByte(buf[buffPosition:n], '\n')
|
i := bytes.IndexByte(buf[buffPosition:], '\n')
|
||||||
if i < 0 {
|
if i < 0 || n == buffPosition {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
buffPosition += i + 1
|
buffPosition += i + 1
|
||||||
|
@ -33,24 +33,23 @@ func getLinesNum(filename string) (int, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if err == io.EOF {
|
if err == io.EOF {
|
||||||
break
|
return sum, nil
|
||||||
} else if err != nil {
|
} else if err != nil {
|
||||||
return 0, err
|
return sum, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return sum, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetTCPCount() (int, error) {
|
func GetTCPCount() (int, error) {
|
||||||
root := HostProc()
|
root := HostProc()
|
||||||
|
|
||||||
tcp4, err := safeGetLinesNum(fmt.Sprintf("%v/net/tcp", root))
|
tcp4, err := getLinesNum(fmt.Sprintf("%v/net/tcp", root))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, err
|
return tcp4, err
|
||||||
}
|
}
|
||||||
tcp6, err := safeGetLinesNum(fmt.Sprintf("%v/net/tcp6", root))
|
tcp6, err := getLinesNum(fmt.Sprintf("%v/net/tcp6", root))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, err
|
return tcp4 + tcp6, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
return tcp4 + tcp6, nil
|
return tcp4 + tcp6, nil
|
||||||
|
@ -59,23 +58,14 @@ func GetTCPCount() (int, error) {
|
||||||
func GetUDPCount() (int, error) {
|
func GetUDPCount() (int, error) {
|
||||||
root := HostProc()
|
root := HostProc()
|
||||||
|
|
||||||
udp4, err := safeGetLinesNum(fmt.Sprintf("%v/net/udp", root))
|
udp4, err := getLinesNum(fmt.Sprintf("%v/net/udp", root))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, err
|
return udp4, err
|
||||||
}
|
}
|
||||||
udp6, err := safeGetLinesNum(fmt.Sprintf("%v/net/udp6", root))
|
udp6, err := getLinesNum(fmt.Sprintf("%v/net/udp6", root))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, err
|
return udp4 + udp6, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
return udp4 + udp6, nil
|
return udp4 + udp6, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func safeGetLinesNum(path string) (int, error) {
|
|
||||||
if _, err := os.Stat(path); os.IsNotExist(err) {
|
|
||||||
return 0, nil
|
|
||||||
} else if err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
return getLinesNum(path)
|
|
||||||
}
|
|
||||||
|
|
|
@ -4,27 +4,21 @@
|
||||||
package sys
|
package sys
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"github.com/shirou/gopsutil/v3/net"
|
||||||
|
|
||||||
"github.com/shirou/gopsutil/v4/net"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func GetConnectionCount(proto string) (int, error) {
|
func GetTCPCount() (int, error) {
|
||||||
if proto != "tcp" && proto != "udp" {
|
stats, err := net.Connections("tcp")
|
||||||
return 0, errors.New("invalid protocol")
|
|
||||||
}
|
|
||||||
|
|
||||||
stats, err := net.Connections(proto)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, err
|
return 0, err
|
||||||
}
|
}
|
||||||
return len(stats), nil
|
return len(stats), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetTCPCount() (int, error) {
|
|
||||||
return GetConnectionCount("tcp")
|
|
||||||
}
|
|
||||||
|
|
||||||
func GetUDPCount() (int, error) {
|
func GetUDPCount() (int, error) {
|
||||||
return GetConnectionCount("udp")
|
stats, err := net.Connections("udp")
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
return len(stats), nil
|
||||||
}
|
}
|
||||||
|
|
28
v2ui/db.go
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
package v2ui
|
||||||
|
|
||||||
|
import (
|
||||||
|
"gorm.io/driver/sqlite"
|
||||||
|
"gorm.io/gorm"
|
||||||
|
"gorm.io/gorm/logger"
|
||||||
|
)
|
||||||
|
|
||||||
|
var v2db *gorm.DB
|
||||||
|
|
||||||
|
func initDB(dbPath string) error {
|
||||||
|
c := &gorm.Config{
|
||||||
|
Logger: logger.Discard,
|
||||||
|
}
|
||||||
|
var err error
|
||||||
|
v2db, err = gorm.Open(sqlite.Open(dbPath), c)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func getV2Inbounds() ([]*V2Inbound, error) {
|
||||||
|
inbounds := make([]*V2Inbound, 0)
|
||||||
|
err := v2db.Model(V2Inbound{}).Find(&inbounds).Error
|
||||||
|
return inbounds, err
|
||||||
|
}
|
41
v2ui/models.go
Normal file
|
@ -0,0 +1,41 @@
|
||||||
|
package v2ui
|
||||||
|
|
||||||
|
import "x-ui/database/model"
|
||||||
|
|
||||||
|
type V2Inbound struct {
|
||||||
|
Id int `gorm:"primaryKey;autoIncrement"`
|
||||||
|
Port int `gorm:"unique"`
|
||||||
|
Listen string
|
||||||
|
Protocol string
|
||||||
|
Settings string
|
||||||
|
StreamSettings string
|
||||||
|
Tag string `gorm:"unique"`
|
||||||
|
Sniffing string
|
||||||
|
Remark string
|
||||||
|
Up int64
|
||||||
|
Down int64
|
||||||
|
Enable bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *V2Inbound) TableName() string {
|
||||||
|
return "inbound"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *V2Inbound) ToInbound(userId int) *model.Inbound {
|
||||||
|
return &model.Inbound{
|
||||||
|
UserId: userId,
|
||||||
|
Up: i.Up,
|
||||||
|
Down: i.Down,
|
||||||
|
Total: 0,
|
||||||
|
Remark: i.Remark,
|
||||||
|
Enable: i.Enable,
|
||||||
|
ExpiryTime: 0,
|
||||||
|
Listen: i.Listen,
|
||||||
|
Port: i.Port,
|
||||||
|
Protocol: model.Protocol(i.Protocol),
|
||||||
|
Settings: i.Settings,
|
||||||
|
StreamSettings: i.StreamSettings,
|
||||||
|
Tag: i.Tag,
|
||||||
|
Sniffing: i.Sniffing,
|
||||||
|
}
|
||||||
|
}
|
51
v2ui/v2ui.go
Normal file
|
@ -0,0 +1,51 @@
|
||||||
|
package v2ui
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"x-ui/config"
|
||||||
|
"x-ui/database"
|
||||||
|
"x-ui/database/model"
|
||||||
|
"x-ui/util/common"
|
||||||
|
"x-ui/web/service"
|
||||||
|
)
|
||||||
|
|
||||||
|
func MigrateFromV2UI(dbPath string) error {
|
||||||
|
err := initDB(dbPath)
|
||||||
|
if err != nil {
|
||||||
|
return common.NewError("init v2-ui database failed:", err)
|
||||||
|
}
|
||||||
|
err = database.InitDB(config.GetDBPath())
|
||||||
|
if err != nil {
|
||||||
|
return common.NewError("init x-ui database failed:", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
v2Inbounds, err := getV2Inbounds()
|
||||||
|
if err != nil {
|
||||||
|
return common.NewError("get v2-ui inbounds failed:", err)
|
||||||
|
}
|
||||||
|
if len(v2Inbounds) == 0 {
|
||||||
|
fmt.Println("migrate v2-ui inbounds success: 0")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
userService := service.UserService{}
|
||||||
|
user, err := userService.GetFirstUser()
|
||||||
|
if err != nil {
|
||||||
|
return common.NewError("get x-ui user failed:", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
inbounds := make([]*model.Inbound, 0)
|
||||||
|
for _, v2inbound := range v2Inbounds {
|
||||||
|
inbounds = append(inbounds, v2inbound.ToInbound(user.Id))
|
||||||
|
}
|
||||||
|
|
||||||
|
inboundService := service.InboundService{}
|
||||||
|
err = inboundService.AddInbounds(inbounds)
|
||||||
|
if err != nil {
|
||||||
|
return common.NewError("add x-ui inbounds failed:", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Println("migrate v2-ui inbounds success:", len(inbounds))
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
8
web/assets/ant-design-vue/antd.min.css
vendored
3
web/assets/ant-design-vue/antd.min.js
vendored
3
web/assets/ant-design-vue@1.7.2/antd-with-locales.min.js
vendored
Normal file
2
web/assets/ant-design-vue@1.7.2/antd.less
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
@import "../lib/style/index.less";
|
||||||
|
@import "../lib/style/components.less";
|
8
web/assets/ant-design-vue@1.7.2/antd.min.css
vendored
Normal file
2
web/assets/ant-design-vue@1.7.2/antd.min.js
vendored
Normal file
11
web/assets/axios/axios.min.js
vendored
1
web/assets/base64/base64.min.js
vendored
Normal file
|
@ -0,0 +1 @@
|
||||||
|
(function(global,factory){typeof exports==="object"&&typeof module!=="undefined"?module.exports=factory(global):typeof define==="function"&&define.amd?define(factory):factory(global)})(typeof self!=="undefined"?self:typeof window!=="undefined"?window:typeof global!=="undefined"?global:this,function(global){"use strict";var _Base64=global.Base64;var version="2.5.0";var buffer;if(typeof module!=="undefined"&&module.exports){try{buffer=eval("require('buffer').Buffer")}catch(err){buffer=undefined}}var b64chars="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";var b64tab=function(bin){var t={};for(var i=0,l=bin.length;i<l;i++)t[bin.charAt(i)]=i;return t}(b64chars);var fromCharCode=String.fromCharCode;var cb_utob=function(c){if(c.length<2){var cc=c.charCodeAt(0);return cc<128?c:cc<2048?fromCharCode(192|cc>>>6)+fromCharCode(128|cc&63):fromCharCode(224|cc>>>12&15)+fromCharCode(128|cc>>>6&63)+fromCharCode(128|cc&63)}else{var cc=65536+(c.charCodeAt(0)-55296)*1024+(c.charCodeAt(1)-56320);return fromCharCode(240|cc>>>18&7)+fromCharCode(128|cc>>>12&63)+fromCharCode(128|cc>>>6&63)+fromCharCode(128|cc&63)}};var re_utob=/[\uD800-\uDBFF][\uDC00-\uDFFFF]|[^\x00-\x7F]/g;var utob=function(u){return u.replace(re_utob,cb_utob)};var cb_encode=function(ccc){var padlen=[0,2,1][ccc.length%3],ord=ccc.charCodeAt(0)<<16|(ccc.length>1?ccc.charCodeAt(1):0)<<8|(ccc.length>2?ccc.charCodeAt(2):0),chars=[b64chars.charAt(ord>>>18),b64chars.charAt(ord>>>12&63),padlen>=2?"=":b64chars.charAt(ord>>>6&63),padlen>=1?"=":b64chars.charAt(ord&63)];return chars.join("")};var btoa=global.btoa?function(b){return global.btoa(b)}:function(b){return b.replace(/[\s\S]{1,3}/g,cb_encode)};var _encode=buffer?buffer.from&&Uint8Array&&buffer.from!==Uint8Array.from?function(u){return(u.constructor===buffer.constructor?u:buffer.from(u)).toString("base64")}:function(u){return(u.constructor===buffer.constructor?u:new buffer(u)).toString("base64")}:function(u){return btoa(utob(u))};var encode=function(u,urisafe){return!urisafe?_encode(String(u)):_encode(String(u)).replace(/[+\/]/g,function(m0){return m0=="+"?"-":"_"}).replace(/=/g,"")};var encodeURI=function(u){return encode(u,true)};var re_btou=new RegExp(["[À-ß][-¿]","[à-ï][-¿]{2}","[ð-÷][-¿]{3}"].join("|"),"g");var cb_btou=function(cccc){switch(cccc.length){case 4:var cp=(7&cccc.charCodeAt(0))<<18|(63&cccc.charCodeAt(1))<<12|(63&cccc.charCodeAt(2))<<6|63&cccc.charCodeAt(3),offset=cp-65536;return fromCharCode((offset>>>10)+55296)+fromCharCode((offset&1023)+56320);case 3:return fromCharCode((15&cccc.charCodeAt(0))<<12|(63&cccc.charCodeAt(1))<<6|63&cccc.charCodeAt(2));default:return fromCharCode((31&cccc.charCodeAt(0))<<6|63&cccc.charCodeAt(1))}};var btou=function(b){return b.replace(re_btou,cb_btou)};var cb_decode=function(cccc){var len=cccc.length,padlen=len%4,n=(len>0?b64tab[cccc.charAt(0)]<<18:0)|(len>1?b64tab[cccc.charAt(1)]<<12:0)|(len>2?b64tab[cccc.charAt(2)]<<6:0)|(len>3?b64tab[cccc.charAt(3)]:0),chars=[fromCharCode(n>>>16),fromCharCode(n>>>8&255),fromCharCode(n&255)];chars.length-=[0,0,2,1][padlen];return chars.join("")};var _atob=global.atob?function(a){return global.atob(a)}:function(a){return a.replace(/\S{1,4}/g,cb_decode)};var atob=function(a){return _atob(String(a).replace(/[^A-Za-z0-9\+\/]/g,""))};var _decode=buffer?buffer.from&&Uint8Array&&buffer.from!==Uint8Array.from?function(a){return(a.constructor===buffer.constructor?a:buffer.from(a,"base64")).toString()}:function(a){return(a.constructor===buffer.constructor?a:new buffer(a,"base64")).toString()}:function(a){return btou(_atob(a))};var decode=function(a){return _decode(String(a).replace(/[-_]/g,function(m0){return m0=="-"?"+":"/"}).replace(/[^A-Za-z0-9\+\/]/g,""))};var noConflict=function(){var Base64=global.Base64;global.Base64=_Base64;return Base64};global.Base64={VERSION:version,atob:atob,btoa:btoa,fromBase64:decode,toBase64:encode,utob:utob,encode:encode,encodeURI:encodeURI,btou:btou,decode:decode,noConflict:noConflict,__buffer__:buffer};if(typeof Object.defineProperty==="function"){var noEnum=function(v){return{value:v,enumerable:false,writable:true,configurable:true}};global.Base64.extendString=function(){Object.defineProperty(String.prototype,"fromBase64",noEnum(function(){return decode(this)}));Object.defineProperty(String.prototype,"toBase64",noEnum(function(urisafe){return encode(this,urisafe)}));Object.defineProperty(String.prototype,"toBase64URI",noEnum(function(){return encode(this,true)}))}}if(global["Meteor"]){Base64=global.Base64}if(typeof module!=="undefined"&&module.exports){module.exports.Base64=global.Base64}else if(typeof define==="function"&&define.amd){define([],function(){return global.Base64})}return{Base64:global.Base64}});
|
7
web/assets/clipboard/clipboard.min.js
vendored
Normal file
1
web/assets/codemirror/codemirror.min.css
vendored
1
web/assets/codemirror/codemirror.min.js
vendored
|
@ -1,119 +0,0 @@
|
||||||
// CodeMirror, copyright (c) by Marijn Haverbeke and others
|
|
||||||
// Distributed under an MIT license: https://codemirror.net/5/LICENSE
|
|
||||||
|
|
||||||
(function(mod) {
|
|
||||||
if (typeof exports == "object" && typeof module == "object") // CommonJS
|
|
||||||
mod(require("../../lib/codemirror"));
|
|
||||||
else if (typeof define == "function" && define.amd) // AMD
|
|
||||||
define(["../../lib/codemirror"], mod);
|
|
||||||
else // Plain browser env
|
|
||||||
mod(CodeMirror);
|
|
||||||
})(function(CodeMirror) {
|
|
||||||
"use strict";
|
|
||||||
|
|
||||||
function bracketFolding(pairs) {
|
|
||||||
return function(cm, start) {
|
|
||||||
var line = start.line, lineText = cm.getLine(line);
|
|
||||||
|
|
||||||
function findOpening(pair) {
|
|
||||||
var tokenType;
|
|
||||||
for (var at = start.ch, pass = 0;;) {
|
|
||||||
var found = at <= 0 ? -1 : lineText.lastIndexOf(pair[0], at - 1);
|
|
||||||
if (found == -1) {
|
|
||||||
if (pass == 1) break;
|
|
||||||
pass = 1;
|
|
||||||
at = lineText.length;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (pass == 1 && found < start.ch) break;
|
|
||||||
tokenType = cm.getTokenTypeAt(CodeMirror.Pos(line, found + 1));
|
|
||||||
if (!/^(comment|string)/.test(tokenType)) return {ch: found + 1, tokenType: tokenType, pair: pair};
|
|
||||||
at = found - 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function findRange(found) {
|
|
||||||
var count = 1, lastLine = cm.lastLine(), end, startCh = found.ch, endCh
|
|
||||||
outer: for (var i = line; i <= lastLine; ++i) {
|
|
||||||
var text = cm.getLine(i), pos = i == line ? startCh : 0;
|
|
||||||
for (;;) {
|
|
||||||
var nextOpen = text.indexOf(found.pair[0], pos), nextClose = text.indexOf(found.pair[1], pos);
|
|
||||||
if (nextOpen < 0) nextOpen = text.length;
|
|
||||||
if (nextClose < 0) nextClose = text.length;
|
|
||||||
pos = Math.min(nextOpen, nextClose);
|
|
||||||
if (pos == text.length) break;
|
|
||||||
if (cm.getTokenTypeAt(CodeMirror.Pos(i, pos + 1)) == found.tokenType) {
|
|
||||||
if (pos == nextOpen) ++count;
|
|
||||||
else if (!--count) { end = i; endCh = pos; break outer; }
|
|
||||||
}
|
|
||||||
++pos;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (end == null || line == end) return null
|
|
||||||
return {from: CodeMirror.Pos(line, startCh),
|
|
||||||
to: CodeMirror.Pos(end, endCh)};
|
|
||||||
}
|
|
||||||
|
|
||||||
var found = []
|
|
||||||
for (var i = 0; i < pairs.length; i++) {
|
|
||||||
var open = findOpening(pairs[i])
|
|
||||||
if (open) found.push(open)
|
|
||||||
}
|
|
||||||
found.sort(function(a, b) { return a.ch - b.ch })
|
|
||||||
for (var i = 0; i < found.length; i++) {
|
|
||||||
var range = findRange(found[i])
|
|
||||||
if (range) return range
|
|
||||||
}
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
CodeMirror.registerHelper("fold", "brace", bracketFolding([["{", "}"], ["[", "]"]]));
|
|
||||||
|
|
||||||
CodeMirror.registerHelper("fold", "brace-paren", bracketFolding([["{", "}"], ["[", "]"], ["(", ")"]]));
|
|
||||||
|
|
||||||
CodeMirror.registerHelper("fold", "import", function(cm, start) {
|
|
||||||
function hasImport(line) {
|
|
||||||
if (line < cm.firstLine() || line > cm.lastLine()) return null;
|
|
||||||
var start = cm.getTokenAt(CodeMirror.Pos(line, 1));
|
|
||||||
if (!/\S/.test(start.string)) start = cm.getTokenAt(CodeMirror.Pos(line, start.end + 1));
|
|
||||||
if (start.type != "keyword" || start.string != "import") return null;
|
|
||||||
// Now find closing semicolon, return its position
|
|
||||||
for (var i = line, e = Math.min(cm.lastLine(), line + 10); i <= e; ++i) {
|
|
||||||
var text = cm.getLine(i), semi = text.indexOf(";");
|
|
||||||
if (semi != -1) return {startCh: start.end, end: CodeMirror.Pos(i, semi)};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var startLine = start.line, has = hasImport(startLine), prev;
|
|
||||||
if (!has || hasImport(startLine - 1) || ((prev = hasImport(startLine - 2)) && prev.end.line == startLine - 1))
|
|
||||||
return null;
|
|
||||||
for (var end = has.end;;) {
|
|
||||||
var next = hasImport(end.line + 1);
|
|
||||||
if (next == null) break;
|
|
||||||
end = next.end;
|
|
||||||
}
|
|
||||||
return {from: cm.clipPos(CodeMirror.Pos(startLine, has.startCh + 1)), to: end};
|
|
||||||
});
|
|
||||||
|
|
||||||
CodeMirror.registerHelper("fold", "include", function(cm, start) {
|
|
||||||
function hasInclude(line) {
|
|
||||||
if (line < cm.firstLine() || line > cm.lastLine()) return null;
|
|
||||||
var start = cm.getTokenAt(CodeMirror.Pos(line, 1));
|
|
||||||
if (!/\S/.test(start.string)) start = cm.getTokenAt(CodeMirror.Pos(line, start.end + 1));
|
|
||||||
if (start.type == "meta" && start.string.slice(0, 8) == "#include") return start.start + 8;
|
|
||||||
}
|
|
||||||
|
|
||||||
var startLine = start.line, has = hasInclude(startLine);
|
|
||||||
if (has == null || hasInclude(startLine - 1) != null) return null;
|
|
||||||
for (var end = startLine;;) {
|
|
||||||
var next = hasInclude(end + 1);
|
|
||||||
if (next == null) break;
|
|
||||||
++end;
|
|
||||||
}
|
|
||||||
return {from: CodeMirror.Pos(startLine, has + 1),
|
|
||||||
to: cm.clipPos(CodeMirror.Pos(end))};
|
|
||||||
});
|
|
||||||
|
|
||||||
});
|
|
|
@ -1,159 +0,0 @@
|
||||||
// CodeMirror, copyright (c) by Marijn Haverbeke and others
|
|
||||||
// Distributed under an MIT license: https://codemirror.net/5/LICENSE
|
|
||||||
|
|
||||||
(function(mod) {
|
|
||||||
if (typeof exports == "object" && typeof module == "object") // CommonJS
|
|
||||||
mod(require("../../lib/codemirror"));
|
|
||||||
else if (typeof define == "function" && define.amd) // AMD
|
|
||||||
define(["../../lib/codemirror"], mod);
|
|
||||||
else // Plain browser env
|
|
||||||
mod(CodeMirror);
|
|
||||||
})(function(CodeMirror) {
|
|
||||||
"use strict";
|
|
||||||
|
|
||||||
function doFold(cm, pos, options, force) {
|
|
||||||
if (options && options.call) {
|
|
||||||
var finder = options;
|
|
||||||
options = null;
|
|
||||||
} else {
|
|
||||||
var finder = getOption(cm, options, "rangeFinder");
|
|
||||||
}
|
|
||||||
if (typeof pos == "number") pos = CodeMirror.Pos(pos, 0);
|
|
||||||
var minSize = getOption(cm, options, "minFoldSize");
|
|
||||||
|
|
||||||
function getRange(allowFolded) {
|
|
||||||
var range = finder(cm, pos);
|
|
||||||
if (!range || range.to.line - range.from.line < minSize) return null;
|
|
||||||
if (force === "fold") return range;
|
|
||||||
|
|
||||||
var marks = cm.findMarksAt(range.from);
|
|
||||||
for (var i = 0; i < marks.length; ++i) {
|
|
||||||
if (marks[i].__isFold) {
|
|
||||||
if (!allowFolded) return null;
|
|
||||||
range.cleared = true;
|
|
||||||
marks[i].clear();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return range;
|
|
||||||
}
|
|
||||||
|
|
||||||
var range = getRange(true);
|
|
||||||
if (getOption(cm, options, "scanUp")) while (!range && pos.line > cm.firstLine()) {
|
|
||||||
pos = CodeMirror.Pos(pos.line - 1, 0);
|
|
||||||
range = getRange(false);
|
|
||||||
}
|
|
||||||
if (!range || range.cleared || force === "unfold") return;
|
|
||||||
|
|
||||||
var myWidget = makeWidget(cm, options, range);
|
|
||||||
CodeMirror.on(myWidget, "mousedown", function(e) {
|
|
||||||
myRange.clear();
|
|
||||||
CodeMirror.e_preventDefault(e);
|
|
||||||
});
|
|
||||||
var myRange = cm.markText(range.from, range.to, {
|
|
||||||
replacedWith: myWidget,
|
|
||||||
clearOnEnter: getOption(cm, options, "clearOnEnter"),
|
|
||||||
__isFold: true
|
|
||||||
});
|
|
||||||
myRange.on("clear", function(from, to) {
|
|
||||||
CodeMirror.signal(cm, "unfold", cm, from, to);
|
|
||||||
});
|
|
||||||
CodeMirror.signal(cm, "fold", cm, range.from, range.to);
|
|
||||||
}
|
|
||||||
|
|
||||||
function makeWidget(cm, options, range) {
|
|
||||||
var widget = getOption(cm, options, "widget");
|
|
||||||
|
|
||||||
if (typeof widget == "function") {
|
|
||||||
widget = widget(range.from, range.to);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (typeof widget == "string") {
|
|
||||||
var text = document.createTextNode(widget);
|
|
||||||
widget = document.createElement("span");
|
|
||||||
widget.appendChild(text);
|
|
||||||
widget.className = "CodeMirror-foldmarker";
|
|
||||||
} else if (widget) {
|
|
||||||
widget = widget.cloneNode(true)
|
|
||||||
}
|
|
||||||
return widget;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Clumsy backwards-compatible interface
|
|
||||||
CodeMirror.newFoldFunction = function(rangeFinder, widget) {
|
|
||||||
return function(cm, pos) { doFold(cm, pos, {rangeFinder: rangeFinder, widget: widget}); };
|
|
||||||
};
|
|
||||||
|
|
||||||
// New-style interface
|
|
||||||
CodeMirror.defineExtension("foldCode", function(pos, options, force) {
|
|
||||||
doFold(this, pos, options, force);
|
|
||||||
});
|
|
||||||
|
|
||||||
CodeMirror.defineExtension("isFolded", function(pos) {
|
|
||||||
var marks = this.findMarksAt(pos);
|
|
||||||
for (var i = 0; i < marks.length; ++i)
|
|
||||||
if (marks[i].__isFold) return true;
|
|
||||||
});
|
|
||||||
|
|
||||||
CodeMirror.commands.toggleFold = function(cm) {
|
|
||||||
cm.foldCode(cm.getCursor());
|
|
||||||
};
|
|
||||||
CodeMirror.commands.fold = function(cm) {
|
|
||||||
cm.foldCode(cm.getCursor(), null, "fold");
|
|
||||||
};
|
|
||||||
CodeMirror.commands.unfold = function(cm) {
|
|
||||||
cm.foldCode(cm.getCursor(), { scanUp: false }, "unfold");
|
|
||||||
};
|
|
||||||
CodeMirror.commands.foldAll = function(cm) {
|
|
||||||
cm.operation(function() {
|
|
||||||
for (var i = cm.firstLine(), e = cm.lastLine(); i <= e; i++)
|
|
||||||
cm.foldCode(CodeMirror.Pos(i, 0), { scanUp: false }, "fold");
|
|
||||||
});
|
|
||||||
};
|
|
||||||
CodeMirror.commands.unfoldAll = function(cm) {
|
|
||||||
cm.operation(function() {
|
|
||||||
for (var i = cm.firstLine(), e = cm.lastLine(); i <= e; i++)
|
|
||||||
cm.foldCode(CodeMirror.Pos(i, 0), { scanUp: false }, "unfold");
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
CodeMirror.registerHelper("fold", "combine", function() {
|
|
||||||
var funcs = Array.prototype.slice.call(arguments, 0);
|
|
||||||
return function(cm, start) {
|
|
||||||
for (var i = 0; i < funcs.length; ++i) {
|
|
||||||
var found = funcs[i](cm, start);
|
|
||||||
if (found) return found;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
CodeMirror.registerHelper("fold", "auto", function(cm, start) {
|
|
||||||
var helpers = cm.getHelpers(start, "fold");
|
|
||||||
for (var i = 0; i < helpers.length; i++) {
|
|
||||||
var cur = helpers[i](cm, start);
|
|
||||||
if (cur) return cur;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
var defaultOptions = {
|
|
||||||
rangeFinder: CodeMirror.fold.auto,
|
|
||||||
widget: "\u2194",
|
|
||||||
minFoldSize: 0,
|
|
||||||
scanUp: false,
|
|
||||||
clearOnEnter: true
|
|
||||||
};
|
|
||||||
|
|
||||||
CodeMirror.defineOption("foldOptions", null);
|
|
||||||
|
|
||||||
function getOption(cm, options, name) {
|
|
||||||
if (options && options[name] !== undefined)
|
|
||||||
return options[name];
|
|
||||||
var editorOptions = cm.options.foldOptions;
|
|
||||||
if (editorOptions && editorOptions[name] !== undefined)
|
|
||||||
return editorOptions[name];
|
|
||||||
return defaultOptions[name];
|
|
||||||
}
|
|
||||||
|
|
||||||
CodeMirror.defineExtension("foldOption", function(options, name) {
|
|
||||||
return getOption(this, options, name);
|
|
||||||
});
|
|
||||||
});
|
|
|
@ -1,20 +0,0 @@
|
||||||
.CodeMirror-foldmarker {
|
|
||||||
color: blue;
|
|
||||||
text-shadow: #b9f 1px 1px 2px, #b9f -1px -1px 2px, #b9f 1px -1px 2px, #b9f -1px 1px 2px;
|
|
||||||
font-family: arial;
|
|
||||||
line-height: .3;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
.CodeMirror-foldgutter {
|
|
||||||
width: .7em;
|
|
||||||
}
|
|
||||||
.CodeMirror-foldgutter-open,
|
|
||||||
.CodeMirror-foldgutter-folded {
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
.CodeMirror-foldgutter-open:after {
|
|
||||||
content: "\25BE";
|
|
||||||
}
|
|
||||||
.CodeMirror-foldgutter-folded:after {
|
|
||||||
content: "\25B8";
|
|
||||||
}
|
|
|
@ -1,169 +0,0 @@
|
||||||
// CodeMirror, copyright (c) by Marijn Haverbeke and others
|
|
||||||
// Distributed under an MIT license: https://codemirror.net/5/LICENSE
|
|
||||||
|
|
||||||
(function(mod) {
|
|
||||||
if (typeof exports == "object" && typeof module == "object") // CommonJS
|
|
||||||
mod(require("../../lib/codemirror"), require("./foldcode"));
|
|
||||||
else if (typeof define == "function" && define.amd) // AMD
|
|
||||||
define(["../../lib/codemirror", "./foldcode"], mod);
|
|
||||||
else // Plain browser env
|
|
||||||
mod(CodeMirror);
|
|
||||||
})(function(CodeMirror) {
|
|
||||||
"use strict";
|
|
||||||
|
|
||||||
CodeMirror.defineOption("foldGutter", false, function(cm, val, old) {
|
|
||||||
if (old && old != CodeMirror.Init) {
|
|
||||||
cm.clearGutter(cm.state.foldGutter.options.gutter);
|
|
||||||
cm.state.foldGutter = null;
|
|
||||||
cm.off("gutterClick", onGutterClick);
|
|
||||||
cm.off("changes", onChange);
|
|
||||||
cm.off("viewportChange", onViewportChange);
|
|
||||||
cm.off("fold", onFold);
|
|
||||||
cm.off("unfold", onFold);
|
|
||||||
cm.off("swapDoc", onChange);
|
|
||||||
cm.off("optionChange", optionChange);
|
|
||||||
}
|
|
||||||
if (val) {
|
|
||||||
cm.state.foldGutter = new State(parseOptions(val));
|
|
||||||
updateInViewport(cm);
|
|
||||||
cm.on("gutterClick", onGutterClick);
|
|
||||||
cm.on("changes", onChange);
|
|
||||||
cm.on("viewportChange", onViewportChange);
|
|
||||||
cm.on("fold", onFold);
|
|
||||||
cm.on("unfold", onFold);
|
|
||||||
cm.on("swapDoc", onChange);
|
|
||||||
cm.on("optionChange", optionChange);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
var Pos = CodeMirror.Pos;
|
|
||||||
|
|
||||||
function State(options) {
|
|
||||||
this.options = options;
|
|
||||||
this.from = this.to = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
function parseOptions(opts) {
|
|
||||||
if (opts === true) opts = {};
|
|
||||||
if (opts.gutter == null) opts.gutter = "CodeMirror-foldgutter";
|
|
||||||
if (opts.indicatorOpen == null) opts.indicatorOpen = "CodeMirror-foldgutter-open";
|
|
||||||
if (opts.indicatorFolded == null) opts.indicatorFolded = "CodeMirror-foldgutter-folded";
|
|
||||||
return opts;
|
|
||||||
}
|
|
||||||
|
|
||||||
function isFolded(cm, line) {
|
|
||||||
var marks = cm.findMarks(Pos(line, 0), Pos(line + 1, 0));
|
|
||||||
for (var i = 0; i < marks.length; ++i) {
|
|
||||||
if (marks[i].__isFold) {
|
|
||||||
var fromPos = marks[i].find(-1);
|
|
||||||
if (fromPos && fromPos.line === line)
|
|
||||||
return marks[i];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function marker(spec) {
|
|
||||||
if (typeof spec == "string") {
|
|
||||||
var elt = document.createElement("div");
|
|
||||||
elt.className = spec + " CodeMirror-guttermarker-subtle";
|
|
||||||
return elt;
|
|
||||||
} else {
|
|
||||||
return spec.cloneNode(true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function updateFoldInfo(cm, from, to) {
|
|
||||||
var opts = cm.state.foldGutter.options, cur = from - 1;
|
|
||||||
var minSize = cm.foldOption(opts, "minFoldSize");
|
|
||||||
var func = cm.foldOption(opts, "rangeFinder");
|
|
||||||
// we can reuse the built-in indicator element if its className matches the new state
|
|
||||||
var clsFolded = typeof opts.indicatorFolded == "string" && classTest(opts.indicatorFolded);
|
|
||||||
var clsOpen = typeof opts.indicatorOpen == "string" && classTest(opts.indicatorOpen);
|
|
||||||
cm.eachLine(from, to, function(line) {
|
|
||||||
++cur;
|
|
||||||
var mark = null;
|
|
||||||
var old = line.gutterMarkers;
|
|
||||||
if (old) old = old[opts.gutter];
|
|
||||||
if (isFolded(cm, cur)) {
|
|
||||||
if (clsFolded && old && clsFolded.test(old.className)) return;
|
|
||||||
mark = marker(opts.indicatorFolded);
|
|
||||||
} else {
|
|
||||||
var pos = Pos(cur, 0);
|
|
||||||
var range = func && func(cm, pos);
|
|
||||||
if (range && range.to.line - range.from.line >= minSize) {
|
|
||||||
if (clsOpen && old && clsOpen.test(old.className)) return;
|
|
||||||
mark = marker(opts.indicatorOpen);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!mark && !old) return;
|
|
||||||
cm.setGutterMarker(line, opts.gutter, mark);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// copied from CodeMirror/src/util/dom.js
|
|
||||||
function classTest(cls) { return new RegExp("(^|\\s)" + cls + "(?:$|\\s)\\s*") }
|
|
||||||
|
|
||||||
function updateInViewport(cm) {
|
|
||||||
var vp = cm.getViewport(), state = cm.state.foldGutter;
|
|
||||||
if (!state) return;
|
|
||||||
cm.operation(function() {
|
|
||||||
updateFoldInfo(cm, vp.from, vp.to);
|
|
||||||
});
|
|
||||||
state.from = vp.from; state.to = vp.to;
|
|
||||||
}
|
|
||||||
|
|
||||||
function onGutterClick(cm, line, gutter) {
|
|
||||||
var state = cm.state.foldGutter;
|
|
||||||
if (!state) return;
|
|
||||||
var opts = state.options;
|
|
||||||
if (gutter != opts.gutter) return;
|
|
||||||
var folded = isFolded(cm, line);
|
|
||||||
if (folded) folded.clear();
|
|
||||||
else cm.foldCode(Pos(line, 0), opts);
|
|
||||||
}
|
|
||||||
|
|
||||||
function optionChange(cm, option) {
|
|
||||||
if (option == "mode") onChange(cm)
|
|
||||||
}
|
|
||||||
|
|
||||||
function onChange(cm) {
|
|
||||||
var state = cm.state.foldGutter;
|
|
||||||
if (!state) return;
|
|
||||||
var opts = state.options;
|
|
||||||
state.from = state.to = 0;
|
|
||||||
clearTimeout(state.changeUpdate);
|
|
||||||
state.changeUpdate = setTimeout(function() { updateInViewport(cm); }, opts.foldOnChangeTimeSpan || 600);
|
|
||||||
}
|
|
||||||
|
|
||||||
function onViewportChange(cm) {
|
|
||||||
var state = cm.state.foldGutter;
|
|
||||||
if (!state) return;
|
|
||||||
var opts = state.options;
|
|
||||||
clearTimeout(state.changeUpdate);
|
|
||||||
state.changeUpdate = setTimeout(function() {
|
|
||||||
var vp = cm.getViewport();
|
|
||||||
if (state.from == state.to || vp.from - state.to > 20 || state.from - vp.to > 20) {
|
|
||||||
updateInViewport(cm);
|
|
||||||
} else {
|
|
||||||
cm.operation(function() {
|
|
||||||
if (vp.from < state.from) {
|
|
||||||
updateFoldInfo(cm, vp.from, state.from);
|
|
||||||
state.from = vp.from;
|
|
||||||
}
|
|
||||||
if (vp.to > state.to) {
|
|
||||||
updateFoldInfo(cm, state.to, vp.to);
|
|
||||||
state.to = vp.to;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}, opts.updateViewportTimeSpan || 400);
|
|
||||||
}
|
|
||||||
|
|
||||||
function onFold(cm, from) {
|
|
||||||
var state = cm.state.foldGutter;
|
|
||||||
if (!state) return;
|
|
||||||
var line = from.line;
|
|
||||||
if (line >= state.from && line < state.to)
|
|
||||||
updateFoldInfo(cm, line, line + 1);
|
|
||||||
}
|
|
||||||
});
|
|
|
@ -1,162 +0,0 @@
|
||||||
// CodeMirror, copyright (c) by Marijn Haverbeke and others
|
|
||||||
// Distributed under an MIT license: https://codemirror.net/5/LICENSE
|
|
||||||
|
|
||||||
(function(mod) {
|
|
||||||
if (typeof exports == "object" && typeof module == "object") // CommonJS
|
|
||||||
mod(require("../../lib/codemirror"));
|
|
||||||
else if (typeof define == "function" && define.amd) // AMD
|
|
||||||
define(["../../lib/codemirror"], mod);
|
|
||||||
else // Plain browser env
|
|
||||||
mod(CodeMirror);
|
|
||||||
})(function(CodeMirror) {
|
|
||||||
var Pos = CodeMirror.Pos;
|
|
||||||
|
|
||||||
function forEach(arr, f) {
|
|
||||||
for (var i = 0, e = arr.length; i < e; ++i) f(arr[i]);
|
|
||||||
}
|
|
||||||
|
|
||||||
function arrayContains(arr, item) {
|
|
||||||
if (!Array.prototype.indexOf) {
|
|
||||||
var i = arr.length;
|
|
||||||
while (i--) {
|
|
||||||
if (arr[i] === item) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return arr.indexOf(item) != -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
function scriptHint(editor, keywords, getToken, options) {
|
|
||||||
// Find the token at the cursor
|
|
||||||
var cur = editor.getCursor(), token = getToken(editor, cur);
|
|
||||||
if (/\b(?:string|comment)\b/.test(token.type)) return;
|
|
||||||
var innerMode = CodeMirror.innerMode(editor.getMode(), token.state);
|
|
||||||
if (innerMode.mode.helperType === "json") return;
|
|
||||||
token.state = innerMode.state;
|
|
||||||
|
|
||||||
// If it's not a 'word-style' token, ignore the token.
|
|
||||||
if (!/^[\w$_]*$/.test(token.string)) {
|
|
||||||
token = {start: cur.ch, end: cur.ch, string: "", state: token.state,
|
|
||||||
type: token.string == "." ? "property" : null};
|
|
||||||
} else if (token.end > cur.ch) {
|
|
||||||
token.end = cur.ch;
|
|
||||||
token.string = token.string.slice(0, cur.ch - token.start);
|
|
||||||
}
|
|
||||||
|
|
||||||
var tprop = token;
|
|
||||||
// If it is a property, find out what it is a property of.
|
|
||||||
while (tprop.type == "property") {
|
|
||||||
tprop = getToken(editor, Pos(cur.line, tprop.start));
|
|
||||||
if (tprop.string != ".") return;
|
|
||||||
tprop = getToken(editor, Pos(cur.line, tprop.start));
|
|
||||||
if (!context) var context = [];
|
|
||||||
context.push(tprop);
|
|
||||||
}
|
|
||||||
return {list: getCompletions(token, context, keywords, options),
|
|
||||||
from: Pos(cur.line, token.start),
|
|
||||||
to: Pos(cur.line, token.end)};
|
|
||||||
}
|
|
||||||
|
|
||||||
function javascriptHint(editor, options) {
|
|
||||||
return scriptHint(editor, javascriptKeywords,
|
|
||||||
function (e, cur) {return e.getTokenAt(cur);},
|
|
||||||
options);
|
|
||||||
}
|
|
||||||
CodeMirror.registerHelper("hint", "javascript", javascriptHint);
|
|
||||||
|
|
||||||
function getCoffeeScriptToken(editor, cur) {
|
|
||||||
// This getToken, it is for coffeescript, imitates the behavior of
|
|
||||||
// getTokenAt method in javascript.js, that is, returning "property"
|
|
||||||
// type and treat "." as independent token.
|
|
||||||
var token = editor.getTokenAt(cur);
|
|
||||||
if (cur.ch == token.start + 1 && token.string.charAt(0) == '.') {
|
|
||||||
token.end = token.start;
|
|
||||||
token.string = '.';
|
|
||||||
token.type = "property";
|
|
||||||
}
|
|
||||||
else if (/^\.[\w$_]*$/.test(token.string)) {
|
|
||||||
token.type = "property";
|
|
||||||
token.start++;
|
|
||||||
token.string = token.string.replace(/\./, '');
|
|
||||||
}
|
|
||||||
return token;
|
|
||||||
}
|
|
||||||
|
|
||||||
function coffeescriptHint(editor, options) {
|
|
||||||
return scriptHint(editor, coffeescriptKeywords, getCoffeeScriptToken, options);
|
|
||||||
}
|
|
||||||
CodeMirror.registerHelper("hint", "coffeescript", coffeescriptHint);
|
|
||||||
|
|
||||||
var stringProps = ("charAt charCodeAt indexOf lastIndexOf substring substr slice trim trimLeft trimRight " +
|
|
||||||
"toUpperCase toLowerCase split concat match replace search").split(" ");
|
|
||||||
var arrayProps = ("length concat join splice push pop shift unshift slice reverse sort indexOf " +
|
|
||||||
"lastIndexOf every some filter forEach map reduce reduceRight ").split(" ");
|
|
||||||
var funcProps = "prototype apply call bind".split(" ");
|
|
||||||
var javascriptKeywords = ("break case catch class const continue debugger default delete do else export extends false finally for function " +
|
|
||||||
"if in import instanceof new null return super switch this throw true try typeof var void while with yield").split(" ");
|
|
||||||
var coffeescriptKeywords = ("and break catch class continue delete do else extends false finally for " +
|
|
||||||
"if in instanceof isnt new no not null of off on or return switch then throw true try typeof until void while with yes").split(" ");
|
|
||||||
|
|
||||||
function forAllProps(obj, callback) {
|
|
||||||
if (!Object.getOwnPropertyNames || !Object.getPrototypeOf) {
|
|
||||||
for (var name in obj) callback(name)
|
|
||||||
} else {
|
|
||||||
for (var o = obj; o; o = Object.getPrototypeOf(o))
|
|
||||||
Object.getOwnPropertyNames(o).forEach(callback)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function getCompletions(token, context, keywords, options) {
|
|
||||||
var found = [], start = token.string, global = options && options.globalScope || window;
|
|
||||||
function maybeAdd(str) {
|
|
||||||
if (str.lastIndexOf(start, 0) == 0 && !arrayContains(found, str)) found.push(str);
|
|
||||||
}
|
|
||||||
function gatherCompletions(obj) {
|
|
||||||
if (typeof obj == "string") forEach(stringProps, maybeAdd);
|
|
||||||
else if (obj instanceof Array) forEach(arrayProps, maybeAdd);
|
|
||||||
else if (obj instanceof Function) forEach(funcProps, maybeAdd);
|
|
||||||
forAllProps(obj, maybeAdd)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (context && context.length) {
|
|
||||||
// If this is a property, see if it belongs to some object we can
|
|
||||||
// find in the current environment.
|
|
||||||
var obj = context.pop(), base;
|
|
||||||
if (obj.type && obj.type.indexOf("variable") === 0) {
|
|
||||||
if (options && options.additionalContext)
|
|
||||||
base = options.additionalContext[obj.string];
|
|
||||||
if (!options || options.useGlobalScope !== false)
|
|
||||||
base = base || global[obj.string];
|
|
||||||
} else if (obj.type == "string") {
|
|
||||||
base = "";
|
|
||||||
} else if (obj.type == "atom") {
|
|
||||||
base = 1;
|
|
||||||
} else if (obj.type == "function") {
|
|
||||||
if (global.jQuery != null && (obj.string == '$' || obj.string == 'jQuery') &&
|
|
||||||
(typeof global.jQuery == 'function'))
|
|
||||||
base = global.jQuery();
|
|
||||||
else if (global._ != null && (obj.string == '_') && (typeof global._ == 'function'))
|
|
||||||
base = global._();
|
|
||||||
}
|
|
||||||
while (base != null && context.length)
|
|
||||||
base = base[context.pop().string];
|
|
||||||
if (base != null) gatherCompletions(base);
|
|
||||||
} else {
|
|
||||||
// If not, just look in the global object, any local scope, and optional additional-context
|
|
||||||
// (reading into JS mode internals to get at the local and global variables)
|
|
||||||
for (var v = token.state.localVars; v; v = v.next) maybeAdd(v.name);
|
|
||||||
for (var c = token.state.context; c; c = c.prev)
|
|
||||||
for (var v = c.vars; v; v = v.next) maybeAdd(v.name)
|
|
||||||
for (var v = token.state.globalVars; v; v = v.next) maybeAdd(v.name);
|
|
||||||
if (options && options.additionalContext != null)
|
|
||||||
for (var key in options.additionalContext)
|
|
||||||
maybeAdd(key);
|
|
||||||
if (!options || options.useGlobalScope !== false)
|
|
||||||
gatherCompletions(global);
|
|
||||||
forEach(keywords, maybeAdd);
|
|
||||||
}
|
|
||||||
return found;
|
|
||||||
}
|
|
||||||
});
|
|
|
@ -1,960 +0,0 @@
|
||||||
// CodeMirror, copyright (c) by Marijn Haverbeke and others
|
|
||||||
// Distributed under an MIT license: https://codemirror.net/5/LICENSE
|
|
||||||
|
|
||||||
(function(mod) {
|
|
||||||
if (typeof exports == "object" && typeof module == "object") // CommonJS
|
|
||||||
mod(require("../../lib/codemirror"));
|
|
||||||
else if (typeof define == "function" && define.amd) // AMD
|
|
||||||
define(["../../lib/codemirror"], mod);
|
|
||||||
else // Plain browser env
|
|
||||||
mod(CodeMirror);
|
|
||||||
})(function(CodeMirror) {
|
|
||||||
"use strict";
|
|
||||||
|
|
||||||
CodeMirror.defineMode("javascript", function(config, parserConfig) {
|
|
||||||
var indentUnit = config.indentUnit;
|
|
||||||
var statementIndent = parserConfig.statementIndent;
|
|
||||||
var jsonldMode = parserConfig.jsonld;
|
|
||||||
var jsonMode = parserConfig.json || jsonldMode;
|
|
||||||
var trackScope = parserConfig.trackScope !== false
|
|
||||||
var isTS = parserConfig.typescript;
|
|
||||||
var wordRE = parserConfig.wordCharacters || /[\w$\xa1-\uffff]/;
|
|
||||||
|
|
||||||
// Tokenizer
|
|
||||||
|
|
||||||
var keywords = function(){
|
|
||||||
function kw(type) {return {type: type, style: "keyword"};}
|
|
||||||
var A = kw("keyword a"), B = kw("keyword b"), C = kw("keyword c"), D = kw("keyword d");
|
|
||||||
var operator = kw("operator"), atom = {type: "atom", style: "atom"};
|
|
||||||
|
|
||||||
return {
|
|
||||||
"if": kw("if"), "while": A, "with": A, "else": B, "do": B, "try": B, "finally": B,
|
|
||||||
"return": D, "break": D, "continue": D, "new": kw("new"), "delete": C, "void": C, "throw": C,
|
|
||||||
"debugger": kw("debugger"), "var": kw("var"), "const": kw("var"), "let": kw("var"),
|
|
||||||
"function": kw("function"), "catch": kw("catch"),
|
|
||||||
"for": kw("for"), "switch": kw("switch"), "case": kw("case"), "default": kw("default"),
|
|
||||||
"in": operator, "typeof": operator, "instanceof": operator,
|
|
||||||
"true": atom, "false": atom, "null": atom, "undefined": atom, "NaN": atom, "Infinity": atom,
|
|
||||||
"this": kw("this"), "class": kw("class"), "super": kw("atom"),
|
|
||||||
"yield": C, "export": kw("export"), "import": kw("import"), "extends": C,
|
|
||||||
"await": C
|
|
||||||
};
|
|
||||||
}();
|
|
||||||
|
|
||||||
var isOperatorChar = /[+\-*&%=<>!?|~^@]/;
|
|
||||||
var isJsonldKeyword = /^@(context|id|value|language|type|container|list|set|reverse|index|base|vocab|graph)"/;
|
|
||||||
|
|
||||||
function readRegexp(stream) {
|
|
||||||
var escaped = false, next, inSet = false;
|
|
||||||
while ((next = stream.next()) != null) {
|
|
||||||
if (!escaped) {
|
|
||||||
if (next == "/" && !inSet) return;
|
|
||||||
if (next == "[") inSet = true;
|
|
||||||
else if (inSet && next == "]") inSet = false;
|
|
||||||
}
|
|
||||||
escaped = !escaped && next == "\\";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Used as scratch variables to communicate multiple values without
|
|
||||||
// consing up tons of objects.
|
|
||||||
var type, content;
|
|
||||||
function ret(tp, style, cont) {
|
|
||||||
type = tp; content = cont;
|
|
||||||
return style;
|
|
||||||
}
|
|
||||||
function tokenBase(stream, state) {
|
|
||||||
var ch = stream.next();
|
|
||||||
if (ch == '"' || ch == "'") {
|
|
||||||
state.tokenize = tokenString(ch);
|
|
||||||
return state.tokenize(stream, state);
|
|
||||||
} else if (ch == "." && stream.match(/^\d[\d_]*(?:[eE][+\-]?[\d_]+)?/)) {
|
|
||||||
return ret("number", "number");
|
|
||||||
} else if (ch == "." && stream.match("..")) {
|
|
||||||
return ret("spread", "meta");
|
|
||||||
} else if (/[\[\]{}\(\),;\:\.]/.test(ch)) {
|
|
||||||
return ret(ch);
|
|
||||||
} else if (ch == "=" && stream.eat(">")) {
|
|
||||||
return ret("=>", "operator");
|
|
||||||
} else if (ch == "0" && stream.match(/^(?:x[\dA-Fa-f_]+|o[0-7_]+|b[01_]+)n?/)) {
|
|
||||||
return ret("number", "number");
|
|
||||||
} else if (/\d/.test(ch)) {
|
|
||||||
stream.match(/^[\d_]*(?:n|(?:\.[\d_]*)?(?:[eE][+\-]?[\d_]+)?)?/);
|
|
||||||
return ret("number", "number");
|
|
||||||
} else if (ch == "/") {
|
|
||||||
if (stream.eat("*")) {
|
|
||||||
state.tokenize = tokenComment;
|
|
||||||
return tokenComment(stream, state);
|
|
||||||
} else if (stream.eat("/")) {
|
|
||||||
stream.skipToEnd();
|
|
||||||
return ret("comment", "comment");
|
|
||||||
} else if (expressionAllowed(stream, state, 1)) {
|
|
||||||
readRegexp(stream);
|
|
||||||
stream.match(/^\b(([gimyus])(?![gimyus]*\2))+\b/);
|
|
||||||
return ret("regexp", "string-2");
|
|
||||||
} else {
|
|
||||||
stream.eat("=");
|
|
||||||
return ret("operator", "operator", stream.current());
|
|
||||||
}
|
|
||||||
} else if (ch == "`") {
|
|
||||||
state.tokenize = tokenQuasi;
|
|
||||||
return tokenQuasi(stream, state);
|
|
||||||
} else if (ch == "#" && stream.peek() == "!") {
|
|
||||||
stream.skipToEnd();
|
|
||||||
return ret("meta", "meta");
|
|
||||||
} else if (ch == "#" && stream.eatWhile(wordRE)) {
|
|
||||||
return ret("variable", "property")
|
|
||||||
} else if (ch == "<" && stream.match("!--") ||
|
|
||||||
(ch == "-" && stream.match("->") && !/\S/.test(stream.string.slice(0, stream.start)))) {
|
|
||||||
stream.skipToEnd()
|
|
||||||
return ret("comment", "comment")
|
|
||||||
} else if (isOperatorChar.test(ch)) {
|
|
||||||
if (ch != ">" || !state.lexical || state.lexical.type != ">") {
|
|
||||||
if (stream.eat("=")) {
|
|
||||||
if (ch == "!" || ch == "=") stream.eat("=")
|
|
||||||
} else if (/[<>*+\-|&?]/.test(ch)) {
|
|
||||||
stream.eat(ch)
|
|
||||||
if (ch == ">") stream.eat(ch)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (ch == "?" && stream.eat(".")) return ret(".")
|
|
||||||
return ret("operator", "operator", stream.current());
|
|
||||||
} else if (wordRE.test(ch)) {
|
|
||||||
stream.eatWhile(wordRE);
|
|
||||||
var word = stream.current()
|
|
||||||
if (state.lastType != ".") {
|
|
||||||
if (keywords.propertyIsEnumerable(word)) {
|
|
||||||
var kw = keywords[word]
|
|
||||||
return ret(kw.type, kw.style, word)
|
|
||||||
}
|
|
||||||
if (word == "async" && stream.match(/^(\s|\/\*([^*]|\*(?!\/))*?\*\/)*[\[\(\w]/, false))
|
|
||||||
return ret("async", "keyword", word)
|
|
||||||
}
|
|
||||||
return ret("variable", "variable", word)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function tokenString(quote) {
|
|
||||||
return function(stream, state) {
|
|
||||||
var escaped = false, next;
|
|
||||||
if (jsonldMode && stream.peek() == "@" && stream.match(isJsonldKeyword)){
|
|
||||||
state.tokenize = tokenBase;
|
|
||||||
return ret("jsonld-keyword", "meta");
|
|
||||||
}
|
|
||||||
while ((next = stream.next()) != null) {
|
|
||||||
if (next == quote && !escaped) break;
|
|
||||||
escaped = !escaped && next == "\\";
|
|
||||||
}
|
|
||||||
if (!escaped) state.tokenize = tokenBase;
|
|
||||||
return ret("string", "string");
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
function tokenComment(stream, state) {
|
|
||||||
var maybeEnd = false, ch;
|
|
||||||
while (ch = stream.next()) {
|
|
||||||
if (ch == "/" && maybeEnd) {
|
|
||||||
state.tokenize = tokenBase;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
maybeEnd = (ch == "*");
|
|
||||||
}
|
|
||||||
return ret("comment", "comment");
|
|
||||||
}
|
|
||||||
|
|
||||||
function tokenQuasi(stream, state) {
|
|
||||||
var escaped = false, next;
|
|
||||||
while ((next = stream.next()) != null) {
|
|
||||||
if (!escaped && (next == "`" || next == "$" && stream.eat("{"))) {
|
|
||||||
state.tokenize = tokenBase;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
escaped = !escaped && next == "\\";
|
|
||||||
}
|
|
||||||
return ret("quasi", "string-2", stream.current());
|
|
||||||
}
|
|
||||||
|
|
||||||
var brackets = "([{}])";
|
|
||||||
// This is a crude lookahead trick to try and notice that we're
|
|
||||||
// parsing the argument patterns for a fat-arrow function before we
|
|
||||||
// actually hit the arrow token. It only works if the arrow is on
|
|
||||||
// the same line as the arguments and there's no strange noise
|
|
||||||
// (comments) in between. Fallback is to only notice when we hit the
|
|
||||||
// arrow, and not declare the arguments as locals for the arrow
|
|
||||||
// body.
|
|
||||||
function findFatArrow(stream, state) {
|
|
||||||
if (state.fatArrowAt) state.fatArrowAt = null;
|
|
||||||
var arrow = stream.string.indexOf("=>", stream.start);
|
|
||||||
if (arrow < 0) return;
|
|
||||||
|
|
||||||
if (isTS) { // Try to skip TypeScript return type declarations after the arguments
|
|
||||||
var m = /:\s*(?:\w+(?:<[^>]*>|\[\])?|\{[^}]*\})\s*$/.exec(stream.string.slice(stream.start, arrow))
|
|
||||||
if (m) arrow = m.index
|
|
||||||
}
|
|
||||||
|
|
||||||
var depth = 0, sawSomething = false;
|
|
||||||
for (var pos = arrow - 1; pos >= 0; --pos) {
|
|
||||||
var ch = stream.string.charAt(pos);
|
|
||||||
var bracket = brackets.indexOf(ch);
|
|
||||||
if (bracket >= 0 && bracket < 3) {
|
|
||||||
if (!depth) { ++pos; break; }
|
|
||||||
if (--depth == 0) { if (ch == "(") sawSomething = true; break; }
|
|
||||||
} else if (bracket >= 3 && bracket < 6) {
|
|
||||||
++depth;
|
|
||||||
} else if (wordRE.test(ch)) {
|
|
||||||
sawSomething = true;
|
|
||||||
} else if (/["'\/`]/.test(ch)) {
|
|
||||||
for (;; --pos) {
|
|
||||||
if (pos == 0) return
|
|
||||||
var next = stream.string.charAt(pos - 1)
|
|
||||||
if (next == ch && stream.string.charAt(pos - 2) != "\\") { pos--; break }
|
|
||||||
}
|
|
||||||
} else if (sawSomething && !depth) {
|
|
||||||
++pos;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (sawSomething && !depth) state.fatArrowAt = pos;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Parser
|
|
||||||
|
|
||||||
var atomicTypes = {"atom": true, "number": true, "variable": true, "string": true,
|
|
||||||
"regexp": true, "this": true, "import": true, "jsonld-keyword": true};
|
|
||||||
|
|
||||||
function JSLexical(indented, column, type, align, prev, info) {
|
|
||||||
this.indented = indented;
|
|
||||||
this.column = column;
|
|
||||||
this.type = type;
|
|
||||||
this.prev = prev;
|
|
||||||
this.info = info;
|
|
||||||
if (align != null) this.align = align;
|
|
||||||
}
|
|
||||||
|
|
||||||
function inScope(state, varname) {
|
|
||||||
if (!trackScope) return false
|
|
||||||
for (var v = state.localVars; v; v = v.next)
|
|
||||||
if (v.name == varname) return true;
|
|
||||||
for (var cx = state.context; cx; cx = cx.prev) {
|
|
||||||
for (var v = cx.vars; v; v = v.next)
|
|
||||||
if (v.name == varname) return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function parseJS(state, style, type, content, stream) {
|
|
||||||
var cc = state.cc;
|
|
||||||
// Communicate our context to the combinators.
|
|
||||||
// (Less wasteful than consing up a hundred closures on every call.)
|
|
||||||
cx.state = state; cx.stream = stream; cx.marked = null, cx.cc = cc; cx.style = style;
|
|
||||||
|
|
||||||
if (!state.lexical.hasOwnProperty("align"))
|
|
||||||
state.lexical.align = true;
|
|
||||||
|
|
||||||
while(true) {
|
|
||||||
var combinator = cc.length ? cc.pop() : jsonMode ? expression : statement;
|
|
||||||
if (combinator(type, content)) {
|
|
||||||
while(cc.length && cc[cc.length - 1].lex)
|
|
||||||
cc.pop()();
|
|
||||||
if (cx.marked) return cx.marked;
|
|
||||||
if (type == "variable" && inScope(state, content)) return "variable-2";
|
|
||||||
return style;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Combinator utils
|
|
||||||
|
|
||||||
var cx = {state: null, column: null, marked: null, cc: null};
|
|
||||||
function pass() {
|
|
||||||
for (var i = arguments.length - 1; i >= 0; i--) cx.cc.push(arguments[i]);
|
|
||||||
}
|
|
||||||
function cont() {
|
|
||||||
pass.apply(null, arguments);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
function inList(name, list) {
|
|
||||||
for (var v = list; v; v = v.next) if (v.name == name) return true
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
function register(varname) {
|
|
||||||
var state = cx.state;
|
|
||||||
cx.marked = "def";
|
|
||||||
if (!trackScope) return
|
|
||||||
if (state.context) {
|
|
||||||
if (state.lexical.info == "var" && state.context && state.context.block) {
|
|
||||||
// FIXME function decls are also not block scoped
|
|
||||||
var newContext = registerVarScoped(varname, state.context)
|
|
||||||
if (newContext != null) {
|
|
||||||
state.context = newContext
|
|
||||||
return
|
|
||||||
}
|
|
||||||
} else if (!inList(varname, state.localVars)) {
|
|
||||||
state.localVars = new Var(varname, state.localVars)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Fall through means this is global
|
|
||||||
if (parserConfig.globalVars && !inList(varname, state.globalVars))
|
|
||||||
state.globalVars = new Var(varname, state.globalVars)
|
|
||||||
}
|
|
||||||
function registerVarScoped(varname, context) {
|
|
||||||
if (!context) {
|
|
||||||
return null
|
|
||||||
} else if (context.block) {
|
|
||||||
var inner = registerVarScoped(varname, context.prev)
|
|
||||||
if (!inner) return null
|
|
||||||
if (inner == context.prev) return context
|
|
||||||
return new Context(inner, context.vars, true)
|
|
||||||
} else if (inList(varname, context.vars)) {
|
|
||||||
return context
|
|
||||||
} else {
|
|
||||||
return new Context(context.prev, new Var(varname, context.vars), false)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function isModifier(name) {
|
|
||||||
return name == "public" || name == "private" || name == "protected" || name == "abstract" || name == "readonly"
|
|
||||||
}
|
|
||||||
|
|
||||||
// Combinators
|
|
||||||
|
|
||||||
function Context(prev, vars, block) { this.prev = prev; this.vars = vars; this.block = block }
|
|
||||||
function Var(name, next) { this.name = name; this.next = next }
|
|
||||||
|
|
||||||
var defaultVars = new Var("this", new Var("arguments", null))
|
|
||||||
function pushcontext() {
|
|
||||||
cx.state.context = new Context(cx.state.context, cx.state.localVars, false)
|
|
||||||
cx.state.localVars = defaultVars
|
|
||||||
}
|
|
||||||
function pushblockcontext() {
|
|
||||||
cx.state.context = new Context(cx.state.context, cx.state.localVars, true)
|
|
||||||
cx.state.localVars = null
|
|
||||||
}
|
|
||||||
pushcontext.lex = pushblockcontext.lex = true
|
|
||||||
function popcontext() {
|
|
||||||
cx.state.localVars = cx.state.context.vars
|
|
||||||
cx.state.context = cx.state.context.prev
|
|
||||||
}
|
|
||||||
popcontext.lex = true
|
|
||||||
function pushlex(type, info) {
|
|
||||||
var result = function() {
|
|
||||||
var state = cx.state, indent = state.indented;
|
|
||||||
if (state.lexical.type == "stat") indent = state.lexical.indented;
|
|
||||||
else for (var outer = state.lexical; outer && outer.type == ")" && outer.align; outer = outer.prev)
|
|
||||||
indent = outer.indented;
|
|
||||||
state.lexical = new JSLexical(indent, cx.stream.column(), type, null, state.lexical, info);
|
|
||||||
};
|
|
||||||
result.lex = true;
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
function poplex() {
|
|
||||||
var state = cx.state;
|
|
||||||
if (state.lexical.prev) {
|
|
||||||
if (state.lexical.type == ")")
|
|
||||||
state.indented = state.lexical.indented;
|
|
||||||
state.lexical = state.lexical.prev;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
poplex.lex = true;
|
|
||||||
|
|
||||||
function expect(wanted) {
|
|
||||||
function exp(type) {
|
|
||||||
if (type == wanted) return cont();
|
|
||||||
else if (wanted == ";" || type == "}" || type == ")" || type == "]") return pass();
|
|
||||||
else return cont(exp);
|
|
||||||
}
|
|
||||||
return exp;
|
|
||||||
}
|
|
||||||
|
|
||||||
function statement(type, value) {
|
|
||||||
if (type == "var") return cont(pushlex("vardef", value), vardef, expect(";"), poplex);
|
|
||||||
if (type == "keyword a") return cont(pushlex("form"), parenExpr, statement, poplex);
|
|
||||||
if (type == "keyword b") return cont(pushlex("form"), statement, poplex);
|
|
||||||
if (type == "keyword d") return cx.stream.match(/^\s*$/, false) ? cont() : cont(pushlex("stat"), maybeexpression, expect(";"), poplex);
|
|
||||||
if (type == "debugger") return cont(expect(";"));
|
|
||||||
if (type == "{") return cont(pushlex("}"), pushblockcontext, block, poplex, popcontext);
|
|
||||||
if (type == ";") return cont();
|
|
||||||
if (type == "if") {
|
|
||||||
if (cx.state.lexical.info == "else" && cx.state.cc[cx.state.cc.length - 1] == poplex)
|
|
||||||
cx.state.cc.pop()();
|
|
||||||
return cont(pushlex("form"), parenExpr, statement, poplex, maybeelse);
|
|
||||||
}
|
|
||||||
if (type == "function") return cont(functiondef);
|
|
||||||
if (type == "for") return cont(pushlex("form"), pushblockcontext, forspec, statement, popcontext, poplex);
|
|
||||||
if (type == "class" || (isTS && value == "interface")) {
|
|
||||||
cx.marked = "keyword"
|
|
||||||
return cont(pushlex("form", type == "class" ? type : value), className, poplex)
|
|
||||||
}
|
|
||||||
if (type == "variable") {
|
|
||||||
if (isTS && value == "declare") {
|
|
||||||
cx.marked = "keyword"
|
|
||||||
return cont(statement)
|
|
||||||
} else if (isTS && (value == "module" || value == "enum" || value == "type") && cx.stream.match(/^\s*\w/, false)) {
|
|
||||||
cx.marked = "keyword"
|
|
||||||
if (value == "enum") return cont(enumdef);
|
|
||||||
else if (value == "type") return cont(typename, expect("operator"), typeexpr, expect(";"));
|
|
||||||
else return cont(pushlex("form"), pattern, expect("{"), pushlex("}"), block, poplex, poplex)
|
|
||||||
} else if (isTS && value == "namespace") {
|
|
||||||
cx.marked = "keyword"
|
|
||||||
return cont(pushlex("form"), expression, statement, poplex)
|
|
||||||
} else if (isTS && value == "abstract") {
|
|
||||||
cx.marked = "keyword"
|
|
||||||
return cont(statement)
|
|
||||||
} else {
|
|
||||||
return cont(pushlex("stat"), maybelabel);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (type == "switch") return cont(pushlex("form"), parenExpr, expect("{"), pushlex("}", "switch"), pushblockcontext,
|
|
||||||
block, poplex, poplex, popcontext);
|
|
||||||
if (type == "case") return cont(expression, expect(":"));
|
|
||||||
if (type == "default") return cont(expect(":"));
|
|
||||||
if (type == "catch") return cont(pushlex("form"), pushcontext, maybeCatchBinding, statement, poplex, popcontext);
|
|
||||||
if (type == "export") return cont(pushlex("stat"), afterExport, poplex);
|
|
||||||
if (type == "import") return cont(pushlex("stat"), afterImport, poplex);
|
|
||||||
if (type == "async") return cont(statement)
|
|
||||||
if (value == "@") return cont(expression, statement)
|
|
||||||
return pass(pushlex("stat"), expression, expect(";"), poplex);
|
|
||||||
}
|
|
||||||
function maybeCatchBinding(type) {
|
|
||||||
if (type == "(") return cont(funarg, expect(")"))
|
|
||||||
}
|
|
||||||
function expression(type, value) {
|
|
||||||
return expressionInner(type, value, false);
|
|
||||||
}
|
|
||||||
function expressionNoComma(type, value) {
|
|
||||||
return expressionInner(type, value, true);
|
|
||||||
}
|
|
||||||
function parenExpr(type) {
|
|
||||||
if (type != "(") return pass()
|
|
||||||
return cont(pushlex(")"), maybeexpression, expect(")"), poplex)
|
|
||||||
}
|
|
||||||
function expressionInner(type, value, noComma) {
|
|
||||||
if (cx.state.fatArrowAt == cx.stream.start) {
|
|
||||||
var body = noComma ? arrowBodyNoComma : arrowBody;
|
|
||||||
if (type == "(") return cont(pushcontext, pushlex(")"), commasep(funarg, ")"), poplex, expect("=>"), body, popcontext);
|
|
||||||
else if (type == "variable") return pass(pushcontext, pattern, expect("=>"), body, popcontext);
|
|
||||||
}
|
|
||||||
|
|
||||||
var maybeop = noComma ? maybeoperatorNoComma : maybeoperatorComma;
|
|
||||||
if (atomicTypes.hasOwnProperty(type)) return cont(maybeop);
|
|
||||||
if (type == "function") return cont(functiondef, maybeop);
|
|
||||||
if (type == "class" || (isTS && value == "interface")) { cx.marked = "keyword"; return cont(pushlex("form"), classExpression, poplex); }
|
|
||||||
if (type == "keyword c" || type == "async") return cont(noComma ? expressionNoComma : expression);
|
|
||||||
if (type == "(") return cont(pushlex(")"), maybeexpression, expect(")"), poplex, maybeop);
|
|
||||||
if (type == "operator" || type == "spread") return cont(noComma ? expressionNoComma : expression);
|
|
||||||
if (type == "[") return cont(pushlex("]"), arrayLiteral, poplex, maybeop);
|
|
||||||
if (type == "{") return contCommasep(objprop, "}", null, maybeop);
|
|
||||||
if (type == "quasi") return pass(quasi, maybeop);
|
|
||||||
if (type == "new") return cont(maybeTarget(noComma));
|
|
||||||
return cont();
|
|
||||||
}
|
|
||||||
function maybeexpression(type) {
|
|
||||||
if (type.match(/[;\}\)\],]/)) return pass();
|
|
||||||
return pass(expression);
|
|
||||||
}
|
|
||||||
|
|
||||||
function maybeoperatorComma(type, value) {
|
|
||||||
if (type == ",") return cont(maybeexpression);
|
|
||||||
return maybeoperatorNoComma(type, value, false);
|
|
||||||
}
|
|
||||||
function maybeoperatorNoComma(type, value, noComma) {
|
|
||||||
var me = noComma == false ? maybeoperatorComma : maybeoperatorNoComma;
|
|
||||||
var expr = noComma == false ? expression : expressionNoComma;
|
|
||||||
if (type == "=>") return cont(pushcontext, noComma ? arrowBodyNoComma : arrowBody, popcontext);
|
|
||||||
if (type == "operator") {
|
|
||||||
if (/\+\+|--/.test(value) || isTS && value == "!") return cont(me);
|
|
||||||
if (isTS && value == "<" && cx.stream.match(/^([^<>]|<[^<>]*>)*>\s*\(/, false))
|
|
||||||
return cont(pushlex(">"), commasep(typeexpr, ">"), poplex, me);
|
|
||||||
if (value == "?") return cont(expression, expect(":"), expr);
|
|
||||||
return cont(expr);
|
|
||||||
}
|
|
||||||
if (type == "quasi") { return pass(quasi, me); }
|
|
||||||
if (type == ";") return;
|
|
||||||
if (type == "(") return contCommasep(expressionNoComma, ")", "call", me);
|
|
||||||
if (type == ".") return cont(property, me);
|
|
||||||
if (type == "[") return cont(pushlex("]"), maybeexpression, expect("]"), poplex, me);
|
|
||||||
if (isTS && value == "as") { cx.marked = "keyword"; return cont(typeexpr, me) }
|
|
||||||
if (type == "regexp") {
|
|
||||||
cx.state.lastType = cx.marked = "operator"
|
|
||||||
cx.stream.backUp(cx.stream.pos - cx.stream.start - 1)
|
|
||||||
return cont(expr)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
function quasi(type, value) {
|
|
||||||
if (type != "quasi") return pass();
|
|
||||||
if (value.slice(value.length - 2) != "${") return cont(quasi);
|
|
||||||
return cont(maybeexpression, continueQuasi);
|
|
||||||
}
|
|
||||||
function continueQuasi(type) {
|
|
||||||
if (type == "}") {
|
|
||||||
cx.marked = "string-2";
|
|
||||||
cx.state.tokenize = tokenQuasi;
|
|
||||||
return cont(quasi);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
function arrowBody(type) {
|
|
||||||
findFatArrow(cx.stream, cx.state);
|
|
||||||
return pass(type == "{" ? statement : expression);
|
|
||||||
}
|
|
||||||
function arrowBodyNoComma(type) {
|
|
||||||
findFatArrow(cx.stream, cx.state);
|
|
||||||
return pass(type == "{" ? statement : expressionNoComma);
|
|
||||||
}
|
|
||||||
function maybeTarget(noComma) {
|
|
||||||
return function(type) {
|
|
||||||
if (type == ".") return cont(noComma ? targetNoComma : target);
|
|
||||||
else if (type == "variable" && isTS) return cont(maybeTypeArgs, noComma ? maybeoperatorNoComma : maybeoperatorComma)
|
|
||||||
else return pass(noComma ? expressionNoComma : expression);
|
|
||||||
};
|
|
||||||
}
|
|
||||||
function target(_, value) {
|
|
||||||
if (value == "target") { cx.marked = "keyword"; return cont(maybeoperatorComma); }
|
|
||||||
}
|
|
||||||
function targetNoComma(_, value) {
|
|
||||||
if (value == "target") { cx.marked = "keyword"; return cont(maybeoperatorNoComma); }
|
|
||||||
}
|
|
||||||
function maybelabel(type) {
|
|
||||||
if (type == ":") return cont(poplex, statement);
|
|
||||||
return pass(maybeoperatorComma, expect(";"), poplex);
|
|
||||||
}
|
|
||||||
function property(type) {
|
|
||||||
if (type == "variable") {cx.marked = "property"; return cont();}
|
|
||||||
}
|
|
||||||
function objprop(type, value) {
|
|
||||||
if (type == "async") {
|
|
||||||
cx.marked = "property";
|
|
||||||
return cont(objprop);
|
|
||||||
} else if (type == "variable" || cx.style == "keyword") {
|
|
||||||
cx.marked = "property";
|
|
||||||
if (value == "get" || value == "set") return cont(getterSetter);
|
|
||||||
var m // Work around fat-arrow-detection complication for detecting typescript typed arrow params
|
|
||||||
if (isTS && cx.state.fatArrowAt == cx.stream.start && (m = cx.stream.match(/^\s*:\s*/, false)))
|
|
||||||
cx.state.fatArrowAt = cx.stream.pos + m[0].length
|
|
||||||
return cont(afterprop);
|
|
||||||
} else if (type == "number" || type == "string") {
|
|
||||||
cx.marked = jsonldMode ? "property" : (cx.style + " property");
|
|
||||||
return cont(afterprop);
|
|
||||||
} else if (type == "jsonld-keyword") {
|
|
||||||
return cont(afterprop);
|
|
||||||
} else if (isTS && isModifier(value)) {
|
|
||||||
cx.marked = "keyword"
|
|
||||||
return cont(objprop)
|
|
||||||
} else if (type == "[") {
|
|
||||||
return cont(expression, maybetype, expect("]"), afterprop);
|
|
||||||
} else if (type == "spread") {
|
|
||||||
return cont(expressionNoComma, afterprop);
|
|
||||||
} else if (value == "*") {
|
|
||||||
cx.marked = "keyword";
|
|
||||||
return cont(objprop);
|
|
||||||
} else if (type == ":") {
|
|
||||||
return pass(afterprop)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
function getterSetter(type) {
|
|
||||||
if (type != "variable") return pass(afterprop);
|
|
||||||
cx.marked = "property";
|
|
||||||
return cont(functiondef);
|
|
||||||
}
|
|
||||||
function afterprop(type) {
|
|
||||||
if (type == ":") return cont(expressionNoComma);
|
|
||||||
if (type == "(") return pass(functiondef);
|
|
||||||
}
|
|
||||||
function commasep(what, end, sep) {
|
|
||||||
function proceed(type, value) {
|
|
||||||
if (sep ? sep.indexOf(type) > -1 : type == ",") {
|
|
||||||
var lex = cx.state.lexical;
|
|
||||||
if (lex.info == "call") lex.pos = (lex.pos || 0) + 1;
|
|
||||||
return cont(function(type, value) {
|
|
||||||
if (type == end || value == end) return pass()
|
|
||||||
return pass(what)
|
|
||||||
}, proceed);
|
|
||||||
}
|
|
||||||
if (type == end || value == end) return cont();
|
|
||||||
if (sep && sep.indexOf(";") > -1) return pass(what)
|
|
||||||
return cont(expect(end));
|
|
||||||
}
|
|
||||||
return function(type, value) {
|
|
||||||
if (type == end || value == end) return cont();
|
|
||||||
return pass(what, proceed);
|
|
||||||
};
|
|
||||||
}
|
|
||||||
function contCommasep(what, end, info) {
|
|
||||||
for (var i = 3; i < arguments.length; i++)
|
|
||||||
cx.cc.push(arguments[i]);
|
|
||||||
return cont(pushlex(end, info), commasep(what, end), poplex);
|
|
||||||
}
|
|
||||||
function block(type) {
|
|
||||||
if (type == "}") return cont();
|
|
||||||
return pass(statement, block);
|
|
||||||
}
|
|
||||||
function maybetype(type, value) {
|
|
||||||
if (isTS) {
|
|
||||||
if (type == ":") return cont(typeexpr);
|
|
||||||
if (value == "?") return cont(maybetype);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
function maybetypeOrIn(type, value) {
|
|
||||||
if (isTS && (type == ":" || value == "in")) return cont(typeexpr)
|
|
||||||
}
|
|
||||||
function mayberettype(type) {
|
|
||||||
if (isTS && type == ":") {
|
|
||||||
if (cx.stream.match(/^\s*\w+\s+is\b/, false)) return cont(expression, isKW, typeexpr)
|
|
||||||
else return cont(typeexpr)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
function isKW(_, value) {
|
|
||||||
if (value == "is") {
|
|
||||||
cx.marked = "keyword"
|
|
||||||
return cont()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
function typeexpr(type, value) {
|
|
||||||
if (value == "keyof" || value == "typeof" || value == "infer" || value == "readonly") {
|
|
||||||
cx.marked = "keyword"
|
|
||||||
return cont(value == "typeof" ? expressionNoComma : typeexpr)
|
|
||||||
}
|
|
||||||
if (type == "variable" || value == "void") {
|
|
||||||
cx.marked = "type"
|
|
||||||
return cont(afterType)
|
|
||||||
}
|
|
||||||
if (value == "|" || value == "&") return cont(typeexpr)
|
|
||||||
if (type == "string" || type == "number" || type == "atom") return cont(afterType);
|
|
||||||
if (type == "[") return cont(pushlex("]"), commasep(typeexpr, "]", ","), poplex, afterType)
|
|
||||||
if (type == "{") return cont(pushlex("}"), typeprops, poplex, afterType)
|
|
||||||
if (type == "(") return cont(commasep(typearg, ")"), maybeReturnType, afterType)
|
|
||||||
if (type == "<") return cont(commasep(typeexpr, ">"), typeexpr)
|
|
||||||
if (type == "quasi") { return pass(quasiType, afterType); }
|
|
||||||
}
|
|
||||||
function maybeReturnType(type) {
|
|
||||||
if (type == "=>") return cont(typeexpr)
|
|
||||||
}
|
|
||||||
function typeprops(type) {
|
|
||||||
if (type.match(/[\}\)\]]/)) return cont()
|
|
||||||
if (type == "," || type == ";") return cont(typeprops)
|
|
||||||
return pass(typeprop, typeprops)
|
|
||||||
}
|
|
||||||
function typeprop(type, value) {
|
|
||||||
if (type == "variable" || cx.style == "keyword") {
|
|
||||||
cx.marked = "property"
|
|
||||||
return cont(typeprop)
|
|
||||||
} else if (value == "?" || type == "number" || type == "string") {
|
|
||||||
return cont(typeprop)
|
|
||||||
} else if (type == ":") {
|
|
||||||
return cont(typeexpr)
|
|
||||||
} else if (type == "[") {
|
|
||||||
return cont(expect("variable"), maybetypeOrIn, expect("]"), typeprop)
|
|
||||||
} else if (type == "(") {
|
|
||||||
return pass(functiondecl, typeprop)
|
|
||||||
} else if (!type.match(/[;\}\)\],]/)) {
|
|
||||||
return cont()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
function quasiType(type, value) {
|
|
||||||
if (type != "quasi") return pass();
|
|
||||||
if (value.slice(value.length - 2) != "${") return cont(quasiType);
|
|
||||||
return cont(typeexpr, continueQuasiType);
|
|
||||||
}
|
|
||||||
function continueQuasiType(type) {
|
|
||||||
if (type == "}") {
|
|
||||||
cx.marked = "string-2";
|
|
||||||
cx.state.tokenize = tokenQuasi;
|
|
||||||
return cont(quasiType);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
function typearg(type, value) {
|
|
||||||
if (type == "variable" && cx.stream.match(/^\s*[?:]/, false) || value == "?") return cont(typearg)
|
|
||||||
if (type == ":") return cont(typeexpr)
|
|
||||||
if (type == "spread") return cont(typearg)
|
|
||||||
return pass(typeexpr)
|
|
||||||
}
|
|
||||||
function afterType(type, value) {
|
|
||||||
if (value == "<") return cont(pushlex(">"), commasep(typeexpr, ">"), poplex, afterType)
|
|
||||||
if (value == "|" || type == "." || value == "&") return cont(typeexpr)
|
|
||||||
if (type == "[") return cont(typeexpr, expect("]"), afterType)
|
|
||||||
if (value == "extends" || value == "implements") { cx.marked = "keyword"; return cont(typeexpr) }
|
|
||||||
if (value == "?") return cont(typeexpr, expect(":"), typeexpr)
|
|
||||||
}
|
|
||||||
function maybeTypeArgs(_, value) {
|
|
||||||
if (value == "<") return cont(pushlex(">"), commasep(typeexpr, ">"), poplex, afterType)
|
|
||||||
}
|
|
||||||
function typeparam() {
|
|
||||||
return pass(typeexpr, maybeTypeDefault)
|
|
||||||
}
|
|
||||||
function maybeTypeDefault(_, value) {
|
|
||||||
if (value == "=") return cont(typeexpr)
|
|
||||||
}
|
|
||||||
function vardef(_, value) {
|
|
||||||
if (value == "enum") {cx.marked = "keyword"; return cont(enumdef)}
|
|
||||||
return pass(pattern, maybetype, maybeAssign, vardefCont);
|
|
||||||
}
|
|
||||||
function pattern(type, value) {
|
|
||||||
if (isTS && isModifier(value)) { cx.marked = "keyword"; return cont(pattern) }
|
|
||||||
if (type == "variable") { register(value); return cont(); }
|
|
||||||
if (type == "spread") return cont(pattern);
|
|
||||||
if (type == "[") return contCommasep(eltpattern, "]");
|
|
||||||
if (type == "{") return contCommasep(proppattern, "}");
|
|
||||||
}
|
|
||||||
function proppattern(type, value) {
|
|
||||||
if (type == "variable" && !cx.stream.match(/^\s*:/, false)) {
|
|
||||||
register(value);
|
|
||||||
return cont(maybeAssign);
|
|
||||||
}
|
|
||||||
if (type == "variable") cx.marked = "property";
|
|
||||||
if (type == "spread") return cont(pattern);
|
|
||||||
if (type == "}") return pass();
|
|
||||||
if (type == "[") return cont(expression, expect(']'), expect(':'), proppattern);
|
|
||||||
return cont(expect(":"), pattern, maybeAssign);
|
|
||||||
}
|
|
||||||
function eltpattern() {
|
|
||||||
return pass(pattern, maybeAssign)
|
|
||||||
}
|
|
||||||
function maybeAssign(_type, value) {
|
|
||||||
if (value == "=") return cont(expressionNoComma);
|
|
||||||
}
|
|
||||||
function vardefCont(type) {
|
|
||||||
if (type == ",") return cont(vardef);
|
|
||||||
}
|
|
||||||
function maybeelse(type, value) {
|
|
||||||
if (type == "keyword b" && value == "else") return cont(pushlex("form", "else"), statement, poplex);
|
|
||||||
}
|
|
||||||
function forspec(type, value) {
|
|
||||||
if (value == "await") return cont(forspec);
|
|
||||||
if (type == "(") return cont(pushlex(")"), forspec1, poplex);
|
|
||||||
}
|
|
||||||
function forspec1(type) {
|
|
||||||
if (type == "var") return cont(vardef, forspec2);
|
|
||||||
if (type == "variable") return cont(forspec2);
|
|
||||||
return pass(forspec2)
|
|
||||||
}
|
|
||||||
function forspec2(type, value) {
|
|
||||||
if (type == ")") return cont()
|
|
||||||
if (type == ";") return cont(forspec2)
|
|
||||||
if (value == "in" || value == "of") { cx.marked = "keyword"; return cont(expression, forspec2) }
|
|
||||||
return pass(expression, forspec2)
|
|
||||||
}
|
|
||||||
function functiondef(type, value) {
|
|
||||||
if (value == "*") {cx.marked = "keyword"; return cont(functiondef);}
|
|
||||||
if (type == "variable") {register(value); return cont(functiondef);}
|
|
||||||
if (type == "(") return cont(pushcontext, pushlex(")"), commasep(funarg, ")"), poplex, mayberettype, statement, popcontext);
|
|
||||||
if (isTS && value == "<") return cont(pushlex(">"), commasep(typeparam, ">"), poplex, functiondef)
|
|
||||||
}
|
|
||||||
function functiondecl(type, value) {
|
|
||||||
if (value == "*") {cx.marked = "keyword"; return cont(functiondecl);}
|
|
||||||
if (type == "variable") {register(value); return cont(functiondecl);}
|
|
||||||
if (type == "(") return cont(pushcontext, pushlex(")"), commasep(funarg, ")"), poplex, mayberettype, popcontext);
|
|
||||||
if (isTS && value == "<") return cont(pushlex(">"), commasep(typeparam, ">"), poplex, functiondecl)
|
|
||||||
}
|
|
||||||
function typename(type, value) {
|
|
||||||
if (type == "keyword" || type == "variable") {
|
|
||||||
cx.marked = "type"
|
|
||||||
return cont(typename)
|
|
||||||
} else if (value == "<") {
|
|
||||||
return cont(pushlex(">"), commasep(typeparam, ">"), poplex)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
function funarg(type, value) {
|
|
||||||
if (value == "@") cont(expression, funarg)
|
|
||||||
if (type == "spread") return cont(funarg);
|
|
||||||
if (isTS && isModifier(value)) { cx.marked = "keyword"; return cont(funarg); }
|
|
||||||
if (isTS && type == "this") return cont(maybetype, maybeAssign)
|
|
||||||
return pass(pattern, maybetype, maybeAssign);
|
|
||||||
}
|
|
||||||
function classExpression(type, value) {
|
|
||||||
// Class expressions may have an optional name.
|
|
||||||
if (type == "variable") return className(type, value);
|
|
||||||
return classNameAfter(type, value);
|
|
||||||
}
|
|
||||||
function className(type, value) {
|
|
||||||
if (type == "variable") {register(value); return cont(classNameAfter);}
|
|
||||||
}
|
|
||||||
function classNameAfter(type, value) {
|
|
||||||
if (value == "<") return cont(pushlex(">"), commasep(typeparam, ">"), poplex, classNameAfter)
|
|
||||||
if (value == "extends" || value == "implements" || (isTS && type == ",")) {
|
|
||||||
if (value == "implements") cx.marked = "keyword";
|
|
||||||
return cont(isTS ? typeexpr : expression, classNameAfter);
|
|
||||||
}
|
|
||||||
if (type == "{") return cont(pushlex("}"), classBody, poplex);
|
|
||||||
}
|
|
||||||
function classBody(type, value) {
|
|
||||||
if (type == "async" ||
|
|
||||||
(type == "variable" &&
|
|
||||||
(value == "static" || value == "get" || value == "set" || (isTS && isModifier(value))) &&
|
|
||||||
cx.stream.match(/^\s+#?[\w$\xa1-\uffff]/, false))) {
|
|
||||||
cx.marked = "keyword";
|
|
||||||
return cont(classBody);
|
|
||||||
}
|
|
||||||
if (type == "variable" || cx.style == "keyword") {
|
|
||||||
cx.marked = "property";
|
|
||||||
return cont(classfield, classBody);
|
|
||||||
}
|
|
||||||
if (type == "number" || type == "string") return cont(classfield, classBody);
|
|
||||||
if (type == "[")
|
|
||||||
return cont(expression, maybetype, expect("]"), classfield, classBody)
|
|
||||||
if (value == "*") {
|
|
||||||
cx.marked = "keyword";
|
|
||||||
return cont(classBody);
|
|
||||||
}
|
|
||||||
if (isTS && type == "(") return pass(functiondecl, classBody)
|
|
||||||
if (type == ";" || type == ",") return cont(classBody);
|
|
||||||
if (type == "}") return cont();
|
|
||||||
if (value == "@") return cont(expression, classBody)
|
|
||||||
}
|
|
||||||
function classfield(type, value) {
|
|
||||||
if (value == "!") return cont(classfield)
|
|
||||||
if (value == "?") return cont(classfield)
|
|
||||||
if (type == ":") return cont(typeexpr, maybeAssign)
|
|
||||||
if (value == "=") return cont(expressionNoComma)
|
|
||||||
var context = cx.state.lexical.prev, isInterface = context && context.info == "interface"
|
|
||||||
return pass(isInterface ? functiondecl : functiondef)
|
|
||||||
}
|
|
||||||
function afterExport(type, value) {
|
|
||||||
if (value == "*") { cx.marked = "keyword"; return cont(maybeFrom, expect(";")); }
|
|
||||||
if (value == "default") { cx.marked = "keyword"; return cont(expression, expect(";")); }
|
|
||||||
if (type == "{") return cont(commasep(exportField, "}"), maybeFrom, expect(";"));
|
|
||||||
return pass(statement);
|
|
||||||
}
|
|
||||||
function exportField(type, value) {
|
|
||||||
if (value == "as") { cx.marked = "keyword"; return cont(expect("variable")); }
|
|
||||||
if (type == "variable") return pass(expressionNoComma, exportField);
|
|
||||||
}
|
|
||||||
function afterImport(type) {
|
|
||||||
if (type == "string") return cont();
|
|
||||||
if (type == "(") return pass(expression);
|
|
||||||
if (type == ".") return pass(maybeoperatorComma);
|
|
||||||
return pass(importSpec, maybeMoreImports, maybeFrom);
|
|
||||||
}
|
|
||||||
function importSpec(type, value) {
|
|
||||||
if (type == "{") return contCommasep(importSpec, "}");
|
|
||||||
if (type == "variable") register(value);
|
|
||||||
if (value == "*") cx.marked = "keyword";
|
|
||||||
return cont(maybeAs);
|
|
||||||
}
|
|
||||||
function maybeMoreImports(type) {
|
|
||||||
if (type == ",") return cont(importSpec, maybeMoreImports)
|
|
||||||
}
|
|
||||||
function maybeAs(_type, value) {
|
|
||||||
if (value == "as") { cx.marked = "keyword"; return cont(importSpec); }
|
|
||||||
}
|
|
||||||
function maybeFrom(_type, value) {
|
|
||||||
if (value == "from") { cx.marked = "keyword"; return cont(expression); }
|
|
||||||
}
|
|
||||||
function arrayLiteral(type) {
|
|
||||||
if (type == "]") return cont();
|
|
||||||
return pass(commasep(expressionNoComma, "]"));
|
|
||||||
}
|
|
||||||
function enumdef() {
|
|
||||||
return pass(pushlex("form"), pattern, expect("{"), pushlex("}"), commasep(enummember, "}"), poplex, poplex)
|
|
||||||
}
|
|
||||||
function enummember() {
|
|
||||||
return pass(pattern, maybeAssign);
|
|
||||||
}
|
|
||||||
|
|
||||||
function isContinuedStatement(state, textAfter) {
|
|
||||||
return state.lastType == "operator" || state.lastType == "," ||
|
|
||||||
isOperatorChar.test(textAfter.charAt(0)) ||
|
|
||||||
/[,.]/.test(textAfter.charAt(0));
|
|
||||||
}
|
|
||||||
|
|
||||||
function expressionAllowed(stream, state, backUp) {
|
|
||||||
return state.tokenize == tokenBase &&
|
|
||||||
/^(?:operator|sof|keyword [bcd]|case|new|export|default|spread|[\[{}\(,;:]|=>)$/.test(state.lastType) ||
|
|
||||||
(state.lastType == "quasi" && /\{\s*$/.test(stream.string.slice(0, stream.pos - (backUp || 0))))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Interface
|
|
||||||
|
|
||||||
return {
|
|
||||||
startState: function(basecolumn) {
|
|
||||||
var state = {
|
|
||||||
tokenize: tokenBase,
|
|
||||||
lastType: "sof",
|
|
||||||
cc: [],
|
|
||||||
lexical: new JSLexical((basecolumn || 0) - indentUnit, 0, "block", false),
|
|
||||||
localVars: parserConfig.localVars,
|
|
||||||
context: parserConfig.localVars && new Context(null, null, false),
|
|
||||||
indented: basecolumn || 0
|
|
||||||
};
|
|
||||||
if (parserConfig.globalVars && typeof parserConfig.globalVars == "object")
|
|
||||||
state.globalVars = parserConfig.globalVars;
|
|
||||||
return state;
|
|
||||||
},
|
|
||||||
|
|
||||||
token: function(stream, state) {
|
|
||||||
if (stream.sol()) {
|
|
||||||
if (!state.lexical.hasOwnProperty("align"))
|
|
||||||
state.lexical.align = false;
|
|
||||||
state.indented = stream.indentation();
|
|
||||||
findFatArrow(stream, state);
|
|
||||||
}
|
|
||||||
if (state.tokenize != tokenComment && stream.eatSpace()) return null;
|
|
||||||
var style = state.tokenize(stream, state);
|
|
||||||
if (type == "comment") return style;
|
|
||||||
state.lastType = type == "operator" && (content == "++" || content == "--") ? "incdec" : type;
|
|
||||||
return parseJS(state, style, type, content, stream);
|
|
||||||
},
|
|
||||||
|
|
||||||
indent: function(state, textAfter) {
|
|
||||||
if (state.tokenize == tokenComment || state.tokenize == tokenQuasi) return CodeMirror.Pass;
|
|
||||||
if (state.tokenize != tokenBase) return 0;
|
|
||||||
var firstChar = textAfter && textAfter.charAt(0), lexical = state.lexical, top
|
|
||||||
// Kludge to prevent 'maybelse' from blocking lexical scope pops
|
|
||||||
if (!/^\s*else\b/.test(textAfter)) for (var i = state.cc.length - 1; i >= 0; --i) {
|
|
||||||
var c = state.cc[i];
|
|
||||||
if (c == poplex) lexical = lexical.prev;
|
|
||||||
else if (c != maybeelse && c != popcontext) break;
|
|
||||||
}
|
|
||||||
while ((lexical.type == "stat" || lexical.type == "form") &&
|
|
||||||
(firstChar == "}" || ((top = state.cc[state.cc.length - 1]) &&
|
|
||||||
(top == maybeoperatorComma || top == maybeoperatorNoComma) &&
|
|
||||||
!/^[,\.=+\-*:?[\(]/.test(textAfter))))
|
|
||||||
lexical = lexical.prev;
|
|
||||||
if (statementIndent && lexical.type == ")" && lexical.prev.type == "stat")
|
|
||||||
lexical = lexical.prev;
|
|
||||||
var type = lexical.type, closing = firstChar == type;
|
|
||||||
|
|
||||||
if (type == "vardef") return lexical.indented + (state.lastType == "operator" || state.lastType == "," ? lexical.info.length + 1 : 0);
|
|
||||||
else if (type == "form" && firstChar == "{") return lexical.indented;
|
|
||||||
else if (type == "form") return lexical.indented + indentUnit;
|
|
||||||
else if (type == "stat")
|
|
||||||
return lexical.indented + (isContinuedStatement(state, textAfter) ? statementIndent || indentUnit : 0);
|
|
||||||
else if (lexical.info == "switch" && !closing && parserConfig.doubleIndentSwitch != false)
|
|
||||||
return lexical.indented + (/^(?:case|default)\b/.test(textAfter) ? indentUnit : 2 * indentUnit);
|
|
||||||
else if (lexical.align) return lexical.column + (closing ? 0 : 1);
|
|
||||||
else return lexical.indented + (closing ? 0 : indentUnit);
|
|
||||||
},
|
|
||||||
|
|
||||||
electricInput: /^\s*(?:case .*?:|default:|\{|\})$/,
|
|
||||||
blockCommentStart: jsonMode ? null : "/*",
|
|
||||||
blockCommentEnd: jsonMode ? null : "*/",
|
|
||||||
blockCommentContinue: jsonMode ? null : " * ",
|
|
||||||
lineComment: jsonMode ? null : "//",
|
|
||||||
fold: "brace",
|
|
||||||
closeBrackets: "()[]{}''\"\"``",
|
|
||||||
|
|
||||||
helperType: jsonMode ? "json" : "javascript",
|
|
||||||
jsonldMode: jsonldMode,
|
|
||||||
jsonMode: jsonMode,
|
|
||||||
|
|
||||||
expressionAllowed: expressionAllowed,
|
|
||||||
|
|
||||||
skipExpression: function(state) {
|
|
||||||
parseJS(state, "atom", "atom", "true", new CodeMirror.StringStream("", 2, null))
|
|
||||||
}
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
CodeMirror.registerHelper("wordChars", "javascript", /[\w$]/);
|
|
||||||
|
|
||||||
CodeMirror.defineMIME("text/javascript", "javascript");
|
|
||||||
CodeMirror.defineMIME("text/ecmascript", "javascript");
|
|
||||||
CodeMirror.defineMIME("application/javascript", "javascript");
|
|
||||||
CodeMirror.defineMIME("application/x-javascript", "javascript");
|
|
||||||
CodeMirror.defineMIME("application/ecmascript", "javascript");
|
|
||||||
CodeMirror.defineMIME("application/json", { name: "javascript", json: true });
|
|
||||||
CodeMirror.defineMIME("application/x-json", { name: "javascript", json: true });
|
|
||||||
CodeMirror.defineMIME("application/manifest+json", { name: "javascript", json: true })
|
|
||||||
CodeMirror.defineMIME("application/ld+json", { name: "javascript", jsonld: true });
|
|
||||||
CodeMirror.defineMIME("text/typescript", { name: "javascript", typescript: true });
|
|
||||||
CodeMirror.defineMIME("application/typescript", { name: "javascript", typescript: true });
|
|
||||||
|
|
||||||
});
|
|