aromancer 0.0.4 → 0.0.6
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.
- checksums.yaml +4 -4
- data/.gitignore +0 -1
- data/.ruby-version +0 -1
- data/Gemfile.lock +37 -17
- data/README.md +1 -43
- data/aromancer.gemspec +2 -2
- data/bin/aromancer +1 -12
- data/checksums/aromancer-0.0.5.gem.sha512 +1 -0
- data/checksums/aromancer-0.0.6.gem.sha512 +1 -0
- data/gem_graph.png +0 -0
- data/lib/aromancer/api.rb +31 -15
- data/lib/aromancer/prompt.rb +4 -0
- data/lib/aromancer/routes.rb +451 -237
- data/lib/aromancer/server.rb +53 -41
- data/lib/aromancer/storage.rb +137 -5
- data/lib/aromancer/version.rb +1 -1
- data/lib/aromancer/websocket.rb +82 -0
- data/lib/aromancer.rb +123 -1
- data/lib/reiquire.rb +1 -1
- data/locale/en.yml +21 -1
- metadata +20 -3
data/lib/aromancer/server.rb
CHANGED
|
@@ -13,8 +13,7 @@
|
|
|
13
13
|
module Aromancer
|
|
14
14
|
class Server
|
|
15
15
|
include Singleton
|
|
16
|
-
|
|
17
|
-
SERVER_URL = File.exist?(SERVER_HOST_FILE) ? File.read(SERVER_HOST_FILE).gsub("\n", "") : "http://localhost:3000"
|
|
16
|
+
|
|
18
17
|
HEADERS = { "Content-Type": "application/json" }
|
|
19
18
|
|
|
20
19
|
class InternalError
|
|
@@ -23,26 +22,25 @@ module Aromancer
|
|
|
23
22
|
def initialize(status: -1, body: {})
|
|
24
23
|
@status = status
|
|
25
24
|
@body = body
|
|
25
|
+
Aromancer::Storage.instance.clear_cache!
|
|
26
26
|
end
|
|
27
27
|
|
|
28
28
|
def self.build(e)
|
|
29
29
|
InternalError.new(
|
|
30
30
|
status: -1,
|
|
31
|
-
body: {error_message: e.
|
|
31
|
+
body: {error_message: e.inspect.to_s}.to_json
|
|
32
32
|
)
|
|
33
33
|
end
|
|
34
34
|
end
|
|
35
35
|
|
|
36
|
-
def self.
|
|
37
|
-
{
|
|
38
|
-
api_key: Aromancer::Storage.get_player["api_key"]
|
|
39
|
-
}
|
|
36
|
+
def self.auth_header
|
|
37
|
+
{"Authorization": "Bearer #{Aromancer::Storage.get_player["api_key"]}"}
|
|
40
38
|
end
|
|
41
39
|
|
|
42
40
|
def self.sign_in(email_address, password)
|
|
43
41
|
begin
|
|
44
42
|
Faraday.new(
|
|
45
|
-
url:
|
|
43
|
+
url: Aromancer::Storage.get_server_url,
|
|
46
44
|
headers: HEADERS,
|
|
47
45
|
params: {
|
|
48
46
|
email_address: email_address,
|
|
@@ -57,7 +55,7 @@ module Aromancer
|
|
|
57
55
|
def self.sign_up(email_address, password, password_confirmation)
|
|
58
56
|
begin
|
|
59
57
|
Faraday.new(
|
|
60
|
-
url:
|
|
58
|
+
url: Aromancer::Storage.get_server_url,
|
|
61
59
|
headers: HEADERS,
|
|
62
60
|
params: {
|
|
63
61
|
user: {
|
|
@@ -75,9 +73,8 @@ module Aromancer
|
|
|
75
73
|
def self.sign_out
|
|
76
74
|
begin
|
|
77
75
|
Faraday.new(
|
|
78
|
-
url:
|
|
79
|
-
headers: HEADERS,
|
|
80
|
-
params: api_key_param
|
|
76
|
+
url: Aromancer::Storage.get_server_url,
|
|
77
|
+
headers: HEADERS.merge(auth_header),
|
|
81
78
|
).delete("/session")
|
|
82
79
|
rescue StandardError => e
|
|
83
80
|
InternalError.build(e)
|
|
@@ -87,9 +84,9 @@ module Aromancer
|
|
|
87
84
|
def self.create_legend(params)
|
|
88
85
|
begin
|
|
89
86
|
Faraday.new(
|
|
90
|
-
url:
|
|
91
|
-
headers: HEADERS,
|
|
92
|
-
params: params
|
|
87
|
+
url: Aromancer::Storage.get_server_url,
|
|
88
|
+
headers: HEADERS.merge(auth_header),
|
|
89
|
+
params: params
|
|
93
90
|
).post("/legends")
|
|
94
91
|
rescue StandardError => e
|
|
95
92
|
InternalError.build(e)
|
|
@@ -99,21 +96,31 @@ module Aromancer
|
|
|
99
96
|
def self.get_legends
|
|
100
97
|
begin
|
|
101
98
|
Faraday.new(
|
|
102
|
-
url:
|
|
103
|
-
headers: HEADERS,
|
|
104
|
-
params: api_key_param
|
|
99
|
+
url: Aromancer::Storage.get_server_url,
|
|
100
|
+
headers: HEADERS.merge(auth_header),
|
|
105
101
|
).get("/legends")
|
|
106
102
|
rescue StandardError => e
|
|
107
103
|
InternalError.build(e)
|
|
108
104
|
end
|
|
109
105
|
end
|
|
110
106
|
|
|
107
|
+
def self.find_legends
|
|
108
|
+
begin
|
|
109
|
+
Faraday.new(
|
|
110
|
+
url: Aromancer::Storage.get_server_url,
|
|
111
|
+
headers: HEADERS.merge(auth_header),
|
|
112
|
+
).get("/legends/find")
|
|
113
|
+
rescue StandardError => e
|
|
114
|
+
InternalError.build(e)
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
|
|
111
118
|
def self.update_player(params)
|
|
112
119
|
begin
|
|
113
120
|
Faraday.new(
|
|
114
|
-
url:
|
|
115
|
-
headers: HEADERS,
|
|
116
|
-
params: params
|
|
121
|
+
url: Aromancer::Storage.get_server_url,
|
|
122
|
+
headers: HEADERS.merge(auth_header),
|
|
123
|
+
params: params
|
|
117
124
|
).put("/players/#{Aromancer::Storage.get_player["id"]}")
|
|
118
125
|
rescue StandardError => e
|
|
119
126
|
InternalError.build(e)
|
|
@@ -121,13 +128,13 @@ module Aromancer
|
|
|
121
128
|
end
|
|
122
129
|
|
|
123
130
|
def self.get_arena
|
|
124
|
-
legend_id = Aromancer::Storage.get_player["active_legend_id"]
|
|
131
|
+
legend_id = (Aromancer::Storage.get_player || {})["active_legend_id"]
|
|
132
|
+
return InternalError.build(StandardError.new("no active legend")) if legend_id.nil?
|
|
125
133
|
|
|
126
134
|
begin
|
|
127
135
|
Faraday.new(
|
|
128
|
-
url:
|
|
129
|
-
headers: HEADERS,
|
|
130
|
-
params: api_key_param
|
|
136
|
+
url: Aromancer::Storage.get_server_url,
|
|
137
|
+
headers: HEADERS.merge(auth_header),
|
|
131
138
|
).get("/legends/#{legend_id}/arena")
|
|
132
139
|
rescue StandardError => e
|
|
133
140
|
InternalError.build(e)
|
|
@@ -135,13 +142,13 @@ module Aromancer
|
|
|
135
142
|
end
|
|
136
143
|
|
|
137
144
|
def self.get_stream
|
|
138
|
-
legend_id = Aromancer::Storage.get_player["active_legend_id"]
|
|
145
|
+
legend_id = (Aromancer::Storage.get_player || {})["active_legend_id"]
|
|
146
|
+
return InternalError.build(StandardError.new("no active legend")) if legend_id.nil?
|
|
139
147
|
|
|
140
148
|
begin
|
|
141
149
|
Faraday.new(
|
|
142
|
-
url:
|
|
143
|
-
headers: HEADERS,
|
|
144
|
-
params: api_key_param
|
|
150
|
+
url: Aromancer::Storage.get_server_url,
|
|
151
|
+
headers: HEADERS.merge(auth_header),
|
|
145
152
|
).get("/legends/#{legend_id}/stream")
|
|
146
153
|
rescue StandardError => e
|
|
147
154
|
InternalError.build(e)
|
|
@@ -149,12 +156,13 @@ module Aromancer
|
|
|
149
156
|
end
|
|
150
157
|
|
|
151
158
|
def self.get_word_possibilities(selected_word_id)
|
|
152
|
-
legend_id = Aromancer::Storage.get_player["active_legend_id"]
|
|
159
|
+
legend_id = (Aromancer::Storage.get_player || {})["active_legend_id"]
|
|
160
|
+
return InternalError.build(StandardError.new("no active legend")) if legend_id.nil?
|
|
161
|
+
|
|
153
162
|
begin
|
|
154
163
|
Faraday.new(
|
|
155
|
-
url:
|
|
156
|
-
headers: HEADERS,
|
|
157
|
-
params: api_key_param
|
|
164
|
+
url: Aromancer::Storage.get_server_url,
|
|
165
|
+
headers: HEADERS.merge(auth_header),
|
|
158
166
|
).get("/legends/#{legend_id}/words/#{selected_word_id}/possibilities")
|
|
159
167
|
rescue StandardError => e
|
|
160
168
|
InternalError.build(e)
|
|
@@ -162,12 +170,14 @@ module Aromancer
|
|
|
162
170
|
end
|
|
163
171
|
|
|
164
172
|
def self.create_turn(params)
|
|
165
|
-
legend_id = Aromancer::Storage.get_player["active_legend_id"]
|
|
173
|
+
legend_id = (Aromancer::Storage.get_player || {})["active_legend_id"]
|
|
174
|
+
return InternalError.build(StandardError.new("no active legend")) if legend_id.nil?
|
|
175
|
+
|
|
166
176
|
begin
|
|
167
177
|
Faraday.new(
|
|
168
|
-
url:
|
|
169
|
-
headers: HEADERS,
|
|
170
|
-
params: params
|
|
178
|
+
url: Aromancer::Storage.get_server_url,
|
|
179
|
+
headers: HEADERS.merge(auth_header),
|
|
180
|
+
params: params
|
|
171
181
|
).post("/legends/#{legend_id}/arena")
|
|
172
182
|
rescue StandardError => e
|
|
173
183
|
InternalError.build(e)
|
|
@@ -175,12 +185,14 @@ module Aromancer
|
|
|
175
185
|
end
|
|
176
186
|
|
|
177
187
|
def self.update_turn(params)
|
|
178
|
-
legend_id = Aromancer::Storage.get_player["active_legend_id"]
|
|
188
|
+
legend_id = (Aromancer::Storage.get_player || {})["active_legend_id"]
|
|
189
|
+
return InternalError.build(StandardError.new("no active legend")) if legend_id.nil?
|
|
190
|
+
|
|
179
191
|
begin
|
|
180
192
|
Faraday.new(
|
|
181
|
-
url:
|
|
182
|
-
headers: HEADERS,
|
|
183
|
-
params: params
|
|
193
|
+
url: Aromancer::Storage.get_server_url,
|
|
194
|
+
headers: HEADERS.merge(auth_header),
|
|
195
|
+
params: params
|
|
184
196
|
).put("/legends/#{legend_id}/arena")
|
|
185
197
|
rescue StandardError => e
|
|
186
198
|
InternalError.build(e)
|
data/lib/aromancer/storage.rb
CHANGED
|
@@ -12,10 +12,20 @@ module Aromancer
|
|
|
12
12
|
class Storage
|
|
13
13
|
include Singleton
|
|
14
14
|
|
|
15
|
+
DEFAULT_SERVER = "http://aromancer.com"
|
|
16
|
+
|
|
17
|
+
attr_accessor :storage_open, :cache
|
|
18
|
+
|
|
15
19
|
def initialize
|
|
20
|
+
clear_cache!
|
|
16
21
|
FileUtils.mkdir_p(Aromancer.home_directory)
|
|
17
22
|
end
|
|
18
23
|
|
|
24
|
+
def clear_cache!
|
|
25
|
+
@storage_open = false
|
|
26
|
+
@cache = {}
|
|
27
|
+
end
|
|
28
|
+
|
|
19
29
|
def self.storage_filepath
|
|
20
30
|
filepath = File.join(Aromancer.home_directory, "storage.json")
|
|
21
31
|
|
|
@@ -53,6 +63,17 @@ module Aromancer
|
|
|
53
63
|
instance.set_key(p_key, value)
|
|
54
64
|
end
|
|
55
65
|
|
|
66
|
+
def self.toggle_preference(key)
|
|
67
|
+
set_preference(key, !get_preference(key))
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def self.toggle_force_ssl
|
|
71
|
+
toggle_preference(:force_ssl)
|
|
72
|
+
v = get_preference(:force_ssl)
|
|
73
|
+
self.instance.set_key(:server_url_scheme, "http#{v ? :s : ""}")
|
|
74
|
+
self.instance.set_key(:websocket_scheme, "ws#{v ? :s : ""}")
|
|
75
|
+
end
|
|
76
|
+
|
|
56
77
|
def self.set_signed_out_flag(flag)
|
|
57
78
|
Aromancer::Storage.instance.set_key(:signed_out_flag, flag)
|
|
58
79
|
end
|
|
@@ -101,20 +122,131 @@ module Aromancer
|
|
|
101
122
|
Aromancer::Storage.instance.get_key(:last_error)
|
|
102
123
|
end
|
|
103
124
|
|
|
125
|
+
URI_KEYS = [
|
|
126
|
+
:scheme,
|
|
127
|
+
:host,
|
|
128
|
+
:port
|
|
129
|
+
]
|
|
130
|
+
|
|
131
|
+
def self.set_server_url(server_url)
|
|
132
|
+
uri = URI.parse(server_url)
|
|
133
|
+
if uri.kind_of?(URI::HTTP) or uri.kind_of?(URI::HTTPS)
|
|
134
|
+
URI_KEYS.each{|k|
|
|
135
|
+
uri.kind_of?(URI::HTTP) && get_preference(:force_ssl) ? toggle_force_ssl : nil
|
|
136
|
+
Aromancer::Storage.instance.set_key("server_url_#{k}", uri.select(k).first)
|
|
137
|
+
}
|
|
138
|
+
elsif get_server_url.nil?
|
|
139
|
+
set_server_url(DEFAULT_SERVER)
|
|
140
|
+
end
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
def self.get_server_url
|
|
144
|
+
set_server_url(DEFAULT_SERVER) if URI_KEYS.all?{|k| Aromancer::Storage.instance.get_key("server_url_#{k}").nil?}
|
|
145
|
+
URI.parse(URI_KEYS.map{|k|
|
|
146
|
+
c = Aromancer::Storage.instance.get_key("server_url_#{k}")
|
|
147
|
+
case k
|
|
148
|
+
when :scheme
|
|
149
|
+
"#{c}://"
|
|
150
|
+
when :host
|
|
151
|
+
c
|
|
152
|
+
when :port
|
|
153
|
+
":#{c}"
|
|
154
|
+
end
|
|
155
|
+
}.join("")).to_s
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
def self.get_websocket_url
|
|
159
|
+
# default scheme is ws
|
|
160
|
+
w_scheme = Aromancer::Storage.instance.get_key(:websocket_scheme)
|
|
161
|
+
Aromancer::Storage.instance.set_key(:websocket_scheme, :ws) if w_scheme.nil?
|
|
162
|
+
w_scheme = Aromancer::Storage.instance.get_key(:websocket_scheme)
|
|
163
|
+
|
|
164
|
+
# default path is broadcast
|
|
165
|
+
w_path = Aromancer::Storage.instance.get_key(:websocket_path)
|
|
166
|
+
Aromancer::Storage.instance.set_key(:websocket_path, :broadcast) if w_path.nil?
|
|
167
|
+
w_path = Aromancer::Storage.instance.get_key(:websocket_path)
|
|
168
|
+
|
|
169
|
+
# asserts host and port not nil
|
|
170
|
+
# assumes same-as-server host and port for now
|
|
171
|
+
host = Aromancer::Storage.instance.get_key(:server_url_host)
|
|
172
|
+
port = Aromancer::Storage.instance.get_key(:server_url_port)
|
|
173
|
+
|
|
174
|
+
URI.parse("#{w_scheme}://#{host}:#{port}/#{w_path}").to_s
|
|
175
|
+
end
|
|
176
|
+
|
|
104
177
|
def get_key(key)
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
178
|
+
key = key.to_s
|
|
179
|
+
|
|
180
|
+
# ensure default for key exists
|
|
181
|
+
@cache[key] = {value: nil, persisted: false} unless @cache.keys.include?(key)
|
|
182
|
+
|
|
183
|
+
# return cache value if storage open or the cache value is persisted
|
|
184
|
+
return @cache[key][:value] if @storage_open || @cache[key][:persisted]
|
|
185
|
+
|
|
186
|
+
# open storage
|
|
187
|
+
@storage_open = true
|
|
188
|
+
storage_contents = {}
|
|
189
|
+
begin
|
|
190
|
+
# read storage
|
|
191
|
+
storage_contents = JSON.parse(File.read(Aromancer::Storage.storage_filepath))
|
|
192
|
+
rescue StandardError => e
|
|
193
|
+
@storage_open = false
|
|
194
|
+
FileUtils.rm(Aromancer::Storage.storage_filepath)
|
|
195
|
+
return
|
|
108
196
|
end
|
|
197
|
+
|
|
198
|
+
# only update the cache value if present in storage
|
|
199
|
+
if storage_contents.keys.include?(key)
|
|
200
|
+
@cache[key] = {value: storage_contents[key], persisted: true}
|
|
201
|
+
else
|
|
202
|
+
@cache[key][:persisted] = false
|
|
203
|
+
end
|
|
204
|
+
|
|
205
|
+
@storage_open = false
|
|
206
|
+
@cache[key][:value]
|
|
109
207
|
end
|
|
110
208
|
|
|
111
209
|
def set_key(key, value)
|
|
112
|
-
|
|
113
|
-
|
|
210
|
+
key = key.to_s
|
|
211
|
+
|
|
212
|
+
# ensure default for key exists
|
|
213
|
+
@cache[key] = {value: nil, persisted: false} unless @cache.keys.include?(key)
|
|
214
|
+
|
|
215
|
+
# assign new cache value and set persisted to false
|
|
216
|
+
@cache[key][:value] = value
|
|
217
|
+
@cache[key][:persisted] = false
|
|
218
|
+
|
|
219
|
+
# return if storage open
|
|
220
|
+
# non-persisted cache values update next cycle
|
|
221
|
+
return if @storage_open
|
|
222
|
+
|
|
223
|
+
# open storage
|
|
224
|
+
@storage_open = true
|
|
225
|
+
storage_contents = {}
|
|
226
|
+
begin
|
|
227
|
+
# read storage
|
|
228
|
+
storage_contents = JSON.parse(File.read(Aromancer::Storage.storage_filepath))
|
|
229
|
+
rescue StandardError => e
|
|
230
|
+
@storage_open = false
|
|
231
|
+
FileUtils.rm(Aromancer::Storage.storage_filepath)
|
|
232
|
+
return
|
|
233
|
+
end
|
|
234
|
+
|
|
235
|
+
# update storage and cache
|
|
236
|
+
([key] + @cache.keys.select{|k|
|
|
237
|
+
!@cache[k][:persisted]
|
|
238
|
+
}).each{|k|
|
|
239
|
+
storage_contents[k] = @cache[k][:value]
|
|
240
|
+
@cache[k][:persisted] = true
|
|
241
|
+
}
|
|
114
242
|
|
|
243
|
+
# write storage
|
|
115
244
|
File.open(Aromancer::Storage.storage_filepath, "w+") do |f|
|
|
116
245
|
f.write(storage_contents.to_json)
|
|
117
246
|
end
|
|
247
|
+
|
|
248
|
+
# close storage
|
|
249
|
+
@storage_open = false
|
|
118
250
|
end
|
|
119
251
|
end
|
|
120
252
|
end
|
data/lib/aromancer/version.rb
CHANGED
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
=begin
|
|
4
|
+
|
|
5
|
+
websocket.rb
|
|
6
|
+
|
|
7
|
+
websocket client.
|
|
8
|
+
|
|
9
|
+
by i2097i
|
|
10
|
+
|
|
11
|
+
=end
|
|
12
|
+
|
|
13
|
+
module Aromancer
|
|
14
|
+
class Websocket
|
|
15
|
+
include Singleton
|
|
16
|
+
|
|
17
|
+
TYPES = [
|
|
18
|
+
:arena,
|
|
19
|
+
:legends,
|
|
20
|
+
:player # todo: not used
|
|
21
|
+
]
|
|
22
|
+
|
|
23
|
+
attr_accessor :socket
|
|
24
|
+
|
|
25
|
+
def connect
|
|
26
|
+
return unless self.socket.nil?
|
|
27
|
+
return if Aromancer::Storage.get_player["api_key"].nil?
|
|
28
|
+
|
|
29
|
+
begin
|
|
30
|
+
self.socket = WebSocket::Client::Simple.connect(Aromancer::Storage.get_websocket_url, {headers: Aromancer::Server.auth_header})
|
|
31
|
+
rescue Socket::ResolutionError => e
|
|
32
|
+
disconnect
|
|
33
|
+
return
|
|
34
|
+
end
|
|
35
|
+
self.socket.on :message do |msg|
|
|
36
|
+
payload = JSON.parse(msg.data) rescue msg.data
|
|
37
|
+
if !payload.nil? &&
|
|
38
|
+
!payload["identifier"].nil? &&
|
|
39
|
+
!payload["message"].nil? &&
|
|
40
|
+
!payload["message"]["type"].nil? &&
|
|
41
|
+
TYPES.include?(payload["message"]["type"].to_sym)
|
|
42
|
+
|
|
43
|
+
case payload["message"]["type"].to_sym
|
|
44
|
+
when :arena
|
|
45
|
+
legend_id = payload["message"]["id"]
|
|
46
|
+
lid = Aromancer::Storage.get_player&.symbolize_keys[:active_legend_id]
|
|
47
|
+
if !lid.nil? && lid == legend_id.to_i
|
|
48
|
+
Aromancer::Api.load_arena
|
|
49
|
+
sleep(0.5)
|
|
50
|
+
Aromancer::Cli.instance.start_ui_thread
|
|
51
|
+
end
|
|
52
|
+
when :legends
|
|
53
|
+
Aromancer::Api.get_legends
|
|
54
|
+
when :player
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
self.socket.on :open do
|
|
60
|
+
r = {
|
|
61
|
+
command: "subscribe",
|
|
62
|
+
identifier: {channel: "PlayerChannel"}.to_json
|
|
63
|
+
}.to_json
|
|
64
|
+
Aromancer::Websocket.instance.socket.send(r)
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
self.socket.on :close do |e|
|
|
68
|
+
Aromancer::Websocket.instance.disconnect
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
self.socket.on :error do |e|
|
|
72
|
+
puts e.inspect
|
|
73
|
+
Aromancer::Websocket.instance.disconnect
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def disconnect
|
|
78
|
+
self.socket&.close
|
|
79
|
+
self.socket = nil
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
end
|
data/lib/aromancer.rb
CHANGED
|
@@ -14,8 +14,116 @@ require_relative :reiquire.to_s
|
|
|
14
14
|
Reiquire::aromancer
|
|
15
15
|
|
|
16
16
|
module Aromancer
|
|
17
|
+
THROTTLE_MS = 0.2
|
|
17
18
|
FONT = TTY::Font.new(:standard)
|
|
18
19
|
|
|
20
|
+
UNICODE = {
|
|
21
|
+
close: "\u2A2F",
|
|
22
|
+
check: "\u2713"
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
# instance runs on main thread
|
|
26
|
+
class Cli
|
|
27
|
+
include Singleton
|
|
28
|
+
|
|
29
|
+
attr_accessor :ui_thread, :ws_thread, :busy, :last_render
|
|
30
|
+
|
|
31
|
+
# user entry point
|
|
32
|
+
def self.run
|
|
33
|
+
# ensure preferences exist
|
|
34
|
+
Aromancer::Storage.get_preference(:debug, default: false)
|
|
35
|
+
Aromancer::Storage.get_preference(:force_ssl, default: true)
|
|
36
|
+
Aromancer::Storage.get_preference(:hide_sentences, default: true)
|
|
37
|
+
|
|
38
|
+
self.instance.last_render = 0
|
|
39
|
+
|
|
40
|
+
# load player on-start
|
|
41
|
+
Aromancer::Api.set_active_legend_id(nil) unless Aromancer::Storage.get_player.nil?
|
|
42
|
+
|
|
43
|
+
Thread.main[:route] = :authentication
|
|
44
|
+
Thread.main[:menu_options] = {}
|
|
45
|
+
Aromancer::Prompt.say(I18n.t("shared.welcome", version: Aromancer::VERSION))
|
|
46
|
+
Aromancer::Prompt.say(I18n.t("shared.server_url", url: Aromancer::Storage::get_server_url))
|
|
47
|
+
self.instance.start_ui_thread
|
|
48
|
+
self.instance.start_ws_thread
|
|
49
|
+
loop do
|
|
50
|
+
begin
|
|
51
|
+
self.instance.ui_thread.join
|
|
52
|
+
rescue NoMethodError => e
|
|
53
|
+
if Aromancer::Storage.get_preference(:debug)
|
|
54
|
+
Aromancer::Prompt.say(I18n.t("shared.last_error"))
|
|
55
|
+
Aromancer::Prompt.say(e.inspect)
|
|
56
|
+
sleep(5)
|
|
57
|
+
end
|
|
58
|
+
self.instance.start_ui_thread
|
|
59
|
+
self.instance.start_ws_thread
|
|
60
|
+
sleep(1)
|
|
61
|
+
rescue Interrupt => e
|
|
62
|
+
exit
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def get_view(view_key)
|
|
68
|
+
Thread.main[:menu_options][view_key.to_sym]
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def start_ui_thread(force = false)
|
|
72
|
+
return if (@busy || Time.now - @last_render < 2) && !force
|
|
73
|
+
|
|
74
|
+
@busy = true
|
|
75
|
+
self.ui_thread&.exit
|
|
76
|
+
self.ui_thread&.kill
|
|
77
|
+
Thread.main[:menu_runners] = {}
|
|
78
|
+
Aromancer::Prompt.instance.reload!
|
|
79
|
+
self.ui_thread = Thread.new do
|
|
80
|
+
loop do
|
|
81
|
+
menu_options = Thread.main[:menu_options][Thread.main[:route]]
|
|
82
|
+
menu_runners = Thread.main[:menu_runners][Thread.main[:route]]
|
|
83
|
+
unless menu_options.nil? || menu_runners.nil?
|
|
84
|
+
Aromancer::Routes.resume
|
|
85
|
+
sleep(THROTTLE_MS)
|
|
86
|
+
Aromancer.print_hud
|
|
87
|
+
v = Aromancer::Prompt.p.select("", menu_options, show_help: :always, cycle: true, per_page: 11)
|
|
88
|
+
runners = menu_runners.select{|r| r[:value] == v}
|
|
89
|
+
runners = menu_runners.select{|r| r[:value].nil?} if runners.empty?
|
|
90
|
+
|
|
91
|
+
if runners.any?
|
|
92
|
+
r = runners.first
|
|
93
|
+
r[:proc].call(v)
|
|
94
|
+
else
|
|
95
|
+
exit
|
|
96
|
+
end
|
|
97
|
+
else
|
|
98
|
+
Aromancer::Routes.resume
|
|
99
|
+
end
|
|
100
|
+
Aromancer::Cli.instance.busy = false
|
|
101
|
+
Aromancer::Cli.instance.last_render = Time.now
|
|
102
|
+
# sleep(THROTTLE_MS)
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
def start_ws_thread
|
|
108
|
+
self.ws_thread.kill unless self.ws_thread.nil?
|
|
109
|
+
self.ws_thread = Thread.new do
|
|
110
|
+
Aromancer::Websocket.instance.disconnect
|
|
111
|
+
loop do
|
|
112
|
+
player = Aromancer::Storage.get_player
|
|
113
|
+
if player.nil?
|
|
114
|
+
Aromancer::Websocket.instance.disconnect
|
|
115
|
+
elsif !player["api_key"].nil?
|
|
116
|
+
# attempt websocket connection
|
|
117
|
+
Aromancer::Websocket.instance.connect
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
sleep(4)
|
|
121
|
+
end
|
|
122
|
+
end
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
end
|
|
126
|
+
|
|
19
127
|
def self.get_font(text = :aromancer.to_s, letter_spacing = 1)
|
|
20
128
|
Aromancer::FONT.write(text, letter_spacing: letter_spacing).split("\n")
|
|
21
129
|
end
|
|
@@ -24,6 +132,19 @@ module Aromancer
|
|
|
24
132
|
"#{`echo $HOME`.strip}/.aromancer"
|
|
25
133
|
end
|
|
26
134
|
|
|
135
|
+
def self.print_hud
|
|
136
|
+
websocket_status = "#{Aromancer::Storage.instance.get_key(:websocket_scheme)}: #{Aromancer::Websocket.instance.socket.nil? ? UNICODE[:close] : UNICODE[:check] }"
|
|
137
|
+
player_status = if Aromancer::Storage.get_player.nil?
|
|
138
|
+
I18n.t("routes.main_menu.not_signed_in")
|
|
139
|
+
else
|
|
140
|
+
I18n.t("shared.signed_in_as", as: Aromancer::Storage.get_player["alias"])
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
Aromancer::Prompt.say(
|
|
144
|
+
[nil, player_status, websocket_status, nil].join(" | ")
|
|
145
|
+
)
|
|
146
|
+
end
|
|
147
|
+
|
|
27
148
|
def self.print_last_error(i18n_key = nil)
|
|
28
149
|
last_error = Aromancer::Storage.get_last_error
|
|
29
150
|
return if last_error.nil?
|
|
@@ -35,6 +156,7 @@ module Aromancer
|
|
|
35
156
|
rescue JSON::ParserError => e
|
|
36
157
|
message = last_error[:body]
|
|
37
158
|
end
|
|
38
|
-
Aromancer::Prompt.say(I18n.t(i18n_key, status: last_error[:status], error: message))
|
|
159
|
+
Aromancer::Prompt.say(I18n.t(i18n_key || "shared.last_error", status: last_error[:status], error: message))
|
|
160
|
+
Aromancer::Storage.set_last_error(nil)
|
|
39
161
|
end
|
|
40
162
|
end
|
data/lib/reiquire.rb
CHANGED
|
@@ -13,7 +13,7 @@ require :"active_support/all".to_s
|
|
|
13
13
|
require :i18n.to_s
|
|
14
14
|
require :faraday.to_s
|
|
15
15
|
require :fileutils.to_s
|
|
16
|
-
|
|
16
|
+
require :"websocket-client-simple".to_s
|
|
17
17
|
|
|
18
18
|
# require :"tty-box".to_s # draw box shapes
|
|
19
19
|
# require :"tty-color".to_s # detect color support
|
data/locale/en.yml
CHANGED
|
@@ -7,7 +7,13 @@ en:
|
|
|
7
7
|
inturrupt: "exiting due to user inturrupt..."
|
|
8
8
|
reader_initialized: "input reader initialized."
|
|
9
9
|
welcome: "welcome to aromancer. loading v%{version}..."
|
|
10
|
+
server_url: "current server: %{url}"
|
|
10
11
|
navigate: "navigate to %{route}."
|
|
12
|
+
update_cancel: "update cancelled."
|
|
13
|
+
update_confirm: "confirm update [%{new_value}] (enter y): "
|
|
14
|
+
signed_in_as: "authenticated as %{as}"
|
|
15
|
+
last_error: "error (%{status}). %{error}"
|
|
16
|
+
back: "back"
|
|
11
17
|
exit: "exit"
|
|
12
18
|
routes:
|
|
13
19
|
authentication:
|
|
@@ -17,17 +23,31 @@ en:
|
|
|
17
23
|
title: "authenticate"
|
|
18
24
|
subtitle: ""
|
|
19
25
|
main_menu:
|
|
26
|
+
configure: "configure"
|
|
20
27
|
title: "main menu"
|
|
21
28
|
subtitle: ""
|
|
22
29
|
new_game: "new game"
|
|
23
30
|
load_game: "load game"
|
|
31
|
+
find_game: "find game"
|
|
32
|
+
player_count: "enter player count (1-2): "
|
|
24
33
|
sign_out: "sign out"
|
|
25
|
-
not_signed_in: "
|
|
34
|
+
not_signed_in: "signed out"
|
|
35
|
+
configure:
|
|
36
|
+
title: "configure %{name}"
|
|
37
|
+
new_host: "enter new server host: "
|
|
38
|
+
new_port: "enter new server port: "
|
|
39
|
+
force_ssl_toggle: "ssl preference toggled to %{value}"
|
|
40
|
+
server_host: "host (%{stored})"
|
|
41
|
+
server_port: "port (%{stored})"
|
|
42
|
+
force_ssl: "ssl (%{stored})"
|
|
43
|
+
debug: "debug (%{stored})"
|
|
44
|
+
debug_toggle: "debug preference toggled to %{value}"
|
|
26
45
|
new_game:
|
|
27
46
|
player_count: "number of players"
|
|
28
47
|
story_count: "number of stories"
|
|
29
48
|
starting: "starting new game..."
|
|
30
49
|
status: "status"
|
|
50
|
+
cancelled: "cancelled. game not created."
|
|
31
51
|
legend:
|
|
32
52
|
title: "legend"
|
|
33
53
|
subtitle: "%{subtitle}"
|