Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

1.17.1 #71

Merged
merged 4 commits into from
Mar 23, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions lib/command/generate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,13 +30,15 @@ class GenerateCommand extends Command {

public async run(ctx): Promise<void> {
const config = loadConfig(ctx.cwd, ctx.argv.config, {
// istanbul ignore next
...(ctx.argv.output ? {
output: path.resolve(ctx.cwd, ctx.argv.output),
} : null)
});
await generate(config, ctx.argv.skipFail);
}

// istanbul ignore next
public get description(): string {
return '生成规则';
}
Expand Down
2 changes: 2 additions & 0 deletions lib/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ export class SurgioCommand extends Command {
constructor(rawArgv) {
super(rawArgv);

// istanbul ignore next
if (fs.existsSync(envPath)) {
env2(envPath);
}
Expand All @@ -42,6 +43,7 @@ export class SurgioCommand extends Command {

this.load(path.join(__dirname, './command'));

// istanbul ignore next
if (this.yargs.argv.verbose) {
transports.console.level = 'debug';
}
Expand Down
63 changes: 44 additions & 19 deletions lib/provider/ClashProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import {
ShadowsocksrNodeConfig,
SnellNodeConfig,
SubscriptionUserinfo,
TrojanNodeConfig,
VmessNodeConfig,
} from '../types';
import { lowercaseHeaderKeys } from '../utils';
Expand All @@ -22,7 +23,7 @@ import { SubsciptionCacheItem, SubscriptionCache } from '../utils/cache';
import { NETWORK_TIMEOUT } from '../utils/constant';
import Provider from './Provider';

type SupportConfigTypes = ShadowsocksNodeConfig|VmessNodeConfig|HttpsNodeConfig|HttpNodeConfig|ShadowsocksrNodeConfig|SnellNodeConfig;
type SupportConfigTypes = ShadowsocksNodeConfig|VmessNodeConfig|HttpsNodeConfig|HttpNodeConfig|ShadowsocksrNodeConfig|SnellNodeConfig|TrojanNodeConfig;

const logger = createLogger({
service: 'surgio:ClashProvider',
Expand Down Expand Up @@ -114,22 +115,36 @@ export const getClashSubscription = async (

try {
clashConfig = yaml.parse(response.body);

if (typeof clashConfig !== 'object' || !('Proxy' in clashConfig)) {
throw new Error();
}
} catch (err) {
} catch (err) /* istanbul ignore next */ {
throw new Error(`${url} 不是一个合法的 YAML 文件`);
}

const proxyList: any[] = clashConfig.Proxy;
if (
!_.isPlainObject(clashConfig) ||
(
!('Proxy' in clashConfig) &&
!('proxies' in clashConfig)
)
) {
throw new Error(`${url} 订阅内容有误,请检查后重试`);
}

const proxyList: any[] = clashConfig.Proxy || clashConfig.proxies;

// istanbul ignore next
if (!Array.isArray(proxyList)) {
throw new Error(`${url} 订阅内容有误,请检查后重试`);
}

const nodeList = proxyList.map<SupportConfigTypes>(item => {
return {
nodeList: parseClashConfig(proxyList, udpRelay),
subscriptionUserinfo: response.subscriptionUserinfo,
};
};

export const parseClashConfig = (proxyList: ReadonlyArray<any>, udpRelay?: boolean): ReadonlyArray<SupportConfigTypes> => {
return proxyList
.map<SupportConfigTypes>(item => {
switch (item.type) {
case 'ss': {
// istanbul ignore next
Expand Down Expand Up @@ -170,7 +185,7 @@ export const getClashSubscription = async (
skipCertVerify: item['plugin-opts']['skip-cert-verify'] === true,
} : null),
} : null),
};
} as ShadowsocksNodeConfig;
}

case 'vmess': {
Expand Down Expand Up @@ -201,7 +216,7 @@ export const getClashSubscription = async (
...(item.tls ? {
skipCertVerify: item['skip-cert-verify'] === true,
} : null),
};
} as VmessNodeConfig;
}

case 'http':
Expand All @@ -213,7 +228,7 @@ export const getClashSubscription = async (
port: item.port,
username: item.username /* istanbul ignore next */ || '',
password: item.password /* istanbul ignore next */ || '',
};
} as HttpNodeConfig;
}

return {
Expand All @@ -224,7 +239,7 @@ export const getClashSubscription = async (
username: item.username || '',
password: item.password || '',
skipCertVerify: item['skip-cert-verify'] === true,
};
} as HttpsNodeConfig;

case 'snell':
return {
Expand All @@ -234,7 +249,9 @@ export const getClashSubscription = async (
port: item.port,
psk: item.psk,
obfs: _.get(item, 'obfs-opts.mode', 'http'),
};
...(typeof item?.['obfs-opts']?.host !== 'undefined' ? { 'obfs-host': item['obfs-opts'].host } : null),
...('version' in item ? { version: item.version } : null),
} as SnellNodeConfig;

// istanbul ignore next
case 'ssr':
Expand All @@ -249,19 +266,27 @@ export const getClashSubscription = async (
protocol: item.protocol,
protoparam: item.protocolparam,
method: item.cipher,
};
} as ShadowsocksrNodeConfig;

case 'trojan':
return {
type: NodeTypeEnum.Trojan,
nodeName: item.name,
hostname: item.server,
port: item.port,
password: item.password,
...('skipCertVerify' in item ? { skipCertVerify: item.skipCertVerify } : null),
...('alpn' in item ? { alpn: item.alpn } : null),
...('sni' in item ? { sni: item.sni } : null),
...('udp' in item ? { 'udp-relay': item.udp } : null),
} as TrojanNodeConfig;

default:
logger.warn(`不支持从 Clash 订阅中读取 ${item.type} 的节点,节点 ${item.name} 会被省略`);
return null;
}
})
.filter(item => !!item);

