Skip to content

Commit e9b0c91

Browse files
committed
πŸŽ€ First version β€” 1.0.0
1 parent fe2b915 commit e9b0c91

File tree

4 files changed

+290
-1
lines changed

4 files changed

+290
-1
lines changed

β€Ž.gitignoreβ€Ž

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
node_modules/*
2+
test.js

β€ŽREADME.mdβ€Ž

Lines changed: 65 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,65 @@
1-
# best-samp-query
1+
# πŸ“¦ best-samp-query
2+
3+
Simplified Query API for SAMP: Efficient and easy retrieval of information from the server πŸ”₯
4+
5+
## πŸ’Ύ Installation
6+
7+
```bash
8+
npm install best-samp-query
9+
```
10+
11+
## πŸ“‹ Options
12+
13+
- `host` β€” **Required**
14+
- `port` β€” *Default: 7777* β€” **Optional**
15+
- `timeout` β€” *Default: 1000* β€” **Optional**
16+
17+
## 🎁 Code example
18+
19+
```javascript
20+
const Query = require("best-samp-query");
21+
const Options = {
22+
host: "135.148.89.12",
23+
port: 7777,
24+
timeout: 1000
25+
};
26+
Query(Options, function (err, res) {
27+
if(error) return console.error(err);
28+
else return console.log(res);
29+
});
30+
```
31+
## 🎁 Sample output
32+
33+
```javascript
34+
{
35+
online: 11,
36+
address: "135.148.89.12",
37+
port: 7777,
38+
hostname: ".:( PuroDesmadre V ):. [ DM ] + [ FreeRoam ]",
39+
gamemode: "Dm/FreeRoam/Derby",
40+
mapname: "Espaсol/Latino",
41+
passworded: false,
42+
maxplayers: 50,
43+
rules: {
44+
lagcomp: false,
45+
mapname: "San Andreas",
46+
version: "0.3.7-R2",
47+
weather: 10,
48+
weburl: "discord.gg/BjUGcpcYUt",
49+
worldtime: "12:00"
50+
},
51+
players: [
52+
{ id: 0, name: "Neiikos", score: 323, ping: 101 },
53+
{ id: 1, name: "vorTeX", score: 2359, ping: 163 },
54+
{ id: 2, name: "Kis4Me", score: 1000822, ping: 157 },
55+
{ id: 3, name: "Benjamin_Otero", score: 0, ping: 202 },
56+
{ id: 4, name: "Oier_Millan", score: 4340, ping: 102 },
57+
{ id: 6, name: ".Gs.Ahm6d6l6.Vl", score: 1729246, ping: 127 },
58+
{ id: 7, name: "Canserbero.Tss", score: 1512, ping: 280 },
59+
{ id: 8, name: "cumtrol", score: 267, ping: 66 },
60+
{ id: 10, name: "benja_guerrero", score: 11, ping: 224 },
61+
{ id: 12, name: "pato_pinuer", score: 30, ping: 178 },
62+
{ id: 14, name: "zoom_saaaa", score: 20110, ping: 137 }
63+
]
64+
}
65+
```

β€Žbest-samp-query.jsβ€Ž

Lines changed: 193 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,193 @@
1+
const dgram = require("dgram");
2+
const iconv = require("iconv-lite")
3+
4+
const error = (text) => {
5+
new Error(text);
6+
}
7+
8+
const query = async function (options, callback) {
9+
let self = this;
10+
let response = { online: 0 };
11+
12+
options.port = options.port || 7777;
13+
options.timeout = options.timeout || 1000;
14+
15+
if(!options.host) return callback.apply(options, [ "Invalid \"host\" passed" ]);
16+
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` ]);
17+
18+
request.call(self, options, "i", async function (error, information) {
19+
if(error) return callback.apply(options, [ error ])
20+
21+
response.address = options.host;
22+
response.port = options.port;
23+
response.hostname = information.hostname;
24+
response.gamemode = information.gamemode;
25+
response.mapname = information.mapname;
26+
response.passworded = Boolean(information.passworded);
27+
response.maxplayers = information.maxplayers;
28+
response.online = information.players;
29+
30+
request.call(self, options, "r", async function (error, rules) {
31+
if(error) return callback.apply(options, [ error ])
32+
33+
rules.lagcomp = rules.lagcomp === "On" ? true : false;
34+
rules.weather = parseInt(rules.weather, 10);
35+
response.rules = rules;
36+
37+
if(response.online > 100) {
38+
response.players = []
39+
40+
return callback.apply(options, [ false, response ])
41+
}
42+
else {
43+
request.call(self, options, "d", function(error, players) {
44+
if(error) return callback.apply(options, [ error ])
45+
46+
response.players = players;
47+
48+
return callback.apply(options, [ false, response ])
49+
});
50+
}
51+
});
52+
});
53+
};
54+
55+
const request = function (options, opcode, callback) {
56+
let socket = dgram.createSocket("udp4");
57+
let packet = Buffer.alloc(11);
58+
59+
packet.write("SAMP");
60+
61+
for(let i = 0; i < 4; ++i) packet[i + 4] = options.host.split(".")[i];
62+
63+
packet[8] = options.port & 0xff;
64+
packet[9] = (options.port >> 8) & 0xff;
65+
packet[10] = opcode.charCodeAt(0);
66+
67+
try {
68+
socket.send(packet, 0, packet.length, options.port, options.host, function (error, bytes) {
69+
if(error) return callback.apply(options, [error]);
70+
});
71+
} catch (error) {
72+
return callback.apply(options, [error]);
73+
}
74+
75+
let controller = undefined;
76+
77+
let onTimeOut = () => {
78+
socket.close();
79+
return callback.apply(options, ["Socket timed out."]);
80+
};
81+
82+
controller = setTimeout(onTimeOut, options.timeout);
83+
84+
socket.on("message", function (message) {
85+
if(controller) clearTimeout(controller);
86+
if(message.length < 11) return callback.apply(options, ["Socket invalid"]);
87+
else {
88+
socket.close();
89+
90+
message = message.slice(11);
91+
92+
let object = {};
93+
let array = [];
94+
let strlen = 0;
95+
let offset = 0;
96+
97+
try {
98+
if(opcode == "i") {
99+
object.passworded = message.readUInt8(offset);
100+
offset += 1;
101+
102+
object.players = message.readUInt16LE(offset);
103+
offset += 2;
104+
105+
object.maxplayers = message.readUInt16LE(offset);
106+
offset += 2;
107+
108+
strlen = message.readUInt16LE(offset);
109+
offset += 4;
110+
111+
object.hostname = decode(message.slice(offset, (offset += strlen)));
112+
113+
strlen = message.readUInt16LE(offset);
114+
offset += 4;
115+
116+
object.gamemode = decode(message.slice(offset, (offset += strlen)));
117+
118+
strlen = message.readUInt16LE(offset);
119+
offset += 4;
120+
121+
object.mapname = decode(message.slice(offset, (offset += strlen)));
122+
123+
return callback.apply(options, [false, object]);
124+
}
125+
126+
if(opcode == "r") {
127+
let rulecount = message.readUInt16LE(offset);
128+
offset += 2;
129+
130+
let property,
131+
value = undefined;
132+
133+
while(rulecount) {
134+
strlen = message.readUInt8(offset);
135+
++offset;
136+
137+
property = decode(message.slice(offset, (offset += strlen)));
138+
139+
strlen = message.readUInt8(offset);
140+
++offset;
141+
142+
value = decode(message.slice(offset, (offset += strlen)));
143+
144+
object[property] = value;
145+
146+
--rulecount;
147+
}
148+
149+
return callback.apply(options, [false, object]);
150+
}
151+
152+
if (opcode == "d") {
153+
let playercount = message.readUInt16LE(offset);
154+
offset += 2;
155+
156+
let player = undefined;
157+
158+
while(playercount) {
159+
player = {};
160+
161+
player.id = message.readUInt8(offset);
162+
++offset;
163+
164+
strlen = message.readUInt8(offset);
165+
++offset;
166+
167+
player.name = decode(message.slice(offset, (offset += strlen)));
168+
169+
player.score = message.readUInt32LE(offset);
170+
offset += 4;
171+
172+
player.ping = message.readUInt16LE(offset);
173+
offset += 4;
174+
175+
array.push(player);
176+
177+
--playercount;
178+
}
179+
180+
return callback.apply(options, [false, array]);
181+
}
182+
} catch (exception) {
183+
return callback.apply(options, [exception]);
184+
}
185+
}
186+
});
187+
};
188+
189+
const decode = (buffer) => {
190+
return iconv.decode(buffer, "win1251");
191+
};
192+
193+
module.exports = query;

β€Žpackage.jsonβ€Ž

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
{
2+
"name": "best-samp-query",
3+
"description": "Simplified Query API for SAMP: Efficient and easy retrieval of information from the server πŸ”₯",
4+
"version": "1.0.0",
5+
"main": "best-samp-query.js",
6+
"license": "MIT",
7+
"author": "daniscript18",
8+
"keywords": [
9+
"samp",
10+
"sa-mp",
11+
"fetch",
12+
"sa mp",
13+
"query",
14+
"util",
15+
"tool",
16+
"nodejs",
17+
"samp-query"
18+
],
19+
"repository": {
20+
"type": "git",
21+
"url": "https://github.com/daniscript18/best-samp-query.git"
22+
},
23+
"bugs": {
24+
"url": "https://github.com/daniscript18/best-samp-query/issues"
25+
},
26+
"homepage": "https://github.com/daniscript18/best-samp-query",
27+
"dependencies": {
28+
"iconv-lite": "^0.6.3"
29+
}
30+
}

0 commit comments

Comments
Β (0)