Files
meanTorrent/modules/announce/server/controllers/announces.server.controller.js
2018-06-26 20:37:48 +08:00

1481 lines
46 KiB
JavaScript

'use strict';
/**
* Module dependencies
*/
var path = require('path'),
config = require(path.resolve('./config/config')),
common = require(path.resolve('./config/lib/common')),
dataLog = require(path.resolve('./config/lib/data-log')),
mongoose = require('mongoose'),
User = mongoose.model('User'),
Torrent = mongoose.model('Torrent'),
Peer = mongoose.model('Peer'),
Complete = mongoose.model('Complete'),
Finished = mongoose.model('Finished'),
bcode = require(path.resolve('./config/lib/bencode')),
benc = require('bncode'),
ipRegex = require('ip-regex'),
moment = require('moment'),
async = require('async'),
sprintf = require('sprintf-js').sprintf,
traceLogCreate = require(path.resolve('./config/lib/tracelog')).create,
scoreUpdate = require(path.resolve('./config/lib/score')).update;
var traceConfig = config.meanTorrentConfig.trace;
var scoreConfig = config.meanTorrentConfig.score;
var hnrConfig = config.meanTorrentConfig.hitAndRun;
var announceConfig = config.meanTorrentConfig.announce;
var globalSalesConfig = config.meanTorrentConfig.torrentGlobalSales;
var appConfig = config.meanTorrentConfig.app;
var mtDebug = require(path.resolve('./config/lib/debug'));
const FAILURE_REASONS = {
100: 'Invalid request type: client request was not a HTTP GET',
101: 'Missing info_hash',
102: 'Missing peer_id',
103: 'Missing port',
104: 'Missing passkey',
150: 'Invalid infohash: infohash is not 20 bytes long',
151: 'Invalid peerid: peerid is not 20 bytes long',
152: 'Invalid numwant. Client requested more peers than allowed by tracker',
153: 'Passkey length error (length=32)',
154: 'Invalid passkey, if you changed you passkey, please redownload the torrent file from ' + appConfig.domain,
160: 'Invalid torrent info_hash',
161: 'No torrent with that info_hash has been found',
170: 'your account status is banned',
171: 'your account status is inactive',
172: 'your client is not allowed, here is the blacklist: ' + appConfig.domain + announceConfig.clientBlackListUrl,
173: 'this torrent status is not reviewed by administrators, try again later',
174: 'this torrent is only for VIP members',
175: 'your account status is idle',
176: 'you can not seeding an un-download finished torrent',
180: 'You can not open more than 1 downloading processes on the same torrent',
181: 'You can not open more than 3 seeding processes on the same torrent',
182: 'save peer failed',
183: 'save torrent failed',
184: 'save passkeyuser failed',
185: 'get H&R completeTorrent failed',
186: 'create H&R completeTorrent failed',
187: 'Illegal completed event',
190: 'You have more H&R warning, can not download any torrent now!',
191: 'not find this torrent H&R complete data',
200: 'Your total ratio is less than %.2f, can not download anything',
600: 'This tracker only supports compact mode',
900: 'Generic error'
};
const EVENT_NONE = 0;
const EVENT_COMPLETED = 1;
const EVENT_STARTED = 2;
const EVENT_STOPPED = 3;
const WANT_DEFAULT = 50;
const PEERSTATE_SEEDER = 'seeder';
const PEERSTATE_LEECHER = 'leecher';
const PEER_COMPACT_SIZE = 6;
const ANNOUNCE_INTERVAL = Math.floor(announceConfig.announceInterval / 1000);
const PARAMS_INTEGER = [
'port', 'uploaded', 'downloaded', 'left', 'compact', 'numwant', 'no_peer_id'
];
const PARAMS_STRING = [
'event',
'ipv4',
'ipv6'
];
var isGlobalSaleValid = false;
/**
* event
* @param e
* @returns {number}
*/
function event(e) {
switch (e) {
case 'completed':
return EVENT_COMPLETED;
case 'started':
return EVENT_STARTED;
case 'stopped':
return EVENT_STOPPED;
}
return EVENT_NONE;
}
/**
* eventString
* @param e
* @returns {number}
*/
function eventString(e) {
switch (e) {
case 'completed':
return 'EVENT_COMPLETED';
case 'started':
return 'EVENT_STARTED';
case 'stopped':
return 'EVENT_STOPPED';
}
return 'EVENT_NONE';
}
/**
* Failure
* @param code
* @param reason
* @constructor
*/
function Failure(code, reason) {
this.code = code;
this.reason = reason;
if (reason === undefined && typeof FAILURE_REASONS[this.code] !== 'undefined')
this.reason = FAILURE_REASONS[this.code];
else if (this.code == null)
this.code = 900;
}
/**
* Failure.prototype
* @type {{bencode: Function}}
*/
Failure.prototype = {
bencode: function () {
return 'd14:failure reason' + this.reason.length + ':' + this.reason + '12:failure codei' + this.code + 'ee';
}
};
/**
* info api
* @param req
* @param res
*/
exports.announce = function (req, res) {
req.torrent = undefined;
req.currentPeer = undefined;
req.completeTorrent = undefined;
req.selfpeer = [];
req.seeder = false;
mtDebug.debugGreen('\n\n', 'ANNOUNCE', true, req.passkeyuser);
mtDebug.debugGreen('================================================================================', 'ANNOUNCE', true, req.passkeyuser);
mtDebug.debugGreen(' ANNOUNCE REQUEST ', 'ANNOUNCE', true, req.passkeyuser);
mtDebug.debugGreen('================================================================================', 'ANNOUNCE', true, req.passkeyuser);
mtDebug.debugBlue(req.url, 'ANNOUNCE', true, req.passkeyuser);
var s = req.url.split('?');
var query = common.querystringParse(s[1]);
var passkey = req.params.passkey || query.passkey || undefined;
async.waterfall([
/*---------------------------------------------------------------
validateQueryCheck
---------------------------------------------------------------*/
function (done) {
var i = 0;
var p;
if (req.method !== 'GET') {
done(100);
} else if (typeof query.info_hash === 'undefined') {
done(101);
} else if (typeof query.peer_id === 'undefined') {
done(102);
} else if (typeof query.port === 'undefined') {
done(103);
} else if (query.info_hash.length !== 20) {
done(150);
} else if (query.peer_id.length !== 20) {
done(151);
} else if (typeof query.compact === 'undefined' || query.compact !== '1') {
query.compact = 0;
} else {
for (i = 0; i < PARAMS_INTEGER.length; i++) {
p = PARAMS_INTEGER[i];
if (typeof query[p] !== 'undefined') {
query[p] = parseInt(query[p].toString(), 10);
}
}
for (i = 0; i < PARAMS_STRING.length; i++) {
p = PARAMS_STRING[i];
if (typeof query[p] !== 'undefined') {
query[p] = query[p].toString();
}
}
if (query.ipv4 === undefined) {
query.ipv4 = '';
}
if (query.ipv6 === undefined) {
query.ipv6 = '';
}
if (query.ipv6.toLowerCase().startsWith('fe80')) {
query.ipv6 = '';
}
//write ip v4 v6
query.ip = req.cf_ip;
if (ipRegex.v6({exact: true}).test(query.ip)) {
query.ipv6 = query.ip;
} else {
query.ipv4 = query.ip;
}
query.info_hash = common.binaryToHex(query.info_hash);
req.seeder = (query.left === 0) ? true : false;
// console.log(query);
done(null);
}
},
/*---------------------------------------------------------------
validatePasskeyCheck
---------------------------------------------------------------*/
function (done) {
if (typeof passkey === 'undefined') {
done(104);
} else if (passkey.length !== 32) {
done(153);
} else if (req.passkeyuser === undefined) {
done(154);
} else {
done(null);
}
},
/*---------------------------------------------------------------
validateUserCheck
check normal, banned, idle, inactive
---------------------------------------------------------------*/
function (done) {
switch (req.passkeyuser.status) {
case 'banned':
done(170);
break;
case 'idle':
done(175);
break;
case 'inactive':
done(171);
break;
default:
done(null);
}
},
/*---------------------------------------------------------------
validateClientCheck
check client blacklist
---------------------------------------------------------------*/
function (done) {
var ua = req.get('User-Agent');
var inlist = false;
if (ua) {
config.meanTorrentConfig.clientBlackList.forEach(function (client) {
if (ua.toUpperCase().indexOf(client.name.toUpperCase()) >= 0) {
inlist = true;
}
});
}
if (inlist) {
done(172);
} else {
done(null);
}
},
/*---------------------------------------------------------------
getTorrentItemData
torrent data include peers
---------------------------------------------------------------*/
function (done) {
mtDebug.debugRed('req.passkeyuser._id = ' + req.passkeyuser._id.toString(), 'ANNOUNCE', true, req.passkeyuser);
mtDebug.debugRed('req.torrent.info_hash = ' + query.info_hash, 'ANNOUNCE', true, req.passkeyuser);
Torrent.findOne({
info_hash: query.info_hash
})
.populate('user')
.populate({
path: '_peers'
})
.exec(function (err, t) {
if (err) {
done(160);
} else if (!t) {
done(161);
} else if (t.torrent_status === 'new' && !req.seeder) {
done(173);
} else {
req.torrent = t;
done(null);
}
});
},
/*---------------------------------------------------------------
refresh user`s vip status and ratio
update torrent isSaling status
---------------------------------------------------------------*/
function (done) {
req.passkeyuser.globalUpdateMethod(function (u) {
req.passkeyuser = u;
if (req.torrent.isSaling) {
req.torrent.globalUpdateMethod(function (t) {
req.torrent = t;
done(null);
});
} else {
done(null);
}
});
},
/*---------------------------------------------------------------
check torrent_vip and user_vip status
---------------------------------------------------------------*/
function (done) {
if (!req.seeder && req.torrent.torrent_vip && !req.passkeyuser.isVip) {
done(174);
} else {
done(null);
}
},
/*---------------------------------------------------------------
check whether can seeding
if torrent is not exist in user`s finished list
---------------------------------------------------------------*/
function (done) {
mtDebug.debugGreen('---------------' + eventString(query.event) + '----------------', 'ANNOUNCE', true, req.passkeyuser);
if (req.seeder && event(query.event) !== EVENT_COMPLETED && event(query.event) !== EVENT_STOPPED) {
if (announceConfig.seedingInFinishedCheck) {
if (!req.passkeyuser._id.equals(req.torrent.user._id)) {
Finished.findOne({
user: req.passkeyuser._id,
torrent: req.torrent._id
}).exec(function (err, fini) {
if (!fini) {
done(176);
} else {
done(null);
}
});
} else {
done(null);
}
} else {
done(null);
}
} else {
done(null);
}
},
/*---------------------------------------------------------------
find complete torrent data
if not find and torrent is h&r and user isn`t vip, then create complete record
---------------------------------------------------------------*/
function (done) {
if (hnrConfig.enable && req.torrent.torrent_hnr && !req.passkeyuser.isVip) {
Complete.findOne({
torrent: req.torrent._id,
user: req.passkeyuser._id
})
.populate('user')
.populate('torrent')
.exec(function (err, t) {
if (err) {
done(185);
} else {
if (!t) {
var comp = new Complete();
comp.torrent = req.torrent;
comp.user = req.passkeyuser;
comp.complete = req.seeder ? true : false;
comp.save(function (err) {
if (err) {
done(186);
} else {
req.completeTorrent = comp;
done(null);
}
});
} else {
req.completeTorrent = t;
done(null);
}
}
});
} else {
done(null);
}
},
/*---------------------------------------------------------------
some time, when user close download client directly, maybe some ghost peer stay in peers table and not in torrent._peers
delete them on start event of any user
----------------------------------------------------------------*/
function (done) {
if (event(query.event) === EVENT_STARTED) {
Peer.remove({
torrent: req.torrent._id,
_id: {$nin: req.torrent._peers}
}, function (err, removed) {
if (removed.n > 0) {
mtDebug.debugRed('Removed ' + removed + ' peers not in torrent._peers: ' + req.torrent._id, 'ANNOUNCE', true, req.passkeyuser);
}
done(null);
});
} else {
done(null);
}
},
/*---------------------------------------------------------------
check N&R can download
if user has too more H&R warning numbers, can not download any torrent
but can continue download the warning status torrent
vip user not checked
---------------------------------------------------------------*/
function (done) {
if (!req.seeder && !req.passkeyuser.isVip && event(query.event) === EVENT_STARTED) {
if (hnrConfig.enable) {
if (req.passkeyuser.hnr_warning >= hnrConfig.forbiddenDownloadMinWarningNumber) {
if (!req.torrent.torrent_hnr) {
done(190);
} else {
if (!req.completeTorrent) {
done(191);
} else {
if (!req.completeTorrent.hnr_warning) {
done(190);
} else {
done(null);
}
}
}
} else {
done(null);
}
} else {
done(null);
}
} else {
done(null);
}
},
/*---------------------------------------------------------------
announce download check
ratio check, setting in announce.downloadCheck
vip user not checked
---------------------------------------------------------------*/
function (done) {
if (!req.seeder && !req.passkeyuser.isVip && !req.passkeyuser.isOper && event(query.event) === EVENT_STARTED) {
if (req.passkeyuser.ratio !== -1 && req.passkeyuser.ratio < announceConfig.downloadCheck.ratio) {
var checkTimeBegin = moment(req.passkeyuser.created).add(announceConfig.downloadCheck.checkAfterSignupDays, 'd');
if (checkTimeBegin < moment(Date.now())) {
var reason = sprintf(FAILURE_REASONS[200], announceConfig.downloadCheck.ratio);
done(200, reason);
} else {
done(null);
}
} else {
done(null);
}
} else {
done(null);
}
},
/*---------------------------------------------------------------
find myself peers and get current peer with same peer_id
----------------------------------------------------------------*/
function (done) {
if (req.torrent._peers.length > 0) {
for (var i = req.torrent._peers.length; i > 0; i--) {
var p = req.torrent._peers[i - 1];
if (p.user.equals(req.passkeyuser._id)) {
if (p.last_announce_at > (Date.now() - announceConfig.announceInterval - announceConfig.announceIdleTime)) { //do not add inactive peer
req.selfpeer.push(p);
} else if (p.peer_id === query.peer_id) {
req.selfpeer.push(p);
}
}
}
}
getCurrentPeer(function () {
mtDebug.debugRed('req.currentPeer.isNewCreated = ' + req.currentPeer.isNewCreated, 'ANNOUNCE', true, req.passkeyuser);
mtDebug.debugRed('req.currentPeer._id = ' + req.currentPeer._id.toString(), 'ANNOUNCE', true, req.passkeyuser);
mtDebug.debugRed('req.currentPeer.torrent._id = ' + req.currentPeer.torrent._id, 'ANNOUNCE', true, req.passkeyuser);
done(null);
});
},
/*---------------------------------------------------------------
onEventStarted
if downloading, check download peer num only 1
if seeding, check seed peer num can not more than 3
numbers is in setting announceConfig.announceCheck
---------------------------------------------------------------*/
function (done) {
mtDebug.debugGreen('---------------CHECK USER SEED/LEECH COUNT----------------', 'ANNOUNCE', true, req.passkeyuser);
var lcount = getSelfLeecherCount();
var scount = getSelfSeederCount();
if (lcount > announceConfig.announceCheck.maxLeechNumberPerUserPerTorrent && !req.seeder) {
mtDebug.debugRed('getSelfLeecherCount = ' + lcount, 'ANNOUNCE', true, req.passkeyuser);
removeCurrPeer();
done(180);
} else if (scount > announceConfig.announceCheck.maxSeedNumberPerUserPerTorrent && req.seeder) {
mtDebug.debugRed('getSelfSeederCount = ' + scount, 'ANNOUNCE', true, req.passkeyuser);
removeCurrPeer();
done(181);
} else {
done(null);
}
},
/*---------------------------------------------------------------
writeUpDownData
uploaded,downloaded
update complete data if completeTorrent is exist
if has upload and download data size, write data size,
write time of seed(complete) whether or not u/d is 0
---------------------------------------------------------------*/
function (done) {
mtDebug.debugGreen('---------------WRITE_UP_DOWN_DATA----------------', 'ANNOUNCE', true, req.passkeyuser);
var curru = 0;
var currd = 0;
if (!req.currentPeer.isNewCreated) {
var udr = getUDRatio();
curru = query.uploaded - req.currentPeer.peer_uploaded;
currd = query.downloaded - req.currentPeer.peer_downloaded;
if (curru > 0 || currd > 0) {
var u = Math.round(curru * udr.ur);
var d = Math.round(currd * udr.dr);
//check if is vip
if (req.passkeyuser.isVip) {
u = u * globalSalesConfig.vip.value.Ur;
d = d * globalSalesConfig.vip.value.Dr;
}
//check if is torrent uploader
if (req.passkeyuser._id.equals(req.torrent.user._id)) {
u = u * globalSalesConfig.uploader.value.Ur;
d = d * globalSalesConfig.uploader.value.Dr;
}
//write user uploaded and downloaded
var up_d = {
uploaded: u,
downloaded: d,
true_uploaded: curru,
true_downloaded: currd
};
mtDebug.debugRed(JSON.stringify(up_d), 'ANNOUNCE', true, req.passkeyuser);
if (common.examinationIsValid(req.passkeyuser)) {
up_d['examinationData.uploaded'] = u;
up_d['examinationData.downloaded'] = d;
mtDebug.debugGreen('---------------WRITE EXAMINATION DATA----------------', 'ANNOUNCE', true, req.passkeyuser);
mtDebug.debugRed('examinationData.uploaded: ' + u + ', examinationData.downloaded: ' + d, 'ANNOUNCE', true, req.passkeyuser);
}
req.passkeyuser.update({
$inc: up_d
}).exec();
//write peer speed
var sp = {};
if (curru > 0) {
sp.peer_uspeed = Math.round(curru / (Date.now() - req.currentPeer.last_announce_at) * 1000);
sp.peer_cuspeed = Math.round(curru / (Date.now() - req.currentPeer.last_announce_at) * 1000);
} else {
sp.peer_cuspeed = 0;
}
if (currd > 0) {
sp.peer_dspeed = Math.round(currd / (Date.now() - req.currentPeer.last_announce_at) * 1000);
sp.peer_cdspeed = Math.round(currd / (Date.now() - req.currentPeer.last_announce_at) * 1000);
} else {
sp.peer_cdspeed = 0;
}
req.currentPeer.update({
$set: sp
}).exec();
//update score
//upload score and download score
var totalScore = 0;
var upUnitScore = 1;
var downUnitScore = 1;
var seederUnit = 1;
var lifeUnit = 1;
var action = scoreConfig.action.seedUpDownload;
var slAction = scoreConfig.action.seedSeederAndLife;
if (action.enable) {
var uploadScore = 0;
var downloadScore = 0;
if (curru > 0 && action.uploadEnable) {
if (req.torrent.torrent_size > action.additionSize) {
upUnitScore = Math.sqrt(req.torrent.torrent_size / action.additionSize);
}
var upScore = curru / action.perlSize;
uploadScore = upUnitScore * action.uploadValue * upScore;
//uploader addition
if (req.passkeyuser._id.equals(req.torrent.user._id)) {
uploadScore = uploadScore * action.uploaderRatio;
}
}
if (currd > 0 && action.downloadEnable) {
if (req.torrent.torrent_size > action.additionSize) {
downUnitScore = Math.sqrt(req.torrent.torrent_size / action.additionSize);
}
var downScore = currd / action.perlSize;
downloadScore = downUnitScore * action.downloadValue * downScore;
}
totalScore = uploadScore + downloadScore;
if (totalScore > 0) {
//vip addition
if (action.vipRatio && req.passkeyuser.isVip) {
totalScore = totalScore * action.vipRatio;
}
if (slAction.enable) {
//torrent seeders count addition
if (req.torrent.torrent_seeds <= slAction.seederCount) {
seederUnit = slAction.seederBasicRatio + slAction.seederCoefficient * (slAction.seederCount - req.torrent.torrent_seeds + 1);
totalScore = totalScore * seederUnit;
}
//torrent life addition
var life = moment(Date.now()) - moment(req.torrent.createdat);
var days = life / (60 * 60 * 1000 * 24);
lifeUnit = slAction.lifeBasicRatio + slAction.lifeCoefficientOfDay * days;
lifeUnit = lifeUnit > slAction.lifeMaxRatio ? slAction.lifeMaxRatio : lifeUnit;
totalScore = totalScore * lifeUnit;
}
totalScore = Math.round(totalScore * 100) / 100;
action.params = {
tid: req.torrent._id
};
scoreUpdate(req, req.passkeyuser, action, totalScore, false);
mtDebug.debugRed('announce score: ' + totalScore, 'ANNOUNCE', true, req.passkeyuser);
}
}
//write logs data into db
var logData = {
query_uploaded: query.uploaded,
query_downloaded: query.downloaded,
currentPeer_uploaded: req.currentPeer.peer_uploaded,
currentPeer_downloaded: req.currentPeer.peer_downloaded,
curr_uploaded: curru,
curr_downloaded: currd,
write_uploaded: u,
write_downloaded: d,
write_score: totalScore,
isVip: req.passkeyuser.isVip,
isUploader: req.passkeyuser._id.equals(req.torrent.user._id),
salesSettingValue: {
torrentSalesValue: req.torrent.torrent_sale_status,
globalSalesValue: isGlobalSaleValid ? globalSalesConfig.global.value : undefined,
vipSalesValue: globalSalesConfig.vip.value,
uploaderSalesValue: globalSalesConfig.uploader.value
},
scoreSettingValue: {
upUnitScore: upUnitScore,
downUnitScore: downUnitScore,
seederUnit: seederUnit,
lifeUnit: lifeUnit
},
info: {
agent: req.get('User-Agent'),
ip: req.cf_ip,
port: query.port
}
};
dataLog.announceLog(req.passkeyuser, req.torrent, logData);
} else {
req.currentPeer.update({
$set: {
peer_cuspeed: 0,
peer_cdspeed: 0
}
}).exec();
}
}
//write peer data
req.currentPeer.update({
$set: {
peer_uploaded: query.uploaded,
peer_downloaded: query.downloaded,
peer_left: query.left
}
}).exec();
done(null, curru, currd);
},
/*---------------------------------------------------------------
write complete data to completeTorrent and refresh completed ratio
---------------------------------------------------------------*/
function (curru, currd, done) {
if (curru > 0 || currd > 0) {
if (hnrConfig.enable && req.completeTorrent) {
mtDebug.debugGreen('---------------WRITE COMPLETE DATA----------------', 'ANNOUNCE', true, req.passkeyuser);
req.completeTorrent.update({
$inc: {
total_uploaded: curru,
total_downloaded: currd
}
}, function () {
done(null);
});
} else {
done(null);
}
} else {
done(null);
}
},
/*---------------------------------------------------------------
update H&R completeTorrent.total_seed_time
update H&R ratio in save
---------------------------------------------------------------*/
function (done) {
if (!req.currentPeer.isNewCreated) {
if (hnrConfig.enable && req.completeTorrent && req.completeTorrent.complete && event(query.event) !== EVENT_COMPLETED) {
mtDebug.debugGreen('---------------UPDATE H&R COMPLETE TOTAL_SEED_TIME----------------', 'ANNOUNCE', true, req.passkeyuser);
req.completeTorrent.update({
$inc: {
total_seed_time: (Date.now() - req.currentPeer.last_announce_at)
}
}, function () {
done(null);
});
} else {
done(null);
}
} else {
done(null);
}
},
/*---------------------------------------------------------------
upload user getting score through seed timed
include torrent seeders count coefficient value and life coefficient value
---------------------------------------------------------------*/
function (done) {
if (!req.currentPeer.isNewCreated) {
if (req.seeder && event(query.event) !== EVENT_COMPLETED && event(query.event) !== EVENT_STARTED) {
mtDebug.debugGreen('---------------UPLOAD SCORE THROUGH SEED TIMED----------------', 'ANNOUNCE', true, req.passkeyuser);
if (req.torrent.torrent_status === 'reviewed') {
var action = scoreConfig.action.seedTimed;
var slAction = scoreConfig.action.seedSeederAndLife;
if (action.enable) {
var timed = Date.now() - req.currentPeer.last_announce_at;
var seedUnit = timed / action.additionTime;
var seedScore = seedUnit * action.timedValue;
if (seedScore > 0) {
//vip addition
if (action.vipRatio && req.passkeyuser.isVip) {
seedScore = seedScore * action.vipRatio;
}
if (slAction.enable) {
//torrent seeders count addition
if (req.torrent.torrent_seeds <= slAction.seederCount) {
var seederUnit = slAction.seederBasicRatio + slAction.seederCoefficient * (slAction.seederCount - req.torrent.torrent_seeds + 1);
seedScore = seedScore * seederUnit;
}
//torrent life addition
var life = moment(Date.now()) - moment(req.torrent.createdat);
var days = life / (60 * 60 * 1000 * 24);
var lifeUnit = slAction.lifeBasicRatio + slAction.lifeCoefficientOfDay * days;
lifeUnit = lifeUnit > slAction.lifeMaxRatio ? slAction.lifeMaxRatio : lifeUnit;
seedScore = seedScore * lifeUnit;
}
seedScore = Math.round(seedScore * 100) / 100;
action.params = {
tid: req.torrent._id
};
scoreUpdate(req, req.passkeyuser, action, seedScore);
mtDebug.debugRed('seed timed score: ' + seedScore, 'ANNOUNCE', true, req.passkeyuser);
done(null);
} else {
done(null);
}
} else {
done(null);
}
} else {
done(null);
}
} else {
done(null);
}
} else {
done(null);
}
},
/*---------------------------------------------------------------
update currentPeer.last_announce_at
update complateTorrent refreshat
---------------------------------------------------------------*/
function (done) {
mtDebug.debugGreen('---------------UPDATE LAST_ANNOUNCE_AT----------------', 'ANNOUNCE', true, req.passkeyuser);
if (!req.currentPeer.isNewCreated) {
req.currentPeer.update({
$set: {
last_announce_at: Date.now()
}
}).exec();
}
done(null);
},
/*---------------------------------------------------------------
onEventCompleted
---------------------------------------------------------------*/
function (done) {
if (event(query.event) === EVENT_COMPLETED) {
mtDebug.debugGreen('---------------EVENT_COMPLETED----------------', 'ANNOUNCE', true, req.passkeyuser);
if (req.currentPeer.peer_downloaded > 0 || query.downloaded > 0) {
doCompleteEvent(function () {
done(null);
});
} else {
done(187);
mtDebug.debugRed('Illegal completed event', 'ANNOUNCE', true, req.passkeyuser);
}
} else {
done(null);
}
},
/*---------------------------------------------------------------
count H&R warning for user on normal up/down process
---------------------------------------------------------------*/
function (done) {
if (!req.currentPeer.isNewCreated) {
if (hnrConfig.enable && req.completeTorrent && event(query.event) !== EVENT_COMPLETED) {
mtDebug.debugGreen('---------------COUNT H&R WARNING FOR USER----------------', 'ANNOUNCE', true, req.passkeyuser);
req.completeTorrent.countHnRWarning(false, true);
}
}
done(null);
},
/*---------------------------------------------------------------
onEventStopped
count H&R warning for user when EVENT_STOPPED
delete peers
---------------------------------------------------------------*/
function (done) {
if (event(query.event) === EVENT_STOPPED) {
mtDebug.debugGreen('---------------EVENT_STOPPED----------------', 'ANNOUNCE', true, req.passkeyuser);
if (hnrConfig.enable && req.completeTorrent) {
req.completeTorrent.countHnRWarning(true, false);
}
removeCurrPeer(function () {
done(null);
});
} else {
done(null);
}
},
/*---------------------------------------------------------------
update torrent and user seeding/leeching count numbers
---------------------------------------------------------------*/
function (done) {
mtDebug.debugGreen('---------------COUNT TORRENT SEEDING/LEECHING----------------', 'ANNOUNCE', true, req.passkeyuser);
req.torrent.updateSeedLeechNumbers(function (slCount) {
req.passkeyuser.updateSeedLeechNumbers();
if (slCount) {
mtDebug.debugYellow(JSON.stringify(slCount), 'ANNOUNCE', true, req.passkeyuser);
req.torrent.torrent_seeds = slCount.seedCount;
req.torrent.torrent_leechers = slCount.leechCount;
}
done(null);
});
},
/*---------------------------------------------------------------
sendPeers
compact mode
---------------------------------------------------------------*/
function (done) {
var want = WANT_DEFAULT;
if (typeof query.numwant !== 'undefined' && query.numwant > 0)
want = query.numwant;
var peers = getPeers(want, req.torrent._peers);
var hasV6ip = hasV6IP(peers);
var peersFunction = hasV6ip ? peersDictionary : (query.compact === 0 ? peersDictionary : peersBinary);
var resPeers = peersFunction(peers);
var resp = bcode.encode({
interval: ANNOUNCE_INTERVAL,
complete: req.torrent.torrent_seeds,
incomplete: req.torrent.torrent_leechers,
downloaded: req.torrent.torrent_finished,
peers: resPeers
});
mtDebug.debugGreen('---------------SEND RESPONSE TO USER----------------', 'ANNOUNCE', true, req.passkeyuser);
if (peers.length > 0) {
mtDebug.debug('ip send mode: ' + (hasV6ip ? 'IPv6' : 'IPv4'), 'ANNOUNCE', true, req.passkeyuser);
}
mtDebug.debug(benc.decode(resp, 'ascii'), 'ANNOUNCE', true, req.passkeyuser);
res.writeHead(200, {'Content-Type': 'text/plain'});
res.end(resp, 'ascii');
done(null);
},
/**
* update user, torrent, peer, complete
* @param done
*/
function (done) {
req.passkeyuser.globalUpdateMethod(true);
req.torrent.globalUpdateMethod(true);
if (req.currentPeer) {
req.currentPeer.globalUpdateMethod(true);
}
if (hnrConfig.enable && req.completeTorrent) {
req.completeTorrent.globalUpdateMethod(true);
}
done(null, 'done');
}
], function (err, reason) {
if (err) {
sendError(new Failure(err, reason));
}
});
/**
* getCurrentPeer
* @returns {boolean}
*/
function getCurrentPeer(callback) {
req.selfpeer.every(function (p) {
if (p.peer_id === query.peer_id) {
req.currentPeer = p;
req.currentPeer.torrent = req.torrent;
req.currentPeer.isNewCreated = false;
//if find peer_id, but some time some client (like qbittorrent 4.1.0) the ip or port is changed, update it
if ((req.currentPeer.peer_ip !== req.cf_ip || req.currentPeer.peer_ipv4 !== query.ipv4 || req.currentPeer.peer_ipv6 !== query.ipv6 || req.currentPeer.peer_port !== query.port) && query.port !== 0) {
req.currentPeer.peer_ip = req.cf_ip;
req.currentPeer.peer_ipv4 = query.ipv4;
req.currentPeer.peer_ipv6 = query.ipv6;
req.currentPeer.peer_port = query.port;
req.currentPeer.update({
$set: {
peer_ip: req.cf_ip,
peer_ipv4: query.ipv4,
peer_ipv6: query.ipv6,
peer_port: query.port
}
}).exec();
}
if (req.seeder && req.currentPeer.peer_status !== PEERSTATE_SEEDER && event(query.event) !== EVENT_COMPLETED) {
mtDebug.debugGreen('---------------PEER STATUS CHANGED: Seeder----------------', 'ANNOUNCE', true, req.passkeyuser);
doCompleteEvent(function () {
if (callback) return callback();
});
} else {
if (callback) return callback();
}
}
return true;
});
//if not found then create req.currentPeer
if (!req.currentPeer) {
createCurrentPeer(function () {
if (callback) return callback();
});
}
}
/**
* getCurrentPeerIpMode
* @returns {string}
*/
function getCurrentPeerIpMode() {
if (req.currentPeer) {
if (req.currentPeer.isIpV4V6()) {
return 'IPV4V6';
} else if (req.currentPeer.isIpV6()) {
return 'IPV6';
} else {
return 'IPV4';
}
} else {
return 'unknown';
}
}
/**
* doCompleteEvent
*/
function doCompleteEvent(callback) {
//write completed torrent data into finished
var finished = new Finished();
finished.user = req.passkeyuser;
finished.torrent = req.torrent;
finished.user_ip = req.cf_ip;
finished.user_agent = req.get('User-Agent');
finished.user_port = query.port;
finished.save();
traceLogCreate(req, traceConfig.action.userAnnounceFinished, {
user: req.passkeyuser._id,
torrent: req.torrent._id,
agent: req.get('User-Agent'),
ip: req.cf_ip,
port: query.port
});
req.currentPeer.update({
$set: {
peer_status: PEERSTATE_SEEDER
}
}).exec();
req.torrent.update({
$inc: {
torrent_finished: 1
}
}).exec();
req.passkeyuser.update({
$inc: {
finished: 1
}
}).exec();
//update completeTorrent complete status
if (hnrConfig.enable && req.completeTorrent) {
req.completeTorrent.update({
$set: {
complete: true
}
}, function () {
if (callback) callback();
});
} else {
if (callback) callback();
}
}
/**
* createCurrentPeer
*/
function createCurrentPeer(callback) {
var peer = new Peer();
peer.user = req.passkeyuser;
peer.torrent = req.torrent;
peer.peer_id = query.peer_id;
peer.peer_ip = req.cf_ip;
peer.peer_ipv4 = query.ipv4;
peer.peer_ipv6 = query.ipv6;
peer.peer_port = query.port;
peer.peer_left = query.left;
peer.peer_status = req.seeder ? PEERSTATE_SEEDER : PEERSTATE_LEECHER;
peer.user_agent = req.get('User-Agent');
peer.isNewCreated = true;
peer.last_announce_at = Date.now();
if (req.seeder) {
peer.finishedat = Date.now();
}
req.selfpeer.push(peer);
req.torrent.update({
$addToSet: {_peers: peer}
}).exec();
//save ip to user
req.passkeyuser.addLeechedIp(peer.peer_ip);
req.passkeyuser.addClientAgent(peer.user_agent);
peer.save(function () {
req.currentPeer = peer;
mtDebug.debugGreen('---------------createCurrentPeer()----------------', 'ANNOUNCE', true, req.passkeyuser);
if (callback) callback();
});
}
/**
* removeCurrPeer
*/
function removeCurrPeer(callback) {
req.selfpeer.splice(req.selfpeer.indexOf(req.currentPeer), 1);
req.torrent._peers.forEach(function (_p) {
if (_p._id.equals(req.currentPeer._id)) {
req.torrent._peers.pull(_p);
req.torrent.save();
}
});
Peer.findById(req.currentPeer._id, function (err, _p) {
_p.remove(function (err) {
if (err) {
mtDebug.debugGreen('---------------removeCurrPeer(): Error----------------', 'ANNOUNCE', true, req.passkeyuser);
} else {
mtDebug.debugGreen('---------------removeCurrPeer()----------------', 'ANNOUNCE', true, req.passkeyuser);
}
req.currentPeer = undefined;
if (callback) callback();
});
});
}
/**
* getSelfLeecherCount
* @returns {number}
*/
function getSelfLeecherCount() {
if (req.selfpeer.length === 0) {
return 0;
} else {
var i = 0;
req.selfpeer.forEach(function (p) {
if (p.peer_status === PEERSTATE_LEECHER) {
i++;
}
});
return i;
}
}
/**
* getSelfSeederCount
* @returns {number}
*/
function getSelfSeederCount() {
if (req.selfpeer.length === 0) {
return 0;
} else {
var i = 0;
req.selfpeer.forEach(function (p) {
if (p.peer_status === PEERSTATE_SEEDER) {
i++;
}
});
return i;
}
}
/**
* getUDRatio
* @returns {{}}
*/
function getUDRatio() {
var udr = {};
var sale = req.torrent.torrent_sale_status;
var start = moment(globalSalesConfig.global.startAt, globalSalesConfig.global.timeFormats).valueOf();
var end = start + globalSalesConfig.global.expires;
var now = Date.now();
isGlobalSaleValid = (now > start && now < end && globalSalesConfig.global.value) ? true : false;
if (isGlobalSaleValid && globalSalesConfig.global.value) {
sale = globalSalesConfig.global.value;
mtDebug.debugRed('isGlobalSaleValid = ' + isGlobalSaleValid, 'ANNOUNCE', true, req.passkeyuser);
mtDebug.debugRed('global sale value = ' + sale, 'ANNOUNCE', true, req.passkeyuser);
}
switch (sale) {
case 'U1/FREE':
udr.ur = 1;
udr.dr = 0;
break;
case 'U1/D.3':
udr.ur = 1;
udr.dr = 0.3;
break;
case 'U1/D.5':
udr.ur = 1;
udr.dr = 0.5;
break;
case 'U1/D.8':
udr.ur = 1;
udr.dr = 0.8;
break;
case 'U2/FREE':
udr.ur = 2;
udr.dr = 0;
break;
case 'U2/D.3':
udr.ur = 2;
udr.dr = 0.3;
break;
case 'U2/D.5':
udr.ur = 2;
udr.dr = 0.5;
break;
case 'U2/D.8':
udr.ur = 2;
udr.dr = 0.8;
break;
case 'U2/D1':
udr.ur = 2;
udr.dr = 1;
break;
case 'U3/FREE':
udr.ur = 3;
udr.dr = 0;
break;
case 'U3/D.5':
udr.ur = 3;
udr.dr = 0.5;
break;
case 'U3/D.8':
udr.ur = 3;
udr.dr = 0.8;
break;
case 'U3/D1':
udr.ur = 3;
udr.dr = 1;
break;
default: /* U1D1 */
udr.ur = 1;
udr.dr = 1;
}
return udr;
}
/**
* sendError
* @param failure
*/
function sendError(failure) {
var respc = failure.bencode();
mtDebug.debugRed(respc, 'ANNOUNCE', true, req.passkeyuser);
res.writeHead(200, {
'Content-Length': respc.length,
'Content-Type': 'text/plain'
});
res.end(respc);
}
/**
* getPeers
* @param count
* @param peers
* @returns []
*/
function getPeers(count, peers) {
var ps = [];
if (event(query.event) !== EVENT_STOPPED) {
mtDebug.debugGreen('---------------GET PEERS LIST----------------', 'ANNOUNCE', true, req.passkeyuser);
mtDebug.debugRed('want.count = ' + count, 'ANNOUNCE', true, req.passkeyuser);
mtDebug.debugRed('peers.length = ' + peers.length, 'ANNOUNCE', true, req.passkeyuser);
mtDebug.debugRed('user ip mode: ' + getCurrentPeerIpMode(), 'ANNOUNCE', true, req.passkeyuser);
var wantedPeers;
if (req.currentPeer.isIpV4Only()) { //ipv4
wantedPeers = peers.filter(function (p) {
return p.isIpV4();
}).sort(function () {
return 0.5 - Math.random();
}).slice(0, count);
} else { //ipv6 or v4v6
wantedPeers = peers.slice(0).sort(function () {
return 0.5 - Math.random();
}).slice(0, count);
}
wantedPeers.forEach(function (p) {
if (p !== undefined && p !== req.currentPeer) {
if (p.last_announce_at > (Date.now() - announceConfig.announceInterval - announceConfig.announceIdleTime)) { //do not send inactive peer
var tp;
if (p.user.equals(req.passkeyuser._id)) {
if (announceConfig.peersCheck.peersSendListIncludeOwnSeed) {
tp = {
id: p.peer_id,
ip: req.currentPeer.isIpV4Only() ? p.peer_ipv4 : (p.isIpV6() ? p.peer_ipv6 : p.peer_ipv4),
port: p.peer_port
};
ps.push(tp);
mtDebug.debug(p._id.toString() + ' - SELF PEER - IP:' + tp.ip + ' PORT:' + tp.port, 'ANNOUNCE', true, req.passkeyuser);
}
} else {
tp = {
id: p.peer_id,
ip: req.currentPeer.isIpV4Only() ? p.peer_ipv4 : (p.isIpV6() ? p.peer_ipv6 : p.peer_ipv4),
port: p.peer_port
};
ps.push(tp);
mtDebug.debug(p._id.toString() + ' IP:' + tp.ip + ' PORT:' + tp.port, 'ANNOUNCE', true, req.passkeyuser);
}
}
}
});
}
return ps;
}
/**
* hasV6IP
* @param peers
* @returns {boolean}
*/
function hasV6IP(peers) {
for (let p of peers) {
if (ipRegex.v6({exact: true}).test(p.ip)) {
return true;
}
}
return false;
}
/**
* peersDictionary
* @param peers
*/
function peersDictionary(peers) {
return peers.map(function (peer) {
if (query.no_peer_id === 1) {
return {
'ip': peer.ip,
'port': peer.port
};
} else {
return {
'peer id': peer.id,
'ip': peer.ip,
'port': peer.port
};
}
});
}
/**
* peersBinary
* @param peers
* @returns {string}
*/
function peersBinary(peers) {
var tokens = [];
peers.forEach(function (peer) {
tokens.push(peerBinary(peer.ip, peer.port));
});
return tokens.join('');
}
/**
* peerBinary
* @param ip
* @param port
* @returns {string}
*/
function peerBinary(ip, port) {
var tokens = [];
var octets = ip.split('.');
if (octets.length !== 4) return '';
octets.forEach(function (octet) {
var val = parseInt(octet, 10);
if (!isNaN(val)) tokens.push(val);
});
if (tokens.length !== 4) return '';
tokens.push((port >> 8) & 0xff);
tokens.push(port & 0xff);
return String.fromCharCode.apply(tokens, tokens);
}
};
/**
* userByPasskey
* @param req
* @param res
* @param next
* @param pk
* @returns {*}
*/
exports.userByPasskey = function (req, res, next, pk) {
User.findOne({passkey: pk})
.exec(function (err, u) {
if (u) {
req.passkeyuser = u;
} else {
req.passkeyuser = undefined;
}
next();
});
};