shadowsocks-manager
Version:
A shadowsocks manager tool for multi user and traffic control.
372 lines (341 loc) • 12.4 kB
JavaScript
const log4js = require('log4js');
const logger = log4js.getLogger('account');
const knex = appRequire('init/knex').knex;
const flow = appRequire('plugins/flowSaver/flow');
const manager = appRequire('services/manager');
const config = appRequire('services/config').all();
const sleepTime = 100;
const accountFlow = appRequire('plugins/account/accountFlow');
const sleep = time => {
return new Promise((resolve, reject) => {
setTimeout(() => resolve(), time);
});
};
const randomInt = max => {
return Math.ceil(Math.random() * max % max);
};
const modifyAccountFlow = async (serverId, accountId, time) => {
await knex('account_flow').update({
checkTime: Date.now(),
nextCheckTime: Date.now() + time,
}).where({ serverId, accountId });
};
const isPortExists = async (server, account) => {
const ports = (await manager.send({ command: 'list' }, {
host: server.host,
port: server.port,
password: server.password,
})).map(m => m.port);
if(ports.indexOf(server.shift + account.port) >= 0) {
return true;
} else {
return false;
}
};
const isAccountActive = (server, account) => {
return !!account.active;
};
const hasServer = (server, account) => {
if(!account.server) { return true; }
const serverList = JSON.parse(account.server);
if(serverList.indexOf(server.id) >= 0) { return true; }
return false;
};
const isExpired = (server, account) => {
if(account.type >= 2 && account.type <= 5) {
let timePeriod = 0;
if(account.type === 2) { timePeriod = 7 * 86400 * 1000; }
if(account.type === 3) { timePeriod = 30 * 86400 * 1000; }
if(account.type === 4) { timePeriod = 1 * 86400 * 1000; }
if(account.type === 5) { timePeriod = 3600 * 1000; }
const data = JSON.parse(account.data);
const expireTime = data.create + data.limit * timePeriod;
account.expireTime = expireTime;
if(expireTime <= Date.now() || data.create >= Date.now()) {
const nextCheckTime = 10 * 60 * 1000 + randomInt(30000);
if(account.active && account.autoRemove && expireTime + account.autoRemoveDelay < Date.now()) {
modifyAccountFlow(server.id, account.id, nextCheckTime > account.autoRemoveDelay ? account.autoRemoveDelay : nextCheckTime);
knex('account_plugin').delete().where({ id: account.id }).then();
} else {
modifyAccountFlow(server.id, account.id, nextCheckTime);
}
return true;
} else {
return false;
}
} else {
return false;
}
};
const isBaned = async (server, account) => {
const accountFlowInfo = await knex('account_flow').where({
serverId: server.id,
accountId: account.id,
status: 'ban',
}).then(s => s[0]);
if(!accountFlowInfo) { return false; }
if(!accountFlowInfo.autobanTime || Date.now() > accountFlowInfo.autobanTime) {
await knex('account_flow').update({ status: 'checked' }).where({ id: accountFlowInfo.id });
return false;
}
await knex('account_flow').update({ nextCheckTime: accountFlowInfo.autobanTime }).where({ id: accountFlowInfo.id });
return true;
};
const isOverFlow = async (server, account) => {
let realFlow = 0;
const writeFlow = async (serverId, accountId, flow, time) => {
const exists = await knex('account_flow').where({ serverId, accountId }).then(s => s[0]);
if(exists) {
await knex('account_flow').update({
flow,
checkTime: Date.now(),
nextCheckTime: Date.now() + Math.ceil(time),
}).where({ id: exists.id });
}
};
if(account.type >= 2 && account.type <= 5) {
let timePeriod = 0;
if(account.type === 2) { timePeriod = 7 * 86400 * 1000; }
if(account.type === 3) { timePeriod = 30 * 86400 * 1000; }
if(account.type === 4) { timePeriod = 1 * 86400 * 1000; }
if(account.type === 5) { timePeriod = 3600 * 1000; }
const data = JSON.parse(account.data);
let startTime = data.create;
while(startTime + timePeriod <= Date.now()) {
startTime += timePeriod;
}
const endTime = Date.now();
const isMultiServerFlow = !!account.multiServerFlow;
let servers = [];
if(isMultiServerFlow) {
servers = await knex('server').where({});
} else {
servers = await knex('server').where({ id: server.id });
}
const flows = await flow.getFlowFromSplitTimeWithScale(servers.map(m => m.id), account.id, startTime, endTime);
const serverObj = {};
servers.forEach(server => {
serverObj[server.id] = server;
});
flows.forEach(flo => {
flo.forEach(f => {
if(serverObj[f.id]) {
if(!serverObj[f.id].flow) {
serverObj[f.id].flow = f.sumFlow;
} else {
serverObj[f.id].flow += f.sumFlow;
}
}
});
});
let sumFlow = 0;
for(const s in serverObj) {
const flow = serverObj[s].flow || 0;
if(+s === server.id) { realFlow = flow; }
sumFlow += Math.ceil(flow * serverObj[s].scale);
}
const flowPacks = await knex('webgui_flow_pack').where({ accountId: account.id }).whereBetween('createTime', [startTime, endTime]);
const flowWithFlowPacks = flowPacks.reduce((a, b) => {
return { flow: a.flow + b.flow };
}, { flow: data.flow }).flow;
let nextCheckTime = (flowWithFlowPacks - sumFlow) / 200000000 * 60 * 1000 / server.scale;
if(nextCheckTime >= account.expireTime - Date.now() && account.expireTime - Date.now() > 0) { nextCheckTime = account.expireTime - Date.now(); }
if(nextCheckTime <= 0) { nextCheckTime = 600 * 1000; }
if(nextCheckTime >= 3 * 60 * 60 * 1000) { nextCheckTime = 3 * 60 * 60 * 1000; }
await writeFlow(server.id, account.id, realFlow, nextCheckTime);
return sumFlow >= flowWithFlowPacks;
} else {
await writeFlow(server.id, account.id, 0, 30 * 60 * 1000 + Number(Math.random().toString().substr(2, 7)));
return false;
}
};
const deletePort = (server, account) => {
// console.log(`del ${ server.name } ${ account.port }`);
const portNumber = server.shift + account.port;
manager.send({
command: 'del',
port: portNumber,
}, {
host: server.host,
port: server.port,
password: server.password,
}).catch();
};
const addPort = (server, account) => {
// console.log(`add ${ server.name } ${ account.port }`);
const portNumber = server.shift + account.port;
manager.send({
command: 'add',
port: portNumber,
password: account.password,
}, {
host: server.host,
port: server.port,
password: server.password,
}).catch();
};
const deleteExtraPorts = async serverInfo => {
try {
const currentPorts = await manager.send({ command: 'list' }, {
host: serverInfo.host,
port: serverInfo.port,
password: serverInfo.password,
});
const accounts = await knex('account_plugin').where({});
const accountObj = {};
accounts.forEach(account => {
accountObj[account.port] = account;
});
for(const p of currentPorts) {
if(accountObj[p.port - serverInfo.shift]) { continue; }
await sleep(sleepTime);
deletePort(serverInfo, { port: p.port - serverInfo.shift });
}
} catch(err) {
console.log(err);
}
};
const checkAccount = async (serverId, accountId) => {
try {
const serverInfo = await knex('server').where({ id: serverId }).then(s => s[0]);
if(!serverInfo) {
await knex('account_flow').delete().where({ serverId });
return;
}
const accountInfo = await knex('account_plugin').where({ id: accountId }).then(s => s[0]);
if(!accountInfo) {
await knex('account_flow').delete().where({ serverId, accountId });
return;
}
// 检查当前端口是否存在
const exists = await isPortExists(serverInfo, accountInfo);
// 检查账号是否激活
if(!isAccountActive(serverInfo, accountInfo)) {
exists && deletePort(serverInfo, accountInfo);
return;
}
// 检查账号是否包含该服务器
if(!hasServer(serverInfo, accountInfo)) {
await modifyAccountFlow(serverInfo.id, accountInfo.id, 20 * 60 * 1000 + randomInt(30000));
exists && deletePort(serverInfo, accountInfo);
return;
}
// 检查账号是否过期
if(isExpired(serverInfo, accountInfo)) {
exists && deletePort(serverInfo, accountInfo);
return;
}
// 检查账号是否被ban
if(await isBaned(serverInfo, accountInfo)) {
exists && deletePort(serverInfo, accountInfo);
return;
}
// 检查账号是否超流量
if(await isOverFlow(serverInfo, accountInfo)) {
exists && deletePort(serverInfo, accountInfo);
return;
}
!exists && addPort(serverInfo, accountInfo);
} catch(err) {
console.log(err);
}
};
(async () => {
let time = 67;
while(true) {
const start = Date.now();
try {
await sleep(sleepTime);
const servers = await knex('server').where({});
for(const server of servers) {
await sleep(1000);
await deleteExtraPorts(server);
}
await sleep(sleepTime);
const accounts = await knex('account_plugin').select([
'account_plugin.id as id'
]).crossJoin('server')
.leftJoin('account_flow', function () {
this
.on('account_flow.serverId', 'server.id')
.on('account_flow.accountId', 'account_plugin.id');
}).whereNull('account_flow.id');
for(const account of accounts) {
await sleep(sleepTime);
await accountFlow.add(account.id);
}
const end = Date.now();
if(end - start <= time * 1000) {
await sleep(time * 1000 - (end - start));
}
if(time <= 300) { time += 10; }
} catch(err) {
console.log(err);
const end = Date.now();
if(end - start <= time * 1000) {
await sleep(time * 1000 - (end - start));
}
}
}
})();
(async () => {
while(true) {
const start = Date.now();
let accounts = [];
try {
const datas = await knex('account_flow').select()
.where('nextCheckTime', '<', Date.now())
.orderBy('nextCheckTime', 'asc').limit(600);
accounts = [...accounts, ...datas];
if(datas.length < 30) {
accounts = [...accounts, ...(await knex('account_flow').select()
.where('nextCheckTime', '>', Date.now())
.orderBy('nextCheckTime', 'asc').limit(30 - datas.length))];
}
} catch(err) { console.log(err); }
try {
const datas = await knex('account_flow').select()
.orderBy('updateTime', 'desc').where('checkTime', '<', Date.now() - 60000).limit(15);
accounts = [...accounts, ...datas];
} catch(err) { console.log(err); }
try {
datas = await knex('account_flow').select()
.orderByRaw('rand()').limit(5);
accounts = [...accounts, ...datas];
} catch(err) { }
try {
datas = await knex('account_flow').select()
.orderByRaw('random()').limit(5);
accounts = [...accounts, ...datas];
} catch(err) { }
try {
if(accounts.length <= 120) {
for(const account of accounts) {
const start = Date.now();
await checkAccount(account.serverId, account.accountId).catch();
const time = 60 * 1000 / accounts.length - (Date.now() - start);
await sleep((time <= 0 || time > sleepTime) ? sleepTime : time);
}
} else {
await Promise.all(accounts.map((account, index) => {
return sleep(index * (60 + Math.ceil(accounts.length % 10)) * 1000 / accounts.length).then(() => {
return checkAccount(account.serverId, account.accountId);
});
}));
}
if(accounts.length) {
logger.info(`check ${ accounts.length } accounts, ${ Date.now() - start } ms`);
if(accounts.length < 30) {
await sleep((30 - accounts.length) * 1000);
}
} else {
await sleep(30 * 1000);
}
} catch (err) {
const end = Date.now();
if(end - start <= 60 * 1000) {
await sleep(60 * 1000 - (end - start));
}
}
}
})();