simplex-chat 0.2.0 → 0.4.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.
- checksums.yaml +4 -4
- data/lib/simplex-chat/errors.rb +20 -0
- data/lib/simplex-chat/patches.rb +17 -0
- data/lib/simplex-chat/types.rb +17 -0
- data/lib/simplex-chat/version.rb +1 -1
- data/lib/simplex-chat.rb +111 -37
- metadata +5 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: cb15441c1b0a770fff253bb8eddbacf6bf52df1ddcddd502597ada7385a62c1c
|
4
|
+
data.tar.gz: 0ba06bf88529d8bbd2eb429fd781162c94a23bfe7577ea313ef9ca8db346c407
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: e975110a2bfa186b9f1f9f80264fa0343bc6e148e113a4eaf0d778064abbdeb54e822725e51ca499700f4e2a855fa41a8a06aa532b5561ecc33e75795528adcc
|
7
|
+
data.tar.gz: b681e83e16e20ad1e55f0c16918a5fe438703bd98c29e1f6a4cca2e24af19e3a352162a9bb2dab673f927974b222715fe431cf7d521ff0848431631846fe5d66
|
@@ -0,0 +1,20 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module SimpleXChat
|
4
|
+
# All SimpleX-related errors will inherit from GenericError
|
5
|
+
# These errors should be recoverable
|
6
|
+
class GenericError < StandardError
|
7
|
+
end
|
8
|
+
|
9
|
+
class SendCommandError < GenericError
|
10
|
+
def initialize(cmd)
|
11
|
+
super "Failed to send command: #{cmd}"
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
class UnexpectedResponseError < GenericError
|
16
|
+
def initialize(type, expected_type)
|
17
|
+
super "Unexpected response type: #{type} (expected: #{expected_type})"
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module SimpleXChat
|
4
|
+
require 'net/http'
|
5
|
+
|
6
|
+
# Fixes regex match for status line in HTTPResponse
|
7
|
+
class HTTPResponse < Net::HTTPResponse
|
8
|
+
class << self
|
9
|
+
def read_status_line(sock)
|
10
|
+
str = sock.readline
|
11
|
+
m = /\AHTTP(?:\/(\d+\.\d+))?\s+(\d\d\d)(?:\s+(.*))?\Z/in.match(str) or
|
12
|
+
raise Net::HTTPBadResponse, "wrong status line: #{str.dump}"
|
13
|
+
m.captures
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module SimpleXChat
|
4
|
+
module ChatType
|
5
|
+
DIRECT = '@'
|
6
|
+
GROUP = '#'
|
7
|
+
CONTACT_REQUEST = '<@'
|
8
|
+
end
|
9
|
+
|
10
|
+
module GroupMemberRole
|
11
|
+
AUTHOR = 'author' # reserved and unused as of now, but added anyways
|
12
|
+
OWNER = 'owner'
|
13
|
+
ADMIN = 'admin'
|
14
|
+
MEMBER = 'member'
|
15
|
+
OBSERVER = 'observer'
|
16
|
+
end
|
17
|
+
end
|
data/lib/simplex-chat/version.rb
CHANGED
data/lib/simplex-chat.rb
CHANGED
@@ -1,6 +1,9 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require_relative 'simplex-chat/version'
|
4
|
+
require_relative 'simplex-chat/errors'
|
5
|
+
require_relative 'simplex-chat/patches'
|
6
|
+
require_relative 'simplex-chat/types'
|
4
7
|
|
5
8
|
module SimpleXChat
|
6
9
|
require 'net/http'
|
@@ -10,28 +13,10 @@ module SimpleXChat
|
|
10
13
|
require 'concurrent'
|
11
14
|
require 'time'
|
12
15
|
|
13
|
-
# Fixes regex match for status line in HTTPResponse
|
14
|
-
class HTTPResponse < Net::HTTPResponse
|
15
|
-
class << self
|
16
|
-
def read_status_line(sock)
|
17
|
-
str = sock.readline
|
18
|
-
m = /\AHTTP(?:\/(\d+\.\d+))?\s+(\d\d\d)(?:\s+(.*))?\Z/in.match(str) or
|
19
|
-
raise Net::HTTPBadResponse, "wrong status line: #{str.dump}"
|
20
|
-
m.captures
|
21
|
-
end
|
22
|
-
end
|
23
|
-
end
|
24
|
-
|
25
|
-
module ChatType
|
26
|
-
DIRECT = '@'
|
27
|
-
GROUP = '#'
|
28
|
-
CONTACT_REQUEST = '<@'
|
29
|
-
end
|
30
|
-
|
31
16
|
class ClientAgent
|
32
17
|
attr_accessor :on_message
|
33
18
|
|
34
|
-
def initialize
|
19
|
+
def initialize(client_uri, connect: true, log_level: Logger::INFO, timeout_ms: 10_000, interval_ms: 100)
|
35
20
|
@uri = client_uri
|
36
21
|
@message_queue = SizedQueue.new 4096
|
37
22
|
@chat_message_queue = Queue.new
|
@@ -42,6 +27,8 @@ module SimpleXChat
|
|
42
27
|
@listener_thread = nil
|
43
28
|
@corr_id = Concurrent::AtomicFixnum.new(1) # Correlation ID for mapping client responses to command waiters
|
44
29
|
@command_waiters = Concurrent::Hash.new
|
30
|
+
@timeout_ms = timeout_ms
|
31
|
+
@interval_ms = interval_ms
|
45
32
|
|
46
33
|
@logger = Logger.new($stderr)
|
47
34
|
@logger.level = log_level
|
@@ -50,9 +37,7 @@ module SimpleXChat
|
|
50
37
|
"| [#{severity}] | #{datetime} | (#{progname}) :: #{msg}\n"
|
51
38
|
}
|
52
39
|
|
53
|
-
if connect
|
54
|
-
self.connect
|
55
|
-
end
|
40
|
+
self.connect if connect
|
56
41
|
|
57
42
|
@logger.debug("Initialized ClientAgent")
|
58
43
|
end
|
@@ -79,6 +64,7 @@ module SimpleXChat
|
|
79
64
|
|
80
65
|
msg = JSON.parse obj.to_s
|
81
66
|
# @logger.debug("New message: #{msg}")
|
67
|
+
# @logger.debug("Command waiters: #{@command_waiters}")
|
82
68
|
|
83
69
|
corr_id = msg["corrId"]
|
84
70
|
resp = msg["resp"]
|
@@ -88,6 +74,7 @@ module SimpleXChat
|
|
88
74
|
single_use_queue.push(resp)
|
89
75
|
@logger.debug("Message sent to waiter with corrId '#{corr_id}'")
|
90
76
|
else
|
77
|
+
@logger.debug("Message put on message queue")
|
91
78
|
@message_queue.push resp
|
92
79
|
end
|
93
80
|
rescue IO::WaitReadable
|
@@ -144,8 +131,13 @@ module SimpleXChat
|
|
144
131
|
group = nil
|
145
132
|
sender = nil
|
146
133
|
contact = nil
|
134
|
+
contact_role = nil
|
147
135
|
if chat_type == ChatType::GROUP
|
148
|
-
|
136
|
+
# NOTE: The group can "send messages" without a contact
|
137
|
+
# For example, when a member is removed, the group
|
138
|
+
# sends a message about his removal, with no contact
|
139
|
+
contact = chat_item.dig "chatItem", "chatDir", "groupMember", "localDisplayName"
|
140
|
+
contact_role = chat_item.dig "chatItem", "chatDir", "groupMember", "memberRole"
|
149
141
|
group = chat_item["chatInfo"]["groupInfo"]["localDisplayName"]
|
150
142
|
sender = group
|
151
143
|
else
|
@@ -155,14 +147,17 @@ module SimpleXChat
|
|
155
147
|
|
156
148
|
msg_text = chat_item["chatItem"]["meta"]["itemText"]
|
157
149
|
timestamp = chat_item["chatItem"]["meta"]["updatedAt"]
|
150
|
+
image_preview = chat_item.dig "chatItem", "content", "msgContent", "image"
|
158
151
|
|
159
152
|
chat_message = {
|
160
153
|
:chat_type => chat_type,
|
161
154
|
:sender => sender,
|
155
|
+
:contact_role => contact_role,
|
162
156
|
:contact => contact,
|
163
157
|
:group => group,
|
164
158
|
:msg_text => msg_text,
|
165
|
-
:msg_timestamp => Time.parse(timestamp)
|
159
|
+
:msg_timestamp => Time.parse(timestamp),
|
160
|
+
:img_preview => image_preview
|
166
161
|
}
|
167
162
|
|
168
163
|
@chat_message_queue.push chat_message
|
@@ -182,7 +177,7 @@ module SimpleXChat
|
|
182
177
|
end
|
183
178
|
|
184
179
|
# Sends a raw command to the SimpleX Chat client
|
185
|
-
def send_command(cmd)
|
180
|
+
def send_command(cmd, timeout_ms: @timeout_ms, interval_ms: @interval_ms)
|
186
181
|
corr_id = next_corr_id
|
187
182
|
obj = {
|
188
183
|
"corrId" => corr_id,
|
@@ -198,30 +193,40 @@ module SimpleXChat
|
|
198
193
|
# command response
|
199
194
|
single_use_queue = SizedQueue.new 1
|
200
195
|
@command_waiters[corr_id] = single_use_queue
|
196
|
+
@logger.debug("Created command waiter for command ##{corr_id}")
|
201
197
|
|
198
|
+
@logger.debug("Sending command ##{corr_id}: #{json.to_s}")
|
202
199
|
@socket.write frame.to_s
|
203
200
|
|
201
|
+
@logger.debug("Waiting response for command ##{corr_id}...")
|
204
202
|
msg = nil
|
205
|
-
|
203
|
+
iterations = timeout_ms / interval_ms
|
204
|
+
iterations.times do
|
206
205
|
begin
|
207
206
|
msg = single_use_queue.pop(true)
|
208
207
|
break
|
209
208
|
rescue ThreadError
|
210
|
-
sleep 0
|
209
|
+
sleep(interval_ms / 1000.0)
|
211
210
|
end
|
212
211
|
end
|
213
212
|
|
214
213
|
if msg == nil
|
215
|
-
raise
|
214
|
+
raise SendCommandError.new(json.to_s)
|
216
215
|
end
|
217
216
|
|
217
|
+
@logger.debug("Command ##{corr_id} finished successfully with response: #{msg}")
|
218
|
+
|
218
219
|
msg
|
220
|
+
ensure
|
221
|
+
@command_waiters.delete corr_id
|
222
|
+
@logger.debug("Cleaned up command waiter ##{corr_id}")
|
219
223
|
end
|
220
224
|
|
221
225
|
def api_version
|
222
226
|
resp = send_command '/version'
|
223
227
|
resp_type = resp["type"]
|
224
|
-
|
228
|
+
expected_resp_type = "versionInfo"
|
229
|
+
raise UnexpectedResponseError.new(resp_type, expected_resp_type) unless resp_type == expected_resp_type
|
225
230
|
|
226
231
|
resp["versionInfo"]["version"]
|
227
232
|
end
|
@@ -229,7 +234,8 @@ module SimpleXChat
|
|
229
234
|
def api_profile
|
230
235
|
resp = send_command '/profile'
|
231
236
|
resp_type = resp["type"]
|
232
|
-
|
237
|
+
expected_resp_type = "userProfile"
|
238
|
+
raise UnexpectedResponseError.new(resp_type, expected_resp_type) unless resp_type == expected_resp_type
|
233
239
|
|
234
240
|
{
|
235
241
|
"name" => resp["user"]["profile"]["displayName"],
|
@@ -246,7 +252,8 @@ module SimpleXChat
|
|
246
252
|
return nil
|
247
253
|
end
|
248
254
|
|
249
|
-
|
255
|
+
expected_resp_type = "userContactLink"
|
256
|
+
raise UnexpectedResponseError.new(resp_type, expected_resp_type) unless resp_type == expected_resp_type
|
250
257
|
|
251
258
|
resp["contactLink"]["connReqContact"]
|
252
259
|
end
|
@@ -254,15 +261,35 @@ module SimpleXChat
|
|
254
261
|
def api_create_user_address
|
255
262
|
resp = send_command '/address'
|
256
263
|
resp_type = resp["type"]
|
257
|
-
|
264
|
+
expected_resp_type = "userContactLinkCreated"
|
265
|
+
raise UnexpectedResponseError.new(resp_type, expected_resp_type) unless resp_type == expected_resp_type
|
258
266
|
|
259
267
|
resp["connReqContact"]
|
260
268
|
end
|
261
269
|
|
262
|
-
def api_send_text_message(chat_type,
|
263
|
-
resp = send_command "#{chat_type}#{
|
270
|
+
def api_send_text_message(chat_type, receiver, message)
|
271
|
+
resp = send_command "#{chat_type}#{receiver} #{message}"
|
264
272
|
resp_type = resp["type"]
|
265
|
-
|
273
|
+
expected_resp_type = "newChatItems"
|
274
|
+
raise UnexpectedResponseError.new(resp_type, expected_resp_type) unless resp_type == expected_resp_type
|
275
|
+
|
276
|
+
resp["chatItems"]
|
277
|
+
end
|
278
|
+
|
279
|
+
def api_send_image(chat_type, receiver, file_path)
|
280
|
+
resp = send_command "/image #{chat_type}#{receiver} #{file_path}"
|
281
|
+
resp_type = resp["type"]
|
282
|
+
expected_resp_type = "newChatItems"
|
283
|
+
raise UnexpectedResponseError.new(resp_type, expected_resp_type) unless resp_type == expected_resp_type
|
284
|
+
|
285
|
+
resp["chatItems"]
|
286
|
+
end
|
287
|
+
|
288
|
+
def api_send_file(chat_type, receiver, file_path)
|
289
|
+
resp = send_command "/file #{chat_type}#{receiver} #{file_path}"
|
290
|
+
resp_type = resp["type"]
|
291
|
+
expected_resp_type = "newChatItems"
|
292
|
+
raise UnexpectedResponseError.new(resp_type, expected_resp_type) unless resp_type == expected_resp_type
|
266
293
|
|
267
294
|
resp["chatItems"]
|
268
295
|
end
|
@@ -270,7 +297,8 @@ module SimpleXChat
|
|
270
297
|
def api_contacts
|
271
298
|
resp = send_command "/contacts"
|
272
299
|
resp_type = resp["type"]
|
273
|
-
|
300
|
+
expected_resp_type = "contactsList"
|
301
|
+
raise UnexpectedResponseError.new(resp_type, expected_resp_type) unless resp_type == expected_resp_type
|
274
302
|
|
275
303
|
contacts = resp["contacts"]
|
276
304
|
contacts.map{ |c| {
|
@@ -286,7 +314,8 @@ module SimpleXChat
|
|
286
314
|
def api_groups
|
287
315
|
resp = send_command "/groups"
|
288
316
|
resp_type = resp["type"]
|
289
|
-
|
317
|
+
expected_resp_type = "groupsList"
|
318
|
+
raise UnexpectedResponseError.new(resp_type, expected_resp_type) unless resp_type == expected_resp_type
|
290
319
|
|
291
320
|
groups = resp["groups"]
|
292
321
|
groups.map{ |entry|
|
@@ -308,6 +337,51 @@ module SimpleXChat
|
|
308
337
|
}
|
309
338
|
end
|
310
339
|
|
340
|
+
def api_auto_accept is_enabled
|
341
|
+
onoff = is_enabled && "on" || "off"
|
342
|
+
|
343
|
+
resp = send_command "/auto_accept #{onoff}"
|
344
|
+
resp_type = resp["type"]
|
345
|
+
expected_resp_type = "userContactLinkUpdated"
|
346
|
+
raise UnexpectedResponseError.new(resp_type, expected_resp_type) unless resp_type == expected_resp_type
|
347
|
+
|
348
|
+
nil
|
349
|
+
end
|
350
|
+
|
351
|
+
def api_kick_group_member(group, member)
|
352
|
+
resp = send_command "/remove #{group} #{member}"
|
353
|
+
resp_type = resp["type"]
|
354
|
+
expected_resp_type = "userDeletedMember"
|
355
|
+
raise UnexpectedResponseError.new(resp_type, expected_resp_type) unless resp_type == expected_resp_type
|
356
|
+
end
|
357
|
+
|
358
|
+
# Parameters for /network:
|
359
|
+
# - socks: on/off/<[ipv4]:port>
|
360
|
+
# - socks-mode: always/onion
|
361
|
+
# - smp-proxy: always/unknown/unprotected/never
|
362
|
+
# - smp-proxy-fallback: no/protected/yes
|
363
|
+
# - timeout: <seconds>
|
364
|
+
def api_network(socks: nil, socks_mode: nil, smp_proxy: nil, smp_proxy_fallback: nil, timeout_secs: nil)
|
365
|
+
args = {
|
366
|
+
"socks" => socks,
|
367
|
+
"socks-mode" => socks_mode,
|
368
|
+
"smp-proxy" => smp_proxy,
|
369
|
+
"smp-proxy-fallback" => smp_proxy_fallback,
|
370
|
+
"timeout" => timeout_secs
|
371
|
+
}
|
372
|
+
command = '/network'
|
373
|
+
args.each do |param, value|
|
374
|
+
next if value == nil
|
375
|
+
command += " #{param}=#{value}"
|
376
|
+
end
|
377
|
+
resp = send_command command
|
378
|
+
resp_type = resp["type"]
|
379
|
+
expected_resp_type = "networkConfig"
|
380
|
+
raise UnexpectedResponseError.new(resp_type, expected_resp_type) unless resp_type == expected_resp_type
|
381
|
+
|
382
|
+
resp["networkConfig"]
|
383
|
+
end
|
384
|
+
|
311
385
|
private
|
312
386
|
|
313
387
|
def next_corr_id
|
metadata
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: simplex-chat
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.4.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- rdbo
|
8
8
|
bindir: exe
|
9
9
|
cert_chain: []
|
10
|
-
date: 2025-
|
10
|
+
date: 2025-03-03 00:00:00.000000000 Z
|
11
11
|
dependencies:
|
12
12
|
- !ruby/object:Gem::Dependency
|
13
13
|
name: websocket
|
@@ -48,6 +48,9 @@ files:
|
|
48
48
|
- README.md
|
49
49
|
- Rakefile
|
50
50
|
- lib/simplex-chat.rb
|
51
|
+
- lib/simplex-chat/errors.rb
|
52
|
+
- lib/simplex-chat/patches.rb
|
53
|
+
- lib/simplex-chat/types.rb
|
51
54
|
- lib/simplex-chat/version.rb
|
52
55
|
- showcase.png
|
53
56
|
homepage: https://github.com/rdbo/simplex-chat-ruby
|