diff --git a/docs/guide/custom-provider.md b/docs/guide/custom-provider.md index 2cb9b72a3..b82630865 100644 --- a/docs/guide/custom-provider.md +++ b/docs/guide/custom-provider.md @@ -117,6 +117,13 @@ module.exports = { - 如果你正在使用 [DlerCloud](https://dlercloud.com/auth/register?affid=45071),可以使用「通用」类型的订阅地址 ::: +### compatibleMode + +- 类型: `boolean` +- 默认值: `false` + +部分机场提供的订阅地址不符合标准,提供一个兼容模式进行解析。 + ## clash :::warning 注意 diff --git a/lib/provider/V2rayNSubscribeProvider.ts b/lib/provider/V2rayNSubscribeProvider.ts index 938d461f8..13208c872 100644 --- a/lib/provider/V2rayNSubscribeProvider.ts +++ b/lib/provider/V2rayNSubscribeProvider.ts @@ -11,10 +11,10 @@ import Provider from './Provider'; export default class V2rayNSubscribeProvider extends Provider { private readonly _url: string; - + private readonly _compatibleMode?: boolean; constructor(name: string, config: V2rayNSubscribeProviderConfig) { super(name, config); - + this._compatibleMode = config.compatibleMode; const schema = Joi.object({ url: Joi .string() @@ -46,15 +46,16 @@ export default class V2rayNSubscribeProvider extends Provider { } public getNodeList(): ReturnType { - return getV2rayNSubscription(this.url); + return getV2rayNSubscription(this.url, this._compatibleMode); } } export const getV2rayNSubscription = async ( url: string, + isCompatibleMode: boolean ): Promise> => { assert(url, '未指定订阅地址 url'); - + if (isCompatibleMode) { logger.warn('运行在兼容模式,请注意生成的节点是否正确。'); } async function requestConfigFromRemote(): Promise> { const response = ConfigCache.has(url) ? ConfigCache.get(url) : await (async () => { const res = await got.get(url, { @@ -74,10 +75,9 @@ export const getV2rayNSubscription = async ( const json = JSON.parse(fromBase64(item.replace('vmess://', ''))); // istanbul ignore next - if (!json.v || Number(json.v) !== 2) { - throw new Error(`该订阅 ${url} 可能不是一个有效的 V2rayN 订阅。请参考 http://bit.ly/2N4lZ8X 进行排查`); + if (!isCompatibleMode && (!json.v || Number(json.v) !== 2)) { + throw new Error(`该订阅 ${url} 可能不是一个有效的 V2rayN 订阅。请参考 http://bit.ly/2N4lZ8X 进行排查, 或者将解析模式改为兼容模式`); } - // istanbul ignore next if (['kcp', 'http'].indexOf(json.net) > -1) { logger.warn(`不支持读取 network 类型为 ${json.net} 的 Vmess 节点,节点 ${json.ps} 会被省略`); diff --git a/lib/provider/__tests__/V2rayNSubscribeProvider.test.ts b/lib/provider/__tests__/V2rayNSubscribeProvider.test.ts index 85b1c35ba..00a5d37bb 100644 --- a/lib/provider/__tests__/V2rayNSubscribeProvider.test.ts +++ b/lib/provider/__tests__/V2rayNSubscribeProvider.test.ts @@ -15,7 +15,7 @@ test('V2rayNSubscribeProvider', async t => { test('getV2rayNSubscription', async t => { const url = 'http://example.com/test-v2rayn-sub.txt'; - const configList = await getV2rayNSubscription(url); + const configList = await getV2rayNSubscription(url, false); t.deepEqual(configList[0], { alterId: '64', @@ -44,3 +44,22 @@ test('getV2rayNSubscription', async t => { uuid: '1386f85e-657b-4d6e-9d56-78badb75e1fd', }); }); + +test('getV2rayNSubscription compatible mode', async t => { + const url = 'http://example.com/test-v2rayn-sub-compatible.txt'; + const configList = await getV2rayNSubscription(url, true); + + t.deepEqual(configList[0], { + alterId: '64', + host: 'example.com', + hostname: '1.1.1.1', + method: 'auto', + network: 'ws', + nodeName: '测试 1', + path: '/', + port: 8080, + tls: false, + type: NodeTypeEnum.Vmess, + uuid: '1386f85e-657b-4d6e-9d56-78badb75e1fd', + }); +}); diff --git a/lib/types.ts b/lib/types.ts index 97050a9d5..3940fc4b8 100644 --- a/lib/types.ts +++ b/lib/types.ts @@ -123,6 +123,7 @@ export interface ShadowsocksrSubscribeProviderConfig extends ProviderConfig { export interface V2rayNSubscribeProviderConfig extends ProviderConfig { readonly url: string; + readonly compatibleMode?: boolean; } export interface ClashProviderConfig extends ProviderConfig { diff --git a/test/asset/test-v2rayn-sub-compatible.txt b/test/asset/test-v2rayn-sub-compatible.txt new file mode 100644 index 000000000..4dd00beff --- /dev/null +++ b/test/asset/test-v2rayn-sub-compatible.txt @@ -0,0 +1 @@ +dm1lc3M6Ly9leUp3Y3lJNkl1YTFpK2l2bFNBeElpd2lZV1JrSWpvaU1TNHhMakV1TVNJc0luQnZjblFpT2pnd09EQXNJbWxrSWpvaU1UTTRObVk0TldVdE5qVTNZaTAwWkRabExUbGtOVFl0TnpoaVlXUmlOelZsTVdaa0lpd2lZV2xrSWpvaU5qUWlMQ0p1WlhRaU9pSjNjeUlzSW5SNWNHVWlPaUp1YjI1bElpd2lhRzl6ZENJNkltVjRZVzF3YkdVdVkyOXRJaXdpY0dGMGFDSTZJbHd2SWl3aWJYVjRJanA3SW1WdVlXSnNaV1FpT2lKbVlXeHpaU0o5ZlE9PQ== \ No newline at end of file diff --git a/test/stub-axios.js b/test/stub-axios.js index 3757c55eb..bb1ffe4e6 100644 --- a/test/stub-axios.js +++ b/test/stub-axios.js @@ -33,6 +33,13 @@ const scope = nock('http://example.com') encoding: 'utf8', }) ) + .get(/\/test-v2rayn-sub-compatible\.txt/) + .reply( + 200, + fs.readFileSync(path.join(__dirname, 'asset/test-v2rayn-sub-compatible.txt'), { + encoding: 'utf8', + }) + ) .get(/\/netflix\.list/) .reply( 200,