gamedigz 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (73) hide show
  1. package/GAMES_LIST.md +453 -0
  2. package/LICENSE +21 -0
  3. package/README.md +144 -0
  4. package/bin/gamedig.js +79 -0
  5. package/lib/DnsResolver.js +76 -0
  6. package/lib/GlobalUdpSocket.js +69 -0
  7. package/lib/HexUtil.js +20 -0
  8. package/lib/Logger.js +45 -0
  9. package/lib/Promises.js +18 -0
  10. package/lib/ProtocolResolver.js +7 -0
  11. package/lib/QueryRunner.js +95 -0
  12. package/lib/Results.js +32 -0
  13. package/lib/game-resolver.js +17 -0
  14. package/lib/gamedig.js +23 -0
  15. package/lib/games.js +2747 -0
  16. package/lib/index.js +5 -0
  17. package/lib/reader.js +172 -0
  18. package/package.json +74 -0
  19. package/protocols/armagetron.js +65 -0
  20. package/protocols/asa.js +12 -0
  21. package/protocols/ase.js +45 -0
  22. package/protocols/assettocorsa.js +40 -0
  23. package/protocols/battlefield.js +162 -0
  24. package/protocols/beammp.js +32 -0
  25. package/protocols/beammpmaster.js +17 -0
  26. package/protocols/buildandshoot.js +55 -0
  27. package/protocols/core.js +349 -0
  28. package/protocols/cs2d.js +65 -0
  29. package/protocols/dayz.js +196 -0
  30. package/protocols/discord.js +29 -0
  31. package/protocols/doom3.js +148 -0
  32. package/protocols/eco.js +20 -0
  33. package/protocols/eldewrito.js +21 -0
  34. package/protocols/epic.js +95 -0
  35. package/protocols/ffow.js +38 -0
  36. package/protocols/fivem.js +33 -0
  37. package/protocols/gamespy1.js +181 -0
  38. package/protocols/gamespy2.js +144 -0
  39. package/protocols/gamespy3.js +197 -0
  40. package/protocols/geneshift.js +46 -0
  41. package/protocols/goldsrc.js +8 -0
  42. package/protocols/hexen2.js +14 -0
  43. package/protocols/index.js +61 -0
  44. package/protocols/jc2mp.js +16 -0
  45. package/protocols/kspdmp.js +28 -0
  46. package/protocols/mafia2mp.js +41 -0
  47. package/protocols/mafia2online.js +9 -0
  48. package/protocols/minecraft.js +102 -0
  49. package/protocols/minecraftbedrock.js +72 -0
  50. package/protocols/minecraftvanilla.js +87 -0
  51. package/protocols/mumble.js +39 -0
  52. package/protocols/mumbleping.js +24 -0
  53. package/protocols/nadeo.js +86 -0
  54. package/protocols/openttd.js +127 -0
  55. package/protocols/quake1.js +9 -0
  56. package/protocols/quake2.js +88 -0
  57. package/protocols/quake3.js +24 -0
  58. package/protocols/rfactor.js +69 -0
  59. package/protocols/samp.js +102 -0
  60. package/protocols/savage2.js +25 -0
  61. package/protocols/starmade.js +67 -0
  62. package/protocols/starsiege.js +10 -0
  63. package/protocols/teamspeak2.js +71 -0
  64. package/protocols/teamspeak3.js +69 -0
  65. package/protocols/terraria.js +24 -0
  66. package/protocols/tribes1.js +153 -0
  67. package/protocols/tribes1master.js +80 -0
  68. package/protocols/unreal2.js +150 -0
  69. package/protocols/ut3.js +45 -0
  70. package/protocols/valve.js +455 -0
  71. package/protocols/vcmp.js +10 -0
  72. package/protocols/ventrilo.js +237 -0
  73. package/protocols/warsow.js +13 -0
