Skip to content

Commit

Permalink
feat: 增加查询流量命令
Browse files Browse the repository at this point in the history
  • Loading branch information
geekdada committed Feb 25, 2020
1 parent 1d08b43 commit a94eeab
Show file tree
Hide file tree
Showing 13 changed files with 219 additions and 35 deletions.
7 changes: 5 additions & 2 deletions lib/command/check.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,12 @@ class CheckCommand extends Command {
super(rawArgv);
this.usage = '使用方法: surgio check [provider]';
this.options = {
config: {
alias: 'c',
c: {
alias: 'config',
demandOption: false,
describe: 'Surgio 配置文件',
default: './surgio.conf.js',
type: 'string',
},
};
}
Expand Down
8 changes: 4 additions & 4 deletions lib/command/generate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,13 @@ class GenerateCommand extends Command {
super(rawArgv);
this.usage = '使用方法: surgio generate';
this.options = {
output: {
o: {
type: 'string',
alias: 'o',
alias: 'output',
description: '生成规则的目录',
},
config: {
alias: 'c',
c: {
alias: 'config',
default: './surgio.conf.js',
},
};
Expand Down
113 changes: 113 additions & 0 deletions lib/command/subscriptions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
// istanbul ignore file
import Command from 'common-bin';
import { promises as fsp } from 'fs';
import { basename, join } from 'path';
import { createLogger } from '@surgio/logger';
import BlackSSLProvider from '../provider/BlackSSLProvider';
import ClashProvider from '../provider/ClashProvider';
import CustomProvider from '../provider/CustomProvider';
import ShadowsocksJsonSubscribeProvider from '../provider/ShadowsocksJsonSubscribeProvider';
import ShadowsocksrSubscribeProvider from '../provider/ShadowsocksrSubscribeProvider';
import ShadowsocksSubscribeProvider from '../provider/ShadowsocksSubscribeProvider';
import V2rayNSubscribeProvider from '../provider/V2rayNSubscribeProvider';

import { CommandConfig } from '../types';
import {
loadConfig
} from '../utils/config';
import getProvider from '../utils/get-provider';
import { errorHandler } from '../utils/error-helper';
import { formatSubscriptionUserInfo } from '../utils/subscription';

const logger = createLogger({
service: 'surgio:SubscriptionsCommand',
});
type PossibleProviderType = BlackSSLProvider & ShadowsocksJsonSubscribeProvider & ShadowsocksSubscribeProvider & CustomProvider & V2rayNSubscribeProvider & ShadowsocksrSubscribeProvider & ClashProvider;

class SubscriptionsCommand extends Command {
private options: object;
private config: CommandConfig;

constructor(rawArgv) {
super(rawArgv);
this.usage = '使用方法: surgio subscriptions';
this.options = {
c: {
alias: 'config',
demandOption: false,
describe: 'Surgio 配置文件',
default: './surgio.conf.js',
type: 'string',
},
};
}

public async run(ctx): Promise<void> {
this.config = loadConfig(ctx.cwd, ctx.argv.config);

const providerList = await this.listProviders();

for (const provider of providerList) {
if (provider.getSubscriptionUserInfo) {
const userInfo = await provider.getSubscriptionUserInfo();

if (userInfo) {
const format = formatSubscriptionUserInfo(userInfo);
console.log('🤟 %s 已用流量:%s 剩余流量:%s 有效期至:%s', provider.name, format.used, format.left, format.expire);
} else {
console.log('⚠️ 无法查询 %s 的流量信息', provider.name);
}
} else {
console.log('⚠️ 无法查询 %s 的流量信息', provider.name);
}
}
}

public get description(): string {
return '查询订阅流量';
}

public errorHandler(err): void {
errorHandler.call(this, err);
}

private async listProviders(): Promise<ReadonlyArray<PossibleProviderType>> {
const files = await fsp.readdir(this.config.providerDir, {
encoding: 'utf8',
});
const providerList: PossibleProviderType[] = [];

async function readProvider(path): Promise<PossibleProviderType> {
let provider;

try {
const providerName = basename(path, '.js');

logger.debug('read %s %s', providerName, path);
provider = getProvider(providerName, require(path));
} catch (err) {
logger.debug(`${path} 不是一个合法的模块`);
return undefined;
}

if (!provider?.type) {
logger.debug(`${path} 不是一个 Provider`);
return undefined;
}

logger.debug('got provider %j', provider);
return provider;
}

for (const file of files) {
const result = await readProvider(join(this.config.providerDir, file));
if (result) {
providerList.push(result);
}
}

return providerList;
}
}

export = SubscriptionsCommand;
11 changes: 7 additions & 4 deletions lib/command/upload.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,14 +18,17 @@ class GenerateCommand extends Command {
this.usage = '使用方法: surgio upload';
this.spinner = ora();
this.options = {
output: {
o: {
type: 'string',
alias: 'o',
alias: 'output',
description: '生成规则的目录',
},
config: {
alias: 'c',
c: {
alias: 'config',
demandOption: false,
describe: 'Surgio 配置文件',
default: './surgio.conf.js',
type: 'string',
},
};
}
Expand Down
13 changes: 12 additions & 1 deletion lib/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import fs from 'fs';
import env2 from 'env2';
import path from 'path';
import updateNotifier from 'update-notifier';
import { transports } from '@surgio/logger';

import GenerateCommand from './command/generate';
import UploadCommand from './command/upload';
Expand All @@ -30,8 +31,18 @@ export class SurgioCommand extends Command {
updateNotifier({ pkg: require('../package.json') }).notify();

this.usage = '使用方法: surgio <command> [options]';
this.yargs.option('V', {
alias: 'verbose',
demandOption: false,
describe: '打印调试日志',
type: 'boolean',
});

this.load(path.join(__dirname, './command'));
this.yargs.alias('v', 'version');

if (this.yargs.argv.verbose) {
transports.console.level = 'debug';
}
}

public errorHandler(err): void {
Expand Down
4 changes: 2 additions & 2 deletions lib/provider/ClashProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,8 @@ import {
SnellNodeConfig, SubscriptionUserinfo,
VmessNodeConfig,
} from '../types';
import { parseSubscriptionUserInfo } from '../utils';
import { ConfigCache, SubsciptionCacheItem, SubscriptionCache } from '../utils/cache';
import { parseSubscriptionUserInfo } from '../utils/subscription';
import { SubsciptionCacheItem, SubscriptionCache } from '../utils/cache';
import { NETWORK_TIMEOUT } from '../utils/constant';
import Provider from './Provider';

Expand Down
3 changes: 2 additions & 1 deletion lib/provider/ShadowsocksSubscribeProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@ import {
ShadowsocksSubscribeProviderConfig,
SubscriptionUserinfo,
} from '../types';
import { decodeStringList, fromBase64, fromUrlSafeBase64, parseSubscriptionUserInfo } from '../utils';
import { decodeStringList, fromBase64, fromUrlSafeBase64 } from '../utils';
import { parseSubscriptionUserInfo } from '../utils/subscription';
import { SubsciptionCacheItem, SubscriptionCache } from '../utils/cache';
import { NETWORK_TIMEOUT } from '../utils/constant';
import Provider from './Provider';
Expand Down
12 changes: 11 additions & 1 deletion lib/provider/ShadowsocksrSubscribeProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@ import assert from 'assert';
import got from 'got';

import { ShadowsocksrNodeConfig, ShadowsocksrSubscribeProviderConfig, SubscriptionUserinfo } from '../types';
import { fromBase64, parseSubscriptionUserInfo } from '../utils';
import { fromBase64 } from '../utils';
import { parseSubscriptionUserInfo } from '../utils/subscription';
import { SubsciptionCacheItem, SubscriptionCache } from '../utils/cache';
import { NETWORK_TIMEOUT } from '../utils/constant';
import { parseSSRUri } from '../utils/ssr';
Expand Down Expand Up @@ -40,6 +41,15 @@ export default class ShadowsocksrSubscribeProvider extends Provider {
this.udpRelay = config.udpRelay;
}

public async getSubscriptionUserInfo(): Promise<SubscriptionUserinfo> {
const { subscriptionUserinfo } = await getShadowsocksrSubscription(this.url, this.udpRelay);

if (subscriptionUserinfo) {
return subscriptionUserinfo;
}
return null;
}

public async getNodeList(): Promise<ReadonlyArray<ShadowsocksrNodeConfig>> {
const { nodeList } = await getShadowsocksrSubscription(this.url, this.udpRelay);

Expand Down
7 changes: 7 additions & 0 deletions lib/types.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,11 @@
import BlackSSLProvider from './provider/BlackSSLProvider';
import ClashProvider from './provider/ClashProvider';
import CustomProvider from './provider/CustomProvider';
import Provider from './provider/Provider';
import ShadowsocksJsonSubscribeProvider from './provider/ShadowsocksJsonSubscribeProvider';
import ShadowsocksrSubscribeProvider from './provider/ShadowsocksrSubscribeProvider';
import ShadowsocksSubscribeProvider from './provider/ShadowsocksSubscribeProvider';
import V2rayNSubscribeProvider from './provider/V2rayNSubscribeProvider';

export enum NodeTypeEnum {
HTTPS = 'https',
Expand Down
20 changes: 0 additions & 20 deletions lib/utils/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1232,23 +1232,3 @@ export const applyFilter = <T extends SimpleNodeConfig>(

return nodes;
};

export const parseSubscriptionUserInfo = (str: string): SubscriptionUserinfo => {
const res = {
upload: 0,
download: 0,
total: 0,
expire: 0,
};

str.split(';').forEach(item => {
const pair = item.split('=');
const value = Number(pair[1].trim());

if (!Number.isNaN(value)) {
res[pair[0].trim()] = Number(pair[1].trim())
}
});

return res;
};
44 changes: 44 additions & 0 deletions lib/utils/subscription.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import filesize from 'filesize';
import { format, formatDistanceToNow } from 'date-fns';

import { SubscriptionUserinfo } from '../types';

export const parseSubscriptionUserInfo = (str: string): SubscriptionUserinfo => {
const res = {
upload: 0,
download: 0,
total: 0,
expire: 0,
};

str.split(';').forEach(item => {
const pair = item.split('=');
const value = Number(pair[1].trim());

if (!Number.isNaN(value)) {
res[pair[0].trim()] = Number(pair[1].trim())
}
});

return res;
};

export const formatSubscriptionUserInfo = (userInfo: SubscriptionUserinfo): {
readonly upload: string;
readonly download: string;
readonly used: string;
readonly left: string;
readonly total: string;
readonly expire: string;
} => {
return {
upload: filesize(userInfo.upload),
download: filesize(userInfo.download),
used: filesize(userInfo.upload + userInfo.download),
left: filesize(userInfo.total - userInfo.upload - userInfo.download),
total: filesize(userInfo.total),
expire: userInfo.expire
? `${format(Date.now() + userInfo.expire, 'yyyy-MM-dd')} (${formatDistanceToNow(Date.now() + userInfo.expire)})`
: '无数据',
};
};
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -83,10 +83,12 @@
"chalk": "^3.0.0",
"common-bin": "^2.8.3",
"cross-env": "^7.0.0",
"date-fns": "^2.10.0",
"debug": "^4.1.1",
"emoji-regex": "^8.0.0",
"env2": "^2.2.2",
"execa": "^4.0.0",
"filesize": "^6.1.0",
"fs-extra": "^8.1.0",
"get-port": "^5.1.0",
"global-agent": "^2.1.7",
Expand Down
10 changes: 10 additions & 0 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -3810,6 +3810,11 @@ date-fns@^1.27.2:
resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-1.30.1.tgz#2e71bf0b119153dbb4cc4e88d9ea5acfb50dc05c"
integrity sha512-hBSVCvSmWC+QypYObzwGOd9wqdDpOt+0wl0KbU+R+uuZBS1jN8VsD1ss3irQDknRj5NvxiTF6oj/nDRnN/UQNw==

date-fns@^2.10.0:
version "2.10.0"
resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-2.10.0.tgz#abd10604d8bafb0bcbd2ba2e9b0563b922ae4b6b"
integrity sha512-EhfEKevYGWhWlZbNeplfhIU/+N+x0iCIx7VzKlXma2EdQyznVlZhCptXUY+BegNpPW2kjdx15Rvq503YcXXrcA==

date-time@^2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/date-time/-/date-time-2.1.0.tgz#0286d1b4c769633b3ca13e1e62558d2dbdc2eba2"
Expand Down Expand Up @@ -4926,6 +4931,11 @@ file-uri-to-path@1, file-uri-to-path@1.0.0:
resolved "https://registry.yarnpkg.com/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz#553a7b8446ff6f684359c445f1e37a05dacc33dd"
integrity sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==

filesize@^6.1.0:
version "6.1.0"
resolved "https://registry.yarnpkg.com/filesize/-/filesize-6.1.0.tgz#e81bdaa780e2451d714d71c0d7a4f3238d37ad00"
integrity sha512-LpCHtPQ3sFx67z+uh2HnSyWSLLu5Jxo21795uRDuar/EOuYWXib5EmPaGIBuSnRqH2IODiKA2k5re/K9OnN/Yg==

fill-range@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-4.0.0.tgz#d544811d428f98eb06a63dc402d2403c328c38f7"
Expand Down

0 comments on commit a94eeab

Please sign in to comment.