-
Notifications
You must be signed in to change notification settings - Fork 5
/
Copy pathzmachine.coffee
263 lines (237 loc) · 7.93 KB
/
zmachine.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
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
# Description
# Play zmachine with Hubot
#
# Configuration:
# HUBOT_ZMACHINE_SERVER - A zmachine-api server.
# HUBOT_ZMACHINE_ROOMS - Optional. A comma-delimited list of rooms that don't require prefixes; all text will be zmachineed.
# HUBOT_ZMACHINE_OT_PREFIX - Optional. If you've got an on-topic room, use this text as prefix for off-topic talk.
#
# Commands:
# hubot z list - Lists all in-progress games on a server (also syncs hubot with server)
# hubot z start <game> - Starts a game (default is 'zmachine1')
# hubot z <action> - Performs the action
# hubot z save <name> - Saves a game with the given name via the zmachine-api (you can save via an action, but that will only save a local copy)
# hubot z restore <name> - Loads a game with the given name via the zmachine-api
# hubot z stop - Ends the game for this channel
# hubot z purge - Purges all in-progress games from memory (to be re-synced)
#
# Notes:
# For fun and whimsy!
#
# Author:
# Justin Swift <[email protected]>
# Shamelessly stolen snippets
String::startsWith ?= (s) -> @[...s.length] is s
module.exports = (robot) ->
unless process.env.HUBOT_ZMACHINE_SERVER
robot.logger.error "HUBOT_ZMACHINE_SERVER not defined, cannot play zmachine!"
return
server = process.env.HUBOT_ZMACHINE_SERVER
if process.env.HUBOT_ZMACHINE_ROOMS
zmachineRooms = process.env.HUBOT_ZMACHINE_ROOMS.split(',')
robot.logger.debug "Playing on-topic zmachine in #{zmachineRooms}"
else
robot.logger.debug "No on-topic zmachine rooms"
zmachineRooms = []
if process.env.HUBOT_ZMACHINE_OT_PREFIX
offtopicPrefix = process.env.HUBOT_ZMACHINE_OT_PREFIX
else
offtopicPrefix = "#"
zmachinePids = {}
initialized = false
get_key = (msg) ->
if msg.message.user.room?
return msg.message.user.room
else
return msg.message.user.name
list_games = (msg, callback, args) ->
msg.http("#{server}/games")
.get() (err, res, body) ->
if err
msg.send "Error: #{err}"
return
else
bodyJ = JSON.parse(body)
zmachinePids = {}
zmachinePids[bodyJI.label] = bodyJI.pid for bodyJI in bodyJ
initialized = true
if callback?
callback msg, args
robot.logger.debug "Initialized: #{body}"
else
msg.send "Initialized: #{body}"
purge_list = (msg) ->
zmachinePids = {}
start_game = (msg, game) ->
# Make sure we don't already have a room going
key = get_key msg
if zmachinePids[key]?
msg.send "There's already a game for #{key}!"
return
# Start the game
msg.send "Starting game"
game = game
label = key
data = "game=#{game}&label=#{label}"
msg.http("#{server}/games")
.header('Content-Type', 'application/x-www-form-urlencoded')
.post(data) (err, res, body) ->
if err
msg.send "Error: #{err}"
return
else
bodyJ = JSON.parse(body)
pid = bodyJ.pid
zmachinePids[key] = pid
msg.send "Started game #{pid}"
msg.send bodyJ.data
stop_game = (msg) ->
key = get_key msg
if zmachinePids[key]?
pid = zmachinePids[key]
msg.send "Killing game #{key}"
msg.http("#{server}/games/#{pid}")
.delete() (err, res, body) ->
if err
msg.send "Error: #{err}"
return
else
msg.send body
list_games msg
do_action = (msg, action) ->
# Make sure we have a room going
key = get_key msg
if not zmachinePids[key]
msg.send "There's no game for #{key}!"
return
# Do the action
robot.logger.debug "Doing action: #{action}"
data = "action=#{action}"
pid = zmachinePids[key]
msg.http("#{server}/games/#{pid}/action")
.header('Content-Type', 'application/x-www-form-urlencoded')
.post(data) (err, res, body) ->
if err
msg.send "Error: #{err}"
return
else
try
bodyJ = JSON.parse(body)
msg.send bodyJ.data
catch
msg.send "Received non-JSON response:"
msg.send body
save_game = (msg, name) ->
# Make sure we have a room going
key = get_key msg
if not zmachinePids[key]
msg.send "There's no game for #{key}!"
return
# Start the game
msg.send "Saving game"
name = name
pid = zmachinePids[key]
data = "file=#{name}"
msg.http("#{server}/games/#{pid}/save")
.header('Content-Type', 'application/x-www-form-urlencoded')
.post(data) (err, res, body) ->
if err
msg.send "Error: #{err}"
return
else
try
bodyJ = JSON.parse(body)
msg.send bodyJ.data
catch
msg.send "Received non-JSON response:"
msg.send body
restore_game = (msg, name) ->
# Make sure we have a room going
key = get_key msg
if not zmachinePids[key]
msg.send "There's no game for #{key}! Start the game you want to restore first!"
return
# Restore the game
msg.send "Restoring game"
name = name
pid = zmachinePids[key]
data = "file=#{name}"
msg.http("#{server}/games/#{pid}/restore")
.header('Content-Type', 'application/x-www-form-urlencoded')
.post(data) (err, res, body) ->
if err
msg.send "Error: #{err}"
return
else
try
bodyJ = JSON.parse(body)
msg.send bodyJ.data
catch
msg.send "Received non-JSON response:"
msg.send body
process_zmachine_msg = (msg) ->
action = msg.match[1]
robot.logger.debug "User #{msg.message.user.name} in room #{msg.message.user.room} doing: #{action}"
if action.startsWith("start")
# Default to zmachine1, a classic
game = "zmachine1"
actionList = action.split " "
if actionList.length > 1
# Try whatever game the user specified
game = actionList[1]
start_game msg, game
else if action == "stop"
# End the game
stop_game msg
else if action == "list"
# List all in-progress games on the server (and sync with them)
list_games msg
else if action == "purge"
# Forget all in-progress games; use when you need to re-sync with the server
purge_list msg
else if action.startsWith("save")
# Save to cloud
name = "save"
actionList = action.split " "
if actionList.length > 1
# Try whatever game the user specified
name = actionList[1]
save_game msg, name
else if action.startsWith("restore")
# Restore from cloud
name = "save"
actionList = action.split " "
if actionList.length > 1
# Try whatever game the user specified
name = actionList[1]
restore_game msg, name
else
# Not a special command, so Just zmachine It
do_action msg, action
# For in-character rooms, hear/respond to everything
robot.hear /(.*)/i, (msg) ->
if not msg.message.user.room?
# Private messages always require direct zmachine-ing
return
if msg.message.user.room not in zmachineRooms
# If we're not in a direct room, do nothing
return
action = msg.match[1]
if action.startsWith("/me") or action.startsWith(offtopicPrefix)
# ignore actions and the offtopicPrefix
return
if action.startsWith(robot.name)
# If we're in a direct room, and we hear the robot name, ignore and let respond handle it
return
robot.logger.debug "User #{msg.message.user.name} in room #{msg.message.user.room} doing: #{action}"
if not initialized
list_games msg, do_action, action
else
do_action msg, action
# For all rooms, respond directly to "z " commands
robot.respond /(?:z)(?: me)? (.*)/i, (msg) ->
if not initialized
# If we've just come up, we should sync our list of on-going games
list_games msg, process_zmachine_msg
else
process_zmachine_msg msg