matrix_sdk 0.0.1 → 0.0.2
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/.rubocop.yml +39 -0
- data/README.md +27 -2
- data/examples/simple_client.rb +93 -0
- data/lib/matrix_sdk/api.rb +402 -31
- data/lib/matrix_sdk/client.rb +234 -0
- data/lib/matrix_sdk/errors.rb +21 -5
- data/lib/matrix_sdk/extensions.rb +119 -0
- data/lib/matrix_sdk/room.rb +471 -0
- data/lib/matrix_sdk/user.rb +49 -0
- data/lib/matrix_sdk/version.rb +1 -1
- data/lib/matrix_sdk.rb +24 -2
- data/matrix-sdk.gemspec +9 -7
- metadata +27 -8
data/lib/matrix_sdk/client.rb
CHANGED
@@ -0,0 +1,234 @@
|
|
1
|
+
require 'matrix_sdk'
|
2
|
+
|
3
|
+
require 'forwardable'
|
4
|
+
|
5
|
+
module MatrixSdk
|
6
|
+
class Client
|
7
|
+
extend Forwardable
|
8
|
+
|
9
|
+
attr_reader :api
|
10
|
+
attr_accessor :cache, :mxid, :sync_filter
|
11
|
+
|
12
|
+
events :event, :presence_event, :invite_event, :left_event, :ephemeral_event
|
13
|
+
ignore_inspect :api,
|
14
|
+
:on_event, :on_presence_event, :on_invite_event, :on_left_event, :on_ephemeral_event
|
15
|
+
|
16
|
+
alias user_id mxid
|
17
|
+
alias user_id= mxid=
|
18
|
+
|
19
|
+
def_delegators :@api,
|
20
|
+
:access_token, :access_token=, :device_id, :device_id=, :homeserver, :homeserver=,
|
21
|
+
:validate_certificate, :validate_certificate=
|
22
|
+
|
23
|
+
def initialize(hs_url, params = {})
|
24
|
+
event_initialize
|
25
|
+
|
26
|
+
params[:user_id] = params[:mxid] if params[:mxid]
|
27
|
+
raise ArgumentError, 'Must provide user_id with access_token' if params[:access_token] && !params[:user_id]
|
28
|
+
|
29
|
+
@api = Api.new hs_url, params
|
30
|
+
|
31
|
+
@rooms = {}
|
32
|
+
@cache = :all
|
33
|
+
|
34
|
+
@sync_token = nil
|
35
|
+
@sync_thread = nil
|
36
|
+
@sync_filter = { room: { timeline: { limit: params.fetch(:sync_filter_limit, 20) } } }
|
37
|
+
|
38
|
+
@should_listen = false
|
39
|
+
|
40
|
+
@bad_sync_timeout_limit = 60 * 60
|
41
|
+
|
42
|
+
params.each do |k, v|
|
43
|
+
instance_variable_set("@#{k}", v) if instance_variable_defined? "@#{k}"
|
44
|
+
end
|
45
|
+
|
46
|
+
raise ArgumentError, 'Cache value must be one of of [:all, :some, :none]' unless %i[all some none].include? @cache
|
47
|
+
|
48
|
+
return unless params[:user_id]
|
49
|
+
@mxid = params[:user_id]
|
50
|
+
sync
|
51
|
+
end
|
52
|
+
|
53
|
+
def logger
|
54
|
+
@logger ||= Logging.logger[self.class.name]
|
55
|
+
end
|
56
|
+
|
57
|
+
def rooms
|
58
|
+
@rooms.values
|
59
|
+
end
|
60
|
+
|
61
|
+
def register_as_guest
|
62
|
+
data = api.register(kind: :guest)
|
63
|
+
post_authentication(data)
|
64
|
+
end
|
65
|
+
|
66
|
+
def register_with_password(username, password)
|
67
|
+
data = api.register(auth: { type: 'm.login.dummy' }, username: username, password: password)
|
68
|
+
post_authentication(data)
|
69
|
+
end
|
70
|
+
|
71
|
+
def login(username, password, params = {})
|
72
|
+
data = api.login(user: username, password: password)
|
73
|
+
post_authentication(data)
|
74
|
+
|
75
|
+
sync(timeout: params.fetch(:sync_timeout, 15)) unless params[:no_sync]
|
76
|
+
end
|
77
|
+
|
78
|
+
def logout
|
79
|
+
api.logout
|
80
|
+
@api.access_token = nil
|
81
|
+
@mxid = nil
|
82
|
+
end
|
83
|
+
|
84
|
+
def create_room(room_alias = nil, params = {})
|
85
|
+
api.create_room(params.merge(room_alias: room_alias))
|
86
|
+
end
|
87
|
+
|
88
|
+
def join_room(room_id_or_alias)
|
89
|
+
data = api.join_room(room_id_or_alias)
|
90
|
+
ensure_room(data.fetch(:room_id, room_id_or_alias))
|
91
|
+
end
|
92
|
+
|
93
|
+
def find_room(room_id_or_alias)
|
94
|
+
@rooms.fetch(room_id_or_alias, @rooms.values.find { |r| r.canonical_alias == room_id_or_alias })
|
95
|
+
end
|
96
|
+
|
97
|
+
def get_user(user_id)
|
98
|
+
User.new(self, user_id)
|
99
|
+
end
|
100
|
+
|
101
|
+
def remove_room_alias(room_alias)
|
102
|
+
api.remove_room_alias(room_alias)
|
103
|
+
end
|
104
|
+
|
105
|
+
def upload(content, content_type)
|
106
|
+
data = api.media_upload(content, content_type)
|
107
|
+
return data[:content_uri] if data.key? :content_uri
|
108
|
+
raise MatrixUnexpectedResponseError, 'Upload succeeded, but no media URI returned'
|
109
|
+
end
|
110
|
+
|
111
|
+
def listen_for_events(timeout = 30)
|
112
|
+
sync(timeout: timeout)
|
113
|
+
end
|
114
|
+
|
115
|
+
def start_listener_thread(params = {})
|
116
|
+
@should_listen = true
|
117
|
+
thread = Thread.new { listen_forever(params) }
|
118
|
+
@sync_thread = thread
|
119
|
+
thread.run
|
120
|
+
end
|
121
|
+
|
122
|
+
def stop_listener_thread
|
123
|
+
return unless @sync_thread
|
124
|
+
@should_listen = false
|
125
|
+
@sync_thread.join if @sync_thread.alive?
|
126
|
+
@sync_thread = nil
|
127
|
+
end
|
128
|
+
|
129
|
+
private
|
130
|
+
|
131
|
+
def listen_forever(params = {})
|
132
|
+
timeout = params.fetch(:timeout, 30)
|
133
|
+
params[:bad_sync_timeout] = params.fetch(:bad_sync_timeout, 5)
|
134
|
+
|
135
|
+
bad_sync_timeout = params[:bad_sync_timeout]
|
136
|
+
while @should_listen
|
137
|
+
begin
|
138
|
+
sync(timeout: timeout)
|
139
|
+
bad_sync_timeout = params[:bad_sync_timeout]
|
140
|
+
rescue MatrixRequestError => ex
|
141
|
+
logger.warn("A #{ex.class} occurred during sync")
|
142
|
+
if ex.httpstatus >= 500
|
143
|
+
logger.warn("Serverside error, retrying in #{bad_sync_timeout} seconds...")
|
144
|
+
sleep params[:bad_sync_timeout]
|
145
|
+
bad_sync_timeout = [bad_sync_timeout * 2, @bad_sync_timeout_limit].min
|
146
|
+
end
|
147
|
+
end
|
148
|
+
end
|
149
|
+
end
|
150
|
+
|
151
|
+
def post_authentication(data)
|
152
|
+
@mxid = data[:user_id]
|
153
|
+
@api.access_token = data[:access_token]
|
154
|
+
@api.device_id = data[:device_id]
|
155
|
+
@api.homeserver = data[:home_server]
|
156
|
+
access_token
|
157
|
+
end
|
158
|
+
|
159
|
+
def ensure_room(room_id)
|
160
|
+
room_id = room_id.to_s unless room_id.is_a? String
|
161
|
+
@rooms.fetch(room_id) { @rooms[room_id] = Room.new(self, room_id) }
|
162
|
+
end
|
163
|
+
|
164
|
+
def handle_state(room_id, state_event)
|
165
|
+
return unless state_event.key? :type
|
166
|
+
|
167
|
+
room = ensure_room(room_id)
|
168
|
+
content = state_event[:content]
|
169
|
+
case state_event[:type]
|
170
|
+
when 'm.room.name'
|
171
|
+
room.instance_variable_set '@name', content[:name]
|
172
|
+
when 'm.room.canonical_alias'
|
173
|
+
room.instance_variable_set '@canonical_alias', content[:alias]
|
174
|
+
when 'm.room.topic'
|
175
|
+
room.instance_variable_set '@topic', content[:topic]
|
176
|
+
when 'm.room.aliases'
|
177
|
+
room.instance_variable_get('@aliases').concat content[:aliases]
|
178
|
+
when 'm.room.join_rules'
|
179
|
+
room.instance_variable_set '@join_rule', content[:join_rule].to_sym
|
180
|
+
when 'm.room.guest_access'
|
181
|
+
room.instance_variable_set '@guest_access', content[:guest_access].to_sym
|
182
|
+
when 'm.room.member'
|
183
|
+
return unless cache == :all
|
184
|
+
|
185
|
+
if content[:membership] == 'join'
|
186
|
+
room.send :ensure_member, User.new(self, state_event[:state_key], display_name: content[:displayname])
|
187
|
+
elsif %w[leave kick invite].include? content[:membership]
|
188
|
+
room.members.delete_if { |m| m.id == state_event[:state_key] }
|
189
|
+
end
|
190
|
+
end
|
191
|
+
end
|
192
|
+
|
193
|
+
def sync(params = {})
|
194
|
+
data = api.sync params.merge(filter: sync_filter.to_json)
|
195
|
+
|
196
|
+
data[:presence][:events].each do |presence_update|
|
197
|
+
fire_presence_event(MatrixEvent.new(self, presence_update))
|
198
|
+
end
|
199
|
+
|
200
|
+
data[:rooms][:invite].each do |room_id, invite|
|
201
|
+
fire_invite_event(MatrixEvent.new(self, invite), room_id)
|
202
|
+
end
|
203
|
+
|
204
|
+
data[:rooms][:leave].each do |room_id, left|
|
205
|
+
fire_leave_event(MatrixEvent.new(self, left), room_id)
|
206
|
+
end
|
207
|
+
|
208
|
+
data[:rooms][:join].each do |room_id, join|
|
209
|
+
room = ensure_room(room_id)
|
210
|
+
room.instance_variable_set '@prev_batch', join[:timeline][:prev_batch]
|
211
|
+
join[:state][:events].each do |event|
|
212
|
+
event[:room_id] = room_id
|
213
|
+
handle_state(room_id, event)
|
214
|
+
end
|
215
|
+
|
216
|
+
join[:timeline][:events].each do |event|
|
217
|
+
event[:room_id] = room_id
|
218
|
+
room.send :put_event, event
|
219
|
+
|
220
|
+
fire_event(MatrixEvent.new(self, event), event[:type])
|
221
|
+
end
|
222
|
+
|
223
|
+
join[:ephemeral][:events].each do |event|
|
224
|
+
event[:room_id] = room_id
|
225
|
+
room.send :put_ephemeral_event, event
|
226
|
+
|
227
|
+
fire_ephemeral_event(MatrixEvent.new(self, event), event[:type])
|
228
|
+
end
|
229
|
+
end
|
230
|
+
|
231
|
+
nil
|
232
|
+
end
|
233
|
+
end
|
234
|
+
end
|
data/lib/matrix_sdk/errors.rb
CHANGED
@@ -1,15 +1,31 @@
|
|
1
1
|
module MatrixSdk
|
2
|
-
|
3
|
-
|
2
|
+
# A generic error raised for issues in the MatrixSdk
|
3
|
+
class MatrixError < StandardError
|
4
|
+
end
|
5
|
+
|
6
|
+
# An error specialized and raised for failed requests
|
7
|
+
class MatrixRequestError < MatrixError
|
8
|
+
attr_reader :code, :httpstatus, :message
|
9
|
+
alias error message
|
4
10
|
|
5
11
|
def initialize(error, status)
|
6
|
-
@
|
7
|
-
@error = error[:error]
|
12
|
+
@code = error[:errcode]
|
8
13
|
@httpstatus = status
|
14
|
+
@message = error[:error]
|
15
|
+
|
16
|
+
super error[:error]
|
9
17
|
end
|
10
18
|
|
11
19
|
def to_s
|
12
|
-
"#{
|
20
|
+
"HTTP #{httpstatus} (#{code}): #{message}"
|
13
21
|
end
|
14
22
|
end
|
23
|
+
|
24
|
+
# An error raised when errors occur in the connection layer
|
25
|
+
class MatrixConnectionError < MatrixError
|
26
|
+
end
|
27
|
+
|
28
|
+
# An error raised when the homeserver returns an unexpected response to the client
|
29
|
+
class MatrixUnexpectedResponseError < MatrixError
|
30
|
+
end
|
15
31
|
end
|
@@ -0,0 +1,119 @@
|
|
1
|
+
module URI
|
2
|
+
class MATRIX < Generic
|
3
|
+
def full_path
|
4
|
+
select(:host, :port, :path, :query, :fragment)
|
5
|
+
.reject(&:nil?)
|
6
|
+
.join
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
@@schemes['MXC'] = MATRIX
|
11
|
+
end
|
12
|
+
|
13
|
+
def events(*symbols)
|
14
|
+
module_name = "#{name}Events"
|
15
|
+
|
16
|
+
initializers = []
|
17
|
+
readers = []
|
18
|
+
methods = []
|
19
|
+
|
20
|
+
symbols.each do |sym|
|
21
|
+
name = sym.to_s
|
22
|
+
|
23
|
+
initializers << "
|
24
|
+
@on_#{name} = MatrixSdk::EventHandlerArray.new
|
25
|
+
"
|
26
|
+
readers << ":on_#{name}"
|
27
|
+
methods << "
|
28
|
+
def fire_#{name}(ev, filter = nil)
|
29
|
+
@on_#{name}.fire(ev, filter)
|
30
|
+
when_#{name}(ev) if !ev.handled?
|
31
|
+
end
|
32
|
+
|
33
|
+
def when_#{name}(ev); end
|
34
|
+
"
|
35
|
+
end
|
36
|
+
|
37
|
+
class_eval "
|
38
|
+
module #{module_name}
|
39
|
+
attr_reader #{readers.join ', '}
|
40
|
+
|
41
|
+
def event_initialize
|
42
|
+
#{initializers.join}
|
43
|
+
end
|
44
|
+
|
45
|
+
#{methods.join}
|
46
|
+
end
|
47
|
+
|
48
|
+
include #{module_name}
|
49
|
+
", __FILE__, __LINE__ - 12
|
50
|
+
end
|
51
|
+
|
52
|
+
def ignore_inspect(*symbols)
|
53
|
+
class_eval %*
|
54
|
+
def inspect
|
55
|
+
"#<\#{self.class.name}:\#{"%016x" % (object_id << 1)} \#{instance_variables.reject { |f| %i[#{symbols.map { |s| "@#{s}" }.join ' '}].include? f }.map { |f| "\#{f}=\#{instance_variable_get(f).inspect}" }.join ' ' }>"
|
56
|
+
end
|
57
|
+
*, __FILE__, __LINE__ - 4
|
58
|
+
end
|
59
|
+
|
60
|
+
module MatrixSdk
|
61
|
+
class EventHandlerArray < Hash
|
62
|
+
def add_handler(filter = nil, id = nil, &block)
|
63
|
+
id ||= block.hash
|
64
|
+
self[id] = { filter: filter, id: id, block: block }
|
65
|
+
end
|
66
|
+
|
67
|
+
def remove_handler(id)
|
68
|
+
delete id
|
69
|
+
end
|
70
|
+
|
71
|
+
def fire(event, filter = nil)
|
72
|
+
reverse_each do |_k, h|
|
73
|
+
h[:block].call(event) unless event.matches?(h[:filter], filter)
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
class Event
|
79
|
+
attr_writer :handled
|
80
|
+
|
81
|
+
ignore_inspect :sender
|
82
|
+
|
83
|
+
def initialize(sender)
|
84
|
+
@sender = sender
|
85
|
+
@handled = false
|
86
|
+
end
|
87
|
+
|
88
|
+
def handled?
|
89
|
+
@handled
|
90
|
+
end
|
91
|
+
|
92
|
+
def matches?(_filter)
|
93
|
+
true
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
class MatrixEvent < Event
|
98
|
+
attr_accessor :event, :filter
|
99
|
+
|
100
|
+
ignore_inspect :sender
|
101
|
+
|
102
|
+
def initialize(sender, event = nil, filter = nil)
|
103
|
+
@event = event
|
104
|
+
@filter = filter || @event[:type]
|
105
|
+
super sender
|
106
|
+
end
|
107
|
+
|
108
|
+
def matches?(filter, filter_override = nil)
|
109
|
+
return true if filter_override.nil? && (@filter.nil? || filter.nil?)
|
110
|
+
|
111
|
+
to_match = filter_override || @filter
|
112
|
+
if filter.is_a? Regexp
|
113
|
+
filter.match(to_match) { true } || false
|
114
|
+
else
|
115
|
+
to_match == filter
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|