@@ -0,0 +1,349 @@
1
+ import { EventEmitter } from 'node:events'
2
+ import * as net from 'node:net'
3
+ import got from 'got'
4
+ import Reader from '../lib/reader.js'
5
+ import { debugDump } from '../lib/HexUtil.js'
6
+ import Logger from '../lib/Logger.js'
7
+ import DnsResolver from '../lib/DnsResolver.js'
8
+ import { Results } from '../lib/Results.js'
9
+ import Promises from '../lib/Promises.js'
10
+
11
+ let uid = 0
12
+
13
+ export default class Core extends EventEmitter {
14
+ constructor () {
15
+ super()
16
+ this.encoding = 'utf8'
17
+ this.byteorder = 'le'
18
+ this.delimiter = '\0'
19
+ this.srvRecord = null
20
+ this.abortedPromise = null
21
+ this.logger = new Logger()
22
+ this.dnsResolver = new DnsResolver(this.logger)
23
+
24
+ // Sent to us by QueryRunner
25
+ this.options = null
26
+ /** @type GlobalUdpSocket */
27
+ this.udpSocket = null
28
+ this.shortestRTT = 0
29
+ this.usedTcp = false
30
+ }
31
+
32
+ // Runs a single attempt with a timeout and cleans up afterward
33
+ async runOnceSafe () {
34
+ if (this.options.debug) {
35
+ this.logger.debugEnabled = true
36
+ }
37
+ this.logger.prefix = 'Q#' + (uid++)
38
+
39
+ this.logger.debug('Starting')
40
+ this.logger.debug('Protocol: ' + this.constructor.name)
41
+ this.logger.debug('Options:', this.options)
42
+
43
+ let abortCall = null
44
+ this.abortedPromise = new Promise((resolve, reject) => {
45
+ abortCall = () => reject(new Error('Query is finished -- cancelling outstanding promises'))
46
+ }).catch(() => {
47
+ // Make sure that if this promise isn't attached to, it doesn't throw a unhandled promise rejection
48
+ })
49
+
50
+ let timeout
51
+ try {
52
+ const promise = this.runOnce()
53
+ timeout = Promises.createTimeout(this.options.attemptTimeout, 'Attempt')
54
+ const result = await Promise.race([promise, timeout])
55
+ this.logger.debug('Query was successful')
56
+ return result
57
+ } catch (e) {
58
+ this.logger.debug('Query failed with error', e)
59
+ throw e
60
+ } finally {
61
+ timeout && timeout.cancel()
62
+ try {
63
+ abortCall()
64
+ } catch (e) {
65
+ this.logger.debug('Error during abort cleanup: ' + e.stack)
66
+ }
67
+ }
68
+ }
69
+
70
+ async runOnce () {
71
+ const options = this.options
72
+ if (('host' in options) && !('address' in options)) {
73
+ const resolved = await this.dnsResolver.resolve(options.host, options.ipFamily, this.srvRecord)
74
+ options.address = resolved.address
75
+ if (resolved.port) options.port = resolved.port
76
+ }
77
+
78
+ const state = new Results()
79
+
80
+ await this.run(state)
81
+
82
+ // because lots of servers prefix with spaces to try to appear first
83
+ state.name = (state.name || '').trim()
84
+
85
+ if (!('connect' in state)) {
86
+ state.connect = '' +
87
+ (state.gameHost || this.options.host || this.options.address) +
88
+ ':' +
89
+ (state.gamePort || this.options.port)
90
+ }
91
+ state.ping = this.shortestRTT
92
+ delete state.gameHost
93
+ delete state.gamePort
94
+
95
+ this.logger.debug(log => {
96
+ log('Size of players array: ' + state.players.length)
97
+ log('Size of bots array: ' + state.bots.length)
98
+ })
99
+
100
+ return state
101
+ }
102
+
103
+ async run (/** Results */ state) {}
104
+
105
+ /** Param can be a time in ms, or a promise (which will be timed) */
106
+ registerRtt (param) {
107
+ if (param.then) {
108
+ const start = Date.now()
109
+ param.then(() => {
110
+ const end = Date.now()
111
+ const rtt = end - start
112
+ this.registerRtt(rtt)
113
+ }).catch(() => {})
114
+ } else {
115
+ this.logger.debug('Registered RTT: ' + param + 'ms')
116
+ if (this.shortestRTT === 0 || param < this.shortestRTT) {
117
+ this.shortestRTT = param
118
+ }
119
+ }
120
+ }
121
+
122
+ // utils
123
+ /** @returns {Reader} */
124
+ reader (buffer) {
125
+ return new Reader(this, buffer)
126
+ }
127
+
128
+ translate (obj, trans) {
129
+ for (const from of Object.keys(trans)) {
130
+ const to = trans[from]
131
+ if (from in obj) {
132
+ if (to) obj[to] = obj[from]
133
+ delete obj[from]
134
+ }
135
+ }
136
+ }
137
+
138
+ trueTest (str) {
139
+ if (typeof str === 'boolean') return str
140
+ if (typeof str === 'number') return str !== 0
141
+ if (typeof str === 'string') {
142
+ if (str.toLowerCase() === 'true') return true
143
+ if (str.toLowerCase() === 'yes') return true
144
+ if (str === '1') return true
145
+ }
146
+ return false
147
+ }
148
+
149
+ assertValidPort (port) {
150
+ if (!port) {
151
+ throw new Error('Could not determine port to query. Did you provide a port?')
152
+ }
153
+ if (port < 1 || port > 65535) {
154
+ throw new Error('Invalid tcp/ip port: ' + port)
155
+ }
156
+ }
157
+
158
+ /**
159
+ * @template T
160
+ * @param {function(NodeJS.Socket):Promise<T>} fn
161
+ * @param {number=} port
162
+ * @returns {Promise<T>}
163
+ */
164
+ async withTcp (fn, port) {
165
+ this.usedTcp = true
166
+ const address = this.options.address
167
+ if (!port) port = this.options.port
168
+ this.assertValidPort(port)
169
+
170
+ let socket, connectionTimeout
171
+ try {
172
+ socket = net.connect(port, address)
173
+ socket.setNoDelay(true)
174
+
175
+ // Prevent unhandled 'error' events from dumping straight to console
176
+ socket.on('error', () => {})
177
+
178
+ this.logger.debug(log => {
179
+ this.logger.debug(address + ':' + port + ' TCP Connecting')
180
+ const writeHook = socket.write
181
+ socket.write = (...args) => {
182
+ log(address + ':' + port + ' TCP-->')
183
+ log(debugDump(args[0]))
184
+ writeHook.apply(socket, args)
185
+ }
186
+ socket.on('error', e => log('TCP Error:', e))
187
+ socket.on('close', () => log('TCP Closed'))
188
+ socket.on('data', (data) => {
189
+ log(address + ':' + port + ' <--TCP')
190
+ log(data)
191
+ })
192
+ socket.on('ready', () => log(address + ':' + port + ' TCP Connected'))
193
+ })
194
+
195
+ const connectionPromise = new Promise((resolve, reject) => {
196
+ socket.on('ready', resolve)
197
+ socket.on('close', () => reject(new Error('TCP Connection Refused')))
198
+ })
199
+ this.registerRtt(connectionPromise)
200
+ connectionTimeout = Promises.createTimeout(this.options.socketTimeout, 'TCP Opening')
201
+ await Promise.race([
202
+ connectionPromise,
203
+ connectionTimeout,
204
+ this.abortedPromise
205
+ ])
206
+ return await fn(socket)
207
+ } finally {
208
+ socket && socket.destroy()
209
+ connectionTimeout && connectionTimeout.cancel()
210
+ }
211
+ }
212
+
213
+ /**
214
+ * @template T
215
+ * @param {NodeJS.Socket} socket
216
+ * @param {Buffer|string} buffer
217
+ * @param {function(Buffer):T} ondata
218
+ * @returns Promise<T>
219
+ */
220
+ async tcpSend (socket, buffer, ondata) {
221
+ let timeout
222
+ try {
223
+ const promise = new Promise((resolve, reject) => {
224
+ let received = Buffer.from([])
225
+ const onData = (data) => {
226
+ received = Buffer.concat([received, data])
227
+ const result = ondata(received)
228
+ if (result !== undefined) {
229
+ socket.removeListener('data', onData)
230
+ resolve(result)
231
+ }
232
+ }
233
+ socket.on('data', onData)
234
+ socket.write(buffer)
235
+ })
236
+ timeout = Promises.createTimeout(this.options.socketTimeout, 'TCP')
237
+ return await Promise.race([promise, timeout, this.abortedPromise])
238
+ } finally {
239
+ timeout && timeout.cancel()
240
+ }
241
+ }
242
+
243
+ /**
244
+ * @param {Buffer|string} buffer
245
+ * @param {function(Buffer):T=} onPacket
246
+ * @param {(function():T)=} onTimeout
247
+ * @returns Promise<T>
248
+ * @template T
249
+ */
250
+ async udpSend (buffer, onPacket, onTimeout) {
251
+ const address = this.options.address
252
+ const port = this.options.port
253
+ this.assertValidPort(port)
254
+
255
+ if (typeof buffer === 'string') buffer = Buffer.from(buffer, 'binary')
256
+
257
+ const socket = this.udpSocket
258
+ await socket.send(buffer, address, port, this.options.debug)
259
+
260
+ if (!onPacket && !onTimeout) {
261
+ return null
262
+ }
263
+
264
+ let socketCallback
265
+ let timeout
266
+ try {
267
+ const promise = new Promise((resolve, reject) => {
268
+ const start = Date.now()
269
+ let end = null
270
+ socketCallback = (fromAddress, fromPort, buffer) => {
271
+ try {
272
+ if (fromAddress !== address) return
273
+ if (fromPort !== port) return
274
+ if (end === null) {
275
+ end = Date.now()
276
+ const rtt = end - start
277
+ this.registerRtt(rtt)
278
+ }
279
+ const result = onPacket(buffer)
280
+ if (result !== undefined) {
281
+ this.logger.debug('UDP send finished by callback')
282
+ resolve(result)
283
+ }
284
+ } catch (e) {
285
+ reject(e)
286
+ }
287
+ }
288
+ socket.addCallback(socketCallback, this.options.debug)
289
+ })
290
+ timeout = Promises.createTimeout(this.options.socketTimeout, 'UDP')
291
+ const wrappedTimeout = new Promise((resolve, reject) => {
292
+ timeout.catch((e) => {
293
+ this.logger.debug('UDP timeout detected')
294
+ if (onTimeout) {
295
+ try {
296
+ const result = onTimeout()
297
+ if (result !== undefined) {
298
+ this.logger.debug('UDP timeout resolved by callback')
299
+ resolve(result)
300
+ return
301
+ }
302
+ } catch (e) {
303
+ reject(e)
304
+ }
305
+ }
306
+ reject(e)
307
+ })
308
+ })
309
+ return await Promise.race([promise, wrappedTimeout, this.abortedPromise])
310
+ } finally {
311
+ timeout && timeout.cancel()
312
+ socketCallback && socket.removeCallback(socketCallback)
313
+ }
314
+ }
315
+
316
+ async tcpPing () {
317
+ // This will give a much more accurate RTT than using the rtt of an http request.
318
+ if (!this.usedTcp) {
319
+ await this.withTcp(() => {})
320
+ }
321
+ }
322
+
323
+ async request (params) {
324
+ await this.tcpPing()
325
+
326
+ let requestPromise
327
+ try {
328
+ requestPromise = got({
329
+ ...params,
330
+ timeout: {
331
+ request: this.options.socketTimeout
332
+ }
333
+ })
334
+ this.logger.debug(log => {
335
+ log(() => params.url + ' HTTP-->')
336
+ requestPromise
337
+ .then((response) => log(params.url + ' <--HTTP ' + response.statusCode))
338
+ .catch(() => {})
339
+ })
340
+ const wrappedPromise = requestPromise.then(response => {
341
+ if (response.statusCode !== 200) throw new Error('Bad status code: ' + response.statusCode)
342
+ return response.body
343
+ })
344
+ return await Promise.race([wrappedPromise, this.abortedPromise])
345
+ } finally {
346
+ requestPromise && requestPromise.cancel()
347
+ }
348
+ }
349
+ }
@@ -0,0 +1,65 @@
1
+ import Core from './core.js'
2
+
3
+ export default class cs2d extends Core {
4
+ async run (state) {
5
+ const reader = await this.sendQuery(
6
+ Buffer.from('\x01\x00\xFB\x01\xF5\x03\xFB\x05', 'binary'),
7
+ Buffer.from('\x01\x00\xFB\x01', 'binary')
8
+ )
9
+ const flags = reader.uint(1)
10
+ state.raw.flags = flags
11
+ state.password = this.readFlag(flags, 0)
12
+ state.raw.registeredOnly = this.readFlag(flags, 1)
13
+ state.raw.fogOfWar = this.readFlag(flags, 2)
14
+ state.raw.friendlyFire = this.readFlag(flags, 3)
15
+ state.raw.botsEnabled = this.readFlag(flags, 5)
16
+ state.raw.luaScripts = this.readFlag(flags, 6)
17
+ state.raw.forceLight = this.readFlag(flags, 7)
18
+ state.name = this.readString(reader)
19
+ state.map = this.readString(reader)
20
+ state.numplayers = reader.uint(1)
21
+ state.maxplayers = reader.uint(1)
22
+ if (flags & 32) {
23
+ state.raw.gamemode = reader.uint(1)
24
+ } else {
25
+ state.raw.gamemode = 0
26
+ }
27
+ state.raw.numbots = reader.uint(1)
28
+ const flags2 = reader.uint(1)
29
+ state.raw.flags2 = flags2
30
+ state.raw.recoil = this.readFlag(flags2, 0)
31
+ state.raw.offScreenDamage = this.readFlag(flags2, 1)
32
+ state.raw.hasDownloads = this.readFlag(flags2, 2)
33
+ reader.skip(2)
34
+ const players = reader.uint(1)
35
+ for (let i = 0; i < players; i++) {
36
+ const player = {}
37
+ player.id = reader.uint(1)
38
+ player.name = this.readString(reader)
39
+ player.team = reader.uint(1)
40
+ player.score = reader.uint(4)
41
+ player.deaths = reader.uint(4)
42
+ state.players.push(player)
43
+ }
44
+ }
45
+
46
+ async sendQuery (request, expectedHeader) {
47
+ // Send multiple copies of the request packet, because cs2d likes to just ignore them randomly
48
+ await this.udpSend(request)
49
+ await this.udpSend(request)
50
+ return await this.udpSend(request, (buffer) => {
51
+ const reader = this.reader(buffer)
52
+ const header = reader.part(4)
53
+ if (!header.equals(expectedHeader)) return
54
+ return reader
55
+ })
56
+ }
57
+
58
+ readFlag (flags, offset) {
59
+ return !!(flags & (1 << offset))
60
+ }
61
+
62
+ readString (reader) {
63
+ return reader.pascalString(1)
64
+ }
65
+ }
@@ -0,0 +1,196 @@
1
+ import valve from './valve.js'
2
+ import { Buffer } from 'node:buffer'
3
+
4
+ export default class dayz extends valve {
5
+ async run (state) {
6
+ if (!this.options.port) this.options.port = 27016
7
+ await super.queryInfo(state)
8
+ await super.queryChallenge()
9
+ await super.queryPlayers(state)
10
+ await this.queryRules(state)
11
+
12
+ this.processQueryInfo(state)
13
+ await super.cleanup(state)
14
+ }
15
+
16
+ async queryRules (state) {
17
+ if (!this.options.requestRules) {
18
+ return
19
+ }
20
+
21
+ const rules = {}
22
+ state.raw.rules = rules
23
+ const dayZPayload = []
24
+
25
+ this.logger.debug('Requesting rules ...')
26
+
27
+ const b = await this.sendPacket(0x56, null, 0x45, true)
28
+ if (b === null && !this.options.requestRulesRequired) return // timed out - the server probably has rules disabled
29
+
30
+ let dayZPayloadEnded = false
31
+
32
+ const reader = this.reader(b)
33
+ const num = reader.uint(2)
34
+ for (let i = 0; i < num; i++) {
35
+ if (!dayZPayloadEnded) {
36
+ const one = reader.uint(1)
37
+ const two = reader.uint(1)
38
+ const three = reader.uint(1)
39
+ if (one !== 0 && two !== 0 && three === 0) {
40
+ while (true) {
41
+ const byte = reader.uint(1)
42
+ if (byte === 0) break
43
+ dayZPayload.push(byte)
44
+ }
45
+ continue
46
+ } else {
47
+ reader.skip(-3)
48
+ dayZPayloadEnded = true
49
+ }
50
+ }
51
+
52
+ const key = reader.string()
53
+ rules[key] = reader.string()
54
+ }
55
+
56
+ state.raw.dayzMods = this.readDayzMods(Buffer.from(dayZPayload))
57
+ }
58
+
59
+ processQueryInfo (state) {
60
+ // DayZ embeds some of the server information inside the tags attribute
61
+ if (!state.raw.tags) { return }
62
+
63
+ state.raw.dlcEnabled = false
64
+ state.raw.firstPerson = false
65
+ state.raw.privateHive = false
66
+ state.raw.external = false
67
+ state.raw.official = false
68
+
69
+ for (const tag of state.raw.tags) {
70
+ if (tag.startsWith('lqs')) {
71
+ const value = parseInt(tag.replace('lqs', ''))
72
+ if (!isNaN(value)) {
73
+ state.raw.queue = value
74
+ }
75
+ }
76
+ if (tag.includes('no3rd')) {
77
+ state.raw.firstPerson = true
78
+ }
79
+ if (tag.includes('isDLC')) {
80
+ state.raw.dlcEnabled = true
81
+ }
82
+ if (tag.includes('privHive')) {
83
+ state.raw.privateHive = true;
84
+ }
85
+ if (tag.includes('external')) {
86
+ state.raw.external = true;
87
+ }
88
+ if (tag.includes(':')) {
89
+ state.raw.time = tag
90
+ }
91
+ if (tag.startsWith('etm')) {
92
+ const value = parseInt(tag.replace('etm', ''))
93
+ if (!isNaN(value)) {
94
+ state.raw.dayAcceleration = value
95
+ }
96
+ }
97
+ if (tag.startsWith('entm')) {
98
+ const value = parseInt(tag.replace('entm', ''))
99
+ if (!isNaN(value)) {
100
+ state.raw.nightAcceleration = value
101
+ }
102
+ }
103
+ }
104
+
105
+ if (!state.raw.external && !state.raw.privateHive) {
106
+ state.raw.official = true
107
+ }
108
+ }
109
+
110
+ readDayzMods (/** Buffer */ buffer) {
111
+ if (!buffer.length) {
112
+ return {}
113
+ }
114
+
115
+ this.logger.debug('DAYZ BUFFER')
116
+ this.logger.debug(buffer)
117
+
118
+ const reader = this.reader(buffer)
119
+ const version = this.readDayzByte(reader)
120
+ const overflow = this.readDayzByte(reader)
121
+ const dlc1 = this.readDayzByte(reader)
122
+ const dlc2 = this.readDayzByte(reader)
123
+ this.logger.debug('version ' + version)
124
+ this.logger.debug('overflow ' + overflow)
125
+ this.logger.debug('dlc1 ' + dlc1)
126
+ this.logger.debug('dlc2 ' + dlc2)
127
+ if (dlc1) {
128
+ const unknown = this.readDayzUint(reader, 4) // ?
129
+ this.logger.debug('unknown ' + unknown)
130
+ }
131
+ if (dlc2) {
132
+ const unknown = this.readDayzUint(reader, 4) // ?
133
+ this.logger.debug('unknown ' + unknown)
134
+ }
135
+ const mods = []
136
+ mods.push(...this.readDayzModsSection(reader, true))
137
+ mods.push(...this.readDayzModsSection(reader, false))
138
+ this.logger.debug('dayz buffer rest:', reader.rest())
139
+ return mods
140
+ }
141
+
142
+ readDayzModsSection (/** Reader */ reader, withHeader) {
143
+ const out = []
144
+ const count = this.readDayzByte(reader)
145
+ this.logger.debug('dayz mod section withHeader:' + withHeader + ' count:' + count)
146
+ for (let i = 0; i < count; i++) {
147
+ if (reader.done()) break
148
+ const mod = {}
149
+ if (withHeader) {
150
+ mod.unknown = this.readDayzUint(reader, 4) // ?
151
+
152
+ // For some reason this is 4 on all of them, but doesn't exist on the last one? but only sometimes?
153
+ const offset = reader.offset()
154
+ const flag = this.readDayzByte(reader)
155
+ if (flag !== 4) reader.setOffset(offset)
156
+
157
+ mod.workshopId = this.readDayzUint(reader, 4)
158
+ }
159
+ mod.title = this.readDayzString(reader)
160
+ this.logger.debug(mod)
161
+ out.push(mod)
162
+ }
163
+ return out
164
+ }
165
+
166
+ readDayzUint (reader, bytes) {
167
+ const out = []
168
+ for (let i = 0; i < bytes; i++) {
169
+ out.push(this.readDayzByte(reader))
170
+ }
171
+ const buf = Buffer.from(out)
172
+ const r2 = this.reader(buf)
173
+ return r2.uint(bytes)
174
+ }
175
+
176
+ readDayzByte (reader) {
177
+ const byte = reader.uint(1)
178
+ if (byte === 1) {
179
+ const byte2 = reader.uint(1)
180
+ if (byte2 === 1) return 1
181
+ if (byte2 === 2) return 0
182
+ if (byte2 === 3) return 0xff
183
+ return 0 // ?
184
+ }
185
+ return byte
186
+ }
187
+
188
+ readDayzString (reader) {
189
+ const length = this.readDayzByte(reader)
190
+ const out = []
191
+ for (let i = 0; i < length; i++) {
192
+ out.push(this.readDayzByte(reader))
193
+ }
194
+ return Buffer.from(out).toString('utf8')
195
+ }
196
+ }
@@ -0,0 +1,29 @@
1
+ import Core from './core.js'
2
+
3
+ export default class discord extends Core {
4
+ async run (state) {
5
+ const guildId = this.options.guildId
6
+ if (typeof guildId !== 'string') {
7
+ throw new Error('guildId option must be set when querying discord. Ensure the guildId is a string and not a number.' +
8
+ " (It's too large of a number for javascript to store without losing precision)")
9
+ }
10
+ this.usedTcp = true
11
+ const raw = await this.request({
12
+ url: 'https://discordapp.com/api/guilds/' + guildId + '/widget.json'
13
+ })
14
+ const json = JSON.parse(raw)
15
+ state.name = json.name
16
+ if (json.instant_invite) {
17
+ state.connect = json.instant_invite
18
+ } else {
19
+ state.connect = 'https://discordapp.com/channels/' + guildId
20
+ }
21
+ for (const member of json.members) {
22
+ const { username: name, ...rest } = member
23
+ state.players.push({ name, ...rest })
24
+ }
25
+ delete json.members
26
+ state.maxplayers = 500000
27
+ state.raw = json
28
+ }
29
+ }