shadowsocks-manager
Version:
A shadowsocks manager tool for multi user and traffic control.
274 lines (262 loc) • 8.71 kB
JavaScript
const log4js = require('log4js');
const moment = require('moment');
const logger = log4js.getLogger('autoban');
const knex = appRequire('init/knex').knex;
const config = appRequire('services/config').all();
const banConfig = config.plugins.webgui_autoban.data;
const queue = [];
const convertTimeString = str => {
if(!str.match) { return str; }
if(str.match(/^(\d{1,}.{0,1}\d{0,})[sS]$/)) {
return +str.match(/^(\d{1,}.{0,1}\d{0,})[sS]$/)[1] * 1000;
}
if(str.match(/^(\d{1,}.{0,1}\d{0,})[mM]$/)) {
return +str.match(/^(\d{1,}.{0,1}\d{0,})[mM]$/)[1] * 60 * 1000;
}
if(str.match(/^(\d{1,}.{0,1}\d{0,})[hH]$/)) {
return +str.match(/^(\d{1,}.{0,1}\d{0,})[hH]$/)[1] * 60 * 60 * 1000;
}
};
const convertFlowString = str => {
if(!str.match) { return str; }
if(str.match(/^(\d{1,}.{0,1}\d{0,})[kK]$/)) {
return +str.match(/^(\d{1,}.{0,1}\d{0,})[kK]$/)[1] * 1000;
}
if(str.match(/^(\d{1,}.{0,1}\d{0,})[mM]$/)) {
return +str.match(/^(\d{1,}.{0,1}\d{0,})[mM]$/)[1] * 1000 * 1000;
}
if(str.match(/^(\d{1,}.{0,1}\d{0,})[hH]$/)) {
return +str.match(/^(\d{1,}.{0,1}\d{0,})[gG]$/)[1] * 1000 * 1000 * 1000;
}
};
banConfig.forEach(f => {
const accountIds = [];
const serverIds = [];
f.accountId.split(',').forEach(ports => {
if(ports.indexOf('-') < 0) {
accountIds.push(+ports);
} else {
const start = +ports.split('-')[0];
const end = +ports.split('-')[1];
for(let i = start; i <= end; i++) {
accountIds.push(i);
}
}
});
f.serverId.split(',').forEach(ids => {
if(ids.indexOf('-') < 0) {
serverIds.push(+ids);
} else {
const start = +ids.split('-')[0];
const end = +ids.split('-')[1];
for(let i = start; i <= end; i++) {
serverIds.push(i);
}
}
});
const time = convertTimeString(f.time);
const flow = convertFlowString(f.flow);
const banTime = convertTimeString(f.banTime);
serverIds.forEach(serverId => {
accountIds.forEach(accountId => {
queue.push({
serverId, accountId, time, flow, banTime,
});
});
});
});
const ban = async (serverId, accountId, time) => {
logger.info('ban [' + serverId + '][' + accountId + ']');
await knex('account_flow').update({
status: 'ban',
nextCheckTime: Date.now(),
autobanTime: Date.now() + time,
}).where({
serverId, accountId,
});
};
const splitTime = async (start, end) => {
const time = {
day: [],
hour: [],
fiveMin: [],
origin: [],
};
const now = Date.now();
const getMinute = moment(now).get('minute');
const splitEnd = {
day: moment(now).hour(0).minute(0).second(0).millisecond(0).toDate().getTime(),
hour: moment(now).minute(0).second(0).millisecond(0).toDate().getTime(),
fiveMin: moment(now).minute(getMinute - getMinute%5).second(0).millisecond(0).toDate().getTime(),
};
const isDay = time => {
const hour = moment(time).get('hour');
const minute = moment(time).get('minute');
const second = moment(time).get('second');
const millisecond = moment(time).get('millisecond');
if(hour || minute || second || millisecond) {
return false;
}
return true;
};
const isHour = time => {
const minute = moment(time).get('minute');
const second = moment(time).get('second');
const millisecond = moment(time).get('millisecond');
if(minute || second || millisecond) {
return false;
}
return true;
};
const is5min = time => {
const minute = moment(time).get('minute');
const second = moment(time).get('second');
const millisecond = moment(time).get('millisecond');
if(minute%5 || second || millisecond) {
return false;
}
return true;
};
const next = (time, type) => {
if(type === 'day') {
return moment(time).add(1, 'days').hour(0).minute(0).second(0).millisecond(0).toDate().getTime();
}
if(type === 'hour') {
return moment(time).add(1, 'hours').minute(0).second(0).millisecond(0).toDate().getTime();
}
if(type === '5min') {
const getMinute = moment(time).get('minute');
return moment(time).minute(getMinute - getMinute%5).add(5, 'minutes').second(0).millisecond(0).toDate().getTime();
}
};
let timeStart = start;
let timeEnd = end;
let last;
while(timeStart < timeEnd) {
if(isDay(timeStart) && next(timeStart, 'day') <= splitEnd.day && next(timeStart, 'day') <= end) {
if(last === 'day' && time.day.length) {
const length = time.day.length;
time.day[length - 1] = [
time.day[length - 1][0],
next(timeStart, 'day')
];
} else {
time.day.push([timeStart, next(timeStart, 'day')]);
}
timeStart = next(timeStart, 'day');
last = 'day';
} else if(isHour(timeStart) && next(timeStart, 'hour') <= splitEnd.hour && next(timeStart, 'hour') <= end) {
if(last === 'hour' && time.hour.length) {
const length = time.hour.length;
time.hour[length - 1] = [
time.hour[length - 1][0],
next(timeStart, 'hour')
];
} else {
time.hour.push([timeStart, next(timeStart, 'hour')]);
}
timeStart = next(timeStart, 'hour');
last = 'hour';
} else if(is5min(timeStart) && next(timeStart, '5min') <= splitEnd.fiveMin && next(timeStart, '5min') <= end) {
if(last === '5min' && time.fiveMin.length) {
const length = time.fiveMin.length;
time.fiveMin[length - 1] = [
time.fiveMin[length - 1][0],
next(timeStart, '5min')
];
} else {
time.fiveMin.push([timeStart, next(timeStart, '5min')]);
}
timeStart = next(timeStart, '5min');
last = '5min';
} else if(next(timeStart, '5min') <= end && timeStart === start) {
time.origin.push([timeStart, next(timeStart, '5min')]);
timeStart = next(timeStart, '5min');
last = '5min';
} else {
time.origin.push([timeStart, timeEnd]);
timeStart = timeEnd;
last = 'origin';
}
}
return time;
};
const getFlowFromSplitTime = async (serverId, accountId, start, end) => {
const time = await splitTime(start, end);
const sum = [];
const getFlow = (tableName, startTime, endTime) => {
return knex(tableName)
.sum('flow as sumFlow')
.groupBy('id')
.select(['id'])
.where({
id: serverId,
accountId,
})
.whereBetween('time', [startTime, endTime - 1]).then(success => {
if(success[0]) { return success[0].sumFlow; }
return 0;
});
};
time.day.forEach(f => {
sum.push(getFlow('saveFlowDay', f[0], f[1]));
});
time.hour.forEach(f => {
sum.push(getFlow('saveFlowHour', f[0], f[1]));
});
time.fiveMin.forEach(f => {
sum.push(getFlow('saveFlow5min', f[0], f[1]));
});
time.origin.forEach(f => {
sum.push(getFlow('saveFlow', f[0], f[1]));
});
const result = (await Promise.all(sum)).reduce((a, b) => a + b);
return result;
};
const check = async opt => {
const start = Date.now();
const { serverId, accountId, time, flow, banTime } = opt;
const accountFlowData = await knex('account_flow').where({
serverId, accountId,
}).then(s => s[0]);
if(!accountFlowData) { return 'not exists'; }
let checkTime;
if(accountFlowData && accountFlowData.autobanTime >= Date.now() - time) {
checkTime = accountFlowData.autobanTime;
} else {
checkTime = Date.now() - time;
}
// const lastConnect = await knex('saveFlow5min')
// .select(['time'])
// .where({ id: serverId, accountId })
// .orderBy('time', 'desc')
// .limit(1).then(success => {
// if(success[0]) {
// return success[0].time;
// }
// return 0;
// });
// if(!lastConnect || lastConnect <= checkTime) { logger.info('no need to check'); return; }
const myFlow = await getFlowFromSplitTime(serverId, accountId, checkTime, Date.now());
if(myFlow >= flow) {
await ban(serverId, accountId, banTime);
}
};
let position = 0;
const promise = () => {
const speed = config.plugins.webgui_autoban.speed || 1000;
return check(queue[position]).then(success => {
position += 1;
if(queue.length <= position) { position = 0; }
if(success === 'not exists') {
return promise();
} else {
return new Promise((resolve, reject) => {
setTimeout(() => {
return promise(resolve, reject);
}, speed);
});
}
});
};
promise();