├── Procfile ├── .gitignore ├── op.js ├── package.json ├── webserver.js ├── app.json ├── config.js ├── database.js ├── main.js ├── README.md ├── client.js ├── gift.js └── http.js /Procfile: -------------------------------------------------------------------------------- 1 | worker: node . -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # node stuff 2 | node_modules 3 | package-lock.json 4 | # my stuff 5 | files 6 | test.js 7 | setup_env.cmd -------------------------------------------------------------------------------- /op.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | DISPATCH: 0, 3 | HEARTBEAT: 1, 4 | IDENTIFY: 2, 5 | RECONNECT: 7, 6 | INVALID_SESSION: 9, 7 | HELLO: 10, 8 | HEARBEAT_ACK: 11, 9 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "nitrsnip", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "main.js", 6 | "author": "fyz", 7 | "license": "ISC", 8 | "dependencies": { 9 | "erlpack": "^0.1.3", 10 | "ws": "^7.4.2", 11 | "zlib-sync": "^0.1.7" 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /webserver.js: -------------------------------------------------------------------------------- 1 | const http = require("http"); 2 | 3 | module.exports = { 4 | startWebServer: function (port) { 5 | http.createServer((_req, res) => { 6 | res.writeHead(200); 7 | res.end("bruh"); 8 | }).listen(port, "0.0.0.0", () => { 9 | console.log(`Web server running on ${port}`); 10 | }); 11 | } 12 | } -------------------------------------------------------------------------------- /app.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "nitrsnip", 3 | "description": "snip that nitr", 4 | "repository": "https://github.com/yafyz/nitrsnip", 5 | "env": { 6 | "d_token": { 7 | "required": true 8 | }, 9 | "d_webhook": { 10 | "required": true 11 | }, 12 | "d_err_webhook": { 13 | "required": false 14 | }, 15 | "read_messages_on_redeem_account": { 16 | "required": true, 17 | "value": "true" 18 | }, 19 | "use_multiple_tokens": { 20 | "required": true, 21 | "value": "false" 22 | }, 23 | "tokens": { 24 | "required": false 25 | }, 26 | "show_messages": { 27 | "required": true, 28 | "value": "false" 29 | } 30 | } 31 | } -------------------------------------------------------------------------------- /config.js: -------------------------------------------------------------------------------- 1 | const fs = require("fs"); 2 | if (fs.existsSync("files")) { 3 | module.exports = JSON.parse(fs.readFileSync("files/config.json")); 4 | } else { 5 | module.exports = { 6 | "d_token": process.env.d_token, 7 | "d_gateway": "wss://gateway.discord.gg/?encoding=etf&v=9&compress=zlib-stream", 8 | "d_webhook": process.env.d_webhook, 9 | "d_err_webhook": process.env.d_err_webhook || process.env.d_webhook, 10 | "read_messages_on_redeem_account": process.env.read_messages_on_redeem_account.toLowerCase() == "true", 11 | "debug_webhook": process.env.debug_webhook, 12 | "use_multiple_tokens": process.env.use_multiple_tokens.toLowerCase() == "true", 13 | "tokens": process.env.tokens, 14 | "show_messages": process.env.show_messages.toLowerCase() == "true", 15 | "cache_codes": true, 16 | "improve_latency": typeof process.env.improve_latency != "undefined", 17 | "force_webserver": typeof process.env.force_webserver != "undefined" 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /database.js: -------------------------------------------------------------------------------- 1 | const fs = require("fs"); 2 | function onExit(obj, e) { 3 | fs.writeFileSync(obj.path, JSON.stringify(obj.jObj)); 4 | if (typeof e == "object") { 5 | console.log(e); 6 | } else if (e == "SIGINT") { 7 | process.exit(); 8 | } 9 | } 10 | 11 | class database { 12 | path = ""; 13 | jObj = {}; 14 | 15 | constructor(db_path, autosave=true) { 16 | if (!fs.existsSync(db_path)) { 17 | fs.writeFileSync(db_path, "{}"); 18 | } 19 | { 20 | let str = fs.readFileSync(db_path); 21 | this.jObj = JSON.parse(str != "" && str || "{}"); 22 | this.path = db_path; 23 | } 24 | if (autosave) { 25 | let obj = this; 26 | 27 | process.on('beforeExit', ()=>onExit(obj)); 28 | process.on('SIGINT', ()=>onExit(obj, "SIGINT")); 29 | process.on('uncaughtException', (e)=>onExit(obj, e)); 30 | } 31 | } 32 | 33 | save = function() { 34 | fs.writeFileSync(this.path, JSON.stringify(this.jObj)); 35 | } 36 | 37 | assureValueExists = function(path, value) { 38 | let obj = this.jObj; 39 | let sp = path.split("/"); 40 | let plen = sp.length; 41 | sp.forEach((v,i) => { 42 | if (obj[v] == undefined && plen-1 == i) 43 | obj[v] = value; 44 | if (obj[v] == undefined) 45 | obj[v] = {}; 46 | obj = obj[v]; 47 | }); 48 | } 49 | 50 | getValue = function(path) { 51 | let obj = this.jObj; 52 | path.split("/").forEach(v => { 53 | obj = obj[v]; 54 | }); 55 | return obj; 56 | } 57 | 58 | setValue = function(path, value) { 59 | let obj = this.jObj; 60 | let sp = path.split("/"); 61 | let key = sp.pop(); 62 | sp.forEach(v => { 63 | obj = obj[v]; 64 | }); 65 | obj[key] = value; 66 | return obj; 67 | } 68 | } 69 | 70 | module.exports = database; -------------------------------------------------------------------------------- /main.js: -------------------------------------------------------------------------------- 1 | const fs = require("fs"); 2 | const gifts = require("./gift"); 3 | const dclient = require("./client.js"); 4 | const config = require("./config"); 5 | const webserver = require("./webserver"); 6 | 7 | function handleEvent(packet) { 8 | if (packet.t == "MESSAGE_CREATE") { 9 | if (config["show_messages"]) 10 | console.log(`${packet.d.author.username} > ${packet.d.content}`); 11 | gifts.checkForGift(packet); 12 | } 13 | } 14 | 15 | let tokens = []; 16 | let clients = []; 17 | 18 | if (config["read_messages_on_redeem_account"]) 19 | tokens.push(config["d_token"]); 20 | 21 | if (config["use_multiple_tokens"]) { 22 | if (config["tokens_file"] != undefined) { 23 | let t = fs.readFileSync(config["tokens_file"]).toString(); 24 | t.split("\n").forEach(v=>{ 25 | v = v.split(";")[0].trim(); 26 | if (v != "") 27 | tokens.push(v); 28 | }) 29 | } else { 30 | config["tokens"].split(";").forEach(v=>{ 31 | v = v.trim(); 32 | if (v != "") 33 | tokens.push(v); 34 | }) 35 | } 36 | } 37 | 38 | if (process.env.PORT || config["force_webserver"]) { 39 | webserver.startWebServer(process.env.PORT) 40 | } 41 | 42 | (async ()=>{ 43 | gifts.Init(); 44 | for (let i = 0; i < tokens.length; i++) { 45 | console.log(`Creating dclient for account n${i}`) 46 | 47 | var cl = new dclient(tokens[i], config.d_gateway, handleEvent); 48 | cl.connect(); 49 | 50 | clients[i] = cl; 51 | await new Promise(res=>setTimeout(res, 1000)); 52 | } 53 | 54 | setInterval(() => { 55 | const datenow = Date.now(); 56 | let msg = ""; 57 | 58 | for (idx in clients) { 59 | let cl = clients[idx]; 60 | 61 | if (datenow - cl.last_heartbeat_timestamp > 100000) { 62 | cl.disconnect(); 63 | msg += `Account n${idx} last heartbeat was ${~~(datenow - cl.last_heartbeat_timestamp)/1000}s ago\n`; 64 | } 65 | } 66 | 67 | if (msg != "") { 68 | msg = msg.trim(); 69 | console.log(msg); 70 | 71 | gifts.sendWebhook(config.d_err_webhook ? config.d_err_webhook : config.d_webhook, JSON.stringify({ 72 | "embeds": [{ 73 | "color": 0xFF0000, 74 | "description": msg, 75 | "footer": { 76 | "text": "All above were attempted to reconnect" 77 | }, 78 | } 79 | ] 80 | })); 81 | } 82 | }, 60000); 83 | })(); -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # nitrsnip 2 | a discord nitrosniper which actualy tries to be fast instead of mindlessly using packages 3 | 4 | it does one thing and one only, snipe nitros, hopefully fast, no other BS (like fancy output and ascii art) 5 | # features 6 | Uses it's own HTTP and discord client 7 | 8 | multi-token sniping support 9 | 10 | uses minimal ammount of packages (uses 3 packages, 2 of which are native modules, and has total of 6 dependencies) 11 | 12 | functional push notifications !! 13 | # setup 14 | download the repo as zip (green "Code" button, then click "Download ZIP"), extract it somewhere 15 | 16 | or you can also clone the repo 17 | 18 | get node, run `npm install` in the root directory (you must have a c/c++ compiler, easiest way is to do it through node setup), configure 19 | 20 | then to run it do `node .` in the root directory 21 | # config 22 | create directory `files`, and in there create new file named `config.json`, which is the main config file 23 | 24 | for webhook, enter only the path 25 | 26 | ex. `https://discord.com/api/webhooks/11111111/xx-xx` becomes `/api/webhooks/11111111/xx-xx` 27 | 28 | example main config: 29 | ```json 30 | { 31 | "d_token": "", 32 | "d_gateway": "wss://gateway.discord.gg/?encoding=etf&v=9&compress=zlib-stream", 33 | "d_webhook": "/api/webhooks/11111111/xx-xx", 34 | "d_err_webhook": "/api/webhooks/222222/yy-yy", 35 | "read_messages_on_redeem_account": true, 36 | "use_multiple_tokens": true, 37 | "tokens_file": "files/tokens.txt", 38 | "show_messages": true 39 | } 40 | ``` 41 | the token file supports comments (';'), tokens are seperated by new line 42 | ex. 43 | ``` 44 | xxxx.xxxx ;1 comment 45 | yyyy.yyyy; 2 46 | ``` 47 | 48 | each option explanation 49 | ```json 50 | "d_token" main discord token 51 | "d_gateway" discord gateway, copy the value from example there 52 | "d_webhook" webhook to send notifications about snipe attempts to 53 | "d_err_webhook" webhook to send errors to 54 | "read_messages_on_redeem_account" snipe nitros from your main discord token 55 | "use_multiple_tokens" use multiple tokens, if set to true, it will load tokens from "tokens_file" 56 | "tokens_file" path to file with tokens, can be empty if "use_multiple_tokens" is set to false 57 | "show_messages" if set to true, will log messages to the console 58 | "tokens" used if "tokens_file" is not specified, tokens seperated by ; (no comment support) 59 | "cache_codes" when true, codes are only temporarily cached till the application is restarted, when false they are saved on exit and loaded upon startup 60 | "improve_latency" improves latency on first request after HTTP connect by attempting to claim a random code, might be a risky thing to enable (might not actually make a difference because when this is not enabled, it instead just checks if a random code exists without authentication) 61 | "force_webserver" forces to start a web server even when env var PORT is not set (*ahem* replit *ahem*) 62 | ``` 63 | all config can also be specified using env vars, env vars are only used if the directory files does not exist 64 | # latency vs total time 65 | basicaly, latency is what you care about, for full explanation refer to this issue 66 | 67 | https://github.com/yafyz/nitrsnip/issues/4#issuecomment-904839322 68 | # heroku 69 | heroku deploying and auto-updater was moved to [yafyz/nitrsnip_deploy](https://github.com/yafyz/nitrsnip_deploy) 70 | # TBA 71 | make it so it does not make such a big fingerprint (tbh not like others snipers even try to) 72 | # other 73 | snipe at your own risk as its against discord ToS, probably 74 | 75 | the speed heavily depends on your latency to discord servers (or cloudflare servers, idk, discord website is behind cloudflare proxy), on AWS you often get sub 55ms response times (lowest 33ms) 76 | 77 | idk rewrite in future maybe but idk anymore 78 | 79 | if you got speed improvements ideas, contact me, pull request, whatever floats your boat 80 | 81 | you can donate me BTC `bc1qpxh9vkx4w2r2vcu3ynz7aukfds6szuaut9553z` or ETH `0x77fA812c6AbAbf2bE50Aae5Bf499551F57b57932`, any donation is appreciated 82 | 83 | or you can also star the repo, i don't like asking for it but everyone else does it and has way more stars 84 | 85 | # contact 86 | discord: fyz#0300 87 | 88 | do not message me about settings this up pls 89 | -------------------------------------------------------------------------------- /client.js: -------------------------------------------------------------------------------- 1 | const zlib = require("zlib-sync"); 2 | const WebSocket = require("ws"); 3 | const erlpack = require("erlpack"); 4 | const op = require("./op"); 5 | const {reportErr} = require("./gift") 6 | const ZLIB_SUFFIX = 0x0000FFFF; 7 | 8 | class discord_client { 9 | #handleEvent = null; 10 | 11 | #auth_data = null; 12 | #d_gateway = null; 13 | 14 | #zlib_inflate = null; 15 | 16 | #ws = null; 17 | #heartbeat_id = null; 18 | 19 | #got_heartbeat_ack = null; 20 | #last_sequence = null; 21 | last_heartbeat_timestamp = Date.now(); 22 | 23 | constructor(token, gateway, eventHandler) { 24 | this.#d_gateway = gateway; 25 | this.#auth_data = erlpack.pack({"op":op.IDENTIFY,"d":{"token":token,"capabilities":61,"properties":{"os":"Windows","browser":"Discord Client","release_channel":"stable","client_version":"1.0.9001","os_version":"10.0.19041","os_arch":"x64","system_locale":"en-US","client_build_number":84941,"client_event_source":null},"presence":{"status":"invisible","since":0,"activities":[],"afk":true},"compress":false,"client_state":{"guild_hashes":{},"highest_last_message_id":"0","read_state_version":0,"user_guild_settings_version":-1}}}) 26 | this.#handleEvent = eventHandler; 27 | } 28 | 29 | connect = function() { 30 | this.#ws = new WebSocket(this.#d_gateway); 31 | this.#ws.on("error", (e)=>{reportErr(e, 0x301800, "Caught ws error, things are hopefully fine")}); 32 | 33 | this.#ws.on("open", ()=>{ 34 | this.#zlib_inflate = new zlib.Inflate({chunkSize: 65535, flush: zlib.Z_SYNC_FLUSH}); 35 | this.#got_heartbeat_ack = true; 36 | console.log("| WS | OPEN"); 37 | this.#ws.send(this.#auth_data); 38 | console.log("| WS | SENT AUTH"); 39 | }) 40 | 41 | this.#ws.on('message', data => { 42 | if (data.readUInt32BE(data.length-4) ^ ZLIB_SUFFIX == 0) { 43 | this.#zlib_inflate.push(data, zlib.Z_SYNC_FLUSH); 44 | if (this.#zlib_inflate.result == undefined) 45 | return; 46 | 47 | this.#handlePacket(erlpack.unpack(this.#zlib_inflate.result)); 48 | } else { 49 | console.log(data.buffer.toString()); 50 | } 51 | }); 52 | 53 | this.#ws.on("close", async ()=>{ 54 | console.log("on_disconect"); 55 | await new Promise(res=>setTimeout(res, 1000)); 56 | this.connect(); 57 | }); 58 | } 59 | 60 | disconnect = function() { 61 | this.#ws.close(); 62 | } 63 | 64 | #handlePacket = function (packet) { 65 | if (packet.s != undefined) 66 | this.#last_sequence = packet.s; 67 | 68 | switch (packet.op) { 69 | case op.DISPATCH: 70 | this.#handleEvent(packet) 71 | break 72 | case op.HELLO: 73 | const heartbeat_interval = packet.d.heartbeat_interval; 74 | console.log("| OP | HELLO"); 75 | console.log(`| SET | heartbeat_interval: ${heartbeat_interval}`); 76 | if (this.#heartbeat_id != null) 77 | clearInterval(this.#heartbeat_id) 78 | this.#heartbeat_id = setInterval(()=>{ 79 | if (!this.#got_heartbeat_ack) { 80 | this.#ws.close(); 81 | return; 82 | } 83 | if (this.#ws != null && this.#ws.OPEN) { 84 | this.#ws.send(erlpack.pack({op: op.HEARTBEAT, d: this.#last_sequence})); 85 | console.log("| HEARTBEAT |"); 86 | this.#got_heartbeat_ack = false; 87 | } 88 | }, heartbeat_interval); 89 | break 90 | case op.HEARBEAT_ACK: 91 | this.#got_heartbeat_ack = true; 92 | this.last_heartbeat_timestamp = Date.now(); 93 | console.log("| OP | HEARBEAT_ACK"); 94 | break 95 | case op.RECONNECT: 96 | console.log("| OP | RECONNECT"); 97 | this.#ws.close(); 98 | break; 99 | case op.INVALID_SESSION: 100 | console.log("| OP | INVALID_SESSION"); 101 | this.#ws.close(); 102 | break; 103 | default: 104 | console.log(`| OP | ${JSON.stringify(packet)}`); 105 | } 106 | } 107 | } 108 | 109 | module.exports = discord_client -------------------------------------------------------------------------------- /gift.js: -------------------------------------------------------------------------------- 1 | const node_http = require("https") 2 | const regex_str = /(?:discord\.gift|(?:discord|discordapp)\.com\/gifts)\/([A-Za-z0-9]+)/g 3 | const config = require("./config"); 4 | const http_client = new (require("./http"))("discord.com"); 5 | 6 | let db; 7 | if (!config["cache_codes"]) 8 | db = new (require("./database"))("files/db.json") 9 | 10 | const rawph1 = "POST /api/v9/entitlements/gift-codes/"; 11 | const rawph2 = `/redeem HTTP/1.1 12 | host: discord.com 13 | content-length: 0 14 | authorization: ${config.d_token}\r\n\r\n`; 15 | 16 | const chars = Buffer.from("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"); 17 | const charslen = chars.length; 18 | 19 | function reportErr(e, color = 3092790, title = "Uncaught error") { 20 | console.log(e); 21 | if (config.d_err_webhook == "" || typeof config.d_err_webhook != "string") { 22 | console.log("Invalid error report webhook"); 23 | return; 24 | } 25 | if (e.stack != null && e.stack != undefined) 26 | sendWebhook(config.d_err_webhook, JSON.stringify({"embeds": [{"title": title, "color": color,"description": e.stack}]})); 27 | else 28 | sendWebhook(config.d_err_webhook, JSON.stringify({"embeds": [{"title": title, "color": color,"description": "Stack was null"}]})); 29 | } 30 | let code_queue = [] 31 | let get_code_status; 32 | let set_code_status; 33 | 34 | 35 | function randomString(len) { 36 | let buf = Buffer.allocUnsafe(len); 37 | for (let i = 0; i < len; i++) 38 | buf[i] = chars[~~(Math.random()*charslen)]; 39 | return buf.toString("ascii"); 40 | } 41 | 42 | function Init() { 43 | process.on('uncaughtException', reportErr); 44 | if (config["cache_codes"]) { 45 | let cache = []; 46 | get_code_status = (code) => cache[code]; 47 | set_code_status = (code, status) => cache[code] = status; 48 | } else { 49 | db.assureValueExists("codes", {}); 50 | get_code_status = (code) => db.getValue("codes")[code]; 51 | set_code_status = (code, status) => db.getValue("codes")[code] = status; 52 | } 53 | http_client.connect(true, () => { 54 | if (code_queue.length > 0) { 55 | for (const v of code_queue) 56 | handleGift(v[0], v[1]) 57 | } else { 58 | if (config.improve_latency) 59 | http_client.request_raw(rawph1+randomString(16)+rawph2); 60 | else 61 | http_client.request("POST", "/api/v9/entitlements/gift-codes/"+Math.random()) 62 | } 63 | }); 64 | } 65 | 66 | async function sendWebhook(webhook, body) { 67 | let req = node_http.request({ 68 | method: "POST", 69 | path: webhook, 70 | host: "discord.com", 71 | headers: { 72 | "content-type": "application/json" 73 | } 74 | }); 75 | req.write(body); 76 | req.end(); 77 | } 78 | 79 | async function reportGiftStatus(code, payload, res, latency, timethen) { 80 | let body = res.Body.toString() 81 | console.log(`| REDEEM | ${body}`); 82 | let js = {}; 83 | try { 84 | js = JSON.parse(body); 85 | } catch (error) { 86 | reportErr(error); 87 | return; 88 | } 89 | 90 | if (js.code == 10038) // Code invalid 91 | set_code_status(code, 0); 92 | else if (js.code == 50050) // Code claimed 93 | set_code_status(code, 1); 94 | else if (js.code == 50070) // Gamepass code 95 | set_code_status(code, 2); 96 | else if (js.consumed == true) // Code valid 97 | set_code_status(code, 3); 98 | 99 | sendWebhook(config.d_webhook, JSON.stringify({ 100 | "embeds": [{ 101 | "title": `${code}`, 102 | "color": js.code == 10038 ? 15417396 : 103 | js.code == 50050 ? 15258703 : 104 | js.code == 50070 ? 4360181 : 105 | js.consumed == true ? 1237834 : 0, 106 | "footer": { 107 | "text": `Latency: ${latency}ms, Total time: ${res.pt[0]-Math.floor(Number.parseInt(BigInt(payload.id) >> 22n) + 1420070400000)}ms` 108 | }, 109 | "fields": [ 110 | {"name": "Message", "value": payload.content, "inline": true}, 111 | {"name": "Message link", "value": `https://discord.com/channels/${payload.guild_id}/${payload.channel_id}/${payload.id}`, "inline": true}, 112 | {"name": "Response", "value": body.replace(/"/g, "\\\""), "inline": true} 113 | ] 114 | } 115 | ] 116 | })); 117 | 118 | if (config.debug_webhook) { 119 | let tstr = ""; 120 | for (let t of res.pt) 121 | tstr += (t-timethen)+"/"; 122 | tstr = tstr.substr(0, tstr.length-1); // remove extra "/" 123 | 124 | sendWebhook(config.debug_webhook, JSON.stringify({ 125 | "embeds": [{ 126 | "color": 16515934, 127 | "title": code, 128 | "footer": {"text": `t: ${tstr}ms`}, 129 | "description": res.Raw 130 | } 131 | ] 132 | })); 133 | } 134 | } 135 | 136 | function handleGift(code, payload) { 137 | if (get_code_status(code) != undefined) 138 | return; 139 | if (!http_client.is_connected) { 140 | code_queue.push([code, payload]) 141 | return 142 | } 143 | // synchronously send the request/imediately 144 | let res = http_client.request_raw(rawph1+code+rawph2); 145 | let timethen = Date.now(); // get time after sending for minimal latency instead of before 146 | console.log(`| GIFT | '${code}'`); 147 | (async ()=>{ // wait for it asynchronously 148 | res = await res; 149 | if (res.Error != null) 150 | reportErr(err); 151 | reportGiftStatus(code, payload, res, Date.now()-timethen, timethen); 152 | })(); 153 | } 154 | 155 | function checkForGift(packet) { 156 | for (const match of packet.d.content.matchAll(regex_str)) { 157 | let mlen = match[1].length; 158 | if (mlen === 16 /* normal code length */ /* || mlen === 24 gamepass/game code length - gamepass codes suck */) 159 | handleGift(match[1], packet.d); 160 | } 161 | } 162 | 163 | module.exports = { 164 | checkForGift: checkForGift, 165 | Init: Init, 166 | reportErr: reportErr, 167 | sendWebhook: sendWebhook 168 | } -------------------------------------------------------------------------------- /http.js: -------------------------------------------------------------------------------- 1 | const tls = require("tls"); 2 | 3 | class Response { 4 | StatusCode = "0"; 5 | StatusMessage = ""; 6 | Headers = {}; 7 | ContentLength = 0; 8 | Body = null; 9 | Raw = null; 10 | Error = null; 11 | pt = null; 12 | } 13 | 14 | class httpClient { 15 | socket = null; 16 | options = null; 17 | is_connected = false; 18 | reconnect = false; 19 | 20 | #queue = []; 21 | #is_reconnect = false; 22 | 23 | constructor(host) { 24 | this.options = { 25 | host: host, 26 | port: 443, 27 | rejectUnauthorized:false, 28 | requestCert:false 29 | } 30 | } 31 | connect = function(reconnect, on_reconnect_callback = ()=>{}) { 32 | return new Promise(res=> { 33 | this.reconnect = reconnect; 34 | this.socket = tls.connect(this.options, ()=>{ 35 | this.is_connected = true; 36 | //if (this.#is_reconnect) 37 | on_reconnect_callback(); 38 | res(this.socket.authorized); 39 | }); 40 | this.socket.setEncoding("ascii"); 41 | this.socket.on("data", d => { 42 | if (this.#queue.length > 0) 43 | if (this.#queue[0](d)) 44 | this.#queue.shift(); 45 | }) 46 | if (this.reconnect) { 47 | this.socket.on("end", ()=>{ 48 | console.log("ended") 49 | this.is_connected = false; 50 | this.#is_reconnect = true; 51 | this.connect(this.reconnect, on_reconnect_callback); 52 | }) 53 | } 54 | }); 55 | } 56 | 57 | #get_response = function() { 58 | return new Promise(resolve=>{ 59 | let res = new Response(); 60 | res.Raw = ""; 61 | res.pt = []; 62 | 63 | let content_filled = 0; 64 | 65 | let is_chunked = false; 66 | let got_chunks = false; 67 | let headers_finished = false; 68 | let last_chunk_end = ""; 69 | let chunks = []; 70 | 71 | let callback = d => { 72 | res.pt.push(Date.now()); 73 | res.Raw += d; 74 | 75 | if (res.StatusCode == "0" || !headers_finished) { 76 | d = res.Raw; 77 | 78 | let hthead = d.indexOf("\r\n"); 79 | let scode_start = d.indexOf(" "); 80 | let scode_end = d.indexOf(" ", scode_start+1); 81 | res.StatusCode = d.slice(scode_start+1, scode_end); 82 | res.StatusMessage = d.slice(scode_end+1, hthead); 83 | 84 | let lastidx = hthead+2; 85 | let nextidx; 86 | let hstr; 87 | let colonidx; 88 | 89 | while (true) { 90 | nextidx = d.indexOf("\r\n", lastidx); 91 | if (lastidx > nextidx) 92 | return false; 93 | hstr = d.slice(lastidx, nextidx); 94 | lastidx = nextidx+2; 95 | if (hstr === "") 96 | break; 97 | colonidx = hstr.indexOf(":"); 98 | res.Headers[hstr.slice(0,colonidx).toLowerCase()] = hstr.slice(colonidx+2); 99 | } 100 | 101 | headers_finished = true; 102 | 103 | res.ContentLength = Number.parseInt(res.Headers["content-length"]); 104 | if (!isNaN(res.ContentLength)) { 105 | res.Body = Buffer.allocUnsafe(res.ContentLength); 106 | let bstring = d.slice(lastidx); 107 | content_filled += res.Body.write(bstring, content_filled); 108 | } else { 109 | is_chunked = true; 110 | let szlen = d.indexOf("\r\n", lastidx); 111 | let len = Number.parseInt(d.slice(lastidx, szlen), 16); 112 | let bstring = d.slice(szlen+2); 113 | res.Body = Buffer.allocUnsafe(len); 114 | res.Body.write(bstring,0); 115 | content_filled += res.Body.write(bstring, 0); 116 | } 117 | } else { 118 | if (!is_chunked) { 119 | content_filled += res.Body.write(d, content_filled); 120 | } else { 121 | try { 122 | if (d == "0\r\n\r\n") { 123 | got_chunks = true; 124 | if (chunks.length > 0) { 125 | chunks.push(res.Body); 126 | res.Body = Buffer.concat(chunks); 127 | } 128 | } else { 129 | if (last_chunk_end == "\r\n") { 130 | chunks.push(res.Body); 131 | let szlen = d.indexOf("\r\n"); 132 | let len = Number.parseInt(d.slice(0, szlen), 16); 133 | res.Body = Buffer.allocUnsafe(len); 134 | content_filled = 0; 135 | d = d.slice(szlen+2); 136 | } 137 | last_chunk_end = d.slice(-2); 138 | if (last_chunk_end == "\r\n") 139 | d = d.slice(0,-2); 140 | content_filled += res.Body.write(d, content_filled); 141 | } 142 | } catch (ex) { 143 | res.Error = ex; 144 | resolve(res); 145 | } 146 | } 147 | } 148 | if (content_filled >= res.ContentLength || got_chunks) { 149 | resolve(res); 150 | return true; 151 | } 152 | }; 153 | this.#queue.push(callback); 154 | }); 155 | } 156 | 157 | request = function (method, path, headers={Host: this.options.host}, body = "", get_response=true) { 158 | let payload = `${method} ${path} HTTP/1.1\n`; 159 | if (headers["Host"] == undefined) 160 | payload += `Host: ${this.options.host}\n`; 161 | if (body.length > 0 || method != "GET") 162 | payload += `Content-Length: ${body.length}\n`; 163 | for (const v in headers) 164 | payload += `${v}: ${headers[v]}\n`; 165 | payload += "\n"; 166 | if (body.length != 0) 167 | payload += body; 168 | this.socket.write(payload); 169 | if (get_response) 170 | return this.#get_response(); 171 | return; 172 | } 173 | 174 | request_raw = function(payload="") { 175 | this.socket.write(payload); 176 | return this.#get_response(); 177 | } 178 | } 179 | 180 | module.exports = httpClient --------------------------------------------------------------------------------