Skip to content

Commit

Permalink
Merge pull request #71 from geekdada/dev
Browse files Browse the repository at this point in the history
1.17.1
  • Loading branch information
geekdada authored Mar 23, 2020
2 parents 9cbc3d8 + a498285 commit e5597f5
Show file tree
Hide file tree
Showing 7 changed files with 260 additions and 173 deletions.
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

0 comments on commit e5597f5

Please sign in to comment.