Skip to content

Commit

Permalink
feat: 支持 Surfboard(前端 > 2.14.27)
Browse files Browse the repository at this point in the history
  • Loading branch information
xream committed Dec 26, 2023
1 parent 36db057 commit bc5ae9a
Show file tree
Hide file tree
Showing 6 changed files with 217 additions and 27 deletions.
20 changes: 11 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
</div>

<p align="center" color="#6a737d">
Advanced Subscription Manager for QX, Loon, Surge, Stash and ShadowRocket.
Advanced Subscription Manager for QX, Loon, Surge, Stash and Shadowrocket.
</p>

[![Build](https://github.com/sub-store-org/Sub-Store/actions/workflows/main.yml/badge.svg)](https://github.com/sub-store-org/Sub-Store/actions/workflows/main.yml) ![GitHub](https://img.shields.io/github/license/sub-store-org/Sub-Store) ![GitHub issues](https://img.shields.io/github/issues/sub-store-org/Sub-Store) ![GitHub closed pull requests](https://img.shields.io/github/issues-pr-closed-raw/Peng-Ym/Sub-Store) ![Lines of code](https://img.shields.io/tokei/lines/github/sub-store-org/Sub-Store) ![Size](https://img.shields.io/github/languages/code-size/sub-store-org/Sub-Store)
Expand All @@ -31,23 +31,25 @@ Core functionalities:
- [x] SSD URI
- [x] V2RayN URI
- [x] Hysteria2 URI
- [x] QX (SS, SSR, VMess, Trojan, HTTP)
- [x] Loon (SS, SSR, VMess, Trojan, HTTP, WireGuard, VLESS, Hysteria2)
- [x] Surge (SS, VMess, Trojan, HTTP, TUIC, Snell, Hysteria2, SSR(external, only for macOS), WireGuard(Surge to Surge))
- [x] ShadowRocket (SS, SSR, VMess, Trojan, HTTP, Snell, VLESS, Hysteria2)
- [x] Clash.Meta (SS, SSR, VMess, Trojan, HTTP, Snell, VLESS, WireGuard, Hysteria, Hysteria2)
- [x] Stash (SS, SSR, VMess, Trojan, HTTP, Snell, VLESS, WireGuard, Hysteria)
- [x] Clash (SS, SSR, VMess, Trojan, HTTP, Snell, VLESS, WireGuard)
- [x] QX (SS, SSR, VMess, Trojan, HTTP, SOCKS5)
- [x] Loon (SS, SSR, VMess, Trojan, HTTP, SOCKS5, WireGuard, VLESS, Hysteria2)
- [x] Surge (SS, VMess, Trojan, HTTP, SOCKS5, TUIC, Snell, Hysteria2, SSR(external, only for macOS), WireGuard(Surge to Surge))
- [x] Surfboard (SS, VMess, Trojan, HTTP, SOCKS5, WireGuard(Surfboard to Surfboard))
- [x] Shadowrocket (SS, SSR, VMess, Trojan, HTTP, SOCKS5, Snell, VLESS, WireGuard, Hysteria, Hysteria2, TUIC)
- [x] Clash.Meta (SS, SSR, VMess, Trojan, HTTP, SOCKS5, Snell, VLESS, WireGuard, Hysteria, Hysteria2, TUIC)
- [x] Stash (SS, SSR, VMess, Trojan, HTTP, SOCKS5, Snell, VLESS, WireGuard, Hysteria, TUIC)
- [x] Clash (SS, SSR, VMess, Trojan, HTTP, SOCKS5, Snell, VLESS, WireGuard)

### Supported Target Platforms

- [x] QX
- [x] Loon
- [x] Surge
- [x] Surfboard
- [x] Stash
- [x] Clash.Meta
- [x] Clash
- [x] ShadowRocket
- [x] Shadowrocket
- [x] V2Ray
- [x] V2Ray URI
- [x] Plain JSON
Expand Down
2 changes: 1 addition & 1 deletion backend/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "sub-store",
"version": "2.14.133",
"version": "2.14.134",
"description": "Advanced Subscription Manager for QX, Loon, Surge, Stash and ShadowRocket.",
"main": "src/main.js",
"scripts": {
Expand Down
2 changes: 2 additions & 0 deletions backend/src/core/proxy-utils/producers/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import URI_Producer from './uri';
import V2Ray_Producer from './v2ray';
import QX_Producer from './qx';
import ShadowRocket_Producer from './shadowrocket';
import Surfboard_Producer from './surfboard';

function JSON_Producer() {
const type = 'ALL';
Expand All @@ -27,4 +28,5 @@ export default {
JSON: JSON_Producer(),
Stash: Stash_Producer(),
ShadowRocket: ShadowRocket_Producer(),
Surfboard: Surfboard_Producer(),
};
199 changes: 199 additions & 0 deletions backend/src/core/proxy-utils/producers/surfboard.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,199 @@
import { Result, isPresent } from './utils';
import { isNotBlank } from '@/utils';
// import $ from '@/core/app';

const targetPlatform = 'Surfboard';

export default function Surfboard_Producer() {
const produce = (proxy) => {
switch (proxy.type) {
case 'ss':
return shadowsocks(proxy);
case 'trojan':
return trojan(proxy);
case 'vmess':
return vmess(proxy);
case 'http':
return http(proxy);
case 'socks5':
return socks5(proxy);
case 'wireguard-surge':
return wireguard(proxy);
}
throw new Error(
`Platform ${targetPlatform} does not support proxy type: ${proxy.type}`,
);
};
return { produce };
}

function shadowsocks(proxy) {
const result = new Result(proxy);
result.append(`${proxy.name}=${proxy.type},${proxy.server},${proxy.port}`);
result.append(`,encrypt-method=${proxy.cipher}`);
result.appendIfPresent(`,password=${proxy.password}`, 'password');

// obfs
if (isPresent(proxy, 'plugin')) {
if (proxy.plugin === 'obfs') {
result.append(`,obfs=${proxy['plugin-opts'].mode}`);
result.appendIfPresent(
`,obfs-host=${proxy['plugin-opts'].host}`,
'plugin-opts.host',
);
result.appendIfPresent(
`,obfs-uri=${proxy['plugin-opts'].path}`,
'plugin-opts.path',
);
} else {
throw new Error(`plugin ${proxy.plugin} is not supported`);
}
}

// udp
result.appendIfPresent(`,udp-relay=${proxy.udp}`, 'udp');

return result.toString();
}

function trojan(proxy) {
const result = new Result(proxy);
result.append(`${proxy.name}=${proxy.type},${proxy.server},${proxy.port}`);
result.appendIfPresent(`,password=${proxy.password}`, 'password');

// transport
handleTransport(result, proxy);

// tls
result.appendIfPresent(`,tls=${proxy.tls}`, 'tls');

// tls verification
result.appendIfPresent(`,sni=${proxy.sni}`, 'sni');
result.appendIfPresent(
`,skip-cert-verify=${proxy['skip-cert-verify']}`,
'skip-cert-verify',
);

// tfo
result.appendIfPresent(`,tfo=${proxy.tfo}`, 'tfo');

// udp
result.appendIfPresent(`,udp-relay=${proxy.udp}`, 'udp');

return result.toString();
}

function vmess(proxy) {
const result = new Result(proxy);
result.append(`${proxy.name}=${proxy.type},${proxy.server},${proxy.port}`);
result.appendIfPresent(`,username=${proxy.uuid}`, 'uuid');

// transport
handleTransport(result, proxy);

// AEAD
if (isPresent(proxy, 'aead')) {
result.append(`,vmess-aead=${proxy.aead}`);
} else {
result.append(`,vmess-aead=${proxy.alterId === 0}`);
}

// tls
result.appendIfPresent(`,tls=${proxy.tls}`, 'tls');

// tls verification
result.appendIfPresent(`,sni=${proxy.sni}`, 'sni');
result.appendIfPresent(
`,skip-cert-verify=${proxy['skip-cert-verify']}`,
'skip-cert-verify',
);

// udp
result.appendIfPresent(`,udp-relay=${proxy.udp}`, 'udp');

return result.toString();
}

function http(proxy) {
const result = new Result(proxy);
const type = proxy.tls ? 'https' : 'http';
result.append(`${proxy.name}=${type},${proxy.server},${proxy.port}`);
result.appendIfPresent(`,${proxy.username}`, 'username');
result.appendIfPresent(`,${proxy.password}`, 'password');

// tls verification
result.appendIfPresent(`,sni=${proxy.sni}`, 'sni');
result.appendIfPresent(
`,skip-cert-verify=${proxy['skip-cert-verify']}`,
'skip-cert-verify',
);

// udp
result.appendIfPresent(`,udp-relay=${proxy.udp}`, 'udp');

return result.toString();
}

function socks5(proxy) {
const result = new Result(proxy);
const type = proxy.tls ? 'socks5-tls' : 'socks5';
result.append(`${proxy.name}=${type},${proxy.server},${proxy.port}`);
result.appendIfPresent(`,${proxy.username}`, 'username');
result.appendIfPresent(`,${proxy.password}`, 'password');

// tls verification
result.appendIfPresent(`,sni=${proxy.sni}`, 'sni');
result.appendIfPresent(
`,skip-cert-verify=${proxy['skip-cert-verify']}`,
'skip-cert-verify',
);

// udp
result.appendIfPresent(`,udp-relay=${proxy.udp}`, 'udp');

return result.toString();
}

function wireguard(proxy) {
const result = new Result(proxy);

result.append(`${proxy.name}=wireguard`);

result.appendIfPresent(
`,section-name=${proxy['section-name']}`,
'section-name',
);

return result.toString();
}

function handleTransport(result, proxy) {
if (isPresent(proxy, 'network')) {
if (proxy.network === 'ws') {
result.append(`,ws=true`);
if (isPresent(proxy, 'ws-opts')) {
result.appendIfPresent(
`,ws-path=${proxy['ws-opts'].path}`,
'ws-opts.path',
);
if (isPresent(proxy, 'ws-opts.headers')) {
const headers = proxy['ws-opts'].headers;
const value = Object.keys(headers)
.map((k) => {
let v = headers[k];
if (['Host'].includes(k)) {
v = `"${v}"`;
}
return `${k}:${v}`;
})
.join('|');
if (isNotBlank(value)) {
result.append(`,ws-headers=${value}`);
}
}
}
} else {
throw new Error(`network ${proxy.network} is unsupported`);
}
}
}
19 changes: 2 additions & 17 deletions backend/src/core/proxy-utils/producers/surgemac.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { Result } from './utils';
import Surge_Producer from './surge';

const targetPlatform = 'SurgeMac';
// const targetPlatform = 'SurgeMac';

const surge_Producer = Surge_Producer();

Expand All @@ -10,24 +10,9 @@ export default function SurgeMac_Producer() {
switch (proxy.type) {
case 'ssr':
return shadowsocksr(proxy);
case 'ss':
return surge_Producer.produce(proxy);
case 'trojan':
return surge_Producer.produce(proxy);
case 'vmess':
return surge_Producer.produce(proxy);
case 'http':
return surge_Producer.produce(proxy);
case 'socks5':
return surge_Producer.produce(proxy);
case 'snell':
return surge_Producer.produce(proxy);
case 'tuic':
default:
return surge_Producer.produce(proxy);
}
throw new Error(
`Platform ${targetPlatform} does not support proxy type: ${proxy.type}`,
);
};
return { produce };
}
Expand Down
2 changes: 2 additions & 0 deletions backend/src/utils/platform.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ export function getPlatformFromHeaders(headers) {
}
if (UA.indexOf('Quantumult%20X') !== -1) {
return 'QX';
} else if (UA.indexOf('Surfboard') !== -1) {
return 'Surfboard';
} else if (UA.indexOf('Surge Mac') !== -1) {
return 'SurgeMac';
} else if (UA.indexOf('Surge') !== -1) {
Expand Down

0 comments on commit bc5ae9a

Please sign in to comment.