#开始安装
git clone https://github.com/CareyWang/sub-web.git sub-web
#修改域名缩短服务地址
sub-web/services/.env
#修改服务生成后端
sub-web/src/views/Subconverter.vue
#修改sub-web下配置文件
.env
#重新构造生成镜像
docker build -t subweb-local:latest .
#启动容器
docker run -d -p 58080:80 --restart always --name subweb subweb-local:latest
Subconverter.vue已改代码
<template>
<div>
<el-row style="margin-top: 10px">
<el-col>
<el-card>
<div slot="header">
Subscription Converter
<svg-icon icon-class="github" style="margin-left: 20px" @click="goToProject" />
<div style="display: inline-block; position:absolute; right: 20px">{{ backendVersion }}</div>
</div>
<el-container>
<el-form :model="form" label-width="80px" label-position="left" style="width: 100%">
<el-form-item label="模式设置:">
<el-radio v-model="advanced" label="1">基础模式</el-radio>
<el-radio v-model="advanced" label="2">进阶模式</el-radio>
</el-form-item>
<el-form-item label="订阅链接:">
<el-input
v-model="form.sourceSubUrl"
type="textarea"
rows="3"
placeholder="支持订阅或ss/ssr/vmess链接,多个链接每行一个或用 | 分隔"
@blur="saveSubUrl"
/>
</el-form-item>
<el-form-item label="客户端:">
<el-select v-model="form.clientType" style="width: 100%">
<el-option v-for="(v, k) in options.clientTypes" :key="k" :label="k" :value="v"></el-option>
</el-select>
</el-form-item>
<div v-if="advanced === '2'">
<el-form-item label="后端地址:">
<el-autocomplete
style="width: 100%"
v-model="form.customBackend"
:fetch-suggestions="backendSearch"
placeholder="动动小手,(建议)自行搭建后端服务。例:http://127.0.0.1:25500/sub?"
>
<el-button slot="append" @click="gotoGayhub" icon="el-icon-link">前往项目仓库</el-button>
</el-autocomplete>
</el-form-item>
<el-form-item label="远程配置:">
<el-select
v-model="form.remoteConfig"
allow-create
filterable
placeholder="请选择"
style="width: 100%"
>
<el-option-group
v-for="group in options.remoteConfig"
:key="group.label"
:label="group.label"
>
<el-option
v-for="item in group.options"
:key="item.value"
:label="item.label"
:value="item.value"
></el-option>
</el-option-group>
<el-button slot="append" @click="gotoRemoteConfig" icon="el-icon-link">配置示例</el-button>
</el-select>
</el-form-item>
<el-form-item label="Include:">
<el-input v-model="form.includeRemarks" placeholder="节点名包含的关键字,支持正则" />
</el-form-item>
<el-form-item label="Exclude:">
<el-input v-model="form.excludeRemarks" placeholder="节点名不包含的关键字,支持正则" />
</el-form-item>
<el-form-item label="FileName:">
<el-input v-model="form.filename" placeholder="返回的订阅文件名" />
</el-form-item>
<el-form-item label-width="0px">
<el-row type="flex">
<el-col>
<el-checkbox v-model="form.nodeList" label="输出为 Node List" border></el-checkbox>
</el-col>
<el-popover placement="bottom" v-model="form.extraset">
<el-row>
<el-checkbox v-model="form.emoji" label="Emoji"></el-checkbox>
</el-row>
<el-row>
<el-checkbox v-model="form.new_name" label="Clash New Field"></el-checkbox>
</el-row>
<el-row>
<el-checkbox v-model="form.udp" label="启用 UDP"></el-checkbox>
</el-row>
<el-row>
<el-checkbox v-model="form.appendType" label="节点类型"></el-checkbox>
</el-row>
<el-row>
<el-checkbox v-model="form.sort" label="排序节点"></el-checkbox>
</el-row>
<el-row>
<el-checkbox v-model="form.fdn" label="过滤非法节点"></el-checkbox>
</el-row>
<el-button slot="reference">更多选项</el-button>
</el-popover>
<el-popover placement="bottom" style="margin-left: 10px">
<el-row>
<el-checkbox v-model="form.tpl.surge.doh" label="Surge.DoH"></el-checkbox>
</el-row>
<el-row>
<el-checkbox v-model="form.tpl.clash.doh" label="Clash.DoH"></el-checkbox>
</el-row>
<el-row>
<el-checkbox v-model="form.insert" label="网易云"></el-checkbox>
</el-row>
<el-button slot="reference">定制功能</el-button>
</el-popover>
</el-row>
</el-form-item>
</div>
<div style="margin-top: 50px"></div>
<el-divider content-position="center">
<i class="el-icon-magic-stick"></i>
</el-divider>
<el-form-item label="定制订阅:">
<el-input class="copy-content" disabled v-model="customSubUrl">
<el-button
slot="append"
v-clipboard:copy="customSubUrl"
v-clipboard:success="onCopy"
ref="copy-btn"
icon="el-icon-document-copy"
>复制</el-button>
</el-input>
</el-form-item>
<el-form-item label="订阅短链:">
<el-input class="copy-content" disabled v-model="curtomShortSubUrl">
<el-button
slot="append"
v-clipboard:copy="curtomShortSubUrl"
v-clipboard:success="onCopy"
ref="copy-btn"
icon="el-icon-document-copy"
>复制</el-button>
</el-input>
</el-form-item>
<el-form-item label-width="0px" style="margin-top: 40px; text-align: center">
<el-button
style="width: 120px"
type="danger"
@click="makeUrl"
:disabled="form.sourceSubUrl.length === 0"
>生成订阅链接</el-button>
<el-button
style="width: 120px"
type="danger"
@click="makeShortUrl"
:loading="loading"
:disabled="customSubUrl.length === 0"
>生成短链接</el-button>
<!-- <el-button style="width: 120px" type="primary" @click="surgeInstall" icon="el-icon-connection">一键导入Surge</el-button> -->
</el-form-item>
<el-form-item label-width="0px" style="text-align: center">
<el-button
style="width: 120px"
type="primary"
@click="dialogUploadConfigVisible = true"
icon="el-icon-upload"
:loading="loading"
>上传配置</el-button>
<el-button
style="width: 120px"
type="primary"
@click="clashInstall"
icon="el-icon-connection"
:disabled="customSubUrl.length === 0"
>一键导入Clash</el-button>
</el-form-item>
</el-form>
</el-container>
</el-card>
</el-col>
</el-row>
<el-dialog
:visible.sync="dialogUploadConfigVisible"
:show-close="false"
:close-on-click-modal="false"
:close-on-press-escape="false"
width="700px"
>
<div slot="title">
Remote config upload
<el-popover trigger="hover" placement="right" style="margin-left: 10px">
<el-link type="primary" :href="sampleConfig" target="_blank" icon="el-icon-info">参考配置</el-link>
<i class="el-icon-question" slot="reference"></i>
</el-popover>
</div>
<el-form label-position="left">
<el-form-item prop="uploadConfig">
<el-input
v-model="uploadConfig"
type="textarea"
:autosize="{ minRows: 15, maxRows: 15}"
maxlength="5000"
show-word-limit
></el-input>
</el-form-item>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button @click="uploadConfig = ''; dialogUploadConfigVisible = false">取 消</el-button>
<el-button
type="primary"
@click="confirmUploadConfig"
:disabled="uploadConfig.length === 0"
>确 定</el-button>
</div>
</el-dialog>
</div>
</template>
<script>
const project = process.env.VUE_APP_PROJECT
const remoteConfigSample = process.env.VUE_APP_SUBCONVERTER_REMOTE_CONFIG
const gayhubRelease = process.env.VUE_APP_BACKEND_RELEASE
const defaultBackend = process.env.VUE_APP_SUBCONVERTER_DEFAULT_BACKEND + '/sub?'
const shortUrlBackend = process.env.VUE_APP_MYURLS_DEFAULT_BACKEND + '/short'
const configUploadBackend = process.env.VUE_APP_CONFIG_UPLOAD_BACKEND + '/config/upload'
const tgBotLink = process.env.VUE_APP_BOT_LINK
export default {
data() {
return {
backendVersion: "",
advanced: "2",
// 是否为 PC 端
isPC: true,
options: {
clientTypes: {
Clash: "clash",
ClashR: "clashr",
Surge2: "surge&ver=2",
Surge3: "surge&ver=3",
Surge4: "surge&ver=4",
Quantumult: "quan",
QuantumultX: "quanx",
Surfboard: "surfboard",
Loon: "loon",
ss: "ss",
ssr: "ssr",
ssd: "ssd",
v2ray: "v2ray"
},
customBackend: [{
"localhost":"自用"
}],
backendOptions: [{ value: "https://sub.server.xue.im/sub?" }],
remoteConfig: [
{
label: "ACL4SSR",
options: [
{
label: "自用规则(与Github同步)",
value:
"https://raw.githubusercontent.com/qzoo/guize/master/Clash/config/ACL4SSR_Online.ini"
},
{
label: "ACL4SSR_Online_AdblockPlus 更多去广告 (与Github同步)",
value:
"https://raw.githubusercontent.com/ACL4SSR/ACL4SSR/master/Clash/config/ACL4SSR_Online_AdblockPlus.ini"
},
{
label: "ACL4SSR_Online_NoAuto 无自动测速 (与Github同步)",
value:
"https://raw.githubusercontent.com/ACL4SSR/ACL4SSR/master/Clash/config/ACL4SSR_Online_NoAuto.ini"
},
{
label: "ACL4SSR_Online_NoReject 无广告拦截规则 (与Github同步)",
value:
"https://raw.githubusercontent.com/ACL4SSR/ACL4SSR/master/Clash/config/ACL4SSR_Online_NoReject.ini"
},
{
label: "ACL4SSR_Online_Mini 精简版 (与Github同步)",
value:
"https://raw.githubusercontent.com/ACL4SSR/ACL4SSR/master/Clash/config/ACL4SSR_Online_Mini.ini"
},
{
label: "ACL4SSR_Online_Mini_AdblockPlus.ini 精简版 更多去广告 (与Github同步)",
value:
"https://raw.githubusercontent.com/ACL4SSR/ACL4SSR/master/Clash/config/ACL4SSR_Online_Mini_AdblockPlus.ini"
},
{
label: "ACL4SSR_Online_Mini_NoAuto.ini 精简版 不带自动测速 (与Github同步)",
value:
"https://raw.githubusercontent.com/ACL4SSR/ACL4SSR/master/Clash/config/ACL4SSR_Online_Mini_NoAuto.ini"
},
{
label: "ACL4SSR_Online_Mini_Fallback.ini 精简版 带故障转移 (与Github同步)",
value:
"https://raw.githubusercontent.com/ACL4SSR/ACL4SSR/master/Clash/config/ACL4SSR_Online_Mini_Fallback.ini"
},
{
label: "ACL4SSR_Online_Mini_MultiMode.ini 精简版 自动测速、故障转移、负载均衡 (与Github同步)",
value:
"https://raw.githubusercontent.com/ACL4SSR/ACL4SSR/master/Clash/config/ACL4SSR_Online_Mini_MultiMode.ini"
},
{
label: "ACL4SSR_Online_Full 全分组 重度用户使用 (与Github同步)",
value:
"https://raw.githubusercontent.com/ACL4SSR/ACL4SSR/master/Clash/config/ACL4SSR_Online_Full.ini"
},
{
label: "ACL4SSR_Online_Full_NoAuto.ini 全分组 无自动测速 重度用户使用 (与Github同步)",
value:
"https://raw.githubusercontent.com/ACL4SSR/ACL4SSR/master/Clash/config/ACL4SSR_Online_Full_NoAuto.ini"
},
{
label: "ACL4SSR_Online_Full_AdblockPlus 全分组 重度用户使用 更多去广告 (与Github同步)",
value:
"https://raw.githubusercontent.com/ACL4SSR/ACL4SSR/master/Clash/config/ACL4SSR_Online_Full_AdblockPlus.ini"
},
{
label: "ACL4SSR_Online_Full_Netflix 全分组 重度用户使用 奈飞全量 (与Github同步)",
value:
"https://raw.githubusercontent.com/ACL4SSR/ACL4SSR/master/Clash/config/ACL4SSR_Online_Full_Netflix.ini"
},
{
label: "ACL4SSR 本地 默认版 分组比较全",
value: "config/ACL4SSR.ini"
},
{
label: "ACL4SSR_Mini 本地 精简版",
value: "config/ACL4SSR_Mini.ini"
},
{
label: "ACL4SSR_Mini_NoAuto.ini 本地 精简版+无自动测速",
value: "config/ACL4SSR_Mini_NoAuto.ini"
},
{
label: "ACL4SSR_Mini_Fallback.ini 本地 精简版+fallback",
value: "config/ACL4SSR_Mini_Fallback.ini"
},
{
label: "ACL4SSR_BackCN 本地 回国",
value: "config/ACL4SSR_BackCN.ini"
},
{
label: "ACL4SSR_NoApple 本地 无苹果分流",
value: "config/ACL4SSR_NoApple.ini"
},
{
label: "ACL4SSR_NoAuto 本地 无自动测速 ",
value: "config/ACL4SSR_NoAuto.ini"
},
{
label: "ACL4SSR_NoAuto_NoApple 本地 无自动测速&无苹果分流",
value: "config/ACL4SSR_NoAuto_NoApple.ini"
},
{
label: "ACL4SSR_NoMicrosoft 本地 无微软分流",
value: "config/ACL4SSR_NoMicrosoft.ini"
},
{
label: "ACL4SSR_WithGFW 本地 GFW列表",
value: "config/ACL4SSR_WithGFW.ini"
}
]
},
{
label: "customized",
options: [
{
label: "Maying",
value:
"https://subconverter.oss-ap-southeast-1.aliyuncs.com/Rules/RemoteConfig/customized/maying.ini"
},
{
label: "rixCloud",
value:
"https://subconverter.oss-ap-southeast-1.aliyuncs.com/Rules/RemoteConfig/customized/rixcloud.ini"
},
{
label: "YoYu",
value:
"https://subconverter.oss-ap-southeast-1.aliyuncs.com/Rules/RemoteConfig/customized/yoyu.ini"
},
{
label: "Ytoo",
value:
"https://subconverter.oss-ap-southeast-1.aliyuncs.com/Rules/RemoteConfig/customized/ytoo.ini"
},
{
label: "NyanCAT",
value:
"https://subconverter.oss-ap-southeast-1.aliyuncs.com/Rules/RemoteConfig/customized/nyancat.ini"
},
{
label: "Nexitally",
value:
"https://subconverter.oss-ap-southeast-1.aliyuncs.com/Rules/RemoteConfig/customized/nexitally.ini"
},
{
label: "SoCloud",
value:
"https://subconverter.oss-ap-southeast-1.aliyuncs.com/Rules/RemoteConfig/customized/socloud.ini"
},
{
label: "ARK",
value:
"https://subconverter.oss-ap-southeast-1.aliyuncs.com/Rules/RemoteConfig/customized/ark.ini"
},
{
label: "ssrCloud",
value:
"https://subconverter.oss-ap-southeast-1.aliyuncs.com/Rules/RemoteConfig/customized/ssrcloud.ini"
}
]
}
]
},
form: {
sourceSubUrl: "",
clientType: "",
customBackend: "",
remoteConfig: "",
excludeRemarks: "",
includeRemarks: "",
filename: "",
emoji: true,
nodeList: false,
extraset: false,
sort: false,
udp: false,
tfo: false,
scv: false,
fdn: false,
appendType: false,
insert: false, // 是否插入默认订阅的节点,对应配置项 insert_url
new_name: true, // 是否使用 Clash 新字段
// tpl 定制功能
tpl: {
surge: {
doh: false // dns 查询是否使用 DoH
},
clash: {
doh: false
}
}
},
loading: false,
customSubUrl: "",
curtomShortSubUrl: "",
dialogUploadConfigVisible: false,
uploadConfig: "",
uploadPassword: "",
myBot: tgBotLink,
sampleConfig: remoteConfigSample
};
},
created() {
document.title = "Subscription Converter";
this.isPC = this.$getOS().isPc;
// 获取 url cache
if (process.env.VUE_APP_USE_STORAGE === 'true') {
this.form.sourceSubUrl = this.getLocalStorageItem('sourceSubUrl')
}
},
mounted() {
this.form.clientType = "clash";
this.notify();
this.getBackendVersion();
},
methods: {
onCopy() {
this.$message.success("Copied!");
},
goToProject() {
window.open(project);
},
gotoGayhub() {
window.open(gayhubRelease);
},
gotoRemoteConfig() {
window.open(remoteConfigSample);
},
clashInstall() {
if (this.customSubUrl === "") {
this.$message.error("请先填写必填项,生成订阅链接");
return false;
}
const url = "clash://install-config?url=";
window.open(
url +
encodeURIComponent(
this.curtomShortSubUrl !== ""
? this.curtomShortSubUrl
: this.customSubUrl
)
);
},
surgeInstall() {
if (this.customSubUrl === "") {
this.$message.error("请先填写必填项,生成订阅链接");
return false;
}
const url = "surge://install-config?url=";
window.open(url + this.customSubUrl);
},
makeUrl() {
if (this.form.sourceSubUrl === "" || this.form.clientType === "") {
this.$message.error("订阅链接与客户端为必填项");
return false;
}
let backend =
this.form.customBackend === ""
? defaultBackend
: this.form.customBackend;
let sourceSub = this.form.sourceSubUrl;
sourceSub = sourceSub.replace(/(\n|\r|\n\r)/g, "|");
this.customSubUrl =
backend +
"target=" +
this.form.clientType +
"&url=" +
encodeURIComponent(sourceSub) +
"&insert=" +
this.form.insert;
if (this.advanced === "2") {
if (this.form.remoteConfig !== "") {
this.customSubUrl +=
"&config=" + encodeURIComponent(this.form.remoteConfig);
}
if (this.form.excludeRemarks !== "") {
this.customSubUrl +=
"&exclude=" + encodeURIComponent(this.form.excludeRemarks);
}
if (this.form.includeRemarks !== "") {
this.customSubUrl +=
"&include=" + encodeURIComponent(this.form.includeRemarks);
}
if (this.form.filename !== "") {
this.customSubUrl +=
"&filename=" + encodeURIComponent(this.form.filename);
}
if (this.form.appendType) {
this.customSubUrl +=
"&append_type=" + this.form.appendType.toString();
}
this.customSubUrl +=
"&emoji=" +
this.form.emoji.toString() +
"&list=" +
this.form.nodeList.toString() +
"&udp=" +
this.form.udp.toString() +
"&tfo=" +
this.form.tfo.toString() +
"&scv=" +
this.form.scv.toString() +
"&fdn=" +
this.form.fdn.toString() +
"&sort=" +
this.form.sort.toString();
if (this.form.tpl.surge.doh === true) {
this.customSubUrl += "&surge.doh=true";
}
if (this.form.clientType === "clash") {
if (this.form.tpl.clash.doh === true) {
this.customSubUrl += "&clash.doh=true";
}
this.customSubUrl += "&new_name=" + this.form.new_name.toString();
}
}
this.$copyText(this.customSubUrl);
this.$message.success("定制订阅已复制到剪贴板");
},
makeShortUrl() {
if (this.customSubUrl === "") {
this.$message.warning("请先生成订阅链接,再获取对应短链接");
return false;
}
this.loading = true;
let data = new FormData();
data.append("longUrl", btoa(this.customSubUrl));
this.$axios
.post(shortUrlBackend, data, {
header: {
"Content-Type": "application/form-data; charset=utf-8"
}
})
.then(res => {
if (res.data.Code === 1 && res.data.ShortUrl !== "") {
this.curtomShortSubUrl = res.data.ShortUrl;
this.$copyText(res.data.ShortUrl);
this.$message.success("短链接已复制到剪贴板");
} else {
this.$message.error("短链接获取失败:" + res.data.Message);
}
})
.catch(() => {
this.$message.error("短链接获取失败");
})
.finally(() => {
this.loading = false;
});
},
notify() {
const h = this.$createElement;
this.$notify({
title: "隐私提示",
type: "warning",
message: h(
"i",
{ style: "color: teal" },
"各种订阅链接(短链接服务除外)生成纯前端实现,无隐私问题。默认提供后端转换服务,隐私担忧者请自行搭建后端服务。"
)
});
},
confirmUploadConfig() {
if (this.uploadConfig === "") {
this.$message.warning("远程配置不能为空");
return false;
}
this.loading = true;
let data = new FormData();
data.append("password", this.uploadPassword);
data.append("config", this.uploadConfig);
this.$axios
.post(configUploadBackend, data, {
header: {
"Content-Type": "application/form-data; charset=utf-8"
}
})
.then(res => {
if (res.data.Code === 1 && res.data.url !== "") {
this.$message.success(
"远程配置上传成功,配置链接已复制到剪贴板,有效期三个月望知悉"
);
// 自动填充至『表单-远程配置』
this.form.remoteConfig = res.data.Url;
this.$copyText(this.form.remoteConfig);
this.dialogUploadConfigVisible = false;
} else {
this.$message.error("远程配置上传失败:" + res.data.Message);
}
})
.catch(() => {
this.$message.error("远程配置上传失败");
})
.finally(() => {
this.loading = false;
});
},
backendSearch(queryString, cb) {
let backends = this.options.backendOptions;
let results = queryString
? backends.filter(this.createFilter(queryString))
: backends;
// 调用 callback 返回建议列表的数据
cb(results);
},
createFilter(queryString) {
return candidate => {
return (
candidate.value.toLowerCase().indexOf(queryString.toLowerCase()) === 0
);
};
},
getBackendVersion() {
this.$axios
.get(
defaultBackend.substring(0, defaultBackend.length - 5) + "/version"
)
.then(res => {
this.backendVersion = res.data.replace(/backend\n$/gm, "");
this.backendVersion = this.backendVersion.replace("subconverter", "");
});
},
saveSubUrl() {
if (this.form.sourceSubUrl !== '') {
this.setLocalStorageItem('sourceSubUrl', this.form.sourceSubUrl)
}
},
getLocalStorageItem(itemKey) {
const now = +new Date()
let ls = localStorage.getItem(itemKey)
let itemValue = ''
if (ls !== null) {
let data = JSON.parse(ls)
if (data.expire > now) {
itemValue = data.value
} else {
localStorage.removeItem(itemKey)
}
}
return itemValue
},
setLocalStorageItem(itemKey, itemValue) {
const ttl = process.env.VUE_APP_CACHE_TTL
const now = +new Date()
let data = {
setTime: now,
ttl: parseInt(ttl),
expire: now + ttl * 1000,
value: itemValue
}
localStorage.setItem(itemKey, JSON.stringify(data))
}
},
};
</script>