return {
nodeList,
subscriptionUserinfo: response.subscriptionUserinfo,
};
};

function resolveUdpRelay(val?: boolean, defaultVal = false): boolean {
Expand Down
107 changes: 105 additions & 2 deletions lib/provider/__tests__/ClashProvider.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import test from 'ava';
import ClashProvider, { getClashSubscription } from '../ClashProvider';
import nock from 'nock';

import ClashProvider, { getClashSubscription, parseClashConfig } from '../ClashProvider';
import { NodeTypeEnum, SupportProviderEnum } from '../../types';

test('ClashProvider', async t => {
Expand All @@ -13,6 +15,21 @@ test('ClashProvider', async t => {
});
});

test('ClashProvider new format', async t => {
const scope = nock('http://local')
.get('/success-1')
.reply(200, `
proxies: []
`);

const provider = new ClashProvider('test', {
type: SupportProviderEnum.Clash,
url: 'http://local/success-1',
});

t.deepEqual(await provider.getNodeList(), []);
});

test('ClashProvider.getSubscriptionUserInfo', async t => {
const provider = new ClashProvider('test', {
type: SupportProviderEnum.Clash,
Expand Down Expand Up @@ -199,7 +216,93 @@ test('getClashSubscription udpRelay', async t => {
});

test('getClashSubscription - invalid yaml', async t => {
const scope = nock('http://local')
.get('/fail-1')
.reply(200, '')
.get('/fail-2')
.reply(200, `
foo: bar
`);

await t.throwsAsync(async () => {
await getClashSubscription('http://example.com/test-v2rayn-sub.txt');
}, {instanceOf: Error, message: 'http://example.com/test-v2rayn-sub.txt 不是一个合法的 YAML 文件'});
}, {instanceOf: Error, message: 'http://example.com/test-v2rayn-sub.txt 订阅内容有误,请检查后重试'});

await t.throwsAsync(async () => {
await getClashSubscription('http://local/fail-1');
}, {instanceOf: Error, message: 'http://local/fail-1 订阅内容有误,请检查后重试'});

await t.throwsAsync(async () => {
await getClashSubscription('http://local/fail-2');
}, {instanceOf: Error, message: 'http://local/fail-2 订阅内容有误,请检查后重试'});
});

test('snell Configurations', t => {
t.deepEqual(
parseClashConfig([{
type: 'snell',
name: 'snell',
server: 'server',
port: 44046,
psk: 'yourpsk',
'obfs-opts': {
mode: 'tls',
host: 'example.com'
},
version: '2',
}]),
[{
type: NodeTypeEnum.Snell,
nodeName: 'snell',
hostname: 'server',
port: 44046,
psk: 'yourpsk',
obfs: 'tls',
'obfs-host': 'example.com',
version: '2',
}]
);
});

test('trojan configurations', t => {
t.deepEqual(
parseClashConfig([{
type: 'trojan',
name: 'trojan',
server: 'example.com',
port: 443,
password: 'password1',
}]),
[{
type: NodeTypeEnum.Trojan,
nodeName: 'trojan',
hostname: 'example.com',
port: 443,
password: 'password1',
}]
);
t.deepEqual(
parseClashConfig([{
type: 'trojan',
name: 'trojan',
server: 'example.com',
port: 443,
password: 'password1',
skipCertVerify: true,
alpn: ['http/1.1'],
sni: 'sni.example.com',
udp: true,
}]),
[{
type: NodeTypeEnum.Trojan,
nodeName: 'trojan',
hostname: 'example.com',
port: 443,
password: 'password1',
skipCertVerify: true,
alpn: ['http/1.1'],
sni: 'sni.example.com',
'udp-relay': true,
}]
);
});
16 changes: 8 additions & 8 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,8 @@
"@types/nunjucks": "^3.1.3",
"@types/supertest": "^2.0.8",
"@types/yaml": "^1.2.0",
"@vuepress/plugin-google-analytics": "^1.3.1",
"ava": "^3.5.0",
"@vuepress/plugin-google-analytics": "^1.4.0",
"ava": "^3.5.1",
"codecov": "^3.5.0",
"coffee": "^5.2.2",
"conventional-changelog-cli": "^2.0.31",
Expand All @@ -47,21 +47,21 @@
"ini": "^1.3.5",
"jsdom": "^16.2.1",
"lint-staged": "^10.0.8",
"nock": "^12.0.2",
"nock": "^12.0.3",
"np": "^6.2.0",
"npm-debug-log-cleaner": "^1.0.3",
"npm-run-all": "^4.1.5",
"nyc": "^15.0.0",
"sinon": "^9.0.1",
"source-map-support": "^0.5.13",
"supertest": "^4.0.2",
"ts-node": "^8.5.4",
"ts-node": "^8.8.1",
"tslint": "^6.1.0",
"tslint-config-prettier": "^1.17.0",
"tslint-immutable": "^6.0.1",
"type-fest": "^0.12.0",
"typescript": "^3.8.3",
"vuepress": "^1.3.1",
"vuepress": "^1.4.0",
"vuepress-plugin-sitemap": "^2.3.1"
},
"scripts": {
Expand All @@ -87,7 +87,7 @@
"chalk": "^3.0.0",
"common-bin": "^2.8.3",
"cross-env": "^7.0.2",
"date-fns": "^2.10.0",
"date-fns": "^2.11.0",
"debug": "^4.1.1",
"emoji-regex": "^8.0.0",
"env2": "^2.2.2",
Expand All @@ -107,15 +107,15 @@
"lru-cache": "^5.1.1",
"merge-stream": "^2.0.0",
"node-dir": "^0.1.17",
"nunjucks": "^3.1.6",
"nunjucks": "^3.2.1",
"ora": "^4.0.2",
"query-string": "^6.11.1",
"rimraf": "^3.0.0",
"shelljs": "^0.8.3",
"update-notifier": "^4.1.0",
"urlsafe-base64": "^1.0.0",
"winston": "^3.2.1",
"yaml": "^1.8.2"
"yaml": "^1.8.3"
},
"ava": {
"failFast": true,
Expand Down
3 changes: 2 additions & 1 deletion tslint.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,8 @@
"no-mixed-interface": false,
"no-expression-statement": false,
"no-if-statement": false,
"variable-name": false
"variable-name": false,
"no-object-literal-type-assertion": false
/* end tslint-immutable rules */
}
}
Loading