UNPKG

routexl-client

Version:

this is a request client for routexl.com api

385 lines (372 loc) 12.4 kB
var request = require("request"); var async = require("async"); var permutate = require("enum-permutate"); var rxltouraccounts = [], rxldistanceacount; var tourqueue, tourwdqueue, proxy = [], proxycounter = 0; (rxoptions = {}), (counter = 1); /** * * @param {{address:string,lng:number,lat:number}[]} locations * @param {number} tries number of try in total if attemp failes * @returns {promise} (josn body|false) */ var rxldistances = async function rxldistances(locations, tries, temptoken) { let trycount = 1; let options = { ...(proxy[0] || {}), method: "POST", headers: { Authorization: rxldistanceacount || temptoken }, url: "https://api.routexl.com/distances", form: { locations } }; return new Promise(async resolve => { if (!rxldistanceacount && !temptoken) { return resolve( await new Promise(reso => { tourqueue.push({ locations, tries, type: "distance" }, result => reso(result) ); }) ); } if (tries === 0) resolve({ error: null, body: {} }); request(options, async function(err, response, body) { // logging error requests if (err) return resolve({ error: { title: "request error", err, request: locations }, body: {} }); if (response && response.statusCode != 200) return resolve({ error: { title: "not 200", err: { statusCode: response.statusCode, body }, request: locations }, body: {} }); let result; try { result = { error: null, body: JSON.parse(body) }; } catch (err) { result = { error: { title: "error parsing body", err, request: locations, body }, body: {} }; } while (!result && tries && trycount < tries) { trycount++; result = await rxldistances(locations); } return resolve(result); }); }); }; /** * * @param {{address:string,lng:number,lat:number}[]} locations less then 25 locations is supported at the time * @param {number|{tourtry:number,tourwdtry:number}} tries number of try in total if attemp failes * @returns {promise} (josn body|false) */ var rxltour = async function(locations, tries) { return await new Promise(reso => { let notbusycount = rxltouraccounts.filter(ac => !ac.inuse).length; if (locations.length < 8 && rxoptions.tourwithdistance && !notbusycount) { let tourwdtry = typeof tries == "object" ? tries.tourwdtry : tries; tourwdqueue.push({ locations, tries: tourwdtry }, result => reso(result)); } else { let tourtry = typeof tries == "object" ? tries.tourtry : tries; tourqueue.push({ locations, tries: tourtry }, result => reso(result)); } }); }; /** * * @param {{token:string,username:string,password:string}[]} accounts routexl acounts */ var settourqueue = accounts => { rxltouraccounts = accounts; tourqueue = async.queue(function(input, callback) { if (input.type == "distance") { let currtoken = rxltouraccounts.find(adri => !adri.inuse); if (!currtoken) callback({ error: { title: "no token available", body: {} } }); currtoken.inuse = true; rxldistances(input.locations, input.tries, currtoken.token).then( result => { currtoken.inuse = false; callback(result); } ); } else { rxltourrequest(input.locations, input.tries).then(result => { callback(result); }); } }, accounts.length); }; /** * * @param {number} threadsqty number of concurent workers */ var settourwdequeue = (threadsqty = 20) => { tourwdqueue = async.queue(function(input, callback) { routexltourwithdistance(input.locations, input.tries).then(result => { callback(result); }); }, threadsqty); }; var rxltourrequest = function(locations, tries) { let trycount = 1; return new Promise(resolve => { if (tries === 0) return resolve({ error: { title: "you set tries to zero" }, body: {} }); var currtoken = rxltouraccounts.find(adri => !adri.inuse); // if no free acount is available if (!currtoken) { return resolve({ error: { title: "threads are more then acount length", err: "", request: locations }, body: {} }); } currtoken.inuse = true; Authorization = currtoken.token; if (proxy.length > 1) { proxycounter++; proxycounter = proxycounter > proxy.length - 1 ? 1 : proxycounter; } request( { ...(proxy[proxycounter] || {}), method: "POST", url: "https://api.routexl.com/tour", headers: { Authorization }, form: { locations } }, async (err, response, body) => { //free current account currtoken.inuse = false; let result; if (err) result = { error: { title: "request error", err, request: locations }, body: {} }; if (response && response.statusCode != 200) result = { error: { title: "not 200", err: { statusCode: response.statusCode, body }, request: locations }, body: {} }; try { if (!result) result = { error: null, body: JSON.parse(body) }; } catch (err) { result = { error: { title: "error parsing body", err, request: locations, body }, body: {} }; } while (result.error && tries && trycount < tries) { trycount++; result = await rxltour(locations); } return resolve(result); } ); }); }; // calc possible routes(array wise); //max acceptable is 7 waypoints (3 orders) var possibleroutes = function(waypoints) { return new Promise(async resolve => { //create array of numbers let wparray = [...Array(waypoints.length).keys()]; // all possible orders let possiblearray = permutate(wparray); waypoints.forEach((wp, i) => { if (wp.restrictions && wp.restrictions.after !== undefined) { let after = parseInt(wp.restrictions.after); possiblearray = possiblearray.filter( wpa => wpa.indexOf(i) > wpa.indexOf(after) ); } if (wp.restrictions && wp.restrictions.before !== undefined) { let before = parseInt(wp.restrictions.before); possiblearray = possiblearray.filter( wpa => wpa.indexOf(i) < wpa.indexOf(before) ); } }); resolve(possiblearray); }); }; // simulates routexl tour with its distance api var routexltourwithdistance = async function(waypoints, tries) { return new Promise(async resolve => { let output; let result = await Promise.all([ rxldistances(waypoints, tries), possibleroutes(waypoints) ]); if (result[0].error) resolve(result[0]); try { let routexlresult = Object.values(result[0].body.distances); let proutes = result[1]; proutes.forEach(pr => { let route, thisrout, due, ready, finalresult; let distance = 0; ready = (waypoints[pr[0]].restrictions || {}).ready || 0; servicetime = waypoints[pr[0]].servicetime || 0; let duration = ready + servicetime; due = (waypoints[pr[0]].restrictions || {}).due; route = [ { waypoint: waypoints[pr[0]], name: waypoints[pr[0]].address, arrival: duration, distance: 0, relativeArrival: duration * 60, relativeDistance: 0, passeddue: due ? due < duration : false } ]; for (let i = 1; i < pr.length; i++) { thisrout = routexlresult.find( rr => rr.from == parseInt(pr[i - 1]) && rr.to == parseInt(pr[i]) ); thisrout = thisrout || routexlresult.find( rr => rr.to == parseInt(pr[i - 1]) && rr.from == parseInt(pr[i]) ); ready = (waypoints[pr[0]].restrictions || {}).ready || 0; due = (waypoints[pr[i]].restrictions || {}).due; if (duration < ready) duration = ready; duration += Math.round(thisrout.duration / 60) + (waypoints[pr[i]].servicetime || 0); distance += Math.round(thisrout.distance / 100) / 10; distance = Math.round(distance * 10) / 10; route.push({ waypoint: waypoints[pr[i]], name: waypoints[pr[i]].address, arrival: duration, distance: distance, relativeArrival: thisrout.duration, relativeDistance: thisrout.distance, passeddue: due ? due < duration : false }); } finalresult = { route, distance, duration, feasible: route.find(r => r.passeddue) ? false : true }; output = output || finalresult; if (output.feasible) { output = finalresult.feasible & (output.duration > finalresult.duration) ? finalresult : output; } else { output = finalresult.feasible ? finalresult : output.duration > finalresult.duration ? finalresult : output; } }); resolve({ error: null, body: output }); } catch (err) { resolve({ error: { title: "routewithdistance proccess error", err, request: waypoints }, body: {} }); } }); }; /** * * @param {string[]|{username:string,password:string}[]} toureacounts string or object of acounts * @param {{tourwithdistance:boolean,tourwithdistancethreads:number}} options extra options for connectivity * @param {string|{username:string,password:string}} distanceacount token string or object of acount * only use one instance of me for routexl tour is single threaded */ var initalize = function(toureacounts, options = {}, distanceacount) { if (!toureacounts) throw new Error("you should provide at lease one acount"); if (!Array.isArray(toureacounts)) toureacounts = [toureacounts]; rxltouraccounts = toureacounts.map(createacounttoken); if (distanceacount) rxldistanceacount = createacounttoken(distanceacount).token; if (!rxldistanceacount && options.tourwithdistance) throw new Error("input distance acount when useing route with distance"); rxoptions.tourwithdistance = options.tourwithdistance ? true : false; rxoptions.tourwithdistancethreads = options.tourwithdistancethreads && options.tourwithdistancethreads < 20 ? options.tourwithdistancethreads : 20; if (options.proxy) { if (!Array.isArray(options.proxy)) options.proxy = [options.proxy]; options.proxy.forEach(opro => { if (opro.type == "socks5") proxy.push({ strictSSL: true, agentClass: require("socks5-https-client/lib/Agent"), agentOptions: { socksHost: opro.host, socksPort: opro.port, socksUsername: opro.username, socksPassword: opro.password } }); }); } settourqueue(rxltouraccounts); settourwdequeue(rxoptions.tourwithdistancethreads); return { distances: rxldistances, tour: rxltour, settourqueue, settourwdequeue }; }; // create basic auth token form user and path var createacounttoken = acount => { if (typeof acount == "string") return { token: acount }; if (typeof acount == "object") { if (!acount.username && !acount.password && acount.token) { return { token: acount.token }; } else { return { token: "Basic " + Buffer.from(acount.username + ":" + acount.password).toString( "base64" ) }; } } }; module.exports = initalize;