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
|
|
48
|
use strict; |
|
12
|
|
|
|
|
15
|
|
|
12
|
|
|
|
|
307
|
|
9
|
12
|
|
|
12
|
|
45
|
use warnings; |
|
12
|
|
|
|
|
16
|
|
|
12
|
|
|
|
|
313
|
|
10
|
|
|
|
|
|
|
|
11
|
|
|
|
|
|
|
# Not really a Notifier but we like the ->maybe_invoke_event style |
12
|
12
|
|
|
12
|
|
63
|
use base qw( IO::Async::Notifier ); |
|
12
|
|
|
|
|
13
|
|
|
12
|
|
|
|
|
1283
|
|
13
|
|
|
|
|
|
|
|
14
|
|
|
|
|
|
|
our $VERSION = '0.18_002'; |
15
|
|
|
|
|
|
|
$VERSION = eval $VERSION; |
16
|
|
|
|
|
|
|
|
17
|
12
|
|
|
12
|
|
52
|
use Carp; |
|
12
|
|
|
|
|
17
|
|
|
12
|
|
|
|
|
623
|
|
18
|
|
|
|
|
|
|
|
19
|
12
|
|
|
12
|
|
56
|
use Future; |
|
12
|
|
|
|
|
12
|
|
|
12
|
|
|
|
|
312
|
|
20
|
12
|
|
|
12
|
|
42
|
use Future::Utils qw( repeat ); |
|
12
|
|
|
|
|
13
|
|
|
12
|
|
|
|
|
534
|
|
21
|
|
|
|
|
|
|
|
22
|
12
|
|
|
12
|
|
52
|
use List::Util qw( pairmap ); |
|
12
|
|
|
|
|
16
|
|
|
12
|
|
|
|
|
1099
|
|
23
|
12
|
|
|
12
|
|
58
|
use Time::HiRes qw( time ); |
|
12
|
|
|
|
|
16
|
|
|
12
|
|
|
|
|
63
|
|
24
|
|
|
|
|
|
|
|
25
|
12
|
|
|
12
|
|
5487
|
use Net::Async::Matrix::Room::State; |
|
12
|
|
|
|
|
21
|
|
|
12
|
|
|
|
|
496
|
|
26
|
|
|
|
|
|
|
# TEMPORARY hack |
27
|
|
|
|
|
|
|
*Member = \&Net::Async::Matrix::Room::State::Member; |
28
|
|
|
|
|
|
|
|
29
|
12
|
|
|
12
|
|
54
|
use Data::Dump 'pp'; |
|
12
|
|
|
|
|
15
|
|
|
12
|
|
|
|
|
519
|
|
30
|
|
|
|
|
|
|
|
31
|
12
|
|
|
12
|
|
46
|
use constant TYPING_RESEND_SECONDS => 30; |
|
12
|
|
|
|
|
14
|
|
|
12
|
|
|
|
|
47325
|
|
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
|
|
80
|
my $self = shift; |
133
|
7
|
|
|
|
|
13
|
my ( $params ) = @_; |
134
|
7
|
|
|
|
|
42
|
$self->SUPER::_init( $params ); |
135
|
|
|
|
|
|
|
|
136
|
7
|
|
|
|
|
73
|
$self->{matrix} = delete $params->{matrix}; |
137
|
7
|
|
|
|
|
20
|
$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
|
|
|
|
|
17
|
$self->{typing_members} = {}; |
142
|
|
|
|
|
|
|
|
143
|
7
|
|
|
|
|
55
|
$self->{live_state} = Net::Async::Matrix::Room::State->new( $self ); |
144
|
|
|
|
|
|
|
} |
145
|
|
|
|
|
|
|
|
146
|
|
|
|
|
|
|
sub configure |
147
|
|
|
|
|
|
|
{ |
148
|
10
|
|
|
10
|
1
|
1476
|
my $self = shift; |
149
|
10
|
|
|
|
|
20
|
my %params = @_; |
150
|
|
|
|
|
|
|
|
151
|
10
|
|
|
|
|
25
|
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
|
|
|
|
169
|
$self->{$_} = delete $params{$_} if exists $params{$_}; |
155
|
|
|
|
|
|
|
} |
156
|
|
|
|
|
|
|
|
157
|
10
|
|
|
|
|
55
|
$self->SUPER::configure( %params ); |
158
|
|
|
|
|
|
|
} |
159
|
|
|
|
|
|
|
|
160
|
|
|
|
|
|
|
=head1 METHODS |
161
|
|
|
|
|
|
|
|
162
|
|
|
|
|
|
|
=cut |
163
|
|
|
|
|
|
|
|
164
|
|
|
|
|
|
|
# FUNCTION |
165
|
|
|
|
|
|
|
sub _delete_null_changes |
166
|
|
|
|
|
|
|
{ |
167
|
9
|
|
|
9
|
|
7
|
my ( $changes ) = @_; |
168
|
|
|
|
|
|
|
|
169
|
9
|
|
|
|
|
14
|
foreach ( keys %$changes ) { |
170
|
12
|
|
|
|
|
8
|
my ( $old, $new ) = @{ $changes->{$_} }; |
|
12
|
|
|
|
|
15
|
|
171
|
|
|
|
|
|
|
|
172
|
12
|
100
|
100
|
|
|
95
|
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
|
|
1
|
my ( $ch ) = @_; |
181
|
1
|
|
|
|
|
2
|
my ( $oldhash, $newhash ) = @$ch; |
182
|
|
|
|
|
|
|
|
183
|
1
|
|
|
|
|
2
|
my %changes; |
184
|
|
|
|
|
|
|
|
185
|
1
|
|
|
|
|
3
|
foreach ( keys %$oldhash ) { |
186
|
1
|
|
|
|
|
1
|
my $old = $oldhash->{$_}; |
187
|
1
|
50
|
|
|
|
4
|
if( !exists $newhash->{$_} ) { |
188
|
0
|
0
|
|
|
|
0
|
$changes{$_} = [ $old, undef ] if defined $old; |
189
|
0
|
|
|
|
|
0
|
next; |
190
|
|
|
|
|
|
|
} |
191
|
|
|
|
|
|
|
|
192
|
1
|
|
|
|
|
2
|
my $new = $newhash->{$_}; |
193
|
|
|
|
|
|
|
|
194
|
1
|
50
|
33
|
|
|
19
|
$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
|
|
|
|
|
3
|
foreach ( keys %$newhash ) { |
199
|
1
|
|
|
|
|
2
|
my $new = $newhash->{$_}; |
200
|
1
|
50
|
|
|
|
3
|
next if exists $oldhash->{$_}; |
201
|
|
|
|
|
|
|
|
202
|
0
|
0
|
|
|
|
0
|
$changes{$_} = [ undef, $new ] if defined $new; |
203
|
|
|
|
|
|
|
} |
204
|
|
|
|
|
|
|
|
205
|
1
|
50
|
|
|
|
15
|
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
|
|
6
|
my $self = shift; |
219
|
4
|
|
|
|
|
5
|
my ( $path, $content ) = @_; |
220
|
|
|
|
|
|
|
|
221
|
4
|
|
|
|
|
18
|
$self->{matrix}->_do_PUT_json( "/rooms/$self->{room_id}" . $path, $content ); |
222
|
|
|
|
|
|
|
} |
223
|
|
|
|
|
|
|
|
224
|
|
|
|
|
|
|
sub _do_POST_json |
225
|
|
|
|
|
|
|
{ |
226
|
2
|
|
|
2
|
|
11
|
my $self = shift; |
227
|
2
|
|
|
|
|
10
|
my ( $path, $content ) = @_; |
228
|
|
|
|
|
|
|
|
229
|
2
|
|
|
|
|
35
|
$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
|
|
2
|
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
|
|
21
|
my $self = shift; |
259
|
16
|
|
|
|
|
42
|
my ( $sync ) = @_; |
260
|
|
|
|
|
|
|
|
261
|
16
|
|
|
|
|
35
|
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
|
|
|
|
|
1038
|
my $live_state = $self->live_state; |
268
|
|
|
|
|
|
|
|
269
|
16
|
100
|
66
|
|
|
82
|
if( $sync->{state} and $sync->{state}{events} and @{ $sync->{state}{events} } ) { |
|
7
|
|
66
|
|
|
32
|
|
270
|
5
|
|
|
|
|
9
|
foreach my $event ( @{ $sync->{state}{events} } ) { |
|
5
|
|
|
|
|
12
|
|
271
|
11
|
|
|
|
|
30
|
$live_state->handle_event( $event ); |
272
|
|
|
|
|
|
|
} |
273
|
|
|
|
|
|
|
} |
274
|
|
|
|
|
|
|
|
275
|
16
|
|
|
|
|
31
|
foreach my $event ( @{ $sync->{timeline}{events} } ) { |
|
16
|
|
|
|
|
38
|
|
276
|
8
|
100
|
|
|
|
20
|
if( defined $event->{state_key} ) { |
277
|
7
|
|
|
|
|
17
|
my $old_event = $live_state->get_event( $event->{type}, $event->{state_key} ); |
278
|
7
|
|
|
|
|
16
|
$live_state->handle_event( $event ); |
279
|
7
|
|
|
|
|
9
|
$self->_handle_state_event( $old_event, $event, $live_state ); |
280
|
|
|
|
|
|
|
} |
281
|
|
|
|
|
|
|
else { |
282
|
1
|
|
|
|
|
6
|
$self->_handle_event( forward => $event ); |
283
|
|
|
|
|
|
|
} |
284
|
|
|
|
|
|
|
} |
285
|
|
|
|
|
|
|
|
286
|
16
|
|
|
|
|
128
|
foreach my $event ( @{ $sync->{ephemeral}{events} } ) { |
|
16
|
|
|
|
|
40
|
|
287
|
1
|
|
|
|
|
3
|
$self->_handle_event( ephemeral => $event ); |
288
|
|
|
|
|
|
|
} |
289
|
|
|
|
|
|
|
|
290
|
16
|
100
|
|
|
|
57
|
if( $initial ) { |
291
|
7
|
|
|
|
|
20
|
$self->await_synced->done; |
292
|
7
|
|
|
|
|
529
|
$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
|
1006
|
my $self = shift; |
306
|
30
|
|
66
|
|
|
165
|
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
|
32
|
my $self = shift; |
323
|
31
|
|
|
|
|
66
|
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
|
|
|
|
|
5
|
my $new_content = $new_event->{content}; |
333
|
|
|
|
|
|
|
|
334
|
7
|
|
|
|
|
6
|
my %changes; |
335
|
7
|
|
|
|
|
22
|
$changes{$_}->[0] = $old_content->{$_} for keys %$old_content; |
336
|
7
|
|
|
|
|
17
|
$changes{$_}->[1] = $new_content->{$_} for keys %$new_content; |
337
|
|
|
|
|
|
|
|
338
|
7
|
|
50
|
|
|
20
|
$_->[1] //= undef for values %changes; # Ensure deleted key values become undef |
339
|
|
|
|
|
|
|
|
340
|
7
|
|
|
|
|
12
|
_delete_null_changes \%changes; |
341
|
|
|
|
|
|
|
|
342
|
7
|
|
|
|
|
17
|
my $member = $state->member( $new_event->{sender} ); |
343
|
|
|
|
|
|
|
|
344
|
7
|
|
|
|
|
62
|
my $type = $new_event->{type}; |
345
|
|
|
|
|
|
|
|
346
|
7
|
|
|
|
|
22
|
$type =~ m/^m\.room\.(.*)$/; |
347
|
7
|
50
|
|
|
|
31
|
my $method = $1 ? "_handle_state_event_" . join( "_", split m/\./, $1 ) : undef; |
348
|
|
|
|
|
|
|
|
349
|
7
|
100
|
66
|
|
|
54
|
if( $method and my $code = $self->can( $method ) ) { |
350
|
4
|
|
|
|
|
14
|
$self->$code( $member, $new_event, $state, %changes ); |
351
|
|
|
|
|
|
|
} |
352
|
|
|
|
|
|
|
else { |
353
|
3
|
|
|
|
|
11
|
$self->maybe_invoke_event( on_state_changed => |
354
|
|
|
|
|
|
|
$member, $new_event, %changes |
355
|
|
|
|
|
|
|
); |
356
|
|
|
|
|
|
|
} |
357
|
|
|
|
|
|
|
} |
358
|
|
|
|
|
|
|
|
359
|
|
|
|
|
|
|
sub _handle_event |
360
|
|
|
|
|
|
|
{ |
361
|
2
|
|
|
2
|
|
5
|
my $self = shift; |
362
|
2
|
|
|
|
|
5
|
my ( $direction, $event ) = @_; |
363
|
|
|
|
|
|
|
|
364
|
2
|
50
|
|
|
|
27
|
$event->{type} =~ m/^(m\.room\.)?(.*)$/ or return; |
365
|
|
|
|
|
|
|
|
366
|
2
|
100
|
|
|
|
8
|
my $base = $1 ? "_handle_roomevent_" : "_handle_event_"; |
367
|
2
|
|
|
|
|
23
|
my $method = $base . join( "_", split( m/\./, $2 ), $direction ); |
368
|
|
|
|
|
|
|
|
369
|
2
|
50
|
|
|
|
12
|
if( my $code = $self->can( $method ) ) { |
370
|
2
|
|
|
|
|
7
|
$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
|
829
|
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
|
|
|
3
|
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
|
|
2
|
my $self = shift; |
460
|
1
|
|
|
|
|
2
|
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
|
|
|
|
|
1
|
values %{ $state->get_events( "m.room.aliases" ) }; |
|
1
|
|
|
|
|
3
|
|
467
|
|
|
|
|
|
|
|
468
|
1
|
|
|
|
|
1
|
$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
|
1415
|
my $self = shift; |
500
|
2
|
|
|
|
|
11
|
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
|
1627
|
my $self = shift; |
542
|
2
|
|
|
|
|
4
|
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
|
1320
|
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
|
|
4
|
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
|
|
|
|
|
2
|
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
|
|
|
|
|
8
|
my $user_id = $event->{state_key}; # == user the change applies to |
725
|
|
|
|
|
|
|
|
726
|
2
|
50
|
0
|
|
|
6
|
my $target_member = $state->member( $user_id ) or |
727
|
|
|
|
|
|
|
warn "ARGH: roomevent_member with unknown user id '$user_id'" and return; |
728
|
|
|
|
|
|
|
|
729
|
2
|
|
|
|
|
39
|
_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
|
1798
|
my $self = shift; |
737
|
3
|
|
|
|
|
6
|
return $self->live_state->members; |
738
|
|
|
|
|
|
|
} |
739
|
|
|
|
|
|
|
|
740
|
|
|
|
|
|
|
sub member |
741
|
|
|
|
|
|
|
{ |
742
|
2
|
|
|
2
|
0
|
4
|
my $self = shift; |
743
|
2
|
|
|
|
|
2
|
my ( $user_id ) = @_; |
744
|
2
|
|
|
|
|
5
|
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
|
|
|
|
|
3
|
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
|
|
|
7
|
$_ 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
|
|
|
|
|
1
|
my $default = $event->{content}{user_default}; |
794
|
|
|
|
|
|
|
|
795
|
1
|
|
|
|
|
5
|
foreach my $user_id ( keys %$users ) { |
796
|
1
|
50
|
|
|
|
3
|
my $target = $state->member( $user_id ) or next; |
797
|
1
|
|
|
|
|
9
|
my ( $oldlevel, $newlevel ) = @{ $users->{$user_id} }; |
|
1
|
|
|
|
|
2
|
|
798
|
|
|
|
|
|
|
|
799
|
1
|
|
33
|
|
|
3
|
$oldlevel //= $default; |
800
|
1
|
|
33
|
|
|
3
|
$newlevel //= $default; |
801
|
|
|
|
|
|
|
|
802
|
1
|
|
|
|
|
6
|
$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
|
2393
|
my $self = shift; |
812
|
2
|
|
|
|
|
3
|
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 send_message |
893
|
|
|
|
|
|
|
|
894
|
|
|
|
|
|
|
$event_id = $room->send_message( %args )->get |
895
|
|
|
|
|
|
|
|
896
|
|
|
|
|
|
|
Sends a new message to the room. Requires a C named argument giving the |
897
|
|
|
|
|
|
|
message type. Depending on the type, further keys will be required that |
898
|
|
|
|
|
|
|
specify the message contents: |
899
|
|
|
|
|
|
|
|
900
|
|
|
|
|
|
|
=over 4 |
901
|
|
|
|
|
|
|
|
902
|
|
|
|
|
|
|
=item m.text, m.emote, m.notice |
903
|
|
|
|
|
|
|
|
904
|
|
|
|
|
|
|
Require C |
905
|
|
|
|
|
|
|
|
906
|
|
|
|
|
|
|
=item m.image, m.audio, m.video, m.file |
907
|
|
|
|
|
|
|
|
908
|
|
|
|
|
|
|
Require C |
909
|
|
|
|
|
|
|
|
910
|
|
|
|
|
|
|
=item m.location |
911
|
|
|
|
|
|
|
|
912
|
|
|
|
|
|
|
Require C |
913
|
|
|
|
|
|
|
|
914
|
|
|
|
|
|
|
=back |
915
|
|
|
|
|
|
|
|
916
|
|
|
|
|
|
|
If an additional argument called C is provided, this is used as the |
917
|
|
|
|
|
|
|
transaction ID for the message, which is then sent as a C request instead |
918
|
|
|
|
|
|
|
of a C. |
919
|
|
|
|
|
|
|
|
920
|
|
|
|
|
|
|
$event_id = $room->send_message( $text )->get |
921
|
|
|
|
|
|
|
|
922
|
|
|
|
|
|
|
A convenient shortcut to sending an C message with a body string and |
923
|
|
|
|
|
|
|
no additional content. |
924
|
|
|
|
|
|
|
|
925
|
|
|
|
|
|
|
=cut |
926
|
|
|
|
|
|
|
|
927
|
|
|
|
|
|
|
my %MSG_REQUIRED_FIELDS = ( |
928
|
|
|
|
|
|
|
'm.text' => [qw( body )], |
929
|
|
|
|
|
|
|
'm.emote' => [qw( body )], |
930
|
|
|
|
|
|
|
'm.notice' => [qw( body )], |
931
|
|
|
|
|
|
|
'm.image' => [qw( url )], |
932
|
|
|
|
|
|
|
'm.audio' => [qw( url )], |
933
|
|
|
|
|
|
|
'm.video' => [qw( url )], |
934
|
|
|
|
|
|
|
'm.file' => [qw( url )], |
935
|
|
|
|
|
|
|
'm.location' => [qw( geo_uri )], |
936
|
|
|
|
|
|
|
); |
937
|
|
|
|
|
|
|
|
938
|
|
|
|
|
|
|
sub send_message |
939
|
|
|
|
|
|
|
{ |
940
|
2
|
|
|
2
|
1
|
1072
|
my $self = shift; |
941
|
2
|
50
|
|
|
|
9
|
my %args = ( @_ == 1 ) ? ( type => "m.text", body => shift ) : @_; |
942
|
|
|
|
|
|
|
|
943
|
|
|
|
|
|
|
my $type = $args{msgtype} = delete $args{type} or |
944
|
2
|
50
|
|
|
|
6
|
croak "Require a 'type' field"; |
945
|
|
|
|
|
|
|
|
946
|
2
|
50
|
|
|
|
5
|
$MSG_REQUIRED_FIELDS{$type} or |
947
|
|
|
|
|
|
|
croak "Unrecognised message type '$type'"; |
948
|
|
|
|
|
|
|
|
949
|
2
|
|
|
|
|
2
|
foreach (@{ $MSG_REQUIRED_FIELDS{$type} } ) { |
|
2
|
|
|
|
|
5
|
|
950
|
2
|
50
|
|
|
|
7
|
$args{$_} or croak "'$type' messages require a '$_' field"; |
951
|
|
|
|
|
|
|
} |
952
|
|
|
|
|
|
|
|
953
|
2
|
100
|
|
|
|
5
|
if( defined( my $txn_id = $args{txn_id} ) ) { |
954
|
|
|
|
|
|
|
$self->_do_PUT_json( "/send/m.room.message/$txn_id", \%args )->then( sub { |
955
|
1
|
|
|
1
|
|
74
|
my ( $response ) = @_; |
956
|
1
|
|
|
|
|
3
|
Future->done( $response->{event_id} ); |
957
|
1
|
|
|
|
|
6
|
}); |
958
|
|
|
|
|
|
|
} |
959
|
|
|
|
|
|
|
else { |
960
|
|
|
|
|
|
|
$self->_do_POST_json( "/send/m.room.message", \%args )->then( sub { |
961
|
1
|
|
|
1
|
|
78
|
my ( $response ) = @_; |
962
|
1
|
|
|
|
|
3
|
Future->done( $response->{event_id} ); |
963
|
1
|
|
|
|
|
4
|
}); |
964
|
|
|
|
|
|
|
} |
965
|
|
|
|
|
|
|
} |
966
|
|
|
|
|
|
|
|
967
|
|
|
|
|
|
|
=head2 paginate_messages |
968
|
|
|
|
|
|
|
|
969
|
|
|
|
|
|
|
$room->paginate_messages( limit => $n )->get |
970
|
|
|
|
|
|
|
|
971
|
|
|
|
|
|
|
Requests more messages of back-pagination history. |
972
|
|
|
|
|
|
|
|
973
|
|
|
|
|
|
|
There is no need to maintain a reference on the returned C; it will be |
974
|
|
|
|
|
|
|
adopted by the room object. |
975
|
|
|
|
|
|
|
|
976
|
|
|
|
|
|
|
=cut |
977
|
|
|
|
|
|
|
|
978
|
|
|
|
|
|
|
sub paginate_messages |
979
|
|
|
|
|
|
|
{ |
980
|
0
|
|
|
0
|
1
|
0
|
my $self = shift; |
981
|
0
|
|
|
|
|
0
|
my %args = @_; |
982
|
|
|
|
|
|
|
|
983
|
0
|
|
0
|
|
|
0
|
my $limit = $args{limit} // 20; |
984
|
0
|
|
0
|
|
|
0
|
my $from = $self->{pagination_token} // "END"; |
985
|
|
|
|
|
|
|
|
986
|
0
|
0
|
|
|
|
0
|
croak "Cannot paginate_messages any further since we're already at the start" |
987
|
|
|
|
|
|
|
if $from eq "START"; |
988
|
|
|
|
|
|
|
|
989
|
|
|
|
|
|
|
# Since we're now doing pagination, we'll need a second set of member |
990
|
|
|
|
|
|
|
# objects |
991
|
|
|
|
|
|
|
$self->{back_members_by_userid} //= { |
992
|
0
|
|
0
|
0
|
|
0
|
pairmap { $a => Member( $b->user, $b->displayname, $b->membership ) } %{ $self->{members_by_userid} } |
|
0
|
|
|
|
|
0
|
|
|
0
|
|
|
|
|
0
|
|
993
|
|
|
|
|
|
|
}; |
994
|
|
|
|
|
|
|
$self->{back_aliases_by_hs} //= { |
995
|
0
|
|
0
|
0
|
|
0
|
pairmap { $a => [ @$b ] } %{ $self->{aliases_by_hs} } |
|
0
|
|
|
|
|
0
|
|
|
0
|
|
|
|
|
0
|
|
996
|
|
|
|
|
|
|
}; |
997
|
|
|
|
|
|
|
|
998
|
|
|
|
|
|
|
my $f = $self->_do_GET_json( "/messages", |
999
|
|
|
|
|
|
|
from => $from, |
1000
|
|
|
|
|
|
|
dir => "b", |
1001
|
|
|
|
|
|
|
limit => $limit, |
1002
|
|
|
|
|
|
|
)->then( sub { |
1003
|
0
|
|
|
0
|
|
0
|
my ( $response ) = @_; |
1004
|
|
|
|
|
|
|
|
1005
|
0
|
|
|
|
|
0
|
foreach my $event ( @{ $response->{chunk} } ) { |
|
0
|
|
|
|
|
0
|
|
1006
|
0
|
0
|
|
|
|
0
|
next unless my ( $subtype ) = ( $event->{type} =~ m/^m\.room\.(.*)$/ ); |
1007
|
0
|
|
|
|
|
0
|
$subtype =~ s/\./_/g; |
1008
|
|
|
|
|
|
|
|
1009
|
0
|
0
|
|
|
|
0
|
if( my $code = $self->can( "_handle_roomevent_${subtype}_backward" ) ) { |
1010
|
0
|
|
|
|
|
0
|
$code->( $self, $event ); |
1011
|
|
|
|
|
|
|
} |
1012
|
|
|
|
|
|
|
else { |
1013
|
0
|
|
|
|
|
0
|
$self->{matrix}->log( "TODO: Handle room pagination event $subtype" ); |
1014
|
|
|
|
|
|
|
} |
1015
|
|
|
|
|
|
|
} |
1016
|
|
|
|
|
|
|
|
1017
|
0
|
|
|
|
|
0
|
$self->{pagination_token} = $response->{end}; |
1018
|
0
|
|
|
|
|
0
|
Future->done( $self ); |
1019
|
0
|
|
|
|
|
0
|
}); |
1020
|
0
|
|
|
|
|
0
|
$self->adopt_future( $f ); |
1021
|
|
|
|
|
|
|
} |
1022
|
|
|
|
|
|
|
|
1023
|
|
|
|
|
|
|
=head2 typing_start |
1024
|
|
|
|
|
|
|
|
1025
|
|
|
|
|
|
|
$room->typing_start |
1026
|
|
|
|
|
|
|
|
1027
|
|
|
|
|
|
|
Sends a typing notification that the user is currently typing in this room. |
1028
|
|
|
|
|
|
|
This notification will periodically be re-sent as required by the protocol |
1029
|
|
|
|
|
|
|
until the C method is called. |
1030
|
|
|
|
|
|
|
|
1031
|
|
|
|
|
|
|
=cut |
1032
|
|
|
|
|
|
|
|
1033
|
|
|
|
|
|
|
sub typing_start |
1034
|
|
|
|
|
|
|
{ |
1035
|
1
|
|
|
1
|
1
|
306
|
my $self = shift; |
1036
|
|
|
|
|
|
|
|
1037
|
1
|
50
|
|
|
|
4
|
return if $self->{typing_timer}; |
1038
|
|
|
|
|
|
|
|
1039
|
1
|
|
|
|
|
5
|
my $user_id = $self->{matrix}->myself->user_id; |
1040
|
|
|
|
|
|
|
|
1041
|
|
|
|
|
|
|
my $f = $self->{typing_timer} = repeat { |
1042
|
|
|
|
|
|
|
$self->_do_PUT_json( "/typing/$user_id", { |
1043
|
|
|
|
|
|
|
typing => 1, |
1044
|
|
|
|
|
|
|
timeout => ( TYPING_RESEND_SECONDS + 5 ) * 1000, # msec |
1045
|
|
|
|
|
|
|
})->then( sub { |
1046
|
2
|
|
|
|
|
160
|
$self->{matrix}->{make_delay}->( TYPING_RESEND_SECONDS ); |
1047
|
2
|
|
|
2
|
|
39
|
}); |
1048
|
1
|
|
|
1
|
|
42
|
} while => sub { !shift->failure }; |
|
1
|
|
|
|
|
418
|
|
1049
|
|
|
|
|
|
|
|
1050
|
|
|
|
|
|
|
$f->on_fail( $self->_capture_weakself( sub { |
1051
|
0
|
|
|
0
|
|
0
|
my $self = shift; |
1052
|
0
|
|
|
|
|
0
|
$self->invoke_error( @_ ); |
1053
|
1
|
|
|
|
|
97
|
})); |
1054
|
|
|
|
|
|
|
} |
1055
|
|
|
|
|
|
|
|
1056
|
|
|
|
|
|
|
=head2 typing_stop |
1057
|
|
|
|
|
|
|
|
1058
|
|
|
|
|
|
|
$room->typing_stop |
1059
|
|
|
|
|
|
|
|
1060
|
|
|
|
|
|
|
Sends a typing notification that the user is no longer typing in this room. |
1061
|
|
|
|
|
|
|
This method also cancels the repeating re-send behaviour created by |
1062
|
|
|
|
|
|
|
C. |
1063
|
|
|
|
|
|
|
|
1064
|
|
|
|
|
|
|
=cut |
1065
|
|
|
|
|
|
|
|
1066
|
|
|
|
|
|
|
sub typing_stop |
1067
|
|
|
|
|
|
|
{ |
1068
|
1
|
|
|
1
|
1
|
39
|
my $self = shift; |
1069
|
|
|
|
|
|
|
|
1070
|
1
|
50
|
|
|
|
4
|
return unless my $f = $self->{typing_timer}; |
1071
|
|
|
|
|
|
|
|
1072
|
1
|
|
|
|
|
3
|
$f->cancel; |
1073
|
1
|
|
|
|
|
75
|
undef $self->{typing_timer}; |
1074
|
|
|
|
|
|
|
|
1075
|
1
|
|
|
|
|
4
|
my $user_id = $self->{matrix}->myself->user_id; |
1076
|
|
|
|
|
|
|
|
1077
|
1
|
|
|
|
|
13
|
$self->adopt_future( |
1078
|
|
|
|
|
|
|
$self->_do_PUT_json( "/typing/$user_id", { |
1079
|
|
|
|
|
|
|
typing => 0, |
1080
|
|
|
|
|
|
|
}) |
1081
|
|
|
|
|
|
|
); |
1082
|
|
|
|
|
|
|
} |
1083
|
|
|
|
|
|
|
|
1084
|
|
|
|
|
|
|
=head2 send_read_receipt |
1085
|
|
|
|
|
|
|
|
1086
|
|
|
|
|
|
|
$room->send_read_receipt( event_id => $event_id, ... )->get |
1087
|
|
|
|
|
|
|
|
1088
|
|
|
|
|
|
|
Sends a C receipt to the given room for the given event ID. |
1089
|
|
|
|
|
|
|
|
1090
|
|
|
|
|
|
|
=cut |
1091
|
|
|
|
|
|
|
|
1092
|
|
|
|
|
|
|
sub send_read_receipt |
1093
|
|
|
|
|
|
|
{ |
1094
|
1
|
|
|
1
|
1
|
2579
|
my $self = shift; |
1095
|
1
|
|
|
|
|
3
|
my %args = @_; |
1096
|
|
|
|
|
|
|
|
1097
|
1
|
50
|
|
|
|
16
|
my $event_id = $args{event_id} or croak "Require event_id"; |
1098
|
|
|
|
|
|
|
|
1099
|
1
|
|
|
|
|
56
|
$self->_do_POST_json( "/receipt/m.read/$event_id", {} ); |
1100
|
|
|
|
|
|
|
} |
1101
|
|
|
|
|
|
|
|
1102
|
|
|
|
|
|
|
sub _handle_roomevent_create_forward |
1103
|
|
|
|
|
|
|
{ |
1104
|
0
|
|
|
0
|
|
0
|
my $self = shift; |
1105
|
0
|
|
|
|
|
0
|
my ( $event ) = @_; |
1106
|
|
|
|
|
|
|
|
1107
|
|
|
|
|
|
|
# Nothing interesting here... |
1108
|
|
|
|
|
|
|
} |
1109
|
|
|
|
|
|
|
*_handle_roomevent_create_initial = \&_handle_roomevent_create_forward; |
1110
|
|
|
|
|
|
|
|
1111
|
|
|
|
|
|
|
sub _handle_roomevent_create_backward |
1112
|
|
|
|
|
|
|
{ |
1113
|
0
|
|
|
0
|
|
0
|
my $self = shift; |
1114
|
|
|
|
|
|
|
|
1115
|
|
|
|
|
|
|
# Stop now |
1116
|
0
|
|
|
|
|
0
|
$self->{pagination_token} = "START"; |
1117
|
|
|
|
|
|
|
} |
1118
|
|
|
|
|
|
|
|
1119
|
|
|
|
|
|
|
sub _handle_roomevent_message_forward |
1120
|
|
|
|
|
|
|
{ |
1121
|
1
|
|
|
1
|
|
1
|
my $self = shift; |
1122
|
1
|
|
|
|
|
2
|
my ( $event ) = @_; |
1123
|
|
|
|
|
|
|
|
1124
|
1
|
|
|
|
|
2
|
my $user_id = $event->{sender}; |
1125
|
1
|
50
|
0
|
|
|
3
|
my $member = $self->member( $user_id ) or |
1126
|
|
|
|
|
|
|
warn "TODO: Unknown member '$user_id' for forward message" and return; |
1127
|
|
|
|
|
|
|
|
1128
|
1
|
|
|
|
|
42
|
$self->maybe_invoke_event( on_message => $member, $event->{content}, $event ); |
1129
|
|
|
|
|
|
|
} |
1130
|
|
|
|
|
|
|
|
1131
|
|
|
|
|
|
|
sub _handle_roomevent_message_backward |
1132
|
|
|
|
|
|
|
{ |
1133
|
0
|
|
|
0
|
|
0
|
my $self = shift; |
1134
|
0
|
|
|
|
|
0
|
my ( $event ) = @_; |
1135
|
|
|
|
|
|
|
|
1136
|
0
|
|
|
|
|
0
|
my $user_id = $event->{user_id}; |
1137
|
0
|
0
|
0
|
|
|
0
|
my $member = $self->{back_members_by_userid}{$user_id} or |
1138
|
|
|
|
|
|
|
warn "TODO: Unknown member '$user_id' for backward message" and return; |
1139
|
|
|
|
|
|
|
|
1140
|
0
|
|
|
|
|
0
|
$self->maybe_invoke_event( on_back_message => $member, $event->{content}, $event ); |
1141
|
|
|
|
|
|
|
} |
1142
|
|
|
|
|
|
|
|
1143
|
|
|
|
|
|
|
sub _handle_event_m_presence |
1144
|
|
|
|
|
|
|
{ |
1145
|
0
|
|
|
0
|
|
0
|
my $self = shift; |
1146
|
0
|
|
|
|
|
0
|
my ( $user, %changes ) = @_; |
1147
|
0
|
0
|
|
|
|
0
|
my $member = $self->member( $user->user_id ) or return; |
1148
|
|
|
|
|
|
|
|
1149
|
|
|
|
|
|
|
$changes{$_} and $member->$_ = $changes{$_}[1] |
1150
|
0
|
|
0
|
|
|
0
|
for qw( displayname ); |
1151
|
|
|
|
|
|
|
|
1152
|
0
|
|
|
|
|
0
|
$self->maybe_invoke_event( on_presence => $member, %changes ); |
1153
|
|
|
|
|
|
|
} |
1154
|
|
|
|
|
|
|
|
1155
|
|
|
|
|
|
|
sub _handle_event_m_typing_ephemeral |
1156
|
|
|
|
|
|
|
{ |
1157
|
0
|
|
|
0
|
|
0
|
my $self = shift; |
1158
|
0
|
|
|
|
|
0
|
my ( $event ) = @_; |
1159
|
|
|
|
|
|
|
|
1160
|
0
|
|
|
|
|
0
|
my $typing = $self->{typing_members}; |
1161
|
0
|
|
|
|
|
0
|
my %not_typing = %$typing; |
1162
|
|
|
|
|
|
|
|
1163
|
0
|
|
|
|
|
0
|
foreach my $user_id ( @{ $event->{content}{user_ids} } ) { |
|
0
|
|
|
|
|
0
|
|
1164
|
0
|
|
|
|
|
0
|
delete $not_typing{$user_id}; |
1165
|
0
|
0
|
|
|
|
0
|
next if $typing->{$user_id}; |
1166
|
|
|
|
|
|
|
|
1167
|
0
|
|
|
|
|
0
|
$typing->{$user_id}++; |
1168
|
0
|
0
|
|
|
|
0
|
my $member = $self->member( $user_id ) or next; |
1169
|
0
|
|
|
|
|
0
|
$self->maybe_invoke_event( on_typing => $member, 1 ); |
1170
|
|
|
|
|
|
|
} |
1171
|
|
|
|
|
|
|
|
1172
|
0
|
|
|
|
|
0
|
foreach my $user_id ( keys %not_typing ) { |
1173
|
0
|
0
|
|
|
|
0
|
my $member = $self->member( $user_id ) or next; |
1174
|
0
|
|
|
|
|
0
|
$self->maybe_invoke_event( on_typing => $member, 0 ); |
1175
|
0
|
|
|
|
|
0
|
delete $typing->{$user_id}; |
1176
|
|
|
|
|
|
|
} |
1177
|
|
|
|
|
|
|
|
1178
|
0
|
|
|
|
|
0
|
my @members = map { $self->member( $_ ) } keys %$typing; |
|
0
|
|
|
|
|
0
|
|
1179
|
0
|
|
|
|
|
0
|
$self->maybe_invoke_event( on_members_typing => grep { defined } @members ); |
|
0
|
|
|
|
|
0
|
|
1180
|
|
|
|
|
|
|
} |
1181
|
|
|
|
|
|
|
|
1182
|
|
|
|
|
|
|
sub _handle_event_m_receipt_ephemeral |
1183
|
|
|
|
|
|
|
{ |
1184
|
1
|
|
|
1
|
|
1
|
my $self = shift; |
1185
|
1
|
|
|
|
|
1
|
my ( $event ) = @_; |
1186
|
|
|
|
|
|
|
|
1187
|
1
|
|
|
|
|
3
|
my $content = $event->{content}; |
1188
|
1
|
|
|
|
|
2
|
foreach my $event_id ( keys %$content ) { |
1189
|
1
|
|
|
|
|
1
|
my $receipt = $content->{$event_id}; |
1190
|
1
|
50
|
|
|
|
3
|
my $read_receipt = $receipt->{"m.read"} or next; |
1191
|
|
|
|
|
|
|
|
1192
|
1
|
|
|
|
|
2
|
foreach my $user_id ( keys %$read_receipt ) { |
1193
|
1
|
|
|
|
|
1
|
my $content = $read_receipt->{$user_id}; |
1194
|
1
|
50
|
|
|
|
4
|
my $member = $self->member( $user_id ) or next; |
1195
|
|
|
|
|
|
|
|
1196
|
1
|
|
|
|
|
32
|
$self->maybe_invoke_event( on_read_receipt => $member, $event_id, $content ); |
1197
|
|
|
|
|
|
|
} |
1198
|
|
|
|
|
|
|
} |
1199
|
|
|
|
|
|
|
} |
1200
|
|
|
|
|
|
|
|
1201
|
|
|
|
|
|
|
=head1 MEMBERSHIP STRUCTURES |
1202
|
|
|
|
|
|
|
|
1203
|
|
|
|
|
|
|
Parameters documented as C<$member> receive a membership struct, which |
1204
|
|
|
|
|
|
|
supports the following methods: |
1205
|
|
|
|
|
|
|
|
1206
|
|
|
|
|
|
|
=head2 $user = $member->user |
1207
|
|
|
|
|
|
|
|
1208
|
|
|
|
|
|
|
User object of the member. |
1209
|
|
|
|
|
|
|
|
1210
|
|
|
|
|
|
|
=head2 $displayname = $member->displayname |
1211
|
|
|
|
|
|
|
|
1212
|
|
|
|
|
|
|
Profile displayname of the user. |
1213
|
|
|
|
|
|
|
|
1214
|
|
|
|
|
|
|
=head2 $membership = $member->membership |
1215
|
|
|
|
|
|
|
|
1216
|
|
|
|
|
|
|
Membership state. One of C or C. |
1217
|
|
|
|
|
|
|
|
1218
|
|
|
|
|
|
|
=head1 AUTHOR |
1219
|
|
|
|
|
|
|
|
1220
|
|
|
|
|
|
|
Paul Evans |
1221
|
|
|
|
|
|
|
|
1222
|
|
|
|
|
|
|
=cut |
1223
|
|
|
|
|
|
|
|
1224
|
|
|
|
|
|
|
0x55AA; |