matrix_sdk 0.0.1 → 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
@@ -1,15 +1,31 @@
1
1
  module MatrixSdk
2
- class MatrixError
3
- attr_reader :errcode, :error, :httpstatus
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
- @errcode = error[:errcode]
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
- "#{errcode}: #{error}"
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