diff options
| author | MHSanaei <ho3ein.sanaei@gmail.com> | 2023-05-06 19:51:14 +0300 |
|---|---|---|
| committer | MHSanaei <ho3ein.sanaei@gmail.com> | 2023-05-06 19:51:14 +0300 |
| commit | f22dd6b53d736556377080a305c40be2db3c8cce (patch) | |
| tree | caae9937645094ccf2513ac46a2bfa5ca446df3b /web | |
| parent | 735df6bd4ed18d656355067826a6f5e50495c63e (diff) | |
[feature] multi-user shadowsocks @alireza0
Co-Authored-By: Alireza Ahmadi <alireza7@gmail.com>
Diffstat (limited to 'web')
| -rw-r--r-- | web/assets/js/model/xray.js | 162 | ||||
| -rw-r--r-- | web/assets/js/util/utils.js | 6 | ||||
| -rw-r--r-- | web/html/xui/client_modal.html | 11 | ||||
| -rw-r--r-- | web/html/xui/form/client.html | 3 | ||||
| -rw-r--r-- | web/html/xui/form/protocol/shadowsocks.html | 83 | ||||
| -rw-r--r-- | web/html/xui/form/protocol/trojan.html | 6 | ||||
| -rw-r--r-- | web/html/xui/form/protocol/vless.html | 6 | ||||
| -rw-r--r-- | web/html/xui/form/protocol/vmess.html | 6 | ||||
| -rw-r--r-- | web/html/xui/inbound_modal.html | 1 | ||||
| -rw-r--r-- | web/html/xui/inbounds.html | 30 | ||||
| -rw-r--r-- | web/service/inbound.go | 5 | ||||
| -rw-r--r-- | web/service/sub.go | 24 |
12 files changed, 254 insertions, 89 deletions
diff --git a/web/assets/js/model/xray.js b/web/assets/js/model/xray.js index c8b02641..22573a23 100644 --- a/web/assets/js/model/xray.js +++ b/web/assets/js/model/xray.js @@ -1012,66 +1012,6 @@ class Inbound extends XrayCommonClass { return this.network === "http"; } - // VMess & VLess - get uuid() { - switch (this.protocol) { - case Protocols.VMESS: - return this.settings.vmesses[0].id; - case Protocols.VLESS: - return this.settings.vlesses[0].id; - default: - return ""; - } - } - - // VLess & Trojan - get flow() { - switch (this.protocol) { - case Protocols.VLESS: - return this.settings.vlesses[0].flow; - case Protocols.TROJAN: - return this.settings.trojans[0].flow; - default: - return ""; - } - } - - // VMess - get alterId() { - switch (this.protocol) { - case Protocols.VMESS: - return this.settings.vmesses[0].alterId; - default: - return ""; - } - } - - // Socks & HTTP - get username() { - switch (this.protocol) { - case Protocols.SOCKS: - case Protocols.HTTP: - return this.settings.accounts[0].user; - default: - return ""; - } - } - - // Trojan & Shadowsocks & Socks & HTTP - get password() { - switch (this.protocol) { - case Protocols.TROJAN: - return this.settings.trojans[0].password; - case Protocols.SHADOWSOCKS: - return this.settings.password; - case Protocols.SOCKS: - case Protocols.HTTP: - return this.settings.accounts[0].pass; - default: - return ""; - } - } - // Shadowsocks get method() { switch (this.protocol) { @@ -1146,9 +1086,13 @@ class Inbound extends XrayCommonClass { return this.settings.vlesses[index].expiryTime < new Date().getTime(); return false case Protocols.TROJAN: - if(this.settings.trojans[index].expiryTime > 0) - return this.settings.trojans[index].expiryTime < new Date().getTime(); - return false + if(this.settings.trojans[index].expiryTime > 0) + return this.settings.trojans[index].expiryTime < new Date().getTime(); + return false + case Protocols.SHADOWSOCKS: + if(this.settings.shadowsockses[index].expiryTime > 0) + return this.settings.shadowsockses[index].expiryTime < new Date().getTime(); + return false default: return false; } @@ -1159,7 +1103,6 @@ class Inbound extends XrayCommonClass { case Protocols.VMESS: case Protocols.VLESS: case Protocols.TROJAN: - case Protocols.SHADOWSOCKS: break; default: return false; @@ -1228,7 +1171,6 @@ class Inbound extends XrayCommonClass { case Protocols.VMESS: case Protocols.VLESS: case Protocols.TROJAN: - case Protocols.SHADOWSOCKS: return true; default: return false; @@ -1443,13 +1385,11 @@ class Inbound extends XrayCommonClass { return url.toString(); } - genSSLink(address='', remark='') { + genSSLink(address='', remark='', clientIndex = 0) { let settings = this.settings; - const server = this.stream.tls.server; - if (!ObjectUtil.isEmpty(server)) { - address = server; - } - return 'ss://' + safeBase64(settings.method + ':' + settings.password) + `@${address}:${this.port}#${encodeURIComponent(remark)}`; + const port = this.port; + + return 'ss://' + safeBase64(settings.method + ':' + settings.password + ':' +settings.shadowsockses[clientIndex].password) + '@' + address + ':' + this.port + '#' + encodeURIComponent(remark); } genTrojanLink(address = '', remark = '', clientIndex = 0) { @@ -1569,7 +1509,11 @@ class Inbound extends XrayCommonClass { remark += '-' + this.settings.vlesses[clientIndex].email } return this.genVLESSLink(address, remark, clientIndex); - case Protocols.SHADOWSOCKS: return this.genSSLink(address, remark); + case Protocols.SHADOWSOCKS: + if (this.settings.shadowsockses[clientIndex].email != ""){ + remark = this.settings.shadowsockses[clientIndex].email + } + return this.genSSLink(address, remark, clientIndex); case Protocols.TROJAN: if (this.settings.trojans[clientIndex].email != ""){ remark += '-' + this.settings.trojans[clientIndex].email @@ -2033,13 +1977,15 @@ Inbound.TrojanSettings.Fallback = class extends XrayCommonClass { Inbound.ShadowsocksSettings = class extends Inbound.Settings { constructor(protocol, method=SSMethods.BLAKE3_AES_256_GCM, - password=RandomUtil.randomSeq(44), - network='tcp,udp' + password=RandomUtil.randomShadowsocksPassword(), + network='tcp,udp', + shadowsockses=[new Inbound.ShadowsocksSettings.Shadowsocks()] ) { super(protocol); this.method = method; this.password = password; this.network = network; + this.shadowsockses = shadowsockses; } static fromJson(json={}) { @@ -2048,6 +1994,7 @@ Inbound.ShadowsocksSettings = class extends Inbound.Settings { json.method, json.password, json.network, + json.clients.map(client => Inbound.ShadowsocksSettings.Shadowsocks.fromJson(client)), ); } @@ -2056,10 +2003,77 @@ Inbound.ShadowsocksSettings = class extends Inbound.Settings { method: this.method, password: this.password, network: this.network, + clients: Inbound.ShadowsocksSettings.toJsonArray(this.shadowsockses) }; } }; +Inbound.ShadowsocksSettings.Shadowsocks = class extends XrayCommonClass { + constructor(password=RandomUtil.randomShadowsocksPassword(), email=RandomUtil.randomText(),limitIp=0, totalGB=0, expiryTime=0, enable=true, tgId='', subId='') { + super(); + this.password = password; + this.email = email; + this.limitIp = limitIp; + this.totalGB = totalGB; + this.expiryTime = expiryTime; + this.enable = enable; + this.tgId = tgId; + this.subId = subId; + } + + toJson() { + return { + password: this.password, + email: this.email, + limitIp: this.limitIp, + totalGB: this.totalGB, + expiryTime: this.expiryTime, + enable: this.enable, + tgId: this.tgId, + subId: this.subId, + }; + } + + static fromJson(json = {}) { + return new Inbound.ShadowsocksSettings.Shadowsocks( + json.password, + json.email, + json.limitIp, + json.totalGB, + json.expiryTime, + json.enable, + json.tgId, + json.subId, + ); + } + + get _expiryTime() { + if (this.expiryTime === 0 || this.expiryTime === "") { + return null; + } + if (this.expiryTime < 0){ + return this.expiryTime / -86400000; + } + return moment(this.expiryTime); + } + + set _expiryTime(t) { + if (t == null || t === "") { + this.expiryTime = 0; + } else { + this.expiryTime = t.valueOf(); + } + } + get _totalGB() { + return toFixed(this.totalGB / ONE_GB, 2); + } + + set _totalGB(gb) { + this.totalGB = toFixed(gb * ONE_GB, 0); + } + +}; + Inbound.DokodemoSettings = class extends Inbound.Settings { constructor(protocol, address, port, network='tcp,udp', followRedirect=false) { super(protocol); diff --git a/web/assets/js/util/utils.js b/web/assets/js/util/utils.js index 451f63e9..9c441537 100644 --- a/web/assets/js/util/utils.js +++ b/web/assets/js/util/utils.js @@ -165,6 +165,12 @@ class RandomUtil { str += this.randomShortIdSeq(8) return str; } + + static randomShadowsocksPassword(){ + let array = new Uint8Array(32); + window.crypto.getRandomValues(array); + return btoa(String.fromCharCode.apply(null, array)); + } } class ObjectUtil { diff --git a/web/html/xui/client_modal.html b/web/html/xui/client_modal.html index bf4ed92a..e459b520 100644 --- a/web/html/xui/client_modal.html +++ b/web/html/xui/client_modal.html @@ -44,7 +44,7 @@ if (this.clients[index].expiryTime < 0){ this.delayedStart = true; } - this.oldClientId = this.dbInbound.protocol == "trojan" ? this.clients[index].password : this.clients[index].id; + this.oldClientId = this.getClientId(dbInbound.protocol,clients[index]); } else { this.addClient(this.inbound.protocol, this.clients); } @@ -56,14 +56,23 @@ case Protocols.VMESS: return clientSettings.vmesses; case Protocols.VLESS: return clientSettings.vlesses; case Protocols.TROJAN: return clientSettings.trojans; + case Protocols.SHADOWSOCKS: return clientSettings.shadowsockses; default: return null; } }, + getClientId(protocol, client) { + switch(protocol){ + case Protocols.TROJAN: return client.password; + case Protocols.SHADOWSOCKS: return client.email; + default: return client.id; + } + }, addClient(protocol, clients) { switch (protocol) { case Protocols.VMESS: return clients.push(new Inbound.VmessSettings.Vmess()); case Protocols.VLESS: return clients.push(new Inbound.VLESSSettings.VLESS()); case Protocols.TROJAN: return clients.push(new Inbound.TrojanSettings.Trojan()); + case Protocols.SHADOWSOCKS: return clients.push(new Inbound.ShadowsocksSettings.Shadowsocks()); default: return null; } }, diff --git a/web/html/xui/form/client.html b/web/html/xui/form/client.html index 501861db..1740e74b 100644 --- a/web/html/xui/form/client.html +++ b/web/html/xui/form/client.html @@ -18,7 +18,8 @@ <a-form-item label='{{ i18n "pages.inbounds.enable" }}'> <a-switch v-model="client.enable"></a-switch> </a-form-item> - <a-form-item label="Password" v-if="inbound.protocol === Protocols.TROJAN"> + <a-form-item label="Password" v-if="inbound.protocol === Protocols.TROJAN || inbound.protocol === Protocols.SHADOWSOCKS"> + <a-icon v-if="inbound.protocol === Protocols.SHADOWSOCKS" @click="client.password = RandomUtil.randomShadowsocksPassword()" type="sync"> </a-icon> <a-input v-model.trim="client.password" style="width: 150px;" ></a-input> </a-form-item> <a-form-item label='{{ i18n "additional" }} ID' v-if="inbound.protocol === Protocols.VMESS"> diff --git a/web/html/xui/form/protocol/shadowsocks.html b/web/html/xui/form/protocol/shadowsocks.html index 718ba894..3c63fe01 100644 --- a/web/html/xui/form/protocol/shadowsocks.html +++ b/web/html/xui/form/protocol/shadowsocks.html @@ -1,5 +1,87 @@ {{define "form/shadowsocks"}} <a-form layout="inline"> + <a-collapse activeKey="0" v-for="(client, index) in inbound.settings.shadowsockses.slice(0,1)" v-if="!isEdit"> + <a-collapse-panel header='{{ i18n "pages.inbounds.client" }}'> + <a-form-item> + <span slot="label"> + <span>{{ i18n "pages.inbounds.Email" }}</span> + <a-tooltip> + <template slot="title"> + <span>{{ i18n "pages.inbounds.EmailDesc" }}</span> + </template> + <a-icon @click="getNewEmail(client)" type="sync"></a-icon> + </a-tooltip> + </span> + <a-input v-model.trim="client.email" style="width: 150px;"></a-input> + </a-form-item> + <a-form-item label="Password"> + <a-icon @click="client.password = RandomUtil.randomShadowsocksPassword()" type="sync"> </a-icon> + <a-input v-model.trim="client.password" style="width: 150px;"></a-input> + </a-form-item> + <a-form-item label="Subscription" v-if="client.email"> + <a-input v-model.trim="client.subId"></a-input> + </a-form-item> + <a-form-item label="Telegram Username" v-if="client.email"> + <a-input v-model.trim="client.tgId"></a-input> + </a-form-item> + <a-form-item> + <span slot="label"> + <span>{{ i18n "pages.inbounds.IPLimit" }}</span> + <a-tooltip> + <template slot="title"> + <span>{{ i18n "pages.inbounds.IPLimitDesc" }}</span> + </template> + <a-icon type="question-circle" theme="filled"></a-icon> + </a-tooltip> + </span> + <a-input-number v-model="client.limitIp" min="0" style="width: 70px;"></a-input-number> + </a-form-item> + <a-form-item> + <span slot="label"> + <span >{{ i18n "pages.inbounds.totalFlow" }}</span> (GB) + <a-tooltip> + <template slot="title"> + 0 <span>{{ i18n "pages.inbounds.meansNoLimit" }}</span> + </template> + <a-icon type="question-circle" theme="filled"></a-icon> + </a-tooltip> + </span> + <a-input-number v-model="client._totalGB" :min="0"></a-input-number> + </a-form-item> + <a-form-item label='{{ i18n "pages.client.delayedStart" }}'> + <a-switch v-model="delayedStart" @click="client._expiryTime=0"></a-switch> + </a-form-item> + <a-form-item label='{{ i18n "pages.client.expireDays" }}'> + <a-input-number v-model.number="delayedExpireDays" :min="0"></a-input-number> + </a-form-item> + <a-form-item> + <span slot="label"> + <span >{{ i18n "pages.inbounds.expireDate" }}</span> + <a-tooltip> + <template slot="title"> + <span>{{ i18n "pages.inbounds.leaveBlankToNeverExpire" }}</span> + </template> + <a-icon type="question-circle" theme="filled"></a-icon> + </a-tooltip> + </span> + <a-date-picker :show-time="{ format: 'HH:mm:ss' }" format="YYYY-MM-DD HH:mm:ss" + :dropdown-class-name="siderDrawer.isDarkTheme ? 'ant-card-dark' : ''" + v-model="client._expiryTime" style="width: 170px;"></a-date-picker> + </a-form-item> + </a-collapse-panel> + </a-collapse> + <a-collapse v-else> + <a-collapse-panel :header="'{{ i18n "pages.client.clientCount"}} : ' + inbound.settings.shadowsockses.length"> + <table width="100%"> + <tr class="client-table-header"> + <th v-for="col in Object.keys(inbound.settings.shadowsockses[0]).slice(0, 3)">[[ col ]]</th> + </tr> + <tr v-for="(client, index) in inbound.settings.shadowsockses" :class="index % 2 == 1 ? 'client-table-odd-row' : ''"> + <td v-for="col in Object.values(client).slice(0, 3)">[[ col ]]</td> + </tr> + </table> + </a-collapse-panel> + </a-collapse> <a-form-item label='{{ i18n "encryption" }}'> <a-select v-model="inbound.settings.method" style="width: 250px;" :dropdown-class-name="siderDrawer.isDarkTheme ? 'ant-card-dark' : ''"> <a-select-option v-for="method in SSMethods" :value="method">[[ method ]]</a-select-option> @@ -15,5 +97,4 @@ <a-select-option value="udp">UDP</a-select-option> </a-select> </a-form-item> -</a-form> {{end}}
\ No newline at end of file diff --git a/web/html/xui/form/protocol/trojan.html b/web/html/xui/form/protocol/trojan.html index e63943e6..1581b07c 100644 --- a/web/html/xui/form/protocol/trojan.html +++ b/web/html/xui/form/protocol/trojan.html @@ -53,6 +53,12 @@ </span> <a-input-number v-model="client._totalGB" :min="0"></a-input-number> </a-form-item> + <a-form-item label='{{ i18n "pages.client.delayedStart" }}'> + <a-switch v-model="delayedStart" @click="client._expiryTime=0"></a-switch> + </a-form-item> + <a-form-item label='{{ i18n "pages.client.expireDays" }}'> + <a-input-number v-model.number="delayedExpireDays" :min="0"></a-input-number> + </a-form-item> <a-form-item> <span slot="label"> <span >{{ i18n "pages.inbounds.expireDate" }}</span> diff --git a/web/html/xui/form/protocol/vless.html b/web/html/xui/form/protocol/vless.html index 33e0c170..023c04b9 100644 --- a/web/html/xui/form/protocol/vless.html +++ b/web/html/xui/form/protocol/vless.html @@ -59,6 +59,12 @@ </span> <a-input-number v-model="client._totalGB" :min="0"></a-input-number> </a-form-item> + <a-form-item label='{{ i18n "pages.client.delayedStart" }}'> + <a-switch v-model="delayedStart" @click="client._expiryTime=0"></a-switch> + </a-form-item> + <a-form-item label='{{ i18n "pages.client.expireDays" }}'> + <a-input-number v-model.number="delayedExpireDays" :min="0"></a-input-number> + </a-form-item> <a-form-item> <span slot="label"> <span >{{ i18n "pages.inbounds.expireDate" }}</span> diff --git a/web/html/xui/form/protocol/vmess.html b/web/html/xui/form/protocol/vmess.html index 6471e20d..62b80468 100644 --- a/web/html/xui/form/protocol/vmess.html +++ b/web/html/xui/form/protocol/vmess.html @@ -50,6 +50,12 @@ </span> <a-input-number v-model="client._totalGB" :min="0"></a-input-number> </a-form-item> + <a-form-item label='{{ i18n "pages.client.delayedStart" }}'> + <a-switch v-model="delayedStart" @click="client._expiryTime=0"></a-switch> + </a-form-item> + <a-form-item label='{{ i18n "pages.client.expireDays" }}'> + <a-input-number v-model.number="delayedExpireDays" :min="0"></a-input-number> + </a-form-item> <a-form-item> <span slot="label"> <span >{{ i18n "pages.inbounds.expireDate" }}</span> diff --git a/web/html/xui/inbound_modal.html b/web/html/xui/inbound_modal.html index 966de8dd..7c338e26 100644 --- a/web/html/xui/inbound_modal.html +++ b/web/html/xui/inbound_modal.html @@ -48,6 +48,7 @@ case Protocols.VMESS: return clientSettings.vmesses; case Protocols.VLESS: return clientSettings.vlesses; case Protocols.TROJAN: return clientSettings.trojans; + case Protocols.SHADOWSOCKS: return clientSettings.shadowsockses; default: return null; } }, diff --git a/web/html/xui/inbounds.html b/web/html/xui/inbounds.html index 2792eb75..5d7e041f 100644 --- a/web/html/xui/inbounds.html +++ b/web/html/xui/inbounds.html @@ -116,15 +116,11 @@ <a-dropdown :trigger="['click']"> <a @click="e => e.preventDefault()">{{ i18n "pages.inbounds.operate" }}</a> <a-menu slot="overlay" @click="a => clickAction(a, dbInbound)" :theme="siderDrawer.theme"> - <a-menu-item v-if="dbInbound.isSS" key="qrcode"> - <a-icon type="qrcode"></a-icon> - {{ i18n "qrCode" }} - </a-menu-item> <a-menu-item key="edit"> <a-icon type="edit"></a-icon> {{ i18n "edit" }} </a-menu-item> - <template v-if="dbInbound.isTrojan || dbInbound.isVLess || dbInbound.isVMess"> + <template v-if="dbInbound.isTrojan || dbInbound.isVLess || dbInbound.isVMess || dbInbound.isSS"> <a-menu-item key="addClient"> <a-icon type="user-add"></a-icon> {{ i18n "pages.client.add"}} @@ -168,7 +164,7 @@ </template> <template slot="protocol" slot-scope="text, dbInbound"> <a-tag style="margin:0;" color="blue">[[ dbInbound.protocol ]]</a-tag> - <template v-if="dbInbound.isVMess || dbInbound.isVLess || dbInbound.isTrojan || dbInbound.isSS"> + <template v-if="dbInbound.isVMess || dbInbound.isVLess || dbInbound.isTrojan"> <a-tag style="margin:0;" color="green">[[ dbInbound.toInbound().stream.network ]]</a-tag> <a-tag style="margin:0;" v-if="dbInbound.toInbound().stream.isTls" color="cyan">TLS</a-tag> <a-tag style="margin:0;" v-if="dbInbound.toInbound().stream.isXtls" color="cyan">XTLS</a-tag> @@ -231,7 +227,7 @@ {{template "client_table"}} </a-table> <a-table - v-else-if="record.protocol === Protocols.TROJAN" + v-else-if="record.protocol === Protocols.TROJAN || record.protocol === Protocols.SHADOWSOCKS" :row-key="client => client.id" :columns="innerTrojanColumns" :data-source="getInboundClients(record)" @@ -671,7 +667,7 @@ }, delClient(dbInboundId,client) { dbInbound = this.dbInbounds.find(row => row.id === dbInboundId); - clientId = dbInbound.protocol == "trojan" ? client.password : client.id; + clientId = this.getClientId(dbInbound.protocol,client); this.$confirm({ title: '{{ i18n "pages.inbounds.deleteInbound"}}', content: '{{ i18n "pages.inbounds.deleteInboundContent"}}', @@ -686,9 +682,17 @@ case Protocols.VMESS: return clientSettings.vmesses; case Protocols.VLESS: return clientSettings.vlesses; case Protocols.TROJAN: return clientSettings.trojans; + case Protocols.SHADOWSOCKS: return clientSettings.shadowsockses; default: return null; } }, + getClientId(protocol, client) { + switch(protocol){ + case Protocols.TROJAN: return client.password; + case Protocols.SHADOWSOCKS: return client.email; + default: return client.id; + } + }, showQrcode(dbInbound, clientIndex) { const link = dbInbound.genLink(clientIndex); qrModal.show('{{ i18n "qrCode"}}', link, dbInbound); @@ -707,7 +711,7 @@ clients = this.getClients(dbInbound.protocol, inbound.settings); index = this.findIndexOfClient(clients, client); clients[index].enable = !clients[index].enable; - clientId = dbInbound.protocol == "trojan" ? clients[index].password : clients[index].id; + clientId = this.getClientId(dbInbound.protocol,clients[index]); await this.updateClient(clients[index],dbInboundId, clientId); this.loading(false); }, @@ -719,11 +723,13 @@ }, getInboundClients(dbInbound) { if(dbInbound.protocol == Protocols.VLESS) { - return dbInbound.toInbound().settings.vlesses + return dbInbound.toInbound().settings.vlesses; } else if(dbInbound.protocol == Protocols.VMESS) { - return dbInbound.toInbound().settings.vmesses + return dbInbound.toInbound().settings.vmesses; } else if(dbInbound.protocol == Protocols.TROJAN) { - return dbInbound.toInbound().settings.trojans + return dbInbound.toInbound().settings.trojans; + } else if(dbInbound.protocol == Protocols.SHADOWSOCKS) { + return dbInbound.toInbound().settings.shadowsockses; } }, resetClientTraffic(client,dbInboundId) { diff --git a/web/service/inbound.go b/web/service/inbound.go index b14f7b4f..7e838b5f 100644 --- a/web/service/inbound.go +++ b/web/service/inbound.go @@ -332,6 +332,9 @@ func (s *InboundService) DelInboundClient(inboundId int, clientId string) error if oldInbound.Protocol == "trojan" { client_key = "password" } + if oldInbound.Protocol == "shadowsocks" { + client_key = "email" + } inerfaceClients := settings["clients"].([]interface{}) var newClients []interface{} @@ -398,6 +401,8 @@ func (s *InboundService) UpdateInboundClient(data *model.Inbound, clientId strin oldClientId := "" if oldInbound.Protocol == "trojan" { oldClientId = oldClient.Password + } else if oldInbound.Protocol == "shadowsocks" { + oldClientId = oldClient.Email } else { oldClientId = oldClient.ID } diff --git a/web/service/sub.go b/web/service/sub.go index 69cafda1..3f8b7685 100644 --- a/web/service/sub.go +++ b/web/service/sub.go @@ -97,6 +97,8 @@ func (s *SubService) getLink(inbound *model.Inbound, email string) string { return s.genVlessLink(inbound, email) case "trojan": return s.genTrojanLink(inbound, email) + case "shadowsocks": + return s.genShadowsocksLink(inbound, email) } return "" } @@ -565,6 +567,28 @@ func (s *SubService) genTrojanLink(inbound *model.Inbound, email string) string return url.String() } +func (s *SubService) genShadowsocksLink(inbound *model.Inbound, email string) string { + address := s.address + if inbound.Protocol != model.Shadowsocks { + return "" + } + clients, _ := s.inboundService.getClients(inbound) + + var settings map[string]interface{} + 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 + } + } + encPart := fmt.Sprintf("%s:%s:%s", method, inboundPassword, clients[clientIndex].Password) + return fmt.Sprintf("ss://%s@%s:%d#%s", base64.StdEncoding.EncodeToString([]byte(encPart)), address, inbound.Port, clients[clientIndex].Email) +} + func searchKey(data interface{}, key string) (interface{}, bool) { switch val := data.(type) { case map[string]interface{}: |
