Optional Tor support
This commit is contained in:
@@ -1,13 +1,16 @@
|
||||
const fetch = require("node-fetch").default
|
||||
|
||||
function request(url) {
|
||||
console.log("-> [OUT]", String(url)) // todo: make more like pinski?
|
||||
return fetch(url, {
|
||||
function request(url, options = {}, settings = {}) {
|
||||
if (settings.statusLine === undefined) settings.statusLine = "OUT"
|
||||
if (settings.log === undefined) settings.log = true
|
||||
if (settings.log) console.log(`-> [${settings.statusLine}] ${url}`) // todo: make more like pinski?
|
||||
// @ts-ignore
|
||||
return fetch(url, Object.assign({
|
||||
headers: {
|
||||
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/74.0.3729.169 Safari/537.36"
|
||||
},
|
||||
redirect: "manual"
|
||||
})
|
||||
}, options))
|
||||
}
|
||||
|
||||
module.exports.request = request
|
||||
|
||||
79
src/lib/utils/tor.js
Normal file
79
src/lib/utils/tor.js
Normal file
@@ -0,0 +1,79 @@
|
||||
const SocksProxyAgent = require("socks-proxy-agent")
|
||||
const {connect} = require("net");
|
||||
const constants = require("../constants")
|
||||
const {request} = require("./request")
|
||||
|
||||
class TorManager {
|
||||
/**
|
||||
* @param {import("@deadcanaries/granax/lib/controller")} tor
|
||||
* @param {number} port
|
||||
*/
|
||||
constructor(tor, port) {
|
||||
this.tor = tor
|
||||
this.port = port
|
||||
this.agent = new SocksProxyAgent("socks5://localhost:"+this.port)
|
||||
}
|
||||
|
||||
async request(url, test) {
|
||||
let result = null
|
||||
while (!result) {
|
||||
const req = await request(url, {agent: this.agent}, {log: true, statusLine: "TOR"})
|
||||
try {
|
||||
result = await test(req)
|
||||
} catch (e) {
|
||||
await this.newCircuit()
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
newCircuit() {
|
||||
return new Promise(resolve => {
|
||||
this.tor.cleanCircuits(() => resolve())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
var granax = require("@deadcanaries/granax")
|
||||
} catch (e) {}
|
||||
|
||||
/** @type {Promise<TorManager>} */
|
||||
module.exports = new Promise(resolve => {
|
||||
if (granax) {
|
||||
/** @type {import("@deadcanaries/granax/lib/controller")} */
|
||||
// @ts-ignore
|
||||
let tor
|
||||
if (constants.tor_password == null) {
|
||||
// @ts-ignore
|
||||
tor = new granax()
|
||||
} else {
|
||||
tor = new granax.TorController(connect(9051), {authOnConnect: false})
|
||||
tor.authenticate(`"${constants.tor_password}"`, err => {
|
||||
if (err) console.log("Tor auth error:", err)
|
||||
})
|
||||
}
|
||||
|
||||
console.log("Starting tor...")
|
||||
|
||||
tor.once("ready", () => {
|
||||
tor.getInfo("net/listeners/socks", (err, result) => {
|
||||
if (err) throw err
|
||||
// result is string containing something like "127.0.0.1:36977"
|
||||
// yes, the string contains double quotes!
|
||||
const port = +result.match(/:(\d+)/)[1]
|
||||
const torManager = new TorManager(tor, port)
|
||||
console.log("Tor is ready, using SOCKS port "+port)
|
||||
resolve(torManager)
|
||||
})
|
||||
})
|
||||
|
||||
tor.on("error", function() {
|
||||
console.log("Tor error!")
|
||||
console.log(...arguments)
|
||||
})
|
||||
} else {
|
||||
console.log("Note: Tor functionality not installed. You may wish to run `npm install @deadcanaries/granax`. (78+ MB download required.)")
|
||||
resolve(null)
|
||||
}
|
||||
})
|
||||
41
src/lib/utils/torswitcher.js
Normal file
41
src/lib/utils/torswitcher.js
Normal file
@@ -0,0 +1,41 @@
|
||||
const constants = require("../constants")
|
||||
const {request} = require("./request")
|
||||
|
||||
class TorSwitcher {
|
||||
constructor() {
|
||||
this.torManager = null
|
||||
}
|
||||
|
||||
setManager(torManager) {
|
||||
this.torManager = torManager
|
||||
}
|
||||
|
||||
/**
|
||||
* Request from the URL.
|
||||
* The test function will be called with the response object.
|
||||
* If the test function succeeds, its return value will be returned here.
|
||||
* If the test function fails, its error will be rejected here.
|
||||
* Only include rate limit logic in the test function!
|
||||
* @param {string} url
|
||||
* @param {(res: import("node-fetch").Response) => Promise<T>} test
|
||||
* @returns {Promise<T>}
|
||||
* @template T the return value of the test function
|
||||
*/
|
||||
request(url, test) {
|
||||
if (this.torManager) {
|
||||
return this.torManager.request(url, test)
|
||||
} else {
|
||||
return request(url).then(res => test(res))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const switcher = new TorSwitcher()
|
||||
|
||||
if (constants.use_tor) {
|
||||
require("./tor").then(torManager => {
|
||||
if (torManager) switcher.setManager(torManager)
|
||||
})
|
||||
}
|
||||
|
||||
module.exports = switcher
|
||||
Reference in New Issue
Block a user