1
0
mirror of https://github.com/nitezs/sub2clash.git synced 2024-12-23 20:34:41 -05:00

Reordered import statements across multiple files

for consistency
This commit is contained in:
Nite07 2023-11-03 02:35:30 +08:00
parent d0696aad5b
commit 73e94ad856
23 changed files with 10454 additions and 10394 deletions

View File

@ -5,4 +5,4 @@ REQUEST_RETRY_TIMES=3
REQUEST_MAX_FILE_SIZE=1048576 REQUEST_MAX_FILE_SIZE=1048576
CACHE_EXPIRE=300 CACHE_EXPIRE=300
LOG_LEVEL=info LOG_LEVEL=info
BASE_PATH=/ BASE_PATH=/

View File

@ -2,16 +2,16 @@
# hooks: # hooks:
# - go mod tidy # - go mod tidy
builds: builds:
- env: - env:
- CGO_ENABLED=0 - CGO_ENABLED=0
goos: goos:
- linux - linux
- windows - windows
- darwin - darwin
goarch: goarch:
- amd64 - amd64
- arm64 - arm64
ldflags: ldflags:
- -s -w -X sub2clash/config.Version={{ .Version }} - -s -w -X sub2clash/config.Version={{ .Version }}
no_unique_dist_dir: true no_unique_dist_dir: true
binary: "{{ .ProjectName }}-{{ .Os }}-{{ .Arch }}" binary: "{{ .ProjectName }}-{{ .Os }}-{{ .Arch }}"

13
.vscode/launch.json vendored Normal file
View File

@ -0,0 +1,13 @@
{
"configurations": [
{
"name": "Debug",
"type": "go",
"request": "launch",
"mode": "debug",
"program": "${workspaceFolder}/main.go",
"output": "${workspaceFolder}/dist/main.exe",
"buildFlags": "-ldflags '-X sub2clash/config.Dev=true -X sub2clash/config.Version=dev'"
}
]
}

View File

@ -2,36 +2,36 @@
获取 Clash/Clash.Meta 配置链接 获取 Clash/Clash.Meta 配置链接
| Query 参数 | 类型 | 是否必须 | 默认值 | 说明 | | Query 参数 | 类型 | 是否必须 | 默认值 | 说明 |
|--------------|--------|-------------------|-----------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | ------------ | ------ | ------------------------ | --------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --- |
| sub | string | sub/proxy 至少有一项存在 | - | 订阅链接,可以在链接结尾加上`#名称`,来给订阅中的节点加上统一前缀(可以输入多个,用 `,` 分隔) | | sub | string | sub/proxy 至少有一项存在 | - | 订阅链接,可以在链接结尾加上`#名称`,来给订阅中的节点加上统一前缀(可以输入多个,用 `,` 分隔) |
| proxy | string | sub/proxy 至少有一项存在 | - | 节点分享链接(可以输入多个,用 `,` 分隔) | | proxy | string | sub/proxy 至少有一项存在 | - | 节点分享链接(可以输入多个,用 `,` 分隔) |
| refresh | bool | 否 | `false` | 强制刷新配置(默认缓存 5 分钟) | | refresh | bool | 否 | `false` | 强制刷新配置(默认缓存 5 分钟) |
| template | string | 否 | - | 外部模板链接或内部模板名称 | | template | string | 否 | - | 外部模板链接或内部模板名称 |
| ruleProvider | string | 否 | - | 格式 `[Behavior,Url,Group,Prepend,Name],[Behavior,Url,Group,Prepend,Name]...`,其中 `Group` 是该规则集使用的策略组名,`Prepend` 为 bool 类型,如果为 `true` 规则将被添加到规则列表顶部否则添加到规则列表底部会调整到MATCH规则之前 | | ruleProvider | string | 否 | - | 格式 `[Behavior,Url,Group,Prepend,Name],[Behavior,Url,Group,Prepend,Name]...`,其中 `Group` 是该规则集使用的策略组名,`Prepend` 为 bool 类型,如果为 `true` 规则将被添加到规则列表顶部,否则添加到规则列表底部(会调整到 MATCH 规则之前) |
| rule | string | 否 | - | 格式 `[Rule,Prepend],[Rule,Prepend]...`,其中 `Prepend` 为 bool 类型,如果为 `true` 规则将被添加到规则列表顶部否则添加到规则列表底部会调整到MATCH规则之前 | | rule | string | 否 | - | 格式 `[Rule,Prepend],[Rule,Prepend]...`,其中 `Prepend` 为 bool 类型,如果为 `true` 规则将被添加到规则列表顶部,否则添加到规则列表底部(会调整到 MATCH 规则之前) |
| autoTest | bool | 否 | `false` | 国家策略组是否自动测速 | | autoTest | bool | 否 | `false` | 国家策略组是否自动测速 |
| lazy | bool | 否 | `false` | 自动测速是否启用 lazy | | lazy | bool | 否 | `false` | 自动测速是否启用 lazy |
| sort | string | 否 | `nameasc` | 国家策略组排序策略,可选值 `nameasc`、`namedesc`、`sizeasc`、`sizedesc` | | sort | string | 否 | `nameasc` | 国家策略组排序策略,可选值 `nameasc`、`namedesc`、`sizeasc`、`sizedesc` |
| replace | string | 否 | - | 通过正则表达式重命名节点,格式 `[<ReplaceKey>,<ReplaceTo>],[<ReplaceKey>,<ReplaceTo>]...` | | replace | string | 否 | - | 通过正则表达式重命名节点,格式 `[<ReplaceKey>,<ReplaceTo>],[<ReplaceKey>,<ReplaceTo>]...` |
| remove | string | 否 | - | 通过正则表达式删除节点 | | remove | string | 否 | - | 通过正则表达式删除节点 |
| nodeList | bool | 否 | `false` | 只输出节点 | | | nodeList | bool | 否 | `false` | 只输出节点 | |
# `/short` # `/short`
获取短链Content-Type 为 `application/json` 获取短链Content-Type 为 `application/json`
具体参考使用可以参考 [api\templates\index.html](api/static/index.html) 具体参考使用可以参考 [api\templates\index.html](api/static/index.html)
| Body 参数 | 类型 | 是否必须 | 默认值 | 说明 | | Body 参数 | 类型 | 是否必须 | 默认值 | 说明 |
|----------|--------|------|-----|------------------| | --------- | ------ | -------- | ------ | ------------------------- |
| url | string | 是 | - | 需要转换的 Query 参数部分 | | url | string | 是 | - | 需要转换的 Query 参数部分 |
| password | string | 否 | - | 短链密码 | | password | string | 否 | - | 短链密码 |
# `/s/:hash` # `/s/:hash`
短链跳转 短链跳转
`hash` 为动态路由参数,可以通过 `/short` 接口获取 `hash` 为动态路由参数,可以通过 `/short` 接口获取
| Query 参数 | 类型 | 是否必须 | 默认值 | 说明 | | Query 参数 | 类型 | 是否必须 | 默认值 | 说明 |
|----------|--------|------|-----|------| | ---------- | ------ | -------- | ------ | -------- |
| password | string | 否 | - | 短链密码 | | password | string | 否 | - | 短链密码 |

View File

