| line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
|
1
|
|
|
|
|
|
|
# You may distribute under the terms of either the GNU General Public License |
|
2
|
|
|
|
|
|
|
# or the Artistic License (the same terms as Perl itself) |
|
3
|
|
|
|
|
|
|
# |
|
4
|
|
|
|
|
|
|
# (C) Paul Evans, 2014-2016 -- leonerd@leonerd.org.uk |
|
5
|
|
|
|
|
|
|
|
|
6
|
|
|
|
|
|
|
package Net::Async::Matrix::Room; |
|
7
|
|
|
|
|
|
|
|
|
8
|
12
|
|
|
12
|
|
44
|
use strict; |
|
|
12
|
|
|
|
|
15
|
|
|
|
12
|
|
|
|
|
280
|
|
|
9
|
12
|
|
|
12
|
|
38
|
use warnings; |
|
|
12
|
|
|
|
|
13
|
|
|
|
12
|
|
|
|
|
271
|
|
|
10
|
|
|
|
|
|
|
|
|
11
|
|
|
|
|
|
|
# Not really a Notifier but we like the ->maybe_invoke_event style |
|
12
|
12
|
|
|
12
|
|
53
|
use base qw( IO::Async::Notifier ); |
|
|
12
|
|
|
|
|
12
|
|
|
|
12
|
|
|
|
|
1115
|
|
|
13
|
|
|
|
|
|
|
|
|
14
|
|
|
|
|
|
|
our $VERSION = '0.19'; |
|
15
|
|
|
|
|
|
|
$VERSION = eval $VERSION; |
|
16
|
|
|
|
|
|
|
|
|
17
|
12
|
|
|
12
|
|
47
|
use Carp; |
|
|
12
|
|
|
|
|
15
|
|
|
|
12
|
|
|
|
|
519
|
|
|
18
|
|
|
|
|
|
|
|
|
19
|
12
|
|
|
12
|
|
41
|
use Future; |
|
|
12
|
|
|
|
|
15
|
|
|
|
12
|
|
|
|
|
222
|
|
|
20
|
12
|
|
|
12
|
|
41
|
use Future::Utils qw( repeat ); |
|
|
12
|
|
|
|
|
13
|
|
|
|
12
|
|
|
|
|
423
|
|
|
21
|
|
|
|
|
|
|
|
|
22
|
12
|
|
|
12
|
|
47
|
use List::Util qw( pairmap ); |
|
|
12
|
|
|
|
|
14
|
|
|
|
12
|
|
|
|
|
508
|
|
|
23
|
12
|
|
|
12
|
|
38
|
use Time::HiRes qw( time ); |
|
|
12
|
|
|
|
|
18
|
|
|
|
12
|
|
|
|
|
53
|
|
|
24
|
|
|
|
|
|
|
|
|
25
|
12
|
|
|
12
|
|
4953
|
use Net::Async::Matrix::Room::State; |
|
|
12
|
|
|
|
|
19
|
|
|
|
12
|
|
|
|
|
454
|
|
|
26
|
|
|
|
|
|
|
# TEMPORARY hack |
|
27
|
|
|
|
|
|
|
*Member = \&Net::Async::Matrix::Room::State::Member; |
|
28
|
|
|
|
|
|
|
|
|
29
|
12
|
|
|
12
|
|
50
|
use Data::Dump 'pp'; |
|
|
12
|
|
|
|
|
13
|
|
|
|
12
|
|
|
|
|
457
|
|
|
30
|
|
|
|
|
|
|
|
|
31
|
12
|
|
|
12
|
|
44
|
use constant TYPING_RESEND_SECONDS => 30; |
|
|
12
|
|
|
|
|
119
|
|
|
|
12
|
|
|
|
|
43680
|
|
|
32
|
|
|
|
|
|
|
|
|
33
|
|
|
|
|
|
|
=head1 NAME |
|
34
|
|
|
|
|
|
|
|
|
35
|
|
|
|
|
|
|
C - a single Matrix room |
|
36
|
|
|
|
|
|
|
|
|
37
|
|
|
|
|
|
|
=head1 DESCRIPTION |
|
38
|
|
|
|
|
|
|
|
|
39
|
|
|
|
|
|
|
An instances in this class are used by L to represent a |
|
40
|
|
|
|
|
|
|
single Matrix room. |
|
41
|
|
|
|
|
|
|
|
|
42
|
|
|
|
|
|
|
=cut |
|
43
|
|
|
|
|
|
|
|
|
44
|
|
|
|
|
|
|
=head1 EVENTS |
|
45
|
|
|
|
|
|
|
|
|
46
|
|
|
|
|
|
|
The following events are invoked, either using subclass methods or C |
|
47
|
|
|
|
|
|
|
references in parameters: |
|
48
|
|
|
|
|
|
|
|
|
49
|
|
|
|
|
|
|
=head2 on_synced_state |
|
50
|
|
|
|
|
|
|
|
|
51
|
|
|
|
|
|
|
Invoked after the initial sync of the room has been completed as far as the |
|
52
|
|
|
|
|
|
|
state. |
|
53
|
|
|
|
|
|
|
|
|
54
|
|
|
|
|
|
|
=head2 on_message $member, $content, $event |
|
55
|
|
|
|
|
|
|
|
|
56
|
|
|
|
|
|
|
=head2 on_back_message $member, $content, $event |
|
57
|
|
|
|
|
|
|
|
|
58
|
|
|
|
|
|
|
Invoked on receipt of a new message from the given member, either "live" from |
|
59
|
|
|
|
|
|
|
the event stream, or from backward pagination. |
|
60
|
|
|
|
|
|
|
|
|
61
|
|
|
|
|
|
|
=head2 on_membership $member, $event, $subject_member, %changes |
|
62
|
|
|
|
|
|
|
|
|
63
|
|
|
|
|
|
|
=head2 on_back_membership $member, $event, $subject_member, %changes |
|
64
|
|
|
|
|
|
|
|
|
65
|
|
|
|
|
|
|
Invoked on receipt of a membership change event for the given member, either |
|
66
|
|
|
|
|
|
|
"live" from the event stream, or from backward pagination. C<%changes> will be |
|
67
|
|
|
|
|
|
|
a key/value list of state field names that were changed, whose values are |
|
68
|
|
|
|
|
|
|
2-element ARRAY references containing the before/after values of those fields. |
|
69
|
|
|
|
|
|
|
|
|
70
|
|
|
|
|
|
|
on_membership: $field_name => [ $old_value, $new_value ] |
|
71
|
|
|
|
|
|
|
on_back_membership: $field_name => [ $new_value, $old_value ] |
|
72
|
|
|
|
|
|
|
|
|
73
|
|
|
|
|
|
|
Note carefully that the second value in each array gives the "updated" value, |
|
74
|
|
|
|
|
|
|
in the direction of the change - that is, for C it gives the |
|
75
|
|
|
|
|
|
|
new value after the change but for C it gives the old value |
|
76
|
|
|
|
|
|
|
before. Fields whose values did not change are not present in the C<%changes> |
|
77
|
|
|
|
|
|
|
list; the values of these can be inspected on the C<$member> object. |
|
78
|
|
|
|
|
|
|
|
|
79
|
|
|
|
|
|
|
It is unspecified what values the C<$member> object has for fields present in |
|
80
|
|
|
|
|
|
|
the change list - client code should not rely on these fields. |
|
81
|
|
|
|
|
|
|
|
|
82
|
|
|
|
|
|
|
In most cases when users change their own membership status (such as normal |
|
83
|
|
|
|
|
|
|
join or leave), the C<$member> and C<$subject_member> parameters refer to the |
|
84
|
|
|
|
|
|
|
same object. In other cases, such as invites or kicks, the C<$member> |
|
85
|
|
|
|
|
|
|
parameter refers to the member performing the change, and the |
|
86
|
|
|
|
|
|
|
C<$subject_member> refers to member that the change is about. |
|
87
|
|
|
|
|
|
|
|
|
88
|
|
|
|
|
|
|
=head2 on_state_changed $member, $event, %changes |
|
89
|
|
|
|
|
|
|
|
|
90
|
|
|
|
|
|
|
=head2 on_back_state_changed $member, $event, %changes |
|
91
|
|
|
|
|
|
|
|
|
92
|
|
|
|
|
|
|
Invoked on receipt of a change of room state (such as name or topic). |
|
93
|
|
|
|
|
|
|
|
|
94
|
|
|
|
|
|
|
In the special case of room aliases, because they are considered "state" but |
|
95
|
|
|
|
|
|
|
are stored per-homeserver, the changes value will consist of three fields; the |
|
96
|
|
|
|
|
|
|
old and new values I, and a list of the known aliases |
|
97
|
|
|
|
|
|
|
from all the other servers: |
|
98
|
|
|
|
|
|
|
|
|
99
|
|
|
|
|
|
|
on_state_changed: aliases => [ $old, $new, $other ] |
|
100
|
|
|
|
|
|
|
on_back_state_changed: aliases => [ $new, $old, $other ] |
|
101
|
|
|
|
|
|
|
|
|
102
|
|
|
|
|
|
|
This allows a client to detect deletions and additions by comparing the before |
|
103
|
|
|
|
|
|
|
and after lists, while still having access to the full set of before or after |
|
104
|
|
|
|
|
|
|
aliases, should it require it. |
|
105
|
|
|
|
|
|
|
|
|
106
|
|
|
|
|
|
|
=head2 on_presence $member, %changes |
|
107
|
|
|
|
|
|
|
|
|
108
|
|
|
|
|
|
|
Invoked when a member of the room changes membership or presence state. The |
|
109
|
|
|
|
|
|
|
C<$member> object will already be in the new state. C<%changes> will be a |
|
110
|
|
|
|
|
|
|
key/value list of state fields names that were changed, and references to |
|
111
|
|
|
|
|
|
|
2-element ARRAYs containing the old and new values for this field. |
|
112
|
|
|
|
|
|
|
|
|
113
|
|
|
|
|
|
|
=head2 on_typing $member, $is_typing |
|
114
|
|
|
|
|
|
|
|
|
115
|
|
|
|
|
|
|
Invoked on receipt of a typing notification change, when the given member |
|
116
|
|
|
|
|
|
|
either starts or stops typing. |
|
117
|
|
|
|
|
|
|
|
|
118
|
|
|
|
|
|
|
=head2 on_members_typing @members |
|
119
|
|
|
|
|
|
|
|
|
120
|
|
|
|
|
|
|
Invoked on receipt of a typing notification change to give the full set of |
|
121
|
|
|
|
|
|
|
currently-typing members. This is invoked after the individual C |
|
122
|
|
|
|
|
|
|
events. |
|
123
|
|
|
|
|
|
|
|
|
124
|
|
|
|
|
|
|
=head2 on_read_receipt $member, $event_id, $content |
|
125
|
|
|
|
|
|
|
|
|
126
|
|
|
|
|
|
|
Invoked on receipt of a C type of receipt message. |
|
127
|
|
|
|
|
|
|
|
|
128
|
|
|
|
|
|
|
=cut |
|
129
|
|
|
|
|
|
|
|
|
130
|
|
|
|
|
|
|
sub _init |
|
131
|
|
|
|
|
|
|
{ |
|
132
|
7
|
|
|
7
|
|
72
|
my $self = shift; |
|
133
|
7
|
|
|
|
|
11
|
my ( $params ) = @_; |
|
134
|
7
|
|
|
|
|
34
|
$self->SUPER::_init( $params ); |
|
135
|
|
|
|
|
|
|
|
|
136
|
7
|
|
|
|
|
37
|
$self->{matrix} = delete $params->{matrix}; |
|
137
|
7
|
|
|
|
|
12
|
$self->{room_id} = delete $params->{room_id}; |
|
138
|
|
|
|
|
|
|
|
|
139
|
|
|
|
|
|
|
# Server gives us entire sets of typing user_ids all at once. We have to |
|
140
|
|
|
|
|
|
|
# remember state |
|
141
|
7
|
|
|
|
|
14
|
$self->{typing_members} = {}; |
|
142
|
|
|
|
|
|
|
|
|
143
|
7
|
|
|
|
|
51
|
$self->{live_state} = Net::Async::Matrix::Room::State->new( $self ); |
|
144
|
|
|
|
|
|
|
} |
|
145
|
|
|
|
|
|
|
|
|
146
|
|
|
|
|
|
|
sub configure |
|
147
|
|
|
|
|
|
|
{ |
|
148
|
10
|
|
|
10
|
1
|
1170
|
my $self = shift; |
|
149
|
10
|
|
|
|
|
20
|
my %params = @_; |
|
150
|
|
|
|
|
|
|
|
|
151
|
10
|
|
|
|
|
20
|
foreach (qw( on_message on_back_message on_membership on_back_membership |
|
152
|
|
|
|
|
|
|
on_presence on_synced_state on_state_changed on_back_state_changed |
|
153
|
|
|
|
|
|
|
on_typing on_members_typing on_read_receipt )) { |
|
154
|
110
|
100
|
|
|
|
159
|
$self->{$_} = delete $params{$_} if exists $params{$_}; |
|
155
|
|
|
|
|
|
|
} |
|
156
|
|
|
|
|
|
|
|
|
157
|
10
|
|
|
|
|
44
|
$self->SUPER::configure( %params ); |
|
158
|
|
|
|
|
|
|
} |
|
159
|
|
|
|
|
|
|
|
|
160
|
|
|
|
|
|
|
=head1 METHODS |
|
161
|
|
|
|
|
|
|
|
|
162
|
|
|
|
|
|
|
=cut |
|
163
|
|
|
|
|
|
|
|
|
164
|
|
|
|
|
|
|
# FUNCTION |
|
165
|
|
|
|
|
|
|
sub _delete_null_changes |
|
166
|
|
|
|
|
|
|
{ |
|
167
|
9
|
|
|
9
|
|
9
|
my ( $changes ) = @_; |
|
168
|
|
|
|
|
|
|
|
|
169
|
9
|
|
|
|
|
15
|
foreach ( keys %$changes ) { |
|
170
|
12
|
|
|
|
|
10
|
my ( $old, $new ) = @{ $changes->{$_} }; |
|
|
12
|
|
|
|
|
14
|
|
|
171
|
|
|
|
|
|
|
|
|
172
|
12
|
100
|
100
|
|
|
84
|
delete $changes->{$_} if !defined $old and !defined $new or |
|
|
|
|
66
|
|
|
|
|
|
|
|
|
100
|
|
|
|
|
|
|
|
|
66
|
|
|
|
|
|
173
|
|
|
|
|
|
|
defined $old and defined $new and $old eq $new; |
|
174
|
|
|
|
|
|
|
} |
|
175
|
|
|
|
|
|
|
} |
|
176
|
|
|
|
|
|
|
|
|
177
|
|
|
|
|
|
|
# FUNCTION |
|
178
|
|
|
|
|
|
|
sub _pushdown_changes |
|
179
|
|
|
|
|
|
|
{ |
|
180
|
1
|
|
|
1
|
|
2
|
my ( $ch ) = @_; |
|
181
|
1
|
|
|
|
|
2
|
my ( $oldhash, $newhash ) = @$ch; |
|
182
|
|
|
|
|
|
|
|
|
183
|
1
|
|
|
|
|
2
|
my %changes; |
|
184
|
|
|
|
|
|
|
|
|
185
|
1
|
|
|
|
|
2
|
foreach ( keys %$oldhash ) { |
|
186
|
1
|
|
|
|
|
2
|
my $old = $oldhash->{$_}; |
|
187
|
1
|
50
|
|
|
|
3
|
if( !exists $newhash->{$_} ) { |
|
188
|
0
|
0
|
|
|
|
0
|
$changes{$_} = [ $old, undef ] if defined $old; |
|
189
|
0
|
|
|
|
|
0
|
next; |
|
190
|
|
|
|
|
|
|
} |
|
191
|
|
|
|
|
|
|
|
|
192
|
1
|
|
|
|
|
1
|
my $new = $newhash->{$_}; |
|
193
|
|
|
|
|
|
|
|
|
194
|
1
|
50
|
33
|
|
|
20
|
$changes{$_} = [ $old, $new ] unless !defined $old and !defined $new or |
|
|
|
|
33
|
|
|
|
|
|
|
|
|
33
|
|
|
|
|
|
|
|
|
33
|
|
|
|
|
|
195
|
|
|
|
|
|
|
defined $old and defined $new and $old eq $new; |
|
196
|
|
|
|
|
|
|
} |
|
197
|
|
|
|
|
|
|
|
|
198
|
1
|
|
|
|
|
2
|
foreach ( keys %$newhash ) { |
|
199
|
1
|
|
|
|
|
2
|
my $new = $newhash->{$_}; |
|
200
|
1
|
50
|
|
|
|
5
|
next if exists $oldhash->{$_}; |
|
201
|
|
|
|
|
|
|
|
|
202
|
0
|
0
|
|
|
|
0
|
$changes{$_} = [ undef, $new ] if defined $new; |
|
203
|
|
|
|
|
|
|
} |
|
204
|
|
|
|
|
|
|
|
|
205
|
1
|
50
|
|
|
|
4
|
return keys %changes ? \%changes : undef; |
|
206
|
|
|
|
|
|
|
} |
|
207
|
|
|
|
|
|
|
|
|
208
|
|
|
|
|
|
|
sub _do_GET_json |
|
209
|
|
|
|
|
|
|
{ |
|
210
|
0
|
|
|
0
|
|
0
|
my $self = shift; |
|
211
|
0
|
|
|
|
|
0
|
my ( $path, @args ) = @_; |
|
212
|
|
|
|
|
|
|
|
|
213
|
0
|
|
|
|
|
0
|
$self->{matrix}->_do_GET_json( "/rooms/$self->{room_id}" . $path, @args ); |
|
214
|
|
|
|
|
|
|
} |
|
215
|
|
|
|
|
|
|
|
|
216
|
|
|
|
|
|
|
sub _do_PUT_json |
|
217
|
|
|
|
|
|
|
{ |
|
218
|
4
|
|
|
4
|
|
7
|
my $self = shift; |
|
219
|
4
|
|
|
|
|
5
|
my ( $path, $content ) = @_; |
|
220
|
|
|
|
|
|
|
|
|
221
|
4
|
|
|
|
|
22
|
$self->{matrix}->_do_PUT_json( "/rooms/$self->{room_id}" . $path, $content ); |
|
222
|
|
|
|
|
|
|
} |
|
223
|
|
|
|
|
|
|
|
|
224
|
|
|
|
|
|
|
sub _do_POST_json |
|
225
|
|
|
|
|
|
|
{ |
|
226
|
2
|
|
|
2
|
|
3
|
my $self = shift; |
|
227
|
2
|
|
|
|
|
3
|
my ( $path, $content ) = @_; |
|
228
|
|
|
|
|
|
|
|
|
229
|
2
|
|
|
|
|
10
|
$self->{matrix}->_do_POST_json( "/rooms/$self->{room_id}" . $path, $content ); |
|
230
|
|
|
|
|
|
|
} |
|
231
|
|
|
|
|
|
|
|
|
232
|
|
|
|
|
|
|
=head2 await_synced |
|
233
|
|
|
|
|
|
|
|
|
234
|
|
|
|
|
|
|
$f = $room->await_synced |
|
235
|
|
|
|
|
|
|
|
|
236
|
|
|
|
|
|
|
Returns a L stored within the room that will complete (with no value) |
|
237
|
|
|
|
|
|
|
once the room initial state sync has been completed. This completes just |
|
238
|
|
|
|
|
|
|
I the C event. |
|
239
|
|
|
|
|
|
|
|
|
240
|
|
|
|
|
|
|
=cut |
|
241
|
|
|
|
|
|
|
|
|
242
|
|
|
|
|
|
|
sub _reset_for_sync |
|
243
|
|
|
|
|
|
|
{ |
|
244
|
1
|
|
|
1
|
|
1
|
my $self = shift; |
|
245
|
|
|
|
|
|
|
|
|
246
|
1
|
|
|
|
|
3
|
undef $self->{synced_future}; |
|
247
|
|
|
|
|
|
|
} |
|
248
|
|
|
|
|
|
|
|
|
249
|
|
|
|
|
|
|
sub _incoming_sync_invite |
|
250
|
|
|
|
|
|
|
{ |
|
251
|
0
|
|
|
0
|
|
0
|
my $self = shift; |
|
252
|
0
|
|
|
|
|
0
|
my ( $sync ) = @_; |
|
253
|
0
|
|
|
|
|
0
|
warn "TODO handle incoming sync data in invite state"; |
|
254
|
|
|
|
|
|
|
} |
|
255
|
|
|
|
|
|
|
|
|
256
|
|
|
|
|
|
|
sub _incoming_sync_join |
|
257
|
|
|
|
|
|
|
{ |
|
258
|
16
|
|
|
16
|
|
19
|
my $self = shift; |
|
259
|
16
|
|
|
|
|
19
|
my ( $sync ) = @_; |
|
260
|
|
|
|
|
|
|
|
|
261
|
16
|
|
|
|
|
30
|
my $initial = not $self->await_synced->is_done; |
|
262
|
|
|
|
|
|
|
|
|
263
|
|
|
|
|
|
|
# Toplevel fields for now I'm ignoring |
|
264
|
|
|
|
|
|
|
# account_data |
|
265
|
|
|
|
|
|
|
# unread_notifications |
|
266
|
|
|
|
|
|
|
|
|
267
|
16
|
|
|
|
|
884
|
my $live_state = $self->live_state; |
|
268
|
|
|
|
|
|
|
|
|
269
|
16
|
100
|
66
|
|
|
68
|
if( $sync->{state} and $sync->{state}{events} and @{ $sync->{state}{events} } ) { |
|
|
7
|
|
66
|
|
|
28
|
|
|
270
|
5
|
|
|
|
|
7
|
foreach my $event ( @{ $sync->{state}{events} } ) { |
|
|
5
|
|
|
|
|
12
|
|
|
271
|
11
|
|
|
|
|
29
|
$live_state->handle_event( $event ); |
|
272
|
|
|
|
|
|
|
} |
|
273
|
|
|
|
|
|
|
} |
|
274
|
|
|
|
|
|
|
|
|
275
|
16
|
|
|
|
|
18
|
foreach my $event ( @{ $sync->{timeline}{events} } ) { |
|
|
16
|
|
|
|
|
37
|
|
|
276
|
8
|
100
|
|
|
|
18
|
if( defined $event->{state_key} ) { |
|
277
|
7
|
|
|
|
|
16
|
my $old_event = $live_state->get_event( $event->{type}, $event->{state_key} ); |
|
278
|
7
|
|
|
|
|
12
|
$live_state->handle_event( $event ); |
|
279
|
7
|
|
|
|
|
12
|
$self->_handle_state_event( $old_event, $event, $live_state ); |
|
280
|
|
|
|
|
|
|
} |
|
281
|
|
|
|
|
|
|
else { |
|
282
|
1
|
|
|
|
|
4
|
$self->_handle_event( forward => $event ); |
|
283
|
|
|
|
|
|
|
} |
|
284
|
|
|
|
|
|
|
} |
|
285
|
|
|
|
|
|
|
|
|
286
|
16
|
|
|
|
|
135
|
foreach my $event ( @{ $sync->{ephemeral}{events} } ) { |
|
|
16
|
|
|
|
|
36
|
|
|
287
|
1
|
|
|
|
|
3
|
$self->_handle_event( ephemeral => $event ); |
|
288
|
|
|
|
|
|
|
} |
|
289
|
|
|
|
|
|
|
|
|
290
|
16
|
100
|
|
|
|
60
|
if( $initial ) { |
|
291
|
7
|
|
|
|
|
15
|
$self->await_synced->done; |
|
292
|
7
|
|
|
|
|
503
|
$self->maybe_invoke_event( on_synced_state => ); |
|
293
|
|
|
|
|
|
|
} |
|
294
|
|
|
|
|
|
|
} |
|
295
|
|
|
|
|
|
|
|
|
296
|
|
|
|
|
|
|
sub _incoming_sync_leave |
|
297
|
|
|
|
|
|
|
{ |
|
298
|
0
|
|
|
0
|
|
0
|
my $self = shift; |
|
299
|
0
|
|
|
|
|
0
|
my ( $sync ) = @_; |
|
300
|
|
|
|
|
|
|
# don't care for now |
|
301
|
|
|
|
|
|
|
} |
|
302
|
|
|
|
|
|
|
|
|
303
|
|
|
|
|
|
|
sub await_synced |
|
304
|
|
|
|
|
|
|
{ |
|
305
|
30
|
|
|
30
|
1
|
839
|
my $self = shift; |
|
306
|
30
|
|
66
|
|
|
144
|
return $self->{synced_future} //= $self->loop->new_future; |
|
307
|
|
|
|
|
|
|
} |
|
308
|
|
|
|
|
|
|
|
|
309
|
|
|
|
|
|
|
=head2 live_state |
|
310
|
|
|
|
|
|
|
|
|
311
|
|
|
|
|
|
|
$state = $room->live_state |
|
312
|
|
|
|
|
|
|
|
|
313
|
|
|
|
|
|
|
Returns a L instance representing the current |
|
314
|
|
|
|
|
|
|
live-tracking state of the room. |
|
315
|
|
|
|
|
|
|
|
|
316
|
|
|
|
|
|
|
This instance will mutate and change as new state events are received. |
|
317
|
|
|
|
|
|
|
|
|
318
|
|
|
|
|
|
|
=cut |
|
319
|
|
|
|
|
|
|
|
|
320
|
|
|
|
|
|
|
sub live_state |
|
321
|
|
|
|
|
|
|
{ |
|
322
|
31
|
|
|
31
|
1
|
26
|
my $self = shift; |
|
323
|
31
|
|
|
|
|
69
|
return $self->{live_state}; |
|
324
|
|
|
|
|
|
|
} |
|
325
|
|
|
|
|
|
|
|
|
326
|
|
|
|
|
|
|
sub _handle_state_event |
|
327
|
|
|
|
|
|
|
{ |
|
328
|
7
|
|
|
7
|
|
7
|
my $self = shift; |
|
329
|
7
|
|
|
|
|
7
|
my ( $old_event, $new_event, $state ) = @_; |
|
330
|
|
|
|
|
|
|
|
|
331
|
7
|
|
|
|
|
7
|
my $old_content = $old_event->{content}; |
|
332
|
7
|
|
|
|
|
7
|
my $new_content = $new_event->{content}; |
|
333
|
|
|
|
|
|
|
|
|
334
|
7
|
|
|
|
|
7
|
my %changes; |
|
335
|
7
|
|
|
|
|
21
|
$changes{$_}->[0] = $old_content->{$_} for keys %$old_content; |
|
336
|
7
|
|
|
|
|
16
|
$changes{$_}->[1] = $new_content->{$_} for keys %$new_content; |
|
337
|
|
|
|
|
|
|
|
|
338
|
7
|
|
50
|
|
|
18
|
$_->[1] //= undef for values %changes; # Ensure deleted key values become undef |
|
339
|
|
|
|
|
|
|
|
|
340
|
7
|
|
|
|
|
13
|
_delete_null_changes \%changes; |
|
341
|
|
|
|
|
|
|
|
|
342
|
7
|
|
|
|
|
19
|
my $member = $state->member( $new_event->{sender} ); |
|
343
|
|
|
|
|
|
|
|
|
344
|
7
|
|
|
|
|
39
|
my $type = $new_event->{type}; |
|
345
|
|
|
|
|
|
|
|
|
346
|
7
|
|
|
|
|
24
|
$type =~ m/^m\.room\.(.*)$/; |
|
347
|
7
|
50
|
|
|
|
31
|
my $method = $1 ? "_handle_state_event_" . join( "_", split m/\./, $1 ) : undef; |
|
348
|
|
|
|
|
|
|
|
|
349
|
7
|
100
|
66
|
|
|
49
|
if( $method and my $code = $self->can( $method ) ) { |
|
350
|
4
|
|
|
|
|
11
|
$self->$code( $member, $new_event, $state, %changes ); |
|
351
|
|
|
|
|
|
|
} |
|
352
|
|
|
|
|
|
|
else { |
|
353
|
3
|
|
|
|
|
16
|
$self->maybe_invoke_event( on_state_changed => |
|
354
|
|
|
|
|
|
|
$member, $new_event, %changes |
|
355
|
|
|
|
|
|
|
); |
|
356
|
|
|
|
|
|
|
} |
|
357
|
|
|
|
|
|
|
} |
|
358
|
|
|
|
|
|
|
|
|
359
|
|
|
|
|
|
|
sub _handle_event |
|
360
|
|
|
|
|
|
|
{ |
|
361
|
2
|
|
|
2
|
|
3
|
my $self = shift; |
|
362
|
2
|
|
|
|
|
2
|
my ( $direction, $event ) = @_; |
|
363
|
|
|
|
|
|
|
|
|
364
|
2
|
50
|
|
|
|
14
|
$event->{type} =~ m/^(m\.room\.)?(.*)$/ or return; |
|
365
|
|
|
|
|
|
|
|
|
366
|
2
|
100
|
|
|
|
7
|
my $base = $1 ? "_handle_roomevent_" : "_handle_event_"; |
|
367
|
2
|
|
|
|
|
9
|
my $method = $base . join( "_", split( m/\./, $2 ), $direction ); |
|
368
|
|
|
|
|
|
|
|
|
369
|
2
|
50
|
|
|
|
11
|
if( my $code = $self->can( $method ) ) { |
|
370
|
2
|
|
|
|
|
6
|
$code->( $self, $event ); |
|
371
|
|
|
|
|
|
|
} |
|
372
|
|
|
|
|
|
|
else { |
|
373
|
0
|
|
|
|
|
0
|
warn "TODO: $direction event $event->{type}\n"; |
|
374
|
|
|
|
|
|
|
} |
|
375
|
|
|
|
|
|
|
} |
|
376
|
|
|
|
|
|
|
|
|
377
|
|
|
|
|
|
|
sub _handle_state_backward |
|
378
|
|
|
|
|
|
|
{ |
|
379
|
0
|
|
|
0
|
|
0
|
my $self = shift; |
|
380
|
0
|
|
|
|
|
0
|
my ( $field, $event ) = @_; |
|
381
|
|
|
|
|
|
|
|
|
382
|
0
|
|
|
|
|
0
|
my $newvalue = $event->{content}{$field}; |
|
383
|
0
|
|
|
|
|
0
|
my $oldvalue = $event->{prev_content}{$field}; |
|
384
|
|
|
|
|
|
|
|
|
385
|
|
|
|
|
|
|
$self->maybe_invoke_event( on_back_state_changed => |
|
386
|
0
|
|
|
|
|
0
|
$self->{back_members_by_userid}{$event->{user_id}}, $event, |
|
387
|
|
|
|
|
|
|
$field => [ $newvalue, $oldvalue ] |
|
388
|
|
|
|
|
|
|
); |
|
389
|
|
|
|
|
|
|
} |
|
390
|
|
|
|
|
|
|
|
|
391
|
|
|
|
|
|
|
=head2 room_id |
|
392
|
|
|
|
|
|
|
|
|
393
|
|
|
|
|
|
|
$id = $room->room_id |
|
394
|
|
|
|
|
|
|
|
|
395
|
|
|
|
|
|
|
Returns the opaque room ID string for the room. Usually this would not be |
|
396
|
|
|
|
|
|
|
required, except for long-term persistence uniqueness purposes, or for |
|
397
|
|
|
|
|
|
|
inclusion in direct protocol URLs. |
|
398
|
|
|
|
|
|
|
|
|
399
|
|
|
|
|
|
|
=cut |
|
400
|
|
|
|
|
|
|
|
|
401
|
|
|
|
|
|
|
sub room_id |
|
402
|
|
|
|
|
|
|
{ |
|
403
|
1
|
|
|
1
|
1
|
569
|
my $self = shift; |
|
404
|
1
|
|
|
|
|
4
|
return $self->{room_id}; |
|
405
|
|
|
|
|
|
|
} |
|
406
|
|
|
|
|
|
|
|
|
407
|
|
|
|
|
|
|
=head2 name |
|
408
|
|
|
|
|
|
|
|
|
409
|
|
|
|
|
|
|
$name = $room->name |
|
410
|
|
|
|
|
|
|
|
|
411
|
|
|
|
|
|
|
Returns the room name, if defined, otherwise the opaque room ID. |
|
412
|
|
|
|
|
|
|
|
|
413
|
|
|
|
|
|
|
=cut |
|
414
|
|
|
|
|
|
|
|
|
415
|
|
|
|
|
|
|
sub _handle_roomevent_name_backward |
|
416
|
|
|
|
|
|
|
{ |
|
417
|
0
|
|
|
0
|
|
0
|
my $self = shift; |
|
418
|
0
|
|
|
|
|
0
|
my ( $event ) = @_; |
|
419
|
0
|
|
|
|
|
0
|
$self->_handle_state_backward( name => $event ); |
|
420
|
|
|
|
|
|
|
} |
|
421
|
|
|
|
|
|
|
|
|
422
|
|
|
|
|
|
|
sub name |
|
423
|
|
|
|
|
|
|
{ |
|
424
|
2
|
|
|
2
|
1
|
12
|
my $self = shift; |
|
425
|
2
|
|
33
|
|
|
4
|
return $self->live_state->name || $self->room_id; |
|
426
|
|
|
|
|
|
|
} |
|
427
|
|
|
|
|
|
|
|
|
428
|
|
|
|
|
|
|
=head2 set_name |
|
429
|
|
|
|
|
|
|
|
|
430
|
|
|
|
|
|
|
$room->set_name( $name )->get |
|
431
|
|
|
|
|
|
|
|
|
432
|
|
|
|
|
|
|
Requests to set a new room name. |
|
433
|
|
|
|
|
|
|
|
|
434
|
|
|
|
|
|
|
=cut |
|
435
|
|
|
|
|
|
|
|
|
436
|
|
|
|
|
|
|
sub set_name |
|
437
|
|
|
|
|
|
|
{ |
|
438
|
0
|
|
|
0
|
1
|
0
|
my $self = shift; |
|
439
|
0
|
|
|
|
|
0
|
my ( $name ) = @_; |
|
440
|
|
|
|
|
|
|
|
|
441
|
0
|
|
|
|
|
0
|
$self->_do_PUT_json( "/state/m.room.name", { name => $name } ) |
|
442
|
|
|
|
|
|
|
->then_done(); |
|
443
|
|
|
|
|
|
|
} |
|
444
|
|
|
|
|
|
|
|
|
445
|
|
|
|
|
|
|
=head2 aliases |
|
446
|
|
|
|
|
|
|
|
|
447
|
|
|
|
|
|
|
@aliases = $room->aliases |
|
448
|
|
|
|
|
|
|
|
|
449
|
|
|
|
|
|
|
Returns a list of all the known room alias names taken from the |
|
450
|
|
|
|
|
|
|
C events. Note that these are simply names I to have |
|
451
|
|
|
|
|
|
|
aliases from the alias events; a client ought to still check that these are |
|
452
|
|
|
|
|
|
|
valid before presenting them to the user as such, or in other ways relying on |
|
453
|
|
|
|
|
|
|
their values. |
|
454
|
|
|
|
|
|
|
|
|
455
|
|
|
|
|
|
|
=cut |
|
456
|
|
|
|
|
|
|
|
|
457
|
|
|
|
|
|
|
sub _handle_state_event_aliases |
|
458
|
|
|
|
|
|
|
{ |
|
459
|
1
|
|
|
1
|
|
1
|
my $self = shift; |
|
460
|
1
|
|
|
|
|
3
|
my ( $member, $event, $state, %changes ) = @_; |
|
461
|
|
|
|
|
|
|
|
|
462
|
1
|
|
|
|
|
1
|
my $homeserver = $event->{state_key}; |
|
463
|
|
|
|
|
|
|
|
|
464
|
0
|
|
|
|
|
0
|
my @others = map { $_->{content}{aliases} } |
|
465
|
1
|
|
|
|
|
4
|
grep { $_->{state_key} ne $homeserver } |
|
466
|
1
|
|
|
|
|
2
|
values %{ $state->get_events( "m.room.aliases" ) }; |
|
|
1
|
|
|
|
|
34
|
|
|
467
|
|
|
|
|
|
|
|
|
468
|
1
|
|
|
|
|
2
|
$changes{aliases}[2] = \@others; |
|
469
|
|
|
|
|
|
|
|
|
470
|
1
|
|
|
|
|
4
|
$self->maybe_invoke_event( on_state_changed => |
|
471
|
|
|
|
|
|
|
$member, $event, %changes |
|
472
|
|
|
|
|
|
|
); |
|
473
|
|
|
|
|
|
|
} |
|
474
|
|
|
|
|
|
|
|
|
475
|
|
|
|
|
|
|
sub _handle_roomevent_aliases_backward |
|
476
|
|
|
|
|
|
|
{ |
|
477
|
0
|
|
|
0
|
|
0
|
my $self = shift; |
|
478
|
0
|
|
|
|
|
0
|
my ( $event ) = @_; |
|
479
|
|
|
|
|
|
|
|
|
480
|
0
|
|
|
|
|
0
|
my $homeserver = $event->{state_key}; |
|
481
|
|
|
|
|
|
|
|
|
482
|
0
|
|
0
|
|
|
0
|
my $new = $event->{prev_content}{aliases} // []; |
|
483
|
0
|
|
0
|
|
|
0
|
my $old = $event->{content}{aliases} // []; |
|
484
|
|
|
|
|
|
|
|
|
485
|
0
|
|
|
|
|
0
|
$self->{back_aliases_by_hs}{$homeserver} = [ @$new ]; |
|
486
|
|
|
|
|
|
|
|
|
487
|
0
|
|
|
|
|
0
|
my @others = map { @{ $self->{back_aliases_by_hs}{$_} } } |
|
|
0
|
|
|
|
|
0
|
|
|
488
|
0
|
|
|
|
|
0
|
grep { $_ ne $homeserver } |
|
489
|
0
|
|
|
|
|
0
|
keys %{ $self->{back_aliases_by_hs} }; |
|
|
0
|
|
|
|
|
0
|
|
|
490
|
|
|
|
|
|
|
|
|
491
|
|
|
|
|
|
|
$self->maybe_invoke_event( on_back_state_changed => |
|
492
|
0
|
|
|
|
|
0
|
$self->{back_members_by_userid}{$event->{user_id}}, $event, |
|
493
|
|
|
|
|
|
|
aliases => [ $old, $new, \@others ] |
|
494
|
|
|
|
|
|
|
); |
|
495
|
|
|
|
|
|
|
} |
|
496
|
|
|
|
|
|
|
|
|
497
|
|
|
|
|
|
|
sub aliases |
|
498
|
|
|
|
|
|
|
{ |
|
499
|
2
|
|
|
2
|
1
|
1115
|
my $self = shift; |
|
500
|
2
|
|
|
|
|
4
|
return $self->live_state->aliases; |
|
501
|
|
|
|
|
|
|
} |
|
502
|
|
|
|
|
|
|
|
|
503
|
|
|
|
|
|
|
=head2 join_rule |
|
504
|
|
|
|
|
|
|
|
|
505
|
|
|
|
|
|
|
$rule = $room->join_rule |
|
506
|
|
|
|
|
|
|
|
|
507
|
|
|
|
|
|
|
Returns the current C for the room; a string giving the type of |
|
508
|
|
|
|
|
|
|
access new members may get: |
|
509
|
|
|
|
|
|
|
|
|
510
|
|
|
|
|
|
|
=over 4 |
|
511
|
|
|
|
|
|
|
|
|
512
|
|
|
|
|
|
|
=item * public |
|
513
|
|
|
|
|
|
|
|
|
514
|
|
|
|
|
|
|
Any user may join without further permission |
|
515
|
|
|
|
|
|
|
|
|
516
|
|
|
|
|
|
|
=item * invite |
|
517
|
|
|
|
|
|
|
|
|
518
|
|
|
|
|
|
|
Users may only join if explicitly invited |
|
519
|
|
|
|
|
|
|
|
|
520
|
|
|
|
|
|
|
=item * knock |
|
521
|
|
|
|
|
|
|
|
|
522
|
|
|
|
|
|
|
Any user may send a knock message to request access; may only join if invited |
|
523
|
|
|
|
|
|
|
|
|
524
|
|
|
|
|
|
|
=item * private |
|
525
|
|
|
|
|
|
|
|
|
526
|
|
|
|
|
|
|
No new users may join the room |
|
527
|
|
|
|
|
|
|
|
|
528
|
|
|
|
|
|
|
=back |
|
529
|
|
|
|
|
|
|
|
|
530
|
|
|
|
|
|
|
=cut |
|
531
|
|
|
|
|
|
|
|
|
532
|
|
|
|
|
|
|
sub _handle_roomevent_join_rules_backward |
|
533
|
|
|
|
|
|
|
{ |
|
534
|
0
|
|
|
0
|
|
0
|
my $self = shift; |
|
535
|
0
|
|
|
|
|
0
|
my ( $event ) = @_; |
|
536
|
0
|
|
|
|
|
0
|
$self->_handle_state_backward( join_rule => $event ); |
|
537
|
|
|
|
|
|
|
} |
|
538
|
|
|
|
|
|
|
|
|
539
|
|
|
|
|
|
|
sub join_rule |
|
540
|
|
|
|
|
|
|
{ |
|
541
|
2
|
|
|
2
|
1
|
1297
|
my $self = shift; |
|
542
|
2
|
|
|
|
|
6
|
return $self->live_state->join_rule; |
|
543
|
|
|
|
|
|
|
} |
|
544
|
|
|
|
|
|
|
|
|
545
|
|
|
|
|
|
|
=head2 topic |
|
546
|
|
|
|
|
|
|
|
|
547
|
|
|
|
|
|
|
$topic = $room->topic |
|
548
|
|
|
|
|
|
|
|
|
549
|
|
|
|
|
|
|
Returns the room topic, if defined |
|
550
|
|
|
|
|
|
|
|
|
551
|
|
|
|
|
|
|
=cut |
|
552
|
|
|
|
|
|
|
|
|
553
|
|
|
|
|
|
|
sub _handle_roomevent_topic_backward |
|
554
|
|
|
|
|
|
|
{ |
|
555
|
0
|
|
|
0
|
|
0
|
my $self = shift; |
|
556
|
0
|
|
|
|
|
0
|
my ( $event ) = @_; |
|
557
|
0
|
|
|
|
|
0
|
$self->_handle_state_backward( topic => $event ); |
|
558
|
|
|
|
|
|
|
} |
|
559
|
|
|
|
|
|
|
|
|
560
|
|
|
|
|
|
|
sub topic |
|
561
|
|
|
|
|
|
|
{ |
|
562
|
2
|
|
|
2
|
1
|
1065
|
my $self = shift; |
|
563
|
2
|
|
|
|
|
5
|
return $self->live_state->topic; |
|
564
|
|
|
|
|
|
|
} |
|
565
|
|
|
|
|
|
|
|
|
566
|
|
|
|
|
|
|
=head2 set_topic |
|
567
|
|
|
|
|
|
|
|
|
568
|
|
|
|
|
|
|
$room->set_topic( $topic )->get |
|
569
|
|
|
|
|
|
|
|
|
570
|
|
|
|
|
|
|
Requests to set a new room topic. |
|
571
|
|
|
|
|
|
|
|
|
572
|
|
|
|
|
|
|
=cut |
|
573
|
|
|
|
|
|
|
|
|
574
|
|
|
|
|
|
|
sub set_topic |
|
575
|
|
|
|
|
|
|
{ |
|
576
|
0
|
|
|
0
|
1
|
0
|
my $self = shift; |
|
577
|
0
|
|
|
|
|
0
|
my ( $topic ) = @_; |
|
578
|
|
|
|
|
|
|
|
|
579
|
0
|
|
|
|
|
0
|
$self->_do_PUT_json( "/state/m.room.topic", { topic => $topic } ) |
|
580
|
|
|
|
|
|
|
->then_done(); |
|
581
|
|
|
|
|
|
|
} |
|
582
|
|
|
|
|
|
|
|
|
583
|
|
|
|
|
|
|
=head2 levels |
|
584
|
|
|
|
|
|
|
|
|
585
|
|
|
|
|
|
|
%levels = $room->levels |
|
586
|
|
|
|
|
|
|
|
|
587
|
|
|
|
|
|
|
Returns a key/value list of the room levels; that is, the member power level |
|
588
|
|
|
|
|
|
|
required to perform each of the named actions. |
|
589
|
|
|
|
|
|
|
|
|
590
|
|
|
|
|
|
|
=cut |
|
591
|
|
|
|
|
|
|
|
|
592
|
|
|
|
|
|
|
sub _handle_generic_level |
|
593
|
|
|
|
|
|
|
{ |
|
594
|
0
|
|
|
0
|
|
0
|
my $self = shift; |
|
595
|
0
|
|
|
|
|
0
|
my ( $phase, $level, $convert, $event ) = @_; |
|
596
|
|
|
|
|
|
|
|
|
597
|
0
|
|
|
|
|
0
|
foreach my $k (qw( content prev_content )) { |
|
598
|
0
|
0
|
|
|
|
0
|
next unless my $levels = $event->{$k}; |
|
599
|
|
|
|
|
|
|
|
|
600
|
|
|
|
|
|
|
$event->{$k} = { |
|
601
|
0
|
|
|
|
|
0
|
map { $convert->{$_} => $levels->{$_} } keys %$convert |
|
|
0
|
|
|
|
|
0
|
|
|
602
|
|
|
|
|
|
|
}; |
|
603
|
|
|
|
|
|
|
} |
|
604
|
|
|
|
|
|
|
|
|
605
|
0
|
0
|
|
|
|
0
|
if( $phase eq "initial" ) { |
|
|
|
0
|
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
606
|
0
|
|
|
|
|
0
|
my $levels = $event->{content}; |
|
607
|
|
|
|
|
|
|
|
|
608
|
0
|
|
|
|
|
0
|
$self->{levels}{$_} = $levels->{$_} for keys %$levels; |
|
609
|
|
|
|
|
|
|
} |
|
610
|
|
|
|
|
|
|
elsif( $phase eq "forward" ) { |
|
611
|
0
|
|
|
|
|
0
|
my $newlevels = $event->{content}; |
|
612
|
0
|
|
|
|
|
0
|
my $oldlevels = $event->{prev_content}; |
|
613
|
|
|
|
|
|
|
|
|
614
|
0
|
|
|
|
|
0
|
my %changes; |
|
615
|
0
|
|
|
|
|
0
|
foreach ( keys %$newlevels ) { |
|
616
|
0
|
|
|
|
|
0
|
$self->{levels}{$_} = $newlevels->{$_}; |
|
617
|
|
|
|
|
|
|
|
|
618
|
|
|
|
|
|
|
$changes{"level.$_"} = [ $oldlevels->{$_}, $newlevels->{$_} ] |
|
619
|
0
|
0
|
0
|
|
|
0
|
if !defined $oldlevels->{$_} or $oldlevels->{$_} != $newlevels->{$_}; |
|
620
|
|
|
|
|
|
|
} |
|
621
|
|
|
|
|
|
|
|
|
622
|
0
|
|
|
|
|
0
|
my $member = $self->member( $event->{sender} ); |
|
623
|
0
|
|
|
|
|
0
|
$self->maybe_invoke_event( on_state_changed => |
|
624
|
|
|
|
|
|
|
$member, $event, %changes |
|
625
|
|
|
|
|
|
|
); |
|
626
|
|
|
|
|
|
|
} |
|
627
|
|
|
|
|
|
|
elsif( $phase eq "backward" ) { |
|
628
|
0
|
|
|
|
|
0
|
my $newlevels = $event->{content}; |
|
629
|
0
|
|
|
|
|
0
|
my $oldlevels = $event->{prev_content}; |
|
630
|
|
|
|
|
|
|
|
|
631
|
0
|
|
|
|
|
0
|
my %changes; |
|
632
|
0
|
|
|
|
|
0
|
foreach ( keys %$newlevels ) { |
|
633
|
|
|
|
|
|
|
$changes{"level.$_"} = [ $newlevels->{$_}, $oldlevels->{$_} ] |
|
634
|
0
|
0
|
0
|
|
|
0
|
if !defined $oldlevels->{$_} or $oldlevels->{$_} != $newlevels->{$_}; |
|
635
|
|
|
|
|
|
|
} |
|
636
|
|
|
|
|
|
|
|
|
637
|
0
|
|
|
|
|
0
|
my $member = $self->{back_members_by_userid}{$event->{user_id}}; |
|
638
|
0
|
|
|
|
|
0
|
$self->maybe_invoke_event( on_back_state_changed => |
|
639
|
|
|
|
|
|
|
$member, $event, %changes |
|
640
|
|
|
|
|
|
|
); |
|
641
|
|
|
|
|
|
|
} |
|
642
|
|
|
|
|
|
|
} |
|
643
|
|
|
|
|
|
|
|
|
644
|
|
|
|
|
|
|
sub levels |
|
645
|
|
|
|
|
|
|
{ |
|
646
|
0
|
|
|
0
|
1
|
0
|
my $self = shift; |
|
647
|
0
|
|
|
|
|
0
|
return %{ $self->{levels} }; |
|
|
0
|
|
|
|
|
0
|
|
|
648
|
|
|
|
|
|
|
} |
|
649
|
|
|
|
|
|
|
|
|
650
|
|
|
|
|
|
|
=head2 change_levels |
|
651
|
|
|
|
|
|
|
|
|
652
|
|
|
|
|
|
|
$room->change_levels( %levels )->get |
|
653
|
|
|
|
|
|
|
|
|
654
|
|
|
|
|
|
|
Performs a room levels change, submitting new values for the given keys while |
|
655
|
|
|
|
|
|
|
leaving other keys unchanged. |
|
656
|
|
|
|
|
|
|
|
|
657
|
|
|
|
|
|
|
=cut |
|
658
|
|
|
|
|
|
|
|
|
659
|
|
|
|
|
|
|
sub change_levels |
|
660
|
|
|
|
|
|
|
{ |
|
661
|
0
|
|
|
0
|
1
|
0
|
my $self = shift; |
|
662
|
0
|
|
|
|
|
0
|
my %levels = @_; |
|
663
|
|
|
|
|
|
|
|
|
664
|
|
|
|
|
|
|
# Delete null changes |
|
665
|
0
|
|
|
|
|
0
|
foreach ( keys %levels ) { |
|
666
|
0
|
0
|
|
|
|
0
|
delete $levels{$_} if $self->{levels}{$_} == $levels{$_}; |
|
667
|
|
|
|
|
|
|
} |
|
668
|
|
|
|
|
|
|
|
|
669
|
0
|
|
|
|
|
0
|
my %events; |
|
670
|
|
|
|
|
|
|
|
|
671
|
|
|
|
|
|
|
# These go in their own event with the content key 'level' |
|
672
|
0
|
|
|
|
|
0
|
foreach (qw( send_event add_state )) { |
|
673
|
0
|
0
|
|
|
|
0
|
$events{"${_}_level"} = { level => $levels{$_} } if exists $levels{$_}; |
|
674
|
|
|
|
|
|
|
} |
|
675
|
|
|
|
|
|
|
|
|
676
|
|
|
|
|
|
|
# These go in an 'ops_levels' event |
|
677
|
0
|
|
|
|
|
0
|
foreach (qw( ban kick redact )) { |
|
678
|
0
|
0
|
|
|
|
0
|
$events{ops_levels}{"${_}_level"} = $levels{$_} if exists $levels{$_}; |
|
679
|
|
|
|
|
|
|
} |
|
680
|
|
|
|
|
|
|
|
|
681
|
|
|
|
|
|
|
# Fill in remaining 'ops_levels' keys |
|
682
|
0
|
0
|
|
|
|
0
|
if( $events{ops_levels} ) { |
|
683
|
0
|
|
0
|
|
|
0
|
$events{ops_levels}{"${_}_level"} //= $self->{levels}{$_} for qw( ban kick redact ); |
|
684
|
|
|
|
|
|
|
} |
|
685
|
|
|
|
|
|
|
|
|
686
|
|
|
|
|
|
|
Future->needs_all( |
|
687
|
0
|
|
|
|
|
0
|
map { $self->_do_PUT_json( "/state/m.room.$_", $events{$_} ) } keys %events |
|
|
0
|
|
|
|
|
0
|
|
|
688
|
|
|
|
|
|
|
)->then_done(); |
|
689
|
|
|
|
|
|
|
} |
|
690
|
|
|
|
|
|
|
|
|
691
|
|
|
|
|
|
|
=head2 members |
|
692
|
|
|
|
|
|
|
|
|
693
|
|
|
|
|
|
|
@members = $room->members |
|
694
|
|
|
|
|
|
|
|
|
695
|
|
|
|
|
|
|
Returns a list of member structs containing the currently known members of the |
|
696
|
|
|
|
|
|
|
room, in no particular order. This list will include users who are not yet |
|
697
|
|
|
|
|
|
|
members of the room, but simply have been invited. |
|
698
|
|
|
|
|
|
|
|
|
699
|
|
|
|
|
|
|
=cut |
|
700
|
|
|
|
|
|
|
|
|
701
|
|
|
|
|
|
|
sub _handle_roomevent_member_backward |
|
702
|
|
|
|
|
|
|
{ |
|
703
|
0
|
|
|
0
|
|
0
|
my $self = shift; |
|
704
|
0
|
|
|
|
|
0
|
my ( $event ) = @_; |
|
705
|
|
|
|
|
|
|
|
|
706
|
|
|
|
|
|
|
# $self->_handle_roomevent_member( on_back_membership => $event, |
|
707
|
|
|
|
|
|
|
# $self->{back_members_by_userid}, $event->{content}, $event->{prev_content} ); |
|
708
|
|
|
|
|
|
|
} |
|
709
|
|
|
|
|
|
|
|
|
710
|
|
|
|
|
|
|
sub _handle_state_event_member |
|
711
|
|
|
|
|
|
|
{ |
|
712
|
2
|
|
|
2
|
|
2
|
my $self = shift; |
|
713
|
2
|
|
|
|
|
5
|
my ( $member, $event, $state, %changes ) = @_; |
|
714
|
|
|
|
|
|
|
|
|
715
|
|
|
|
|
|
|
# Currently, the server "deletes" users from the room by setting their |
|
716
|
|
|
|
|
|
|
# membership to "leave". It's neater if we consider an empty content in |
|
717
|
|
|
|
|
|
|
# that case. |
|
718
|
2
|
|
|
|
|
4
|
foreach my $idx ( 0, 1 ) { |
|
719
|
4
|
50
|
100
|
|
|
17
|
next unless ( $changes{membership}[$idx] // "" ) eq "leave"; |
|
720
|
|
|
|
|
|
|
|
|
721
|
0
|
|
|
|
|
0
|
undef $changes{$_}[$idx] for keys %changes; |
|
722
|
|
|
|
|
|
|
} |
|
723
|
|
|
|
|
|
|
|
|
724
|
2
|
|
|
|
|
4
|
my $user_id = $event->{state_key}; # == user the change applies to |
|
725
|
|
|
|
|
|
|
|
|
726
|
2
|
50
|
0
|
|
|
5
|
my $target_member = $state->member( $user_id ) or |
|
727
|
|
|
|
|
|
|
warn "ARGH: roomevent_member with unknown user id '$user_id'" and return; |
|
728
|
|
|
|
|
|
|
|
|
729
|
2
|
|
|
|
|
49
|
_delete_null_changes \%changes; |
|
730
|
|
|
|
|
|
|
|
|
731
|
2
|
|
|
|
|
8
|
$self->maybe_invoke_event( on_membership => $member, $event, $target_member, %changes ); |
|
732
|
|
|
|
|
|
|
} |
|
733
|
|
|
|
|
|
|
|
|
734
|
|
|
|
|
|
|
sub members |
|
735
|
|
|
|
|
|
|
{ |
|
736
|
3
|
|
|
3
|
1
|
1547
|
my $self = shift; |
|
737
|
3
|
|
|
|
|
8
|
return $self->live_state->members; |
|
738
|
|
|
|
|
|
|
} |
|
739
|
|
|
|
|
|
|
|
|
740
|
|
|
|
|
|
|
sub member |
|
741
|
|
|
|
|
|
|
{ |
|
742
|
2
|
|
|
2
|
0
|
2
|
my $self = shift; |
|
743
|
2
|
|
|
|
|
3
|
my ( $user_id ) = @_; |
|
744
|
2
|
|
|
|
|
4
|
return $self->live_state->member( $user_id ); |
|
745
|
|
|
|
|
|
|
} |
|
746
|
|
|
|
|
|
|
|
|
747
|
|
|
|
|
|
|
=head2 joined_members |
|
748
|
|
|
|
|
|
|
|
|
749
|
|
|
|
|
|
|
@members = $room->joined_members |
|
750
|
|
|
|
|
|
|
|
|
751
|
|
|
|
|
|
|
Returns the subset of C who actually in the C<"join"> state - |
|
752
|
|
|
|
|
|
|
i.e. are not invitees, or have left. |
|
753
|
|
|
|
|
|
|
|
|
754
|
|
|
|
|
|
|
=cut |
|
755
|
|
|
|
|
|
|
|
|
756
|
|
|
|
|
|
|
sub joined_members |
|
757
|
|
|
|
|
|
|
{ |
|
758
|
0
|
|
|
0
|
1
|
0
|
my $self = shift; |
|
759
|
0
|
|
0
|
|
|
0
|
return grep { ( $_->membership // "" ) eq "join" } $self->members; |
|
|
0
|
|
|
|
|
0
|
|
|
760
|
|
|
|
|
|
|
} |
|
761
|
|
|
|
|
|
|
|
|
762
|
|
|
|
|
|
|
=head2 member_level |
|
763
|
|
|
|
|
|
|
|
|
764
|
|
|
|
|
|
|
$level = $room->member_level( $user_id ) |
|
765
|
|
|
|
|
|
|
|
|
766
|
|
|
|
|
|
|
Returns the current cached value for the power level of the given user ID, or |
|
767
|
|
|
|
|
|
|
the default value if no specific value exists for the given ID. |
|
768
|
|
|
|
|
|
|
|
|
769
|
|
|
|
|
|
|
=cut |
|
770
|
|
|
|
|
|
|
|
|
771
|
|
|
|
|
|
|
sub _handle_roomevent_power_levels_backward |
|
772
|
|
|
|
|
|
|
{ |
|
773
|
0
|
|
|
0
|
|
0
|
my $self = shift; |
|
774
|
0
|
|
|
|
|
0
|
my ( $event ) = @_; |
|
775
|
|
|
|
|
|
|
|
|
776
|
|
|
|
|
|
|
# $self->_handle_roomevent_power_levels( on_back_membership => |
|
777
|
|
|
|
|
|
|
# $event, $self->{back_members_by_userid}, $event->{content}, $event->{prev_content} |
|
778
|
|
|
|
|
|
|
# ); |
|
779
|
|
|
|
|
|
|
} |
|
780
|
|
|
|
|
|
|
|
|
781
|
|
|
|
|
|
|
sub _handle_state_event_power_levels |
|
782
|
|
|
|
|
|
|
{ |
|
783
|
1
|
|
|
1
|
|
2
|
my $self = shift; |
|
784
|
1
|
|
|
|
|
2
|
my ( $member, $event, $state, %changes ) = @_; |
|
785
|
|
|
|
|
|
|
|
|
786
|
|
|
|
|
|
|
# Before we go any further we should also clean up null changes in 'users' |
|
787
|
|
|
|
|
|
|
# and 'events' hashes by pushing the 'old+new' diff ARRAYrefs down into the |
|
788
|
|
|
|
|
|
|
# hashes |
|
789
|
1
|
|
66
|
|
|
5
|
$_ and $_ = _pushdown_changes $_ for $changes{users}, $changes{events}; |
|
790
|
|
|
|
|
|
|
|
|
791
|
1
|
50
|
|
|
|
3
|
if( my $users = $changes{users} ) { |
|
792
|
|
|
|
|
|
|
# TODO: handle default changes |
|
793
|
1
|
|
|
|
|
2
|
my $default = $event->{content}{user_default}; |
|
794
|
|
|
|
|
|
|
|
|
795
|
1
|
|
|
|
|
2
|
foreach my $user_id ( keys %$users ) { |
|
796
|
1
|
50
|
|
|
|
2
|
my $target = $state->member( $user_id ) or next; |
|
797
|
1
|
|
|
|
|
8
|
my ( $oldlevel, $newlevel ) = @{ $users->{$user_id} }; |
|
|
1
|
|
|
|
|
2
|
|
|
798
|
|
|
|
|
|
|
|
|
799
|
1
|
|
33
|
|
|
3
|
$oldlevel //= $default; |
|
800
|
1
|
|
33
|
|
|
2
|
$newlevel //= $default; |
|
801
|
|
|
|
|
|
|
|
|
802
|
1
|
|
|
|
|
5
|
$self->maybe_invoke_event( on_membership => |
|
803
|
|
|
|
|
|
|
$member, $event, $target, level => [ $oldlevel, $newlevel ] |
|
804
|
|
|
|
|
|
|
); |
|
805
|
|
|
|
|
|
|
} |
|
806
|
|
|
|
|
|
|
} |
|
807
|
|
|
|
|
|
|
} |
|
808
|
|
|
|
|
|
|
|
|
809
|
|
|
|
|
|
|
sub member_level |
|
810
|
|
|
|
|
|
|
{ |
|
811
|
2
|
|
|
2
|
1
|
1849
|
my $self = shift; |
|
812
|
2
|
|
|
|
|
2
|
my ( $user_id ) = @_; |
|
813
|
2
|
|
|
|
|
4
|
return $self->live_state->member_level( $user_id ); |
|
814
|
|
|
|
|
|
|
} |
|
815
|
|
|
|
|
|
|
|
|
816
|
|
|
|
|
|
|
=head2 change_member_levels |
|
817
|
|
|
|
|
|
|
|
|
818
|
|
|
|
|
|
|
$room->change_member_levels( %levels )->get |
|
819
|
|
|
|
|
|
|
|
|
820
|
|
|
|
|
|
|
Performs a member power level change, submitting new values for user IDs to |
|
821
|
|
|
|
|
|
|
the home server. As there is no server API to make individual mutations, this |
|
822
|
|
|
|
|
|
|
is done by taking the currently cached values, applying the changes given by |
|
823
|
|
|
|
|
|
|
the C<%levels> key/value list, and submitting the resulting whole as the new |
|
824
|
|
|
|
|
|
|
value for the C room state. |
|
825
|
|
|
|
|
|
|
|
|
826
|
|
|
|
|
|
|
The C<%levels> key/value list should provide new values for keys giving user |
|
827
|
|
|
|
|
|
|
IDs, or the special user ID of C to change the overall default value |
|
828
|
|
|
|
|
|
|
for users not otherwise mentioned. Setting the special value of C for a |
|
829
|
|
|
|
|
|
|
user ID will remove that ID from the set, reverting them to the default. |
|
830
|
|
|
|
|
|
|
|
|
831
|
|
|
|
|
|
|
=cut |
|
832
|
|
|
|
|
|
|
|
|
833
|
|
|
|
|
|
|
sub change_member_levels |
|
834
|
|
|
|
|
|
|
{ |
|
835
|
0
|
|
|
0
|
1
|
0
|
my $self = shift; |
|
836
|
|
|
|
|
|
|
|
|
837
|
|
|
|
|
|
|
# Can't just edit the local cache as maybe the server will reject it. Clone |
|
838
|
|
|
|
|
|
|
# it and if the server accepts our modification the cache will be updated |
|
839
|
|
|
|
|
|
|
# on the incoming event. |
|
840
|
|
|
|
|
|
|
|
|
841
|
0
|
|
|
|
|
0
|
my %user_levels = %{ $self->{powerlevels}{users} }; |
|
|
0
|
|
|
|
|
0
|
|
|
842
|
|
|
|
|
|
|
|
|
843
|
0
|
|
|
|
|
0
|
while( @_ ) { |
|
844
|
0
|
|
|
|
|
0
|
my $user_id = shift; |
|
845
|
0
|
|
|
|
|
0
|
my $value = shift; |
|
846
|
|
|
|
|
|
|
|
|
847
|
0
|
0
|
|
|
|
0
|
if( defined $value ) { |
|
848
|
0
|
|
|
|
|
0
|
$user_levels{$user_id} = $value; |
|
849
|
|
|
|
|
|
|
} |
|
850
|
|
|
|
|
|
|
else { |
|
851
|
0
|
|
|
|
|
0
|
delete $user_levels{$user_id}; |
|
852
|
|
|
|
|
|
|
} |
|
853
|
|
|
|
|
|
|
} |
|
854
|
|
|
|
|
|
|
|
|
855
|
|
|
|
|
|
|
$self->_do_PUT_json( "/state/m.room.power_levels", |
|
856
|
0
|
|
|
|
|
0
|
{ %{ $self->{powerlevels} }, users => \%user_levels } |
|
|
0
|
|
|
|
|
0
|
|
|
857
|
|
|
|
|
|
|
)->then_done(); |
|
858
|
|
|
|
|
|
|
} |
|
859
|
|
|
|
|
|
|
|
|
860
|
|
|
|
|
|
|
=head2 leave |
|
861
|
|
|
|
|
|
|
|
|
862
|
|
|
|
|
|
|
$room->leave->get |
|
863
|
|
|
|
|
|
|
|
|
864
|
|
|
|
|
|
|
Requests to leave the room. After this completes, the user will no longer be |
|
865
|
|
|
|
|
|
|
a member of the room. |
|
866
|
|
|
|
|
|
|
|
|
867
|
|
|
|
|
|
|
=cut |
|
868
|
|
|
|
|
|
|
|
|
869
|
|
|
|
|
|
|
sub leave |
|
870
|
|
|
|
|
|
|
{ |
|
871
|
0
|
|
|
0
|
1
|
0
|
my $self = shift; |
|
872
|
0
|
|
|
|
|
0
|
$self->_do_POST_json( "/leave", {} ); |
|
873
|
|
|
|
|
|
|
} |
|
874
|
|
|
|
|
|
|
|
|
875
|
|
|
|
|
|
|
=head2 invite |
|
876
|
|
|
|
|
|
|
|
|
877
|
|
|
|
|
|
|
$room->invite( $user_id )->get |
|
878
|
|
|
|
|
|
|
|
|
879
|
|
|
|
|
|
|
Sends an invitation for the user with the given User ID to join the room. |
|
880
|
|
|
|
|
|
|
|
|
881
|
|
|
|
|
|
|
=cut |
|
882
|
|
|
|
|
|
|
|
|
883
|
|
|
|
|
|
|
sub invite |
|
884
|
|
|
|
|
|
|
{ |
|
885
|
0
|
|
|
0
|
1
|
0
|
my $self = shift; |
|
886
|
0
|
|
|
|
|
0
|
my ( $user_id ) = @_; |
|
887
|
|
|
|
|
|
|
|
|
888
|
0
|
|
|
|
|
0
|
$self->_do_POST_json( "/invite", { user_id => $user_id } ) |
|
889
|
|
|
|
|
|
|
->then_done(); |
|
890
|
|
|
|
|
|
|
} |
|
891
|
|
|
|
|
|
|
|
|
892
|
|
|
|
|
|
|
=head2 kick |
|
893
|
|
|
|
|
|
|
|
|
894
|
|
|
|
|
|
|
$room->kick( $user_id, $reason )->get |
|
895
|
|
|
|
|
|
|
|
|
896
|
|
|
|
|
|
|
Requests to remove the user with the given User ID from the room. |
|
897
|
|
|
|
|
|
|
|
|
898
|
|
|
|
|
|
|
Optionally, a textual description reason can also be provided. |
|
899
|
|
|
|
|
|
|
|
|
900
|
|
|
|
|
|
|
=cut |
|
901
|
|
|
|
|
|
|
|
|
902
|
|
|
|
|
|
|
sub kick |
|
903
|
|
|
|
|
|
|
{ |
|
904
|
0
|
|
|
0
|
1
|
0
|
my $self = shift; |
|
905
|
0
|
|
|
|
|
0
|
my ( $user_id, $reason ) = @_; |
|
906
|
|
|
|
|
|
|
|
|
907
|
0
|
|
|
|
|
0
|
$self->_do_POST_json( "/kick", { user_id => $user_id, reason => $reason } ) |
|
908
|
|
|
|
|
|
|
->then_done(); |
|
909
|
|
|
|
|
|
|
} |
|
910
|
|
|
|
|
|
|
|
|
911
|
|
|
|
|
|
|
=head2 send_message |
|
912
|
|
|
|
|
|
|
|
|
913
|
|
|
|
|
|
|
$event_id = $room->send_message( %args )->get |
|
914
|
|
|
|
|
|
|
|
|
915
|
|
|
|
|
|
|
Sends a new message to the room. Requires a C named argument giving the |
|
916
|
|
|
|
|
|
|
message type. Depending on the type, further keys will be required that |
|
917
|
|
|
|
|
|
|
specify the message contents: |
|
918
|
|
|
|
|
|
|
|
|
919
|
|
|
|
|
|
|
=over 4 |
|
920
|
|
|
|
|
|
|
|
|
921
|
|
|
|
|
|
|
=item m.text, m.emote, m.notice |
|
922
|
|
|
|
|
|
|
|
|
923
|
|
|
|
|
|
|
Require C |
|
924
|
|
|
|
|
|
|
|
|
925
|
|
|
|
|
|
|
=item m.image, m.audio, m.video, m.file |
|
926
|
|
|
|
|
|
|
|
|
927
|
|
|
|
|
|
|
Require C |
|
928
|
|
|
|
|
|
|
|
|
929
|
|
|
|
|
|
|
=item m.location |
|
930
|
|
|
|
|
|
|
|
|
931
|
|
|
|
|
|
|
Require C |
|
932
|
|
|
|
|
|
|
|
|
933
|
|
|
|
|
|
|
=back |
|
934
|
|
|
|
|
|
|
|
|
935
|
|
|
|
|
|
|
If an additional argument called C is provided, this is used as the |
|
936
|
|
|
|
|
|
|
transaction ID for the message, which is then sent as a C request instead |
|
937
|
|
|
|
|
|
|
of a C. |
|
938
|
|
|
|
|
|
|
|
|
939
|
|
|
|
|
|
|
$event_id = $room->send_message( $text )->get |
|
940
|
|
|
|
|
|
|
|
|
941
|
|
|
|
|
|
|
A convenient shortcut to sending an C message with a body string and |
|
942
|
|
|
|
|
|
|
no additional content. |
|
943
|
|
|
|
|
|
|
|
|
944
|
|
|
|
|
|
|
=cut |
|
945
|
|
|
|
|
|
|
|
|
946
|
|
|
|
|
|
|
my %MSG_REQUIRED_FIELDS = ( |
|
947
|
|
|
|
|
|
|
'm.text' => [qw( body )], |
|
948
|
|
|
|
|
|
|
'm.emote' => [qw( body )], |
|
949
|
|
|
|
|
|
|
'm.notice' => [qw( body )], |
|
950
|
|
|
|
|
|
|
'm.image' => [qw( url )], |
|
951
|
|
|
|
|
|
|
'm.audio' => [qw( url )], |
|
952
|
|
|
|
|
|
|
'm.video' => [qw( url )], |
|
953
|
|
|
|
|
|
|
'm.file' => [qw( url )], |
|
954
|
|
|
|
|
|
|
'm.location' => [qw( geo_uri )], |
|
955
|
|
|
|
|
|
|
); |
|
956
|
|
|
|
|
|
|
|
|
957
|
|
|
|
|
|
|
sub send_message |
|
958
|
|
|
|
|
|
|
{ |
|
959
|
2
|
|
|
2
|
1
|
836
|
my $self = shift; |
|
960
|
2
|
50
|
|
|
|
9
|
my %args = ( @_ == 1 ) ? ( type => "m.text", body => shift ) : @_; |
|
961
|
|
|
|
|
|
|
|
|
962
|
|
|
|
|
|
|
my $type = $args{msgtype} = delete $args{type} or |
|
963
|
2
|
50
|
|
|
|
9
|
croak "Require a 'type' field"; |
|
964
|
|
|
|
|
|
|
|
|
965
|
2
|
50
|
|
|
|
5
|
$MSG_REQUIRED_FIELDS{$type} or |
|
966
|
|
|
|
|
|
|
croak "Unrecognised message type '$type'"; |
|
967
|
|
|
|
|
|
|
|
|
968
|
2
|
|
|
|
|
2
|
foreach (@{ $MSG_REQUIRED_FIELDS{$type} } ) { |
|
|
2
|
|
|
|
|
4
|
|
|
969
|
2
|
50
|
|
|
|
5
|
$args{$_} or croak "'$type' messages require a '$_' field"; |
|
970
|
|
|
|
|
|
|
} |
|
971
|
|
|
|
|
|
|
|
|
972
|
2
|
100
|
|
|
|
6
|
if( defined( my $txn_id = $args{txn_id} ) ) { |
|
973
|
|
|
|
|
|
|
$self->_do_PUT_json( "/send/m.room.message/$txn_id", \%args )->then( sub { |
|
974
|
1
|
|
|
1
|
|
75
|
my ( $response ) = @_; |
|
975
|
1
|
|
|
|
|
3
|
Future->done( $response->{event_id} ); |
|
976
|
1
|
|
|
|
|
4
|
}); |
|
977
|
|
|
|
|
|
|
} |
|
978
|
|
|
|
|
|
|
else { |
|
979
|
|
|
|
|
|
|
$self->_do_POST_json( "/send/m.room.message", \%args )->then( sub { |
|
980
|
1
|
|
|
1
|
|
75
|
my ( $response ) = @_; |
|
981
|
1
|
|
|
|
|
3
|
Future->done( $response->{event_id} ); |
|
982
|
1
|
|
|
|
|
3
|
}); |
|
983
|
|
|
|
|
|
|
} |
|
984
|
|
|
|
|
|
|
} |
|
985
|
|
|
|
|
|
|
|
|
986
|
|
|
|
|
|
|
=head2 paginate_messages |
|
987
|
|
|
|
|
|
|
|
|
988
|
|
|
|
|
|
|
$room->paginate_messages( limit => $n )->get |
|
989
|
|
|
|
|
|
|
|
|
990
|
|
|
|
|
|
|
Requests more messages of back-pagination history. |
|
991
|
|
|
|
|
|
|
|
|
992
|
|
|
|
|
|
|
There is no need to maintain a reference on the returned C; it will be |
|
993
|
|
|
|
|
|
|
adopted by the room object. |
|
994
|
|
|
|
|
|
|
|
|
995
|
|
|
|
|
|
|
=cut |
|
996
|
|
|
|
|
|
|
|
|
997
|
|
|
|
|
|
|
sub paginate_messages |
|
998
|
|
|
|
|
|
|
{ |
|
999
|
0
|
|
|
0
|
1
|
0
|
my $self = shift; |
|
1000
|
0
|
|
|
|
|
0
|
my %args = @_; |
|
1001
|
|
|
|
|
|
|
|
|
1002
|
0
|
|
0
|
|
|
0
|
my $limit = $args{limit} // 20; |
|
1003
|
0
|
|
0
|
|
|
0
|
my $from = $self->{pagination_token} // "END"; |
|
1004
|
|
|
|
|
|
|
|
|
1005
|
0
|
0
|
|
|
|
0
|
croak "Cannot paginate_messages any further since we're already at the start" |
|
1006
|
|
|
|
|
|
|
if $from eq "START"; |
|
1007
|
|
|
|
|
|
|
|
|
1008
|
|
|
|
|
|
|
# Since we're now doing pagination, we'll need a second set of member |
|
1009
|
|
|
|
|
|
|
# objects |
|
1010
|
|
|
|
|
|
|
$self->{back_members_by_userid} //= { |
|
1011
|
0
|
|
0
|
0
|
|
0
|
pairmap { $a => Member( $b->user, $b->displayname, $b->membership ) } %{ $self->{members_by_userid} } |
|
|
0
|
|
|
|
|
0
|
|
|
|
0
|
|
|
|
|
0
|
|
|
1012
|
|
|
|
|
|
|
}; |
|
1013
|
|
|
|
|
|
|
$self->{back_aliases_by_hs} //= { |
|
1014
|
0
|
|
0
|
0
|
|
0
|
pairmap { $a => [ @$b ] } %{ $self->{aliases_by_hs} } |
|
|
0
|
|
|
|
|
0
|
|
|
|
0
|
|
|
|
|
0
|
|
|
1015
|
|
|
|
|
|
|
}; |
|
1016
|
|
|
|
|
|
|
|
|
1017
|
|
|
|
|
|
|
my $f = $self->_do_GET_json( "/messages", |
|
1018
|
|
|
|
|
|
|
from => $from, |
|
1019
|
|
|
|
|
|
|
dir => "b", |
|
1020
|
|
|
|
|
|
|
limit => $limit, |
|
1021
|
|
|
|
|
|
|
)->then( sub { |
|
1022
|
0
|
|
|
0
|
|
0
|
my ( $response ) = @_; |
|
1023
|
|
|
|
|
|
|
|
|
1024
|
0
|
|
|
|
|
0
|
foreach my $event ( @{ $response->{chunk} } ) { |
|
|
0
|
|
|
|
|
0
|
|
|
1025
|
0
|
0
|
|
|
|
0
|
next unless my ( $subtype ) = ( $event->{type} =~ m/^m\.room\.(.*)$/ ); |
|
1026
|
0
|
|
|
|
|
0
|
$subtype =~ s/\./_/g; |
|
1027
|
|
|
|
|
|
|
|
|
1028
|
0
|
0
|
|
|
|
0
|
if( my $code = $self->can( "_handle_roomevent_${subtype}_backward" ) ) { |
|
1029
|
0
|
|
|
|
|
0
|
$code->( $self, $event ); |
|
1030
|
|
|
|
|
|
|
} |
|
1031
|
|
|
|
|
|
|
else { |
|
1032
|
0
|
|
|
|
|
0
|
$self->{matrix}->log( "TODO: Handle room pagination event $subtype" ); |
|
1033
|
|
|
|
|
|
|
} |
|
1034
|
|
|
|
|
|
|
} |
|
1035
|
|
|
|
|
|
|
|
|
1036
|
0
|
|
|
|
|
0
|
$self->{pagination_token} = $response->{end}; |
|
1037
|
0
|
|
|
|
|
0
|
Future->done( $self ); |
|
1038
|
0
|
|
|
|
|
0
|
}); |
|
1039
|
0
|
|
|
|
|
0
|
$self->adopt_future( $f ); |
|
1040
|
|
|
|
|
|
|
} |
|
1041
|
|
|
|
|
|
|
|
|
1042
|
|
|
|
|
|
|
=head2 typing_start |
|
1043
|
|
|
|
|
|
|
|
|
1044
|
|
|
|
|
|
|
$room->typing_start |
|
1045
|
|
|
|
|
|
|
|
|
1046
|
|
|
|
|
|
|
Sends a typing notification that the user is currently typing in this room. |
|
1047
|
|
|
|
|
|
|
This notification will periodically be re-sent as required by the protocol |
|
1048
|
|
|
|
|
|
|
until the C method is called. |
|
1049
|
|
|
|
|
|
|
|
|
1050
|
|
|
|
|
|
|
=cut |
|
1051
|
|
|
|
|
|
|
|
|
1052
|
|
|
|
|
|
|
sub typing_start |
|
1053
|
|
|
|
|
|
|
{ |
|
1054
|
1
|
|
|
1
|
1
|
280
|
my $self = shift; |
|
1055
|
|
|
|
|
|
|
|
|
1056
|
1
|
50
|
|
|
|
4
|
return if $self->{typing_timer}; |
|
1057
|
|
|
|
|
|
|
|
|
1058
|
1
|
|
|
|
|
4
|
my $user_id = $self->{matrix}->myself->user_id; |
|
1059
|
|
|
|
|
|
|
|
|
1060
|
|
|
|
|
|
|
my $f = $self->{typing_timer} = repeat { |
|
1061
|
|
|
|
|
|
|
$self->_do_PUT_json( "/typing/$user_id", { |
|
1062
|
|
|
|
|
|
|
typing => 1, |
|
1063
|
|
|
|
|
|
|
timeout => ( TYPING_RESEND_SECONDS + 5 ) * 1000, # msec |
|
1064
|
|
|
|
|
|
|
})->then( sub { |
|
1065
|
2
|
|
|
|
|
161
|
$self->{matrix}->{make_delay}->( TYPING_RESEND_SECONDS ); |
|
1066
|
2
|
|
|
2
|
|
39
|
}); |
|
1067
|
1
|
|
|
1
|
|
46
|
} while => sub { !shift->failure }; |
|
|
1
|
|
|
|
|
399
|
|
|
1068
|
|
|
|
|
|
|
|
|
1069
|
|
|
|
|
|
|
$f->on_fail( $self->_capture_weakself( sub { |
|
1070
|
0
|
|
|
0
|
|
0
|
my $self = shift; |
|
1071
|
0
|
|
|
|
|
0
|
$self->invoke_error( @_ ); |
|
1072
|
1
|
|
|
|
|
92
|
})); |
|
1073
|
|
|
|
|
|
|
} |
|
1074
|
|
|
|
|
|
|
|
|
1075
|
|
|
|
|
|
|
=head2 typing_stop |
|
1076
|
|
|
|
|
|
|
|
|
1077
|
|
|
|
|
|
|
$room->typing_stop |
|
1078
|
|
|
|
|
|
|
|
|
1079
|
|
|
|
|
|
|
Sends a typing notification that the user is no longer typing in this room. |
|
1080
|
|
|
|
|
|
|
This method also cancels the repeating re-send behaviour created by |
|
1081
|
|
|
|
|
|
|
C. |
|
1082
|
|
|
|
|
|
|
|
|
1083
|
|
|
|
|
|
|
=cut |
|
1084
|
|
|
|
|
|
|
|
|
1085
|
|
|
|
|
|
|
sub typing_stop |
|
1086
|
|
|
|
|
|
|
{ |
|
1087
|
1
|
|
|
1
|
1
|
41
|
my $self = shift; |
|
1088
|
|
|
|
|
|
|
|
|
1089
|
1
|
50
|
|
|
|
4
|
return unless my $f = $self->{typing_timer}; |
|
1090
|
|
|
|
|
|
|
|
|
1091
|
1
|
|
|
|
|
4
|
$f->cancel; |
|
1092
|
1
|
|
|
|
|
75
|
undef $self->{typing_timer}; |
|
1093
|
|
|
|
|
|
|
|
|
1094
|
1
|
|
|
|
|
4
|
my $user_id = $self->{matrix}->myself->user_id; |
|
1095
|
|
|
|
|
|
|
|
|
1096
|
1
|
|
|
|
|
15
|
$self->adopt_future( |
|
1097
|
|
|
|
|
|
|
$self->_do_PUT_json( "/typing/$user_id", { |
|
1098
|
|
|
|
|
|
|
typing => 0, |
|
1099
|
|
|
|
|
|
|
}) |
|
1100
|
|
|
|
|
|
|
); |
|
1101
|
|
|
|
|
|
|
} |
|
1102
|
|
|
|
|
|
|
|
|
1103
|
|
|
|
|
|
|
=head2 send_read_receipt |
|
1104
|
|
|
|
|
|
|
|
|
1105
|
|
|
|
|
|
|
$room->send_read_receipt( event_id => $event_id, ... )->get |
|
1106
|
|
|
|
|
|
|
|
|
1107
|
|
|
|
|
|
|
Sends a C receipt to the given room for the given event ID. |
|
1108
|
|
|
|
|
|
|
|
|
1109
|
|
|
|
|
|
|
=cut |
|
1110
|
|
|
|
|
|
|
|
|
1111
|
|
|
|
|
|
|
sub send_read_receipt |
|
1112
|
|
|
|
|
|
|
{ |
|
1113
|
1
|
|
|
1
|
1
|
2469
|
my $self = shift; |
|
1114
|
1
|
|
|
|
|
3
|
my %args = @_; |
|
1115
|
|
|
|
|
|
|
|
|
1116
|
1
|
50
|
|
|
|
4
|
my $event_id = $args{event_id} or croak "Require event_id"; |
|
1117
|
|
|
|
|
|
|
|
|
1118
|
1
|
|
|
|
|
6
|
$self->_do_POST_json( "/receipt/m.read/$event_id", {} ); |
|
1119
|
|
|
|
|
|
|
} |
|
1120
|
|
|
|
|
|
|
|
|
1121
|
|
|
|
|
|
|
sub _handle_roomevent_create_forward |
|
1122
|
|
|
|
|
|
|
{ |
|
1123
|
0
|
|
|
0
|
|
0
|
my $self = shift; |
|
1124
|
0
|
|
|
|
|
0
|
my ( $event ) = @_; |
|
1125
|
|
|
|
|
|
|
|
|
1126
|
|
|
|
|
|
|
# Nothing interesting here... |
|
1127
|
|
|
|
|
|
|
} |
|
1128
|
|
|
|
|
|
|
*_handle_roomevent_create_initial = \&_handle_roomevent_create_forward; |
|
1129
|
|
|
|
|
|
|
|
|
1130
|
|
|
|
|
|
|
sub _handle_roomevent_create_backward |
|
1131
|
|
|
|
|
|
|
{ |
|
1132
|
0
|
|
|
0
|
|
0
|
my $self = shift; |
|
1133
|
|
|
|
|
|
|
|
|
1134
|
|
|
|
|
|
|
# Stop now |
|
1135
|
0
|
|
|
|
|
0
|
$self->{pagination_token} = "START"; |
|
1136
|
|
|
|
|
|
|
} |
|
1137
|
|
|
|
|
|
|
|
|
1138
|
|
|
|
|
|
|
sub _handle_roomevent_message_forward |
|
1139
|
|
|
|
|
|
|
{ |
|
1140
|
1
|
|
|
1
|
|
2
|
my $self = shift; |
|
1141
|
1
|
|
|
|
|
1
|
my ( $event ) = @_; |
|
1142
|
|
|
|
|
|
|
|
|
1143
|
1
|
|
|
|
|
2
|
my $user_id = $event->{sender}; |
|
1144
|
1
|
50
|
0
|
|
|
4
|
my $member = $self->member( $user_id ) or |
|
1145
|
|
|
|
|
|
|
warn "TODO: Unknown member '$user_id' for forward message" and return; |
|
1146
|
|
|
|
|
|
|
|
|
1147
|
1
|
|
|
|
|
35
|
$self->maybe_invoke_event( on_message => $member, $event->{content}, $event ); |
|
1148
|
|
|
|
|
|
|
} |
|
1149
|
|
|
|
|
|
|
|
|
1150
|
|
|
|
|
|
|
sub _handle_roomevent_message_backward |
|
1151
|
|
|
|
|
|
|
{ |
|
1152
|
0
|
|
|
0
|
|
0
|
my $self = shift; |
|
1153
|
0
|
|
|
|
|
0
|
my ( $event ) = @_; |
|
1154
|
|
|
|
|
|
|
|
|
1155
|
0
|
|
|
|
|
0
|
my $user_id = $event->{user_id}; |
|
1156
|
0
|
0
|
0
|
|
|
0
|
my $member = $self->{back_members_by_userid}{$user_id} or |
|
1157
|
|
|
|
|
|
|
warn "TODO: Unknown member '$user_id' for backward message" and return; |
|
1158
|
|
|
|
|
|
|
|
|
1159
|
0
|
|
|
|
|
0
|
$self->maybe_invoke_event( on_back_message => $member, $event->{content}, $event ); |
|
1160
|
|
|
|
|
|
|
} |
|
1161
|
|
|
|
|
|
|
|
|
1162
|
|
|
|
|
|
|
sub _handle_event_m_presence |
|
1163
|
|
|
|
|
|
|
{ |
|
1164
|
0
|
|
|
0
|
|
0
|
my $self = shift; |
|
1165
|
0
|
|
|
|
|
0
|
my ( $user, %changes ) = @_; |
|
1166
|
0
|
0
|
|
|
|
0
|
my $member = $self->member( $user->user_id ) or return; |
|
1167
|
|
|
|
|
|
|
|
|
1168
|
|
|
|
|
|
|
$changes{$_} and $member->$_ = $changes{$_}[1] |
|
1169
|
0
|
|
0
|
|
|
0
|
for qw( displayname ); |
|
1170
|
|
|
|
|
|
|
|
|
1171
|
0
|
|
|
|
|
0
|
$self->maybe_invoke_event( on_presence => $member, %changes ); |
|
1172
|
|
|
|
|
|
|
} |
|
1173
|
|
|
|
|
|
|
|
|
1174
|
|
|
|
|
|
|
sub _handle_event_m_typing_ephemeral |
|
1175
|
|
|
|
|
|
|
{ |
|
1176
|
0
|
|
|
0
|
|
0
|
my $self = shift; |
|
1177
|
0
|
|
|
|
|
0
|
my ( $event ) = @_; |
|
1178
|
|
|
|
|
|
|
|
|
1179
|
0
|
|
|
|
|
0
|
my $typing = $self->{typing_members}; |
|
1180
|
0
|
|
|
|
|
0
|
my %not_typing = %$typing; |
|
1181
|
|
|
|
|
|
|
|
|
1182
|
0
|
|
|
|
|
0
|
foreach my $user_id ( @{ $event->{content}{user_ids} } ) { |
|
|
0
|
|
|
|
|
0
|
|
|
1183
|
0
|
|
|
|
|
0
|
delete $not_typing{$user_id}; |
|
1184
|
0
|
0
|
|
|
|
0
|
next if $typing->{$user_id}; |
|
1185
|
|
|
|
|
|
|
|
|
1186
|
0
|
|
|
|
|
0
|
$typing->{$user_id}++; |
|
1187
|
0
|
0
|
|
|
|
0
|
my $member = $self->member( $user_id ) or next; |
|
1188
|
0
|
|
|
|
|
0
|
$self->maybe_invoke_event( on_typing => $member, 1 ); |
|
1189
|
|
|
|
|
|
|
} |
|
1190
|
|
|
|
|
|
|
|
|
1191
|
0
|
|
|
|
|
0
|
foreach my $user_id ( keys %not_typing ) { |
|
1192
|
0
|
0
|
|
|
|
0
|
my $member = $self->member( $user_id ) or next; |
|
1193
|
0
|
|
|
|
|
0
|
$self->maybe_invoke_event( on_typing => $member, 0 ); |
|
1194
|
0
|
|
|
|
|
0
|
delete $typing->{$user_id}; |
|
1195
|
|
|
|
|
|
|
} |
|
1196
|
|
|
|
|
|
|
|
|
1197
|
0
|
|
|
|
|
0
|
my @members = map { $self->member( $_ ) } keys %$typing; |
|
|
0
|
|
|
|
|
0
|
|
|
1198
|
0
|
|
|
|
|
0
|
$self->maybe_invoke_event( on_members_typing => grep { defined } @members ); |
|
|
0
|
|
|
|
|
0
|
|
|
1199
|
|
|
|
|
|
|
} |
|
1200
|
|
|
|
|
|
|
|
|
1201
|
|
|
|
|
|
|
sub _handle_event_m_receipt_ephemeral |
|
1202
|
|
|
|
|
|
|
{ |
|
1203
|
1
|
|
|
1
|
|
1
|
my $self = shift; |
|
1204
|
1
|
|
|
|
|
5
|
my ( $event ) = @_; |
|
1205
|
|
|
|
|
|
|
|
|
1206
|
1
|
|
|
|
|
1
|
my $content = $event->{content}; |
|
1207
|
1
|
|
|
|
|
3
|
foreach my $event_id ( keys %$content ) { |
|
1208
|
1
|
|
|
|
|
1
|
my $receipt = $content->{$event_id}; |
|
1209
|
1
|
50
|
|
|
|
3
|
my $read_receipt = $receipt->{"m.read"} or next; |
|
1210
|
|
|
|
|
|
|
|
|
1211
|
1
|
|
|
|
|
2
|
foreach my $user_id ( keys %$read_receipt ) { |
|
1212
|
1
|
|
|
|
|
2
|
my $content = $read_receipt->{$user_id}; |
|
1213
|
1
|
50
|
|
|
|
4
|
my $member = $self->member( $user_id ) or next; |
|
1214
|
|
|
|
|
|
|
|
|
1215
|
1
|
|
|
|
|
33
|
$self->maybe_invoke_event( on_read_receipt => $member, $event_id, $content ); |
|
1216
|
|
|
|
|
|
|
} |
|
1217
|
|
|
|
|
|
|
} |
|
1218
|
|
|
|
|
|
|
} |
|
1219
|
|
|
|
|
|
|
|
|
1220
|
|
|
|
|
|
|
=head1 MEMBERSHIP STRUCTURES |
|
1221
|
|
|
|
|
|
|
|
|
1222
|
|
|
|
|
|
|
Parameters documented as C<$member> receive a membership struct, which |
|
1223
|
|
|
|
|
|
|
supports the following methods: |
|
1224
|
|
|
|
|
|
|
|
|
1225
|
|
|
|
|
|
|
=head2 $user = $member->user |
|
1226
|
|
|
|
|
|
|
|
|
1227
|
|
|
|
|
|
|
User object of the member. |
|
1228
|
|
|
|
|
|
|
|
|
1229
|
|
|
|
|
|
|
=head2 $displayname = $member->displayname |
|
1230
|
|
|
|
|
|
|
|
|
1231
|
|
|
|
|
|
|
Profile displayname of the user. |
|
1232
|
|
|
|
|
|
|
|
|
1233
|
|
|
|
|
|
|
=head2 $membership = $member->membership |
|
1234
|
|
|
|
|
|
|
|
|
1235
|
|
|
|
|
|
|
Membership state. One of C or C. |
|
1236
|
|
|
|
|
|
|
|
|
1237
|
|
|
|
|
|
|
=head1 AUTHOR |
|
1238
|
|
|
|
|
|
|
|
|
1239
|
|
|
|
|
|
|
Paul Evans |
|
1240
|
|
|
|
|
|
|
|
|
1241
|
|
|
|
|
|
|
=cut |
|
1242
|
|
|
|
|
|
|
|
|
1243
|
|
|
|
|
|
|
0x55AA; |