This repository has been archived by the owner on Jan 25, 2021. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 1
/
rcon.coffee
128 lines (101 loc) · 3.42 KB
/
rcon.coffee
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
dgram = Npm.require 'dgram'
Buffer = Npm.require('buffer').Buffer
Commands =
status :
command : 'status'
regex : ///
hostname : \s+ (.*) \n
version \s+ : \s+ .* (i?n?secure) .* \n
tcp/ip \s+ : \s+ (.*) \n
map \s+ : \s+ (.*) \s+ at: .* \n
players \s+ : \s+ (\d+) \s+ active \s+ \((\d+) \s+ max\)
///
callback : (match) ->
name : match[1]
secure : match[2] is 'secure'
map : match[4]
currentplayers : +match[5]
maxplayers : +match[6]
class RCON
constructor : (@host, @port, @password) ->
@_challenge = false
@_currentQuery = false
@_queueProcessing = false
@_queue = []
@_connect()
command : (command, callback, errorCallback) ->
callback ?= ->
errorCallback ?= ->
# If the command exist match the regex
if Commands[command]?
item = Commands[command]
@query item.command, (data) ->
match = data.match item.regex
callback if match isnt null then item.callback match else false
, errorCallback
# Error out, if said command isn't defined
else
throw new Exception "Command #{command} isn't defined"
query : (command, callback, errorCallback) ->
callback ?= ->
errorCallback ?= ->
@_queue.push [command, callback, errorCallback]
@_process() if @_challenge
_process : (force = false) ->
@_queueProcessing = false if force
# If we don't have a challenge yet, or we're actively working on the
# queue, or the queue is empty simply return.
return if not @_challenge or @_queueProcessing or @_queue.length is 0
# We're now going to process the next item in the queue
@_queueProcessing = true
@_currentQuery = @_queue.shift()
# Send our currentQuery
@_send @_currentQuery[0]
# Convinience method, for calling RCON commands
_send : (query) ->
@_sendRaw "rcon #{@_challenge} #{@password} #{query}\n"
# Sends the raw data over our socket
_sendRaw : (data) ->
buffer = new Buffer 4 + data.length
buffer.writeInt32LE -1, 0
buffer.write data, 4
@_socket.send buffer, 0, buffer.length, @port, @host
# Keep requesting a challenge token, until we get one
_requestChallengeToken : ->
self = @
@_challenge = false
@_sendRaw "challenge rcon\n"
setTimeout ->
self._requestChallengeToken() if not self._challenge
, 10000
# Handles all the dirty connection and challenge token stuff
_connect : ->
self = @
@_socket = dgram.createSocket 'udp4'
@_socket
# Parse the incoming data
.on 'message', (data) ->
a = data.readUInt32LE 0
if a is 0xffffffff
str = data.toString 'utf-8', 4
tokens = str.split ' '
# If we've got a new challenge token
if tokens.length is 3 and tokens[0] is 'challenge' and tokens[1] is 'rcon'
self._challenge = tokens[2].substr(0, tokens[2].length - 1).trim()
# Call the currentQuery's callback
else if self._currentQuery
self._currentQuery[1] str.substr 1, str.length - 2
# Process the next item in the queue
self._process true
# An error happened, call the currentQuery's errorCallback
else
self._currentQuery[2] 'Received malformed packet' if self._currentQuery
self._process true
# When we've got our connection, ask for a new challenge token
.on 'listening', ->
self._requestChallengeToken()
# Better error handling would be awesome
.on 'error', (err) ->
throw new Exception err
# Set up the connection
@_socket.bind 0