Skip to content

Commit

Permalink
πŸŽ€ First version β€” 1.0.0
Browse files Browse the repository at this point in the history
  • Loading branch information
daniscript18 committed Jul 3, 2023
1 parent fe2b915 commit e9b0c91
Show file tree
Hide file tree
Showing 4 changed files with 290 additions and 1 deletion.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
node_modules/*
test.js
66 changes: 65 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1 +1,65 @@
# best-samp-query
# πŸ“¦ 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 }
]
}
```
193 changes: 193 additions & 0 deletions best-samp-query.js
Original file line number Diff line number Diff line change
@@ -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;
30 changes: 30 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
@@ -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"
}
}

0 comments on commit e9b0c91

Please sign in to comment.