chatrix 1.0.0.pre → 1.0.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.
@@ -0,0 +1,98 @@
1
+ require 'chatrix/matrix'
2
+ require 'chatrix/users'
3
+ require 'chatrix/rooms'
4
+
5
+ require 'wisper'
6
+
7
+ module Chatrix
8
+ # A client wrapping the API in easy-to-use methods.
9
+ class Client
10
+ include Wisper::Publisher
11
+
12
+ # @!attribute [r] me
13
+ # @return [User] The user associated with the access token.
14
+ attr_reader :me
15
+
16
+ # Initializes a new Client instance.
17
+ #
18
+ # Currently it requires a token, future versions will allow login
19
+ # with arbitrary details.
20
+ #
21
+ # @param token [String] The access token to use.
22
+ # @param id [String] The user ID of the token owner.
23
+ # @param homeserver [String,nil] Homeserver to connect to. If not set,
24
+ # the default homeserver defined in Chatrix::Matrix will be used.
25
+ def initialize(token, id, homeserver: nil)
26
+ @matrix = Matrix.new token, homeserver
27
+
28
+ @users = Users.new
29
+ @rooms = Rooms.new @users, @matrix
30
+
31
+ @me = @users.send(:get_user, id)
32
+
33
+ @rooms.on(:added) do |room|
34
+ broadcast(:room_added, room)
35
+ room.timeline.on(:message) { |r, m| broadcast(:room_message, r, m) }
36
+ end
37
+ end
38
+
39
+ # Starts syncing against the homeserver.
40
+ #
41
+ # Launches a new thread that will continously check for new events
42
+ # from the server.
43
+ #
44
+ # @see #sync! See the documentation for {#sync!} for more information
45
+ # and what happens in case of an error during sync.
46
+ def start_syncing
47
+ @sync_thread ||= Thread.new { loop { sync! } }
48
+ end
49
+
50
+ # Stops syncing against the homeserver.
51
+ def stop_syncing
52
+ return unless @sync_thread.is_a? Thread
53
+ @sync_thread.exit
54
+ @sync_thread.join
55
+ @sync_thread = nil
56
+ end
57
+
58
+ # Gets the user with the specified ID or display name.
59
+ #
60
+ # @return [User,nil] Returns a User object if the user could be found,
61
+ # otherwise `nil`.
62
+ def get_user(id)
63
+ @users[id]
64
+ end
65
+
66
+ # Gets the room with the specified ID, alias, or name.
67
+ #
68
+ # @return [Room,nil] Returns a Room object if the room could be found,
69
+ # otherwise `nil`.
70
+ def get_room(id)
71
+ @rooms[id]
72
+ end
73
+
74
+ private
75
+
76
+ # Syncs against the server.
77
+ #
78
+ # If an API error occurs during sync, it will be rescued and broadcasted
79
+ # as `:sync_error`.
80
+ def sync!
81
+ events = @matrix.sync since: @since
82
+ process_sync events
83
+ rescue ApiError => err
84
+ broadcast(:sync_error, err)
85
+ end
86
+
87
+ # Process the sync result.
88
+ #
89
+ # @param events [Hash] The events to sync.
90
+ def process_sync(events)
91
+ return unless events.is_a? Hash
92
+ @since = events['next_batch']
93
+ broadcast(:sync, events)
94
+
95
+ @rooms.process_events events['rooms'] if events.key? 'rooms'
96
+ end
97
+ end
98
+ end
@@ -0,0 +1,58 @@
1
+ require 'chatrix/user'
2
+
3
+ module Chatrix
4
+ module Components
5
+ # Provides administrative actions for a room.
6
+ class Admin
7
+ # Initializes a new Admin instance.
8
+ #
9
+ # @param room [Room] The room to administrate.
10
+ # @param matrix [Matrix] Matrix API instance.
11
+ def initialize(room, matrix)
12
+ @room = room
13
+ @matrix = matrix
14
+ end
15
+
16
+ # Joins the room. Can only be used on public rooms or if the user
17
+ # has been invited.
18
+ def join
19
+ @matrix.rooms.actions.join @room.id
20
+ end
21
+
22
+ # Leaves the room. If the user is currently invited to the room,
23
+ # leaving the room is the same as rejecting the invite.
24
+ def leave
25
+ @matrix.rooms.actions.leave @room.id
26
+ end
27
+
28
+ # Kicks a user from the room.
29
+ #
30
+ # @param user [User,String] The user to kick, can be either a User
31
+ # object or a String (user ID).
32
+ # @param reason [String] The reason for the kick.
33
+ # @return [Boolean] `true` if the user was kicked, otherwise `false`.
34
+ def kick(user, reason)
35
+ @matrix.rooms.actions.kick @room.id, user, reason
36
+ end
37
+
38
+ # Bans a user from the room.
39
+ #
40
+ # @param user [User,String] The user to kick, can be either a User
41
+ # object or a String (user ID).
42
+ # @param reason [String] The reason for the ban.
43
+ # @return [Boolean] `true` if the user was kicked, otherwise `false`.
44
+ def ban(user, reason)
45
+ @matrix.rooms.actions.ban @room.id, user, reason
46
+ end
47
+
48
+ # Unbans a user from the room.
49
+ #
50
+ # @param user [User,String] The user to unban, can be either a User
51
+ # objec or a String (user ID).
52
+ # @return [Boolean] `true` if the user was unbanned, otherwise `false`.
53
+ def unban(user)
54
+ @matrix.rooms.actions.unban @room.id, user
55
+ end
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,42 @@
1
+ module Chatrix
2
+ module Components
3
+ # Class to handle messaging actions for a room.
4
+ class Messaging
5
+ # Initializes a new Messaging instance.
6
+ # @param room [Room] The room to handle messaging for.
7
+ # @param matrix [Matrix] Matrix API instance.
8
+ def initialize(room, matrix)
9
+ @room = room
10
+ @matrix = matrix
11
+ end
12
+
13
+ # Sends a message to the room.
14
+ # @param message [String] The message to send.
15
+ # @return [String] Event ID for the send action.
16
+ def send_message(message)
17
+ @matrix.rooms.actions.send_message @room.id, message
18
+ end
19
+
20
+ # Sends a notice to the room.
21
+ # @param message [String] The notice to send.
22
+ # @return (see #send_message)
23
+ def send_notice(message)
24
+ @matrix.rooms.actions.send_message @room.id, message, 'm.notice'
25
+ end
26
+
27
+ # Sends an emote to the room.
28
+ # @param message [String] The emote text to send.
29
+ # @return (see #send_message)
30
+ def send_emote(message)
31
+ @matrix.rooms.actions.send_message @room.id, message, 'm.emote'
32
+ end
33
+
34
+ # Sends an HTML message to the room.
35
+ # @param message [String] The HTML formatted message to send.
36
+ # @return (see #send_message)
37
+ def send_html(message)
38
+ @matrix.rooms.actions.send_html @room.id, message
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,55 @@
1
+ require 'wisper'
2
+
3
+ module Chatrix
4
+ module Components
5
+ # Helper for parsing permissions in a room.
6
+ class Permissions
7
+ include Wisper::Publisher
8
+
9
+ # Initializes a new Permissions instance.
10
+ # @param room [Room] The room that this permissions set belongs to.
11
+ def initialize(room)
12
+ @room = room
13
+ @actions = {}
14
+ @events = {}
15
+ end
16
+
17
+ # Updates permission data.
18
+ # @param content [Hash] New permission data.
19
+ def update(content)
20
+ @actions[:ban] = content['ban']
21
+ @actions[:kick] = content['kick']
22
+ @actions[:invite] = content['invite']
23
+ @actions[:redact] = content['redact']
24
+
25
+ content['events'].each do |event, level|
26
+ @events[event.match(/\w+$/).to_s.to_sym] = level
27
+ end
28
+
29
+ broadcast :update, @room, self
30
+ end
31
+
32
+ # Check if a user can perform an action.
33
+ #
34
+ # @param user [User] The user to test.
35
+ # @param action [Symbol] The action to check.
36
+ # @return [Boolean] `true` if the user can perform the action,
37
+ # otherwise `false`.
38
+ def can?(user, action)
39
+ return false unless @actions.key? action
40
+ user.power_in(@room) >= @actions[action]
41
+ end
42
+
43
+ # Check if a user can set an event.
44
+ #
45
+ # @param user [User] The user to test.
46
+ # @param event [Symbol] The event to check.
47
+ # @return [Boolean] `true` if the user can set the event,
48
+ # otherwise `false`.
49
+ def can_set?(user, event)
50
+ return false unless @events.key? event
51
+ user.power_in(@room) >= @events[event]
52
+ end
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,160 @@
1
+ require 'chatrix/events'
2
+
3
+ require 'chatrix/components/permissions'
4
+
5
+ require 'set'
6
+ require 'wisper'
7
+
8
+ module Chatrix
9
+ module Components
10
+ # Manages state for a room.
11
+ class State
12
+ include Wisper::Publisher
13
+
14
+ # @!attribute [r] canonical_alias
15
+ # @return [String,nil] The canonical alias, or `nil` if none has
16
+ # been set.
17
+ # @!attribute [r] name
18
+ # @return [String,nil] The name, or `nil` if none has been set.
19
+ # @!attribute [r] topic
20
+ # @return [String,nil] The topic, or `nil` if none has been set.
21
+ # @!attribute [r] creator
22
+ # @return [User] The user who created the room.
23
+ # @!attribute [r] guest_access
24
+ # @return [Boolean] `true` if guests are allowed in the room,
25
+ # otherwise `false`.
26
+ # @!attribute [r] history_visibility
27
+ # @return [String] The room's history visibility.
28
+ # @!attribute [r] join_rule
29
+ # @return [String] Join rules for the room.
30
+ # @!attribute [r] permissions
31
+ # @return [Permissions] Check room permissions.
32
+ attr_reader :canonical_alias, :name, :topic, :creator, :guest_access,
33
+ :join_rule, :history_visibility, :permissions
34
+
35
+ # Initializes a new State instance.
36
+ #
37
+ # @param room [Room] The room the state belongs to.
38
+ # @param users [Users] The user manager.
39
+ def initialize(room, users)
40
+ @room = room
41
+ @users = users
42
+
43
+ @permissions = Permissions.new @room
44
+
45
+ @aliases = []
46
+ @members = Set.new
47
+ end
48
+
49
+ # Returns whether the specified user is a member of the room.
50
+ #
51
+ # @param user [User] The user to check.
52
+ # @return [Boolean] `true` if the user is a member of the room,
53
+ # otherwise `false`.
54
+ def member?(user)
55
+ @members.member? user
56
+ end
57
+
58
+ # Updates the state with new event data.
59
+ # @param data [Hash] Event data.
60
+ def update(data)
61
+ data['events'].each { |e| process_event e } if data.key? 'events'
62
+ end
63
+
64
+ private
65
+
66
+ # Processes a state event.
67
+ # @param event [Hash] Event data.
68
+ def process_event(event)
69
+ return if Events.processed? event
70
+
71
+ name = 'handle_' + event['type'].match(/\w+$/).to_s
72
+ send(name, event) if respond_to? name, true
73
+
74
+ Events.processed event
75
+ end
76
+
77
+ # Handle the `m.room.create` event.
78
+ # @param event [Hash] Event data.
79
+ def handle_create(event)
80
+ @creator = @users.send(:get_user, event['content']['creator'])
81
+ broadcast :creator, @room, @creator
82
+ end
83
+
84
+ # Handle the `m.room.canonical_alias` event.
85
+ # @param (see #handle_create)
86
+ def handle_canonical_alias(event)
87
+ @canonical_alias = event['content']['alias']
88
+ broadcast :canonical_alias, @room, @canonical_alias
89
+ end
90
+
91
+ # Handle the `m.room.aliases` event.
92
+ # @param (see #handle_create)
93
+ def handle_aliases(event)
94
+ @aliases.replace event['content']['aliases']
95
+ broadcast :aliases, @room, @aliases
96
+ end
97
+
98
+ # Handle the `m.room.name` event.
99
+ # @param (see #handle_create)
100
+ def handle_name(event)
101
+ broadcast :name, @room, @name = event['content']['name']
102
+ end
103
+
104
+ # Handle the `m.room.topic` event.
105
+ # @param (see #handle_create)
106
+ def handle_topic(event)
107
+ broadcast :topic, @room, @topic = event['content']['topic']
108
+ end
109
+
110
+ # Handle the `m.room.guest_access` event.
111
+ # @param (see #handle_create)
112
+ def handle_guest_access(event)
113
+ @guest_access = event['content']['guest_access'] == 'can_join'
114
+ broadcast :guest_access, @room, @guest_access
115
+ end
116
+
117
+ # Handle the `m.room.history_visibility` event.
118
+ # @param (see #handle_create)
119
+ def handle_history_visibility(event)
120
+ @history_visibility = event['content']['history_visibility']
121
+ broadcast :history_visibility, @room, @history_visibility
122
+ end
123
+
124
+ # Handle the `m.room.join_rules` event.
125
+ # @param (see #handle_create)
126
+ def handle_join_rules(event)
127
+ @join_rule = event['content']['join_rule']
128
+ broadcast :join_rule, @room, @join_rule
129
+ end
130
+
131
+ # Process a member event.
132
+ # @param event [Hash] The member event.
133
+ def handle_member(event)
134
+ @users.process_member_event self, event
135
+ user = @users[event['sender']]
136
+ membership = event['content']['membership'].to_sym
137
+
138
+ # Don't process invite state change if the user is already a
139
+ # member in the room.
140
+ return if membership == :invite && member?(user)
141
+
142
+ if membership == :join
143
+ @members.add user
144
+ else
145
+ @members.delete user
146
+ end
147
+
148
+ broadcast(membership, @room, user)
149
+ end
150
+
151
+ # Process a power level event.
152
+ # @param event [Hash] Event data.
153
+ def handle_power_levels(event)
154
+ content = event['content']
155
+ @permissions.update content
156
+ @users.process_power_levels @room, content['users']
157
+ end
158
+ end
159
+ end
160
+ end
@@ -0,0 +1,50 @@
1
+ require 'chatrix/message'
2
+ require 'chatrix/events'
3
+
4
+ require 'wisper'
5
+
6
+ module Chatrix
7
+ module Components
8
+ # Manages the timeline for a room.
9
+ class Timeline
10
+ include Wisper::Publisher
11
+
12
+ # Initializes a new Timeline instance.
13
+ #
14
+ # @param room [Room] The room this timeline belongs to.
15
+ # @param users [Users] The user manager.
16
+ def initialize(room, users)
17
+ @room = room
18
+ @users = users
19
+ end
20
+
21
+ # Process timeline events.
22
+ # @param data [Hash] Events to process.
23
+ def update(data)
24
+ data['events'].each { |e| process_event e } if data.key? 'events'
25
+
26
+ # Pass the event data to state to handle any state updates
27
+ # in the timeline
28
+ @room.state.update data
29
+ end
30
+
31
+ private
32
+
33
+ # Processes a timeline event.
34
+ # @param event [Hash] Event data.
35
+ def process_event(event)
36
+ return if Events.processed? event
37
+ name = 'handle_' + event['type'].match(/\w+$/).to_s
38
+ send(name, event) if respond_to? name, true
39
+ end
40
+
41
+ # Process a message event.
42
+ # @param event [Hash] Event data.
43
+ def handle_message(event)
44
+ message = Message.new @users[event['sender']], event['content']
45
+ broadcast(:message, @room, message)
46
+ Events.processed event
47
+ end
48
+ end
49
+ end
50
+ end