From e9b0c9122ea212e365b7d256462a377a3f6786cb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?D=E1=A5=B2=E1=A5=92=CE=B9=E1=A5=B1=E1=A5=A3?= <105122695+daniscript18@users.noreply.github.com> Date: Mon, 3 Jul 2023 06:06:52 +0000 Subject: [PATCH] =?UTF-8?q?=F0=9F=8E=80=20First=20version=20=E2=80=94=201.?= =?UTF-8?q?0.0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 2 + README.md | 66 +++++++++++++++- best-samp-query.js | 193 +++++++++++++++++++++++++++++++++++++++++++++ package.json | 30 +++++++ 4 files changed, 290 insertions(+), 1 deletion(-) create mode 100644 .gitignore create mode 100644 best-samp-query.js create mode 100644 package.json diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..cfa995e --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +node_modules/* +test.js \ No newline at end of file diff --git a/README.md b/README.md index a623c18..fbcd90e 100644 --- a/README.md +++ b/README.md @@ -1 +1,65 @@ -# best-samp-query \ No newline at end of file +# πŸ“¦ best-samp-query + +Simplified Query API for SAMP: Efficient and easy retrieval of information from the server πŸ”₯ + +## πŸ’Ύ Installation + +```bash +npm install best-samp-query +``` + +## πŸ“‹ Options + +- `host` β€” **Required** +- `port` β€” *Default: 7777* β€” **Optional** +- `timeout` β€” *Default: 1000* β€” **Optional** + +## 🎁 Code example + +```javascript +const Query = require("best-samp-query"); +const Options = { + host: "135.148.89.12", + port: 7777, + timeout: 1000 +}; +Query(Options, function (err, res) { + if(error) return console.error(err); + else return console.log(res); +}); +``` +## 🎁 Sample output + +```javascript +{ + online: 11, + address: "135.148.89.12", + port: 7777, + hostname: ".:( PuroDesmadre V ):. [ DM ] + [ FreeRoam ]", + gamemode: "Dm/FreeRoam/Derby", + mapname: "Espaсol/Latino", + passworded: false, + maxplayers: 50, + rules: { + lagcomp: false, + mapname: "San Andreas", + version: "0.3.7-R2", + weather: 10, + weburl: "discord.gg/BjUGcpcYUt", + worldtime: "12:00" + }, + players: [ + { id: 0, name: "Neiikos", score: 323, ping: 101 }, + { id: 1, name: "vorTeX", score: 2359, ping: 163 }, + { id: 2, name: "Kis4Me", score: 1000822, ping: 157 }, + { id: 3, name: "Benjamin_Otero", score: 0, ping: 202 }, + { id: 4, name: "Oier_Millan", score: 4340, ping: 102 }, + { id: 6, name: ".Gs.Ahm6d6l6.Vl", score: 1729246, ping: 127 }, + { id: 7, name: "Canserbero.Tss", score: 1512, ping: 280 }, + { id: 8, name: "cumtrol", score: 267, ping: 66 }, + { id: 10, name: "benja_guerrero", score: 11, ping: 224 }, + { id: 12, name: "pato_pinuer", score: 30, ping: 178 }, + { id: 14, name: "zoom_saaaa", score: 20110, ping: 137 } + ] +} +``` diff --git a/best-samp-query.js b/best-samp-query.js new file mode 100644 index 0000000..d5d4984 --- /dev/null +++ b/best-samp-query.js @@ -0,0 +1,193 @@ +const dgram = require("dgram"); +const iconv = require("iconv-lite") + +const error = (text) => { + new Error(text); +} + +const query = async function (options, callback) { + let self = this; + let response = { online: 0 }; + + options.port = options.port || 7777; + options.timeout = options.timeout || 1000; + + if(!options.host) return callback.apply(options, [ "Invalid \"host\" passed" ]); + if(!isFinite(options.port) || options.port < 1 || options.port > 65535) return callback.apply(options, [ `Invalid port "${options.port}". Port mus"t be larger than 1 and less than 65535` ]); + + request.call(self, options, "i", async function (error, information) { + if(error) return callback.apply(options, [ error ]) + + response.address = options.host; + response.port = options.port; + response.hostname = information.hostname; + response.gamemode = information.gamemode; + response.mapname = information.mapname; + response.passworded = Boolean(information.passworded); + response.maxplayers = information.maxplayers; + response.online = information.players; + + request.call(self, options, "r", async function (error, rules) { + if(error) return callback.apply(options, [ error ]) + + rules.lagcomp = rules.lagcomp === "On" ? true : false; + rules.weather = parseInt(rules.weather, 10); + response.rules = rules; + + if(response.online > 100) { + response.players = [] + + return callback.apply(options, [ false, response ]) + } + else { + request.call(self, options, "d", function(error, players) { + if(error) return callback.apply(options, [ error ]) + + response.players = players; + + return callback.apply(options, [ false, response ]) + }); + } + }); + }); +}; + +const request = function (options, opcode, callback) { + let socket = dgram.createSocket("udp4"); + let packet = Buffer.alloc(11); + + packet.write("SAMP"); + + for(let i = 0; i < 4; ++i) packet[i + 4] = options.host.split(".")[i]; + + packet[8] = options.port & 0xff; + packet[9] = (options.port >> 8) & 0xff; + packet[10] = opcode.charCodeAt(0); + + try { + socket.send(packet, 0, packet.length, options.port, options.host, function (error, bytes) { + if(error) return callback.apply(options, [error]); + }); + } catch (error) { + return callback.apply(options, [error]); + } + + let controller = undefined; + + let onTimeOut = () => { + socket.close(); + return callback.apply(options, ["Socket timed out."]); + }; + + controller = setTimeout(onTimeOut, options.timeout); + + socket.on("message", function (message) { + if(controller) clearTimeout(controller); + if(message.length < 11) return callback.apply(options, ["Socket invalid"]); + else { + socket.close(); + + message = message.slice(11); + + let object = {}; + let array = []; + let strlen = 0; + let offset = 0; + + try { + if(opcode == "i") { + object.passworded = message.readUInt8(offset); + offset += 1; + + object.players = message.readUInt16LE(offset); + offset += 2; + + object.maxplayers = message.readUInt16LE(offset); + offset += 2; + + strlen = message.readUInt16LE(offset); + offset += 4; + + object.hostname = decode(message.slice(offset, (offset += strlen))); + + strlen = message.readUInt16LE(offset); + offset += 4; + + object.gamemode = decode(message.slice(offset, (offset += strlen))); + + strlen = message.readUInt16LE(offset); + offset += 4; + + object.mapname = decode(message.slice(offset, (offset += strlen))); + + return callback.apply(options, [false, object]); + } + + if(opcode == "r") { + let rulecount = message.readUInt16LE(offset); + offset += 2; + + let property, + value = undefined; + + while(rulecount) { + strlen = message.readUInt8(offset); + ++offset; + + property = decode(message.slice(offset, (offset += strlen))); + + strlen = message.readUInt8(offset); + ++offset; + + value = decode(message.slice(offset, (offset += strlen))); + + object[property] = value; + + --rulecount; + } + + return callback.apply(options, [false, object]); + } + + if (opcode == "d") { + let playercount = message.readUInt16LE(offset); + offset += 2; + + let player = undefined; + + while(playercount) { + player = {}; + + player.id = message.readUInt8(offset); + ++offset; + + strlen = message.readUInt8(offset); + ++offset; + + player.name = decode(message.slice(offset, (offset += strlen))); + + player.score = message.readUInt32LE(offset); + offset += 4; + + player.ping = message.readUInt16LE(offset); + offset += 4; + + array.push(player); + + --playercount; + } + + return callback.apply(options, [false, array]); + } + } catch (exception) { + return callback.apply(options, [exception]); + } + } + }); +}; + +const decode = (buffer) => { + return iconv.decode(buffer, "win1251"); +}; + +module.exports = query; \ No newline at end of file diff --git a/package.json b/package.json new file mode 100644 index 0000000..852b775 --- /dev/null +++ b/package.json @@ -0,0 +1,30 @@ +{ + "name": "best-samp-query", + "description": "Simplified Query API for SAMP: Efficient and easy retrieval of information from the server πŸ”₯", + "version": "1.0.0", + "main": "best-samp-query.js", + "license": "MIT", + "author": "daniscript18", + "keywords": [ + "samp", + "sa-mp", + "fetch", + "sa mp", + "query", + "util", + "tool", + "nodejs", + "samp-query" + ], + "repository": { + "type": "git", + "url": "https://github.com/daniscript18/best-samp-query.git" + }, + "bugs": { + "url": "https://github.com/daniscript18/best-samp-query/issues" + }, + "homepage": "https://github.com/daniscript18/best-samp-query", + "dependencies": { + "iconv-lite": "^0.6.3" + } +} \ No newline at end of file