@ -1,58 +1,61 @@
# sub2clash # sub2clash
将订阅链接转换为 Clash、Clash.Meta 配置 将订阅链接转换为 Clash、Clash.Meta 配置
[Demo](https://www.nite07.com/sub)
## 特性 ## 特性
- 开箱即用的规则、策略组配置 - 开箱即用的规则、策略组配置
- 自动根据节点名称按国家划分策略组 - 自动根据节点名称按国家划分策略组
- 支持多订阅合并 - 支持多订阅合并
- 支持多种协议 - 支持添加自定义 Rule Provider、Rule
- Shadowsocks - 支持多种协议
- ShadowsocksR - Shadowsocks
- Vmess - ShadowsocksR
- Vless - Vmess
- Trojan - Vless Clash.Meta
- Trojan
- Hysteria2 Clash.Meta
## 使用 ## 使用
### 运行 ### 运行
- [docker compose](./docker-compose.yml) - [docker compose](./docker-compose.yml)
- 运行[二进制文件](https://github.com/nitezs/sub2clash/releases/latest) - 运行[二进制文件](https://github.com/nitezs/sub2clash/releases/latest)
### 配置 ### 配置
可以通过编辑 .env 文件来修改默认配置docker 直接添加环境变量 可以通过编辑 .env 文件来修改默认配置docker 直接添加环境变量
| 变量名 | 说明 | 默认值 | | 变量名 | 说明 | 默认值 |
|-----------------------|-----------------------------------------------------------|-----------------------| | --------------------- | ---------------------------------------------- | --------------------- |
| PORT | 端口 | `8011` | | PORT | 端口 | `8011` |
| META_TEMPLATE | meta 模板文件名 | `template_meta.yaml` | | META_TEMPLATE | meta 模板文件名 | `template_meta.yaml` |
| CLASH_TEMPLATE | clash 模板文件名 | `template_clash.yaml` | | CLASH_TEMPLATE | clash 模板文件名 | `template_clash.yaml` |
| REQUEST_RETRY_TIMES | Get 请求重试次数 | `3` | | REQUEST_RETRY_TIMES | Get 请求重试次数 | `3` |
| REQUEST_MAX_FILE_SIZE | Get 请求订阅文件最大大小byte | `1048576` | | REQUEST_MAX_FILE_SIZE | Get 请求订阅文件最大大小byte | `1048576` |
| CACHE_EXPIRE | 订阅缓存时间(秒) | `300` | | CACHE_EXPIRE | 订阅缓存时间(秒) | `300` |
| LOG_LEVEL | 日志等级,可选值 `debug`,`info`,`warn`,`error` | `info` | | LOG_LEVEL | 日志等级,可选值 `debug`,`info`,`warn`,`error` | `info` |
| SHORT_LINK_LENGTH | 短链长度 | `6` | | SHORT_LINK_LENGTH | 短链长度 | `6` |
### API ### API
[API文档](./API_README.md) [API 文档](./API_README.md)
### 模板 ### 模板
可以通过变量自定义模板中的策略组代理节点 可以通过变量自定义模板中的策略组代理节点
解释的不太清楚,可以参考下方默认模板 解释的不太清楚,可以参考下方默认模板
- `<all>` 为添加所有节点 - `<all>` 为添加所有节点
- `<countries>` 为添加所有国家策略组 - `<countries>` 为添加所有国家策略组
- `<地区二位字母代码>` 为添加指定地区所有节点,例如 `<hk>` 将添加所有香港节点 - `<地区二位字母代码>` 为添加指定地区所有节点,例如 `<hk>` 将添加所有香港节点
#### 默认模板 #### 默认模板
- [Clash](./templates/template_clash.yaml) - [Clash](./templates/template_clash.yaml)
- [Clash.Meta](./templates/template_meta.yaml) - [Clash.Meta](./templates/template_meta.yaml)
## 已知问题 ## 已知问题

View File

@ -1,12 +1,13 @@
package controller package controller
import ( import (
"github.com/gin-gonic/gin"
"gopkg.in/yaml.v3"
"net/http" "net/http"
"sub2clash/config" "sub2clash/config"
"sub2clash/model" "sub2clash/model"
"sub2clash/validator" "sub2clash/validator"
"github.com/gin-gonic/gin"
"gopkg.in/yaml.v3"
) )
func SubmodHandler(c *gin.Context) { func SubmodHandler(c *gin.Context) {

View File

@ -4,8 +4,6 @@ import (
"crypto/sha256" "crypto/sha256"
"encoding/hex" "encoding/hex"
"errors" "errors"
"go.uber.org/zap"
"gopkg.in/yaml.v3"
"net/url" "net/url"
"regexp" "regexp"
"sort" "sort"
@ -16,6 +14,9 @@ import (
"sub2clash/parser" "sub2clash/parser"
"sub2clash/utils" "sub2clash/utils"
"sub2clash/validator" "sub2clash/validator"
"go.uber.org/zap"
"gopkg.in/yaml.v3"
) )
func BuildSub(clashType model.ClashType, query validator.SubValidator, template string) ( func BuildSub(clashType model.ClashType, query validator.SubValidator, template string) (

View File

@ -2,12 +2,13 @@ package controller
import ( import (
_ "embed" _ "embed"
"github.com/gin-gonic/gin"
"gopkg.in/yaml.v3"
"net/http" "net/http"
"sub2clash/config" "sub2clash/config"
"sub2clash/model" "sub2clash/model"
"sub2clash/validator" "sub2clash/validator"
"github.com/gin-gonic/gin"
"gopkg.in/yaml.v3"
) )
func SubHandler(c *gin.Context) { func SubHandler(c *gin.Context) {

View File

@ -2,9 +2,6 @@ package controller
import ( import (
"errors" "errors"
"github.com/gin-gonic/gin"
"go.uber.org/zap"
"gorm.io/gorm"
"io" "io"
"net/http" "net/http"
"strconv" "strconv"
@ -16,6 +13,10 @@ import (
"sub2clash/utils/database" "sub2clash/utils/database"
"sub2clash/validator" "sub2clash/validator"
"time" "time"
"github.com/gin-gonic/gin"
"go.uber.org/zap"
"gorm.io/gorm"
) )
func ShortLinkGenHandler(c *gin.Context) { func ShortLinkGenHandler(c *gin.Context) {

View File

@ -2,13 +2,14 @@ package api
import ( import (
"embed" "embed"
"github.com/gin-gonic/gin"
"html/template" "html/template"
"log" "log"
"net/http" "net/http"
"sub2clash/api/controller" "sub2clash/api/controller"
"sub2clash/config" "sub2clash/config"
"sub2clash/middleware" "sub2clash/middleware"
"github.com/gin-gonic/gin"
) )
//go:embed static //go:embed static

View File

@ -1,258 +1,283 @@
<!doctype html> <!DOCTYPE html>
<html lang="zh-CN"> <html lang="zh-CN">
<head> <head>
<meta charset="UTF-8" /> <meta charset="UTF-8" />
<meta content="width=device-width, initial-scale=1.0" name="viewport" /> <meta content="width=device-width, initial-scale=1.0" name="viewport" />
<title>sub2clash</title> <title>sub2clash</title>
<!-- Bootstrap CSS --> <!-- Bootstrap CSS -->
<link <link
crossorigin="anonymous" crossorigin="anonymous"
href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css"
integrity="sha384-T3c6CoIi6uLrA9TneNEoa7RxnatzjcDSCmG1MXxSR1GAsXEV/Dwwykc2MPK8M2HN" integrity="sha384-T3c6CoIi6uLrA9TneNEoa7RxnatzjcDSCmG1MXxSR1GAsXEV/Dwwykc2MPK8M2HN"
rel="stylesheet" rel="stylesheet"
/> />
<!-- Bootstrap JS --> <!-- Bootstrap JS -->
<script <script
crossorigin="anonymous" crossorigin="anonymous"
integrity="sha384-C6RzsynM9kWDrMNeT87bh95OGNyZPhcTNXj1NW7RuBCsyN/o0jlpcV8Qyq46cDfL" integrity="sha384-C6RzsynM9kWDrMNeT87bh95OGNyZPhcTNXj1NW7RuBCsyN/o0jlpcV8Qyq46cDfL"
src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js" src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js"
></script> ></script>
<!-- Axios --> <!-- Axios -->
<script src="https://cdn.jsdelivr.net/npm/axios@latest/dist/axios.min.js"></script> <script src="https://cdn.jsdelivr.net/npm/axios@latest/dist/axios.min.js"></script>
<script src="./static/index.js"></script> <script src="./static/index.js"></script>
<style> <style>
.container { .container {
max-width: 800px; max-width: 800px;
} }
.btn-xs { .btn-xs {
padding: 2px 2px; /* 调整内边距以减小按钮大小 */ padding: 2px 2px; /* 调整内边距以减小按钮大小 */
font-size: 10px; /* 设置字体大小 */ font-size: 10px; /* 设置字体大小 */
line-height: 1.2; /* 调整行高 */ line-height: 1.2; /* 调整行高 */
border-radius: 3px; /* 可选的边框半径调整 */ border-radius: 3px; /* 可选的边框半径调整 */
height: 25px; height: 25px;
width: 25px; width: 25px;
} }
</style> </style>
</head> </head>
<body class="bg-light"> <body class="bg-light">
<div class="container mt-5"> <div class="container mt-5">
<div class="mb-4"> <div class="mb-4">
<h2>sub2clash</h2> <h2>sub2clash</h2>
<span class="text-muted fst-italic" <span class="text-muted fst-italic"
>通用订阅链接转 Clash(Meta) 配置工具 >通用订阅链接转 Clash(Meta) 配置工具
<a <a
href="https://github.com/nitezs/sub2clash#clash-meta" href="https://github.com/nitezs/sub2clash#clash-meta"
target="_blank" target="_blank"
>使用文档</a >使用文档</a
></span ></span
> >
</div> </div>
<!-- Input URL --> <!-- Input URL -->
<div class="form-group mb-5"> <div class="form-group mb-5">
<label for="apiLink">解析链接:</label> <label for="apiLink">解析链接:</label>
<div class="input-group mb-2"> <div class="input-group mb-2">
<input <input
class="form-control" class="form-control"
id="urlInput" id="urlInput"
type="text" type="text"
placeholder="通过生成的链接重新填写下方设置" placeholder="通过生成的链接重新填写下方设置"
/> />
<button <button
class="btn btn-primary" class="btn btn-primary"
onclick="parseInputURL()" onclick="parseInputURL()"
type="button" type="button"
> >
解析 解析
</button> </button>
</div> </div>
</div> </div>
<!-- API Endpoint --> <!-- API Endpoint -->
<div class="form-group mb-3"> <div class="form-group mb-3">
<label for="endpoint">客户端类型:</label> <label for="endpoint">客户端类型:</label>
<select class="form-control" id="endpoint" name="endpoint"> <select class="form-control" id="endpoint" name="endpoint">
<option value="clash">Clash</option> <option value="clash">Clash</option>
<option value="meta">Clash.Meta</option> <option value="meta">Clash.Meta</option>
</select> </select>
</div> </div>
<!-- Template --> <!-- Template -->
<div class="form-group mb-3"> <div class="form-group mb-3">
<label for="template">模板链接或名称:</label> <label for="template">模板链接或名称:</label>
<input <input
class="form-control" class="form-control"
id="template" id="template"
name="template" name="template"
placeholder="输入外部模板链接或内部模板名称(可选)" placeholder="输入外部模板链接或内部模板名称(可选)"
type="text" type="text"
/> />
</div> </div>
<!-- Subscription Link --> <!-- Subscription Link -->
<div class="form-group mb-3"> <div class="form-group mb-3">
<label for="sub">订阅链接:</label> <label for="sub">订阅链接:</label>
<textarea <textarea
class="form-control" class="form-control"
id="sub" id="sub"
name="sub" name="sub"
placeholder="每行输入一个订阅链接" placeholder="每行输入一个订阅链接"
rows="5" rows="5"
></textarea> ></textarea>
</div> </div>
<!-- Proxy Link --> <!-- Proxy Link -->
<div class="form-group mb-3"> <div class="form-group mb-3">
<label for="proxy">节点分享链接:</label> <label for="proxy">节点分享链接:</label>
<textarea <textarea
class="form-control" class="form-control"
id="proxy" id="proxy"
name="proxy" name="proxy"
placeholder="每行输入一个节点分享链接" placeholder="每行输入一个节点分享链接"
rows="5" rows="5"
></textarea> ></textarea>
</div> </div>
<!-- Refresh --> <!-- Refresh -->
<div class="form-check mb-3"> <div class="form-check mb-3">
<input <input
class="form-check-input" class="form-check-input"
id="refresh" id="refresh"
name="refresh" name="refresh"
type="checkbox" type="checkbox"
/> />
<label class="form-check-label" for="refresh">强制重新获取订阅</label> <label class="form-check-label" for="refresh"
</div> >强制重新获取订阅</label
<!-- Node List --> >
<div class="form-check mb-3"> </div>
<input <!-- Node List -->
class="form-check-input" <div class="form-check mb-3">
id="nodeList" <input
name="nodeList" class="form-check-input"
type="checkbox" id="nodeList"
/> name="nodeList"
<label class="form-check-label" for="nodeList">输出为 Node List</label> type="checkbox"
</div> />
<!-- Auto Test --> <label class="form-check-label" for="nodeList"
<div class="form-check mb-3"> >输出为 Node List</label
<input >
class="form-check-input" </div>
id="autoTest" <!-- Auto Test -->
name="autoTest" <div class="form-check mb-3">
type="checkbox" <input
/> class="form-check-input"
<label class="form-check-label" for="autoTest" id="autoTest"
>国家策略组自动测速</label name="autoTest"
> type="checkbox"
</div> />
<!-- Lazy --> <label class="form-check-label" for="autoTest"
<div class="form-check mb-3"> >国家策略组自动测速</label
<input class="form-check-input" id="lazy" name="lazy" type="checkbox" /> >
<label class="form-check-label" for="lazy" </div>
>自动测速启用 lazy 模式</label <!-- Lazy -->
> <div class="form-check mb-3">
</div> <input
<!-- Rule Provider --> class="form-check-input"
<div class="form-group mb-3" id="ruleProviderGroup"> id="lazy"
<label>Rule Provider:</label> name="lazy"
<button type="checkbox"
class="btn btn-primary mb-1 btn-xs" />
onclick="addRuleProvider()" <label class="form-check-label" for="lazy"
type="button" >自动测速启用 lazy 模式</label
> >
+ </div>
</button> <!-- Rule Provider -->
</div> <div class="form-group mb-3" id="ruleProviderGroup">
<!-- Rule --> <label>Rule Provider:</label>
<div class="form-group mb-3" id="ruleGroup"> <button
<label>规则:</label> class="btn btn-primary mb-1 btn-xs"
<button onclick="addRuleProvider()"
class="btn btn-primary mb-1 btn-xs" type="button"
onclick="addRule()" >
type="button" +
> </button>
+ </div>
</button> <!-- Rule -->
</div> <div class="form-group mb-3" id="ruleGroup">
<!-- Sort --> <label>规则:</label>
<div class="form-group mb-3"> <button
<label for="sort">国家策略组排序规则:</label> class="btn btn-primary mb-1 btn-xs"
<select class="form-control" id="sort" name="sort"> onclick="addRule()"
<option value="nameasc">名称(升序)</option> type="button"
<option value="namedesc">名称(降序)</option> >
<option value="sizeasc">节点数量(升序)</option> +
<option value="sizedesc">节点数量(降序)</option> </button>
</select> </div>
</div> <!-- Sort -->
<!-- Remove --> <div class="form-group mb-3">
<div class="form-group mb-3"> <label for="sort">国家策略组排序规则:</label>
<label for="remove">排除节点:</label> <select class="form-control" id="sort" name="sort">
<input <option value="nameasc">名称(升序)</option>
class="form-control" <option value="namedesc">名称(降序)</option>
type="text" <option value="sizeasc">节点数量(升序)</option>
name="remove" <option value="sizedesc">节点数量(降序)</option>
id="remove" </select>
placeholder="正则表达式" </div>
/> <!-- Remove -->
</div> <div class="form-group mb-3">
<!-- Rename --> <label for="remove">排除节点:</label>
<div class="form-group mb-3" id="replaceGroup"> <input
<label>节点名称替换:</label> class="form-control"
<button type="text"
class="btn btn-primary mb-1 btn-xs" name="remove"
onclick="addReplace()" id="remove"
type="button" placeholder="正则表达式"
> />
+ </div>
</button> <!-- Rename -->
</div> <div class="form-group mb-3" id="replaceGroup">
<label>节点名称替换:</label>
<button
class="btn btn-primary mb-1 btn-xs"
onclick="addReplace()"
type="button"
>
+
</button>
</div>
<!-- Display the API Link --> <!-- Display the API Link -->
<div class="form-group mb-5"> <div class="form-group mb-5">
<label for="apiLink">配置链接:</label> <label for="apiLink">配置链接:</label>
<div class="input-group mb-2"> <div class="input-group mb-2">
<input class="form-control" id="apiLink" readonly type="text" /> <input
<button class="btn btn-primary" onclick="generateURL()" type="button"> class="form-control"
生成链接 id="apiLink"
</button> readonly
<button type="text"
class="btn btn-primary" />
onclick="copyToClipboard('apiLink',this)" <button
type="button" class="btn btn-primary"
> onclick="generateURL()"
复制链接 type="button"
</button> >
生成链接
</button>
<button
class="btn btn-primary"
onclick="copyToClipboard('apiLink',this)"
type="button"
>
复制链接
</button>
</div>
<div class="input-group">
<input
class="form-control"
id="apiShortLink"
readonly
type="text"
/>
<input
class="form-control"
id="password"
type="text"
placeholder="密码"
/>
<button
class="btn btn-primary"
onclick="generateShortLink()"
type="button"
>
生成短链
</button>
<button
class="btn btn-primary"
onclick="copyToClipboard('apiShortLink',this)"
type="button"
>
复制短链
</button>
</div>
</div>
<!-- footer-->
<footer>
<p class="text-center">
Powered by
<a
class="link-primary"
href="https://github.com/nitezs/sub2clash"
>sub2clash</a
>
</p>
<p class="text-center">Version {{.Version}}</p>
</footer>
</div> </div>
<div class="input-group"> </body>
<input class="form-control" id="apiShortLink" readonly type="text" />
<input
class="form-control"
id="password"
type="text"
placeholder="密码"
/>
<button
class="btn btn-primary"
onclick="generateShortLink()"
type="button"
>
生成短链
</button>
<button
class="btn btn-primary"
onclick="copyToClipboard('apiShortLink',this)"
type="button"
>
复制短链
</button>
</div>
</div>
<!-- footer-->
<footer>
<p class="text-center">
Powered by
<a class="link-primary" href="https://github.com/nitezs/sub2clash"
>sub2clash</a
>
</p>
<p class="text-center">Version {{.Version}}</p>
</footer>
</div>
</body>
</html> </html>

View File

@ -1,329 +1,335 @@
function clearExistingValues() { function clearExistingValues() {
// 清除简单输入框和复选框的值 // 清除简单输入框和复选框的值
document.getElementById("endpoint").value = "clash"; document.getElementById("endpoint").value = "clash";
document.getElementById("sub").value = ""; document.getElementById("sub").value = "";
document.getElementById("proxy").value = ""; document.getElementById("proxy").value = "";
document.getElementById("refresh").checked = false; document.getElementById("refresh").checked = false;
document.getElementById("autoTest").checked = false; document.getElementById("autoTest").checked = false;
document.getElementById("lazy").checked = false; document.getElementById("lazy").checked = false;
document.getElementById("template").value = ""; document.getElementById("template").value = "";
document.getElementById("sort").value = "nameasc"; document.getElementById("sort").value = "nameasc";
document.getElementById("remove").value = ""; document.getElementById("remove").value = "";
document.getElementById("apiLink").value = ""; document.getElementById("apiLink").value = "";
document.getElementById("apiShortLink").value = ""; document.getElementById("apiShortLink").value = "";
document.getElementById("password").value = ""; document.getElementById("password").value = "";
document.getElementById("nodeList").checked = false; document.getElementById("nodeList").checked = false;
// 清除由 createRuleProvider, createReplace, 和 createRule 创建的所有额外输入组 // 清除由 createRuleProvider, createReplace, 和 createRule 创建的所有额外输入组
clearInputGroup("ruleProviderGroup"); clearInputGroup("ruleProviderGroup");
clearInputGroup("replaceGroup"); clearInputGroup("replaceGroup");
clearInputGroup("ruleGroup"); clearInputGroup("ruleGroup");
} }
function generateURI() { function generateURI() {
const queryParams = []; const queryParams = [];
// 获取 API Endpoint // 获取 API Endpoint
const endpoint = document.getElementById("endpoint").value; const endpoint = document.getElementById("endpoint").value;
// 获取并组合订阅链接 // 获取并组合订阅链接
let subLines = document let subLines = document
.getElementById("sub") .getElementById("sub")
.value.split("\n") .value.split("\n")
.filter((line) => line.trim() !== ""); .filter((line) => line.trim() !== "");
let noSub = false; let noSub = false;
// 去除 subLines 中空元素 // 去除 subLines 中空元素
subLines = subLines.map((item) => { subLines = subLines.map((item) => {
if (item !== "") { if (item !== "") {
return item; return item;
}
});
if (subLines.length > 0) {
queryParams.push(`sub=${encodeURIComponent(subLines.join(","))}`);
} else {
noSub = true;
} }
});
if (subLines.length > 0) {
queryParams.push(`sub=${encodeURIComponent(subLines.join(","))}`);
} else {
noSub = true;
}
// 获取并组合节点分享链接 // 获取并组合节点分享链接
let proxyLines = document let proxyLines = document
.getElementById("proxy") .getElementById("proxy")
.value.split("\n") .value.split("\n")
.filter((line) => line.trim() !== ""); .filter((line) => line.trim() !== "");
let noProxy = false; let noProxy = false;
// 去除 proxyLines 中空元素 // 去除 proxyLines 中空元素
proxyLines = proxyLines.map((item) => { proxyLines = proxyLines.map((item) => {
if (item !== "") { if (item !== "") {
return item; return item;
}
});
if (proxyLines.length > 0) {
queryParams.push(`proxy=${encodeURIComponent(proxyLines.join(","))}`);
} else {
noProxy = true;
} }
}); if (noSub && noProxy) {
if (proxyLines.length > 0) { alert("订阅链接和节点分享链接不能同时为空!");
queryParams.push(`proxy=${encodeURIComponent(proxyLines.join(","))}`);
} else {
noProxy = true;
}
if (noSub && noProxy) {
alert("订阅链接和节点分享链接不能同时为空!");
return "";
}
// 获取复选框的值
const refresh = document.getElementById("refresh").checked;
queryParams.push(`refresh=${refresh ? "true" : "false"}`);
const autoTest = document.getElementById("autoTest").checked;
queryParams.push(`autoTest=${autoTest ? "true" : "false"}`);
const lazy = document.getElementById("lazy").checked;
queryParams.push(`lazy=${lazy ? "true" : "false"}`);
const nodeList = document.getElementById("nodeList").checked;
queryParams.push(`nodeList=${nodeList ? "true" : "false"}`);
// 获取模板链接或名称(如果存在)
const template = document.getElementById("template").value;
if (template.trim() !== "") {
queryParams.push(`template=${encodeURIComponent(template)}`);
}
// 获取Rule Provider和规则
const ruleProviders = document.getElementsByName("ruleProvider");
const rules = document.getElementsByName("rule");
let providers = [];
for (let i = 0; i < ruleProviders.length / 5; i++) {
let baseIndex = i * 5;
let behavior = ruleProviders[baseIndex].value;
let url = ruleProviders[baseIndex + 1].value;
let group = ruleProviders[baseIndex + 2].value;
let prepend = ruleProviders[baseIndex + 3].value;
let name = ruleProviders[baseIndex + 4].value;
// 是否存在空值
if (
behavior.trim() === "" ||
url.trim() === "" ||
group.trim() === "" ||
prepend.trim() === "" ||
name.trim() === ""
) {
alert("Rule Provider 中存在空值,请检查后重试!");
return "";
}
providers.push(`[${behavior},${url},${group},${prepend},${name}]`);
}
queryParams.push(`ruleProvider=${encodeURIComponent(providers.join(","))}`);
let ruleList = [];
for (let i = 0; i < rules.length / 3; i++) {
if (rules[i * 3].value.trim() !== "") {
let rule = rules[i * 3].value;
let prepend = rules[i * 3 + 1].value;
let group = rules[i * 3 + 2].value;
// 是否存在空值
if (rule.trim() === "" || prepend.trim() === "" || group.trim() === "") {
alert("Rule 中存在空值,请检查后重试!");
return ""; return "";
}
ruleList.push(`[${rule},${prepend},${group}]`);
} }
} // 获取复选框的值
queryParams.push(`rule=${encodeURIComponent(ruleList.join(","))}`); const refresh = document.getElementById("refresh").checked;
queryParams.push(`refresh=${refresh ? "true" : "false"}`);
const autoTest = document.getElementById("autoTest").checked;
queryParams.push(`autoTest=${autoTest ? "true" : "false"}`);
const lazy = document.getElementById("lazy").checked;
queryParams.push(`lazy=${lazy ? "true" : "false"}`);
const nodeList = document.getElementById("nodeList").checked;
queryParams.push(`nodeList=${nodeList ? "true" : "false"}`);
// 获取排序策略 // 获取模板链接或名称(如果存在)
const sort = document.getElementById("sort").value; const template = document.getElementById("template").value;
queryParams.push(`sort=${sort}`); if (template.trim() !== "") {
queryParams.push(`template=${encodeURIComponent(template)}`);
// 获取删除节点的正则表达式
const remove = document.getElementById("remove").value;
if (remove.trim() !== "") {
queryParams.push(`remove=${encodeURIComponent(remove)}`);
}
// 获取替换节点名称的正则表达式
let replaceList = [];
const replaces = document.getElementsByName("replace");
for (let i = 0; i < replaces.length / 2; i++) {
let replaceStr = `<${replaces[i * 2].value}>`;
let replaceTo = `<${replaces[i * 2 + 1].value}>`;
if (replaceStr.trim() === "") {
alert("重命名设置中存在空值,请检查后重试!");
return "";
} }
replaceList.push(`[${replaceStr},${replaceTo}]`);
}
queryParams.push(`replace=${encodeURIComponent(replaceList.join(","))}`);
return `${endpoint}?${queryParams.join("&")}`; // 获取Rule Provider和规则
const ruleProviders = document.getElementsByName("ruleProvider");
const rules = document.getElementsByName("rule");
let providers = [];
for (let i = 0; i < ruleProviders.length / 5; i++) {
let baseIndex = i * 5;
let behavior = ruleProviders[baseIndex].value;
let url = ruleProviders[baseIndex + 1].value;
let group = ruleProviders[baseIndex + 2].value;
let prepend = ruleProviders[baseIndex + 3].value;
let name = ruleProviders[baseIndex + 4].value;
// 是否存在空值
if (
behavior.trim() === "" ||
url.trim() === "" ||
group.trim() === "" ||
prepend.trim() === "" ||
name.trim() === ""
) {
alert("Rule Provider 中存在空值,请检查后重试!");
return "";
}
providers.push(`[${behavior},${url},${group},${prepend},${name}]`);
}
queryParams.push(`ruleProvider=${encodeURIComponent(providers.join(","))}`);
let ruleList = [];
for (let i = 0; i < rules.length / 3; i++) {
if (rules[i * 3].value.trim() !== "") {
let rule = rules[i * 3].value;
let prepend = rules[i * 3 + 1].value;
let group = rules[i * 3 + 2].value;
// 是否存在空值
if (
rule.trim() === "" ||
prepend.trim() === "" ||
group.trim() === ""
) {
alert("Rule 中存在空值,请检查后重试!");
return "";
}
ruleList.push(`[${rule},${prepend},${group}]`);
}
}
queryParams.push(`rule=${encodeURIComponent(ruleList.join(","))}`);
// 获取排序策略
const sort = document.getElementById("sort").value;
queryParams.push(`sort=${sort}`);
// 获取删除节点的正则表达式
const remove = document.getElementById("remove").value;
if (remove.trim() !== "") {
queryParams.push(`remove=${encodeURIComponent(remove)}`);
}
// 获取替换节点名称的正则表达式
let replaceList = [];
const replaces = document.getElementsByName("replace");
for (let i = 0; i < replaces.length / 2; i++) {
let replaceStr = `<${replaces[i * 2].value}>`;
let replaceTo = `<${replaces[i * 2 + 1].value}>`;
if (replaceStr.trim() === "") {
alert("重命名设置中存在空值,请检查后重试!");
return "";
}
replaceList.push(`[${replaceStr},${replaceTo}]`);
}
queryParams.push(`replace=${encodeURIComponent(replaceList.join(","))}`);
return `${endpoint}?${queryParams.join("&")}`;
} }
// 将输入框中的 URL 解析为参数 // 将输入框中的 URL 解析为参数
function parseInputURL() { function parseInputURL() {
// 获取输入框中的 URL // 获取输入框中的 URL
const inputURL = document.getElementById("urlInput").value; const inputURL = document.getElementById("urlInput").value;
if (!inputURL) { if (!inputURL) {
alert("请输入有效的链接!"); alert("请输入有效的链接!");
return; return;
} }
let url; let url;
try { try {
url = new URL(inputURL); url = new URL(inputURL);
} catch (_) { } catch (_) {
alert("无效的链接!"); alert("无效的链接!");
return; return;
} }
// 清除现有的输入框值 // 清除现有的输入框值
clearExistingValues(); clearExistingValues();
// 获取查询参数 // 获取查询参数
const params = new URLSearchParams(url.search); const params = new URLSearchParams(url.search);
// 分配值到对应的输入框 // 分配值到对应的输入框
const pathSections = url.pathname.split("/"); const pathSections = url.pathname.split("/");
const lastSection = pathSections[pathSections.length - 1]; const lastSection = pathSections[pathSections.length - 1];
const clientTypeSelect = document.getElementById("endpoint"); const clientTypeSelect = document.getElementById("endpoint");
switch (lastSection.toLowerCase()) { switch (lastSection.toLowerCase()) {
case "meta": case "meta":
clientTypeSelect.value = "meta"; clientTypeSelect.value = "meta";
break; break;
case "clash": case "clash":
default: default:
clientTypeSelect.value = "clash"; clientTypeSelect.value = "clash";
break; break;
} }
if (params.has("sub")) { if (params.has("sub")) {
document.getElementById("sub").value = decodeURIComponent(params.get("sub")) document.getElementById("sub").value = decodeURIComponent(
.split(",") params.get("sub")
.join("\n"); )
} .split(",")
.join("\n");
}
if (params.has("proxy")) { if (params.has("proxy")) {
document.getElementById("proxy").value = decodeURIComponent( document.getElementById("proxy").value = decodeURIComponent(
params.get("proxy"), params.get("proxy")
) )
.split(",") .split(",")
.join("\n"); .join("\n");
} }
if (params.has("refresh")) { if (params.has("refresh")) {
document.getElementById("refresh").checked = document.getElementById("refresh").checked =
params.get("refresh") === "true"; params.get("refresh") === "true";
} }
if (params.has("autoTest")) { if (params.has("autoTest")) {
document.getElementById("autoTest").checked = document.getElementById("autoTest").checked =
params.get("autoTest") === "true"; params.get("autoTest") === "true";
} }
if (params.has("lazy")) { if (params.has("lazy")) {
document.getElementById("lazy").checked = params.get("lazy") === "true"; document.getElementById("lazy").checked = params.get("lazy") === "true";
} }
if (params.has("template")) { if (params.has("template")) {
document.getElementById("template").value = decodeURIComponent( document.getElementById("template").value = decodeURIComponent(
params.get("template"), params.get("template")
); );
} }
if (params.has("sort")) { if (params.has("sort")) {
document.getElementById("sort").value = params.get("sort"); document.getElementById("sort").value = params.get("sort");
} }
if (params.has("remove")) { if (params.has("remove")) {
document.getElementById("remove").value = decodeURIComponent( document.getElementById("remove").value = decodeURIComponent(
params.get("remove"), params.get("remove")
); );
} }
if (params.has("replace")) { if (params.has("replace")) {
parseAndFillReplaceParams(decodeURIComponent(params.get("replace"))); parseAndFillReplaceParams(decodeURIComponent(params.get("replace")));
} }
if (params.has("ruleProvider")) { if (params.has("ruleProvider")) {
parseAndFillRuleProviderParams( parseAndFillRuleProviderParams(
decodeURIComponent(params.get("ruleProvider")), decodeURIComponent(params.get("ruleProvider"))
); );
} }
if (params.has("rule")) { if (params.has("rule")) {
parseAndFillRuleParams(decodeURIComponent(params.get("rule"))); parseAndFillRuleParams(decodeURIComponent(params.get("rule")));
} }
if (params.has("nodeList")) { if (params.has("nodeList")) {
document.getElementById("nodeList").checked = document.getElementById("nodeList").checked =
params.get("nodeList") === "true"; params.get("nodeList") === "true";
} }
} }
function clearInputGroup(groupId) { function clearInputGroup(groupId) {
// 清空第二个之后的child // 清空第二个之后的child
const group = document.getElementById(groupId); const group = document.getElementById(groupId);
while (group.children.length > 2) { while (group.children.length > 2) {
group.removeChild(group.lastChild); group.removeChild(group.lastChild);
} }
} }
function parseAndFillReplaceParams(replaceParams) { function parseAndFillReplaceParams(replaceParams) {
const replaceGroup = document.getElementById("replaceGroup"); const replaceGroup = document.getElementById("replaceGroup");
let matches; let matches;
const regex = /\[(<.*?>),(<.*?>)\]/g; const regex = /\[(<.*?>),(<.*?>)\]/g;
const str = decodeURIComponent(replaceParams); const str = decodeURIComponent(replaceParams);
while ((matches = regex.exec(str)) !== null) { while ((matches = regex.exec(str)) !== null) {
const div = createReplace(); const div = createReplace();
const original = matches[1].slice(1, -1); // Remove < and > const original = matches[1].slice(1, -1); // Remove < and >
const replacement = matches[2].slice(1, -1); // Remove < and > const replacement = matches[2].slice(1, -1); // Remove < and >
div.children[0].value = original; div.children[0].value = original;
div.children[1].value = replacement; div.children[1].value = replacement;
replaceGroup.appendChild(div); replaceGroup.appendChild(div);
} }
} }
function parseAndFillRuleProviderParams(ruleProviderParams) { function parseAndFillRuleProviderParams(ruleProviderParams) {
const ruleProviderGroup = document.getElementById("ruleProviderGroup"); const ruleProviderGroup = document.getElementById("ruleProviderGroup");
let matches; let matches;
const regex = /\[(.*?),(.*?),(.*?),(.*?),(.*?)\]/g; const regex = /\[(.*?),(.*?),(.*?),(.*?),(.*?)\]/g;
const str = decodeURIComponent(ruleProviderParams); const str = decodeURIComponent(ruleProviderParams);
while ((matches = regex.exec(str)) !== null) { while ((matches = regex.exec(str)) !== null) {
const div = createRuleProvider(); const div = createRuleProvider();
div.children[0].value = matches[1]; div.children[0].value = matches[1];
div.children[1].value = matches[2]; div.children[1].value = matches[2];
div.children[2].value = matches[3]; div.children[2].value = matches[3];
div.children[3].value = matches[4]; div.children[3].value = matches[4];
div.children[4].value = matches[5]; div.children[4].value = matches[5];
ruleProviderGroup.appendChild(div); ruleProviderGroup.appendChild(div);
} }
} }
function parseAndFillRuleParams(ruleParams) { function parseAndFillRuleParams(ruleParams) {
const ruleGroup = document.getElementById("ruleGroup"); const ruleGroup = document.getElementById("ruleGroup");
let matches; let matches;
const regex = /\[(.*?),(.*?),(.*?)\]/g; const regex = /\[(.*?),(.*?),(.*?)\]/g;
const str = decodeURIComponent(ruleParams); const str = decodeURIComponent(ruleParams);
while ((matches = regex.exec(str)) !== null) { while ((matches = regex.exec(str)) !== null) {
const div = createRule(); const div = createRule();
div.children[0].value = matches[1]; div.children[0].value = matches[1];
div.children[1].value = matches[2]; div.children[1].value = matches[2];
div.children[2].value = matches[3]; div.children[2].value = matches[3];
ruleGroup.appendChild(div); ruleGroup.appendChild(div);
} }
} }
async function copyToClipboard(elem, e) { async function copyToClipboard(elem, e) {
const apiLinkInput = document.querySelector(`#${elem}`).value; const apiLinkInput = document.querySelector(`#${elem}`).value;
try { try {
await navigator.clipboard.writeText(apiLinkInput); await navigator.clipboard.writeText(apiLinkInput);
let text = e.textContent; let text = e.textContent;
e.addEventListener("mouseout", function () { e.addEventListener("mouseout", function () {
e.textContent = text; e.textContent = text;
}); });
e.textContent = "复制成功"; e.textContent = "复制成功";
} catch (err) { } catch (err) {
console.error("复制到剪贴板失败:", err); console.error("复制到剪贴板失败:", err);
} }
} }
function createRuleProvider() { function createRuleProvider() {
const div = document.createElement("div"); const div = document.createElement("div");
div.classList.add("input-group", "mb-2"); div.classList.add("input-group", "mb-2");
div.innerHTML = ` div.innerHTML = `
<input type="text" class="form-control" name="ruleProvider" placeholder="Behavior"> <input type="text" class="form-control" name="ruleProvider" placeholder="Behavior">
<input type="text" class="form-control" name="ruleProvider" placeholder="Url"> <input type="text" class="form-control" name="ruleProvider" placeholder="Url">
<input type="text" class="form-control" name="ruleProvider" placeholder="Group"> <input type="text" class="form-control" name="ruleProvider" placeholder="Group">
@ -331,85 +337,85 @@ function createRuleProvider() {
<input type="text" class="form-control" name="ruleProvider" placeholder="Name"> <input type="text" class="form-control" name="ruleProvider" placeholder="Name">
<button type="button" class="btn btn-danger" onclick="removeElement(this)">删除</button> <button type="button" class="btn btn-danger" onclick="removeElement(this)">删除</button>
`; `;
return div; return div;
} }
function createReplace() { function createReplace() {
const div = document.createElement("div"); const div = document.createElement("div");
div.classList.add("input-group", "mb-2"); div.classList.add("input-group", "mb-2");
div.innerHTML = ` div.innerHTML = `
<input type="text" class="form-control" name="replace" placeholder="原字符串(正则表达式)"> <input type="text" class="form-control" name="replace" placeholder="原字符串(正则表达式)">
<input type="text" class="form-control" name="replace" placeholder="替换为(可为空)"> <input type="text" class="form-control" name="replace" placeholder="替换为(可为空)">
<button type="button" class="btn btn-danger" onclick="removeElement(this)">删除</button> <button type="button" class="btn btn-danger" onclick="removeElement(this)">删除</button>
`; `;
return div; return div;
} }
function createRule() { function createRule() {
const div = document.createElement("div"); const div = document.createElement("div");
div.classList.add("input-group", "mb-2"); div.classList.add("input-group", "mb-2");
div.innerHTML = ` div.innerHTML = `
<input type="text" class="form-control" name="rule" placeholder="Rule"> <input type="text" class="form-control" name="rule" placeholder="Rule">
<input type="text" class="form-control" name="rule" placeholder="Prepend"> <input type="text" class="form-control" name="rule" placeholder="Prepend">
<input type="text" class="form-control" name="rule" placeholder="Group"> <input type="text" class="form-control" name="rule" placeholder="Group">
<button type="button" class="btn btn-danger" onclick="removeElement(this)">删除</button> <button type="button" class="btn btn-danger" onclick="removeElement(this)">删除</button>
`; `;
return div; return div;
} }
function addRuleProvider() { function addRuleProvider() {
const div = createRuleProvider(); const div = createRuleProvider();
document.getElementById("ruleProviderGroup").appendChild(div); document.getElementById("ruleProviderGroup").appendChild(div);
} }
function addRule() { function addRule() {
const div = createRule(); const div = createRule();
document.getElementById("ruleGroup").appendChild(div); document.getElementById("ruleGroup").appendChild(div);
} }
function addReplace() { function addReplace() {
const div = createReplace(); const div = createReplace();
document.getElementById("replaceGroup").appendChild(div); document.getElementById("replaceGroup").appendChild(div);
} }
function removeElement(button) { function removeElement(button) {
button.parentElement.remove(); button.parentElement.remove();
} }
function generateURL() { function generateURL() {
const apiLink = document.getElementById("apiLink"); const apiLink = document.getElementById("apiLink");
let uri = generateURI(); let uri = generateURI();
if (uri === "") { if (uri === "") {
return; return;
} }
apiLink.value = `${window.location.origin}${window.location.pathname}${uri}`; apiLink.value = `${window.location.origin}${window.location.pathname}${uri}`;
} }
function generateShortLink() { function generateShortLink() {
const apiShortLink = document.getElementById("apiShortLink"); const apiShortLink = document.getElementById("apiShortLink");
const password = document.getElementById("password"); const password = document.getElementById("password");
let uri = generateURI(); let uri = generateURI();
if (uri === "") { if (uri === "") {
return; return;
} }
axios axios
.post( .post(
"./short", "./short",
{ {
url: uri, url: uri,
password: password.value.trim(), password: password.value.trim(),
}, },
{ {
headers: { headers: {
"Content-Type": "application/json", "Content-Type": "application/json",
}, },
}, }
) )
.then((response) => { .then((response) => {
apiShortLink.value = `${window.location.origin}${window.location.pathname}s/${response.data}`; apiShortLink.value = `${window.location.origin}${window.location.pathname}s/${response.data}`;
}) })
.catch((error) => { .catch((error) => {
console.log(error); console.log(error);
alert("生成短链失败,请重试!"); alert("生成短链失败,请重试!");
}); });
} }

View File

@ -2,9 +2,10 @@ package config
import ( import (
"errors" "errors"
"github.com/joho/godotenv"
"os" "os"
"strconv" "strconv"
"github.com/joho/godotenv"
) )
type Config struct { type Config struct {

View File

@ -1,21 +1,21 @@
version: '3' version: "3"
services: services:
sub2clash: sub2clash:
container_name: sub2clash container_name: sub2clash
restart: unless-stopped restart: unless-stopped
image: ghcr.io/nitezs/sub2clash:latest image: ghcr.io/nitezs/sub2clash:latest
ports: ports:
- "8011:8011" - "8011:8011"
volumes: volumes:
- ./logs:/app/logs - ./logs:/app/logs
- ./templates:/app/templates - ./templates:/app/templates
- ./data:/app/data - ./data:/app/data
# environment: # environment:
# - PORT=8011 # - PORT=8011
# - META_TEMPLATE=template_meta.yaml # - META_TEMPLATE=template_meta.yaml
# - PROXY_TEMPLATE=template_clash.yaml # - PROXY_TEMPLATE=template_clash.yaml
# - REQUEST_RETRY_TIMES=3 # - REQUEST_RETRY_TIMES=3
# - REQUEST_MAX_FILE_SIZE=1048576 # - REQUEST_MAX_FILE_SIZE=1048576
# - CACHE_EXPIRE=300 # - CACHE_EXPIRE=300
# - LOG_LEVEL=info # - LOG_LEVEL=info

View File

@ -1,11 +1,12 @@
package logger package logger
import ( import (
"go.uber.org/zap"
"go.uber.org/zap/zapcore"
"path/filepath" "path/filepath"
"sync" "sync"
"time" "time"
"go.uber.org/zap"
"go.uber.org/zap/zapcore"
) )
var ( var (

View File

@ -2,8 +2,6 @@ package main
import ( import (
_ "embed" _ "embed"
"github.com/gin-gonic/gin"
"go.uber.org/zap"
"io" "io"
"strconv" "strconv"
"sub2clash/api" "sub2clash/api"
@ -11,6 +9,9 @@ import (
"sub2clash/logger" "sub2clash/logger"
"sub2clash/utils" "sub2clash/utils"
"sub2clash/utils/database" "sub2clash/utils/database"
"github.com/gin-gonic/gin"
"go.uber.org/zap"
) )
//go:embed templates/template_meta.yaml //go:embed templates/template_meta.yaml

View File

@ -1,11 +1,12 @@
package middleware package middleware
import ( import (
"github.com/gin-gonic/gin"
"go.uber.org/zap"
"strconv" "strconv"
"sub2clash/logger" "sub2clash/logger"
"time" "time"
"github.com/gin-gonic/gin"
"go.uber.org/zap"
) )
func ZapLogger() gin.HandlerFunc { func ZapLogger() gin.HandlerFunc {

View File

@ -37,14 +37,15 @@ func ParseHysteria2(proxy string) (model.Proxy, error) {
} }
// 返回结果 // 返回结果
result := model.Proxy{ result := model.Proxy{
Type: "hysteria2", Type: "hysteria2",
Name: params.Get("name"), Name: params.Get("name"),
Server: serverAndPort[0], Server: serverAndPort[0],
Port: port, Port: port,
Password: parts[0], Password: parts[0],
Obfs: params.Get("obfs"), Obfs: params.Get("obfs"),
ObfsParam: params.Get("obfs-password"), ObfsParam: params.Get("obfs-password"),
Sni: params.Get("sni"), Sni: params.Get("sni"),
SkipCertVerify: params.Get("insecure") == "1",
} }
return result, nil return result, nil
} }

File diff suppressed because it is too large Load Diff

View File

@ -5,123 +5,123 @@ mode: Rule
log-level: info log-level: info
proxies: proxies:
proxy-groups: proxy-groups:
- name: 节点选择 - name: 节点选择
type: select type: select
proxies: proxies:
- <countries> - <countries>
- 手动切换 - 手动切换
- DIRECT - DIRECT
- name: 手动切换 - name: 手动切换
type: select type: select
proxies: proxies:
- <all> - <all>
- name: 游戏平台(中国) - name: 游戏平台(中国)
type: select type: select
proxies: proxies:
- 节点选择 - 节点选择
- <countries> - <countries>
- 手动切换 - 手动切换
- DIRECT - DIRECT
- name: 游戏平台(全球) - name: 游戏平台(全球)
type: select type: select
proxies: proxies:
- 节点选择 - 节点选择
- <countries> - <countries>
- 手动切换 - 手动切换
- DIRECT - DIRECT
- name: 巴哈姆特 - name: 巴哈姆特
type: select type: select
proxies: proxies:
- 节点选择 - 节点选择
- <countries> - <countries>
- 手动切换 - 手动切换
- DIRECT - DIRECT
- name: 哔哩哔哩 - name: 哔哩哔哩
type: select type: select
proxies: proxies:
- 节点选择 - 节点选择
- <countries> - <countries>
- 手动切换 - 手动切换
- DIRECT - DIRECT
- name: Telegram - name: Telegram
type: select type: select
proxies: proxies:
- 节点选择 - 节点选择
- <countries> - <countries>
- 手动切换 - 手动切换
- DIRECT - DIRECT
- name: OpenAI - name: OpenAI
type: select type: select
proxies: proxies:
- 节点选择 - 节点选择
- <countries> - <countries>
- 手动切换 - 手动切换
- DIRECT - DIRECT
- name: Youtube - name: Youtube
type: select type: select
proxies: proxies:
- 节点选择 - 节点选择
- <countries> - <countries>
- 手动切换 - 手动切换
- DIRECT - DIRECT
- name: Microsoft - name: Microsoft
type: select type: select
proxies: proxies:
- 节点选择 - 节点选择
- <countries> - <countries>
- 手动切换 - 手动切换
- DIRECT - DIRECT
- name: Onedrive - name: Onedrive
type: select type: select
proxies: proxies:
- 节点选择 - 节点选择
- <countries> - <countries>
- 手动切换 - 手动切换
- DIRECT - DIRECT
- name: Apple - name: Apple
type: select type: select
proxies: proxies:
- 节点选择 - 节点选择
- <countries> - <countries>
- 手动切换 - 手动切换
- DIRECT - DIRECT
- name: Netflix - name: Netflix
type: select type: select
proxies: proxies:
- 节点选择 - 节点选择
- <countries> - <countries>
- 手动切换 - 手动切换
- DIRECT - DIRECT
- name: 广告拦截 - name: 广告拦截
type: select type: select
proxies: proxies:
- REJECT - REJECT
- DIRECT - DIRECT
- name: 漏网之鱼 - name: 漏网之鱼
type: select type: select
proxies: proxies:
- 节点选择 - 节点选择
- <countries> - <countries>
- 手动切换 - 手动切换
- DIRECT - DIRECT
rules: rules:
- GEOSITE,private,DIRECT,no-resolve - GEOSITE,private,DIRECT,no-resolve
- GEOIP,private,DIRECT - GEOIP,private,DIRECT
- GEOSITE,category-ads-all,广告拦截 - GEOSITE,category-ads-all,广告拦截
- GEOSITE,microsoft,Microsoft - GEOSITE,microsoft,Microsoft
- GEOSITE,apple,Apple - GEOSITE,apple,Apple
- GEOSITE,netflix,Netflix - GEOSITE,netflix,Netflix
- GEOIP,netflix,Netflix - GEOIP,netflix,Netflix
- GEOSITE,onedrive,Onedrive - GEOSITE,onedrive,Onedrive
- GEOSITE,youtube,Youtube - GEOSITE,youtube,Youtube
- GEOSITE,telegram,Telegram - GEOSITE,telegram,Telegram
- GEOIP,telegram,Telegram - GEOIP,telegram,Telegram
- GEOSITE,openai,OpenAI - GEOSITE,openai,OpenAI
- GEOSITE,bilibili,哔哩哔哩 - GEOSITE,bilibili,哔哩哔哩
- GEOSITE,bahamut,巴哈姆特 - GEOSITE,bahamut,巴哈姆特
- GEOSITE,category-games@cn,游戏平台(中国) - GEOSITE,category-games@cn,游戏平台(中国)
- GEOSITE,category-games,游戏平台(全球) - GEOSITE,category-games,游戏平台(全球)
- GEOSITE,geolocation-!cn,节点选择 - GEOSITE,geolocation-!cn,节点选择
- GEOSITE,CN,DIRECT - GEOSITE,CN,DIRECT
- GEOIP,CN,DIRECT - GEOIP,CN,DIRECT
- MATCH,漏网之鱼 - MATCH,漏网之鱼

View File

@ -1,13 +1,14 @@
package database package database
import ( import (
"github.com/glebarez/sqlite"
"go.uber.org/zap"
"gorm.io/gorm"
"path/filepath" "path/filepath"
"sub2clash/logger" "sub2clash/logger"
"sub2clash/model" "sub2clash/model"
"sub2clash/utils" "sub2clash/utils"
"github.com/glebarez/sqlite"
"go.uber.org/zap"
"gorm.io/gorm"
) )
var DB *gorm.DB var DB *gorm.DB

View File

@ -1,11 +1,12 @@
package utils package utils
import ( import (
"go.uber.org/zap"
"strings" "strings"
"sub2clash/logger" "sub2clash/logger"
"sub2clash/model" "sub2clash/model"
"sub2clash/parser" "sub2clash/parser"
"go.uber.org/zap"
) )
func GetContryName(countryKey string) string { func GetContryName(countryKey string) string {

View File

@ -4,11 +4,12 @@ import (
"crypto/sha256" "crypto/sha256"
"encoding/hex" "encoding/hex"
"errors" "errors"
"github.com/gin-gonic/gin"
"net/url" "net/url"
"os" "os"
"regexp" "regexp"
"strings" "strings"
"github.com/gin-gonic/gin"
) )
type SubValidator struct { type SubValidator struct {