File Coverage

blib/lib/Mastodon/Client.pm
Criterion Covered Total %
statement 101 228 44.3
branch 15 34 44.1
condition 16 29 55.1
subroutine 22 50 44.0
pod 21 22 95.4
total 175 363 48.2


line stmt bran cond sub pod time code
1             # ABSTRACT: Talk to a Mastodon server
2             package Mastodon::Client;
3              
4 3     3   269912 use strict;
  3         10  
  3         90  
5 3     3   19 use warnings;
  3         9  
  3         78  
6 3     3   40 use v5.10.0;
  3         16  
7              
8             our $VERSION = '0.012';
9              
10 3     3   18 use Carp;
  3         5  
  3         163  
11 3     3   905 use Mastodon::Types qw( Acct Account DateTime Image URI Instance );
  3         12  
  3         100  
12 3     3   4744 use Moo;
  3         16699  
  3         16  
13 3     3   4655 use Types::Common::String qw( NonEmptyStr );
  3         62693  
  3         32  
14             use Types::Standard
15 3     3   1528 qw( Int Str Optional Bool Maybe Undef HashRef ArrayRef Dict slurpy );
  3         6  
  3         18  
16 3     3   4988 use Types::Path::Tiny qw( File );
  3         7  
  3         25  
17              
18 3     3   2259 use Log::Any;
  3         18948  
  3         16  
19             my $log = Log::Any->get_logger(category => 'Mastodon');
20              
21             with 'Mastodon::Role::UserAgent';
22              
23             has coerce_entities => (
24             is => 'rw',
25             isa => Bool,
26             lazy => 1,
27             default => 0,
28             );
29              
30             has access_token => (
31             is => 'rw',
32             isa => NonEmptyStr,
33             lazy => 1,
34             );
35              
36             has authorized => (
37             is => 'rw',
38             isa => DateTime|Bool,
39             lazy => 1,
40             default => sub { defined $_[0]->access_token },
41             coerce => 1,
42             );
43              
44             has client_id => (
45             is => 'rw',
46             isa => NonEmptyStr,
47             lazy => 1,
48             );
49              
50             has client_secret => (
51             is => 'rw',
52             isa => NonEmptyStr,
53             lazy => 1,
54             );
55              
56             has name => (
57             is => 'ro',
58             isa => NonEmptyStr,
59             );
60              
61             has website => (
62             is => 'ro',
63             isa => Str,
64             lazy => 1,
65             default => q{},
66             );
67              
68             has account => (
69             is => 'rw',
70             isa => HashRef|Account,
71             init_arg => undef,
72             lazy => 1,
73             default => sub {
74             $_[0]->get_account;
75             },
76             );
77              
78             has scopes => (
79             is => 'ro',
80             isa => ArrayRef->plus_coercions( Str, sub { [ split / /, $_ ] } ),
81             lazy => 1,
82             default => sub { [ 'read' ] },
83             coerce => 1,
84             );
85              
86             after access_token => sub {
87             my $self = shift;
88             $self->authorized(1);
89             };
90              
91             sub authorize {
92 2     2 1 3247 my $self = shift;
93              
94 2 100 66     41 unless ( $self->client_id and $self->client_secret ) {
95 1         18 croak $log->fatal(
96             'Cannot authorize client without client_id and client_secret');
97             }
98              
99 1 50       49 if ( $self->access_token ) {
100 1         1968 $log->warn('Client is already authorized');
101 1         9 return $self;
102             }
103              
104             state $check = compile(
105             slurpy Dict [
106 0     0   0 access_code => Str->plus_coercions( Undef, sub {q{}} ),
107 0     0   0 username => Str->plus_coercions( Undef, sub {q{}} ),
108 0     0   0 password => Str->plus_coercions( Undef, sub {q{}} ),
  0         0  
109             ],
110             );
111 0         0 my ($params) = $check->(@_);
112              
113 0         0 my $data = {
114             client_id => $self->client_id,
115             client_secret => $self->client_secret,
116             redirect_uri => $self->redirect_uri,
117             };
118              
119 0 0       0 if ( $params->{access_code} ) {
120 0         0 $data->{grant_type} = 'authorization_code';
121 0         0 $data->{code} = $params->{access_code};
122             }
123             else {
124 0         0 $data->{grant_type} = 'password';
125 0         0 $data->{username} = $params->{username};
126 0         0 $data->{password} = $params->{password};
127             }
128              
129 0         0 my $response = $self->post( 'oauth/token' => $data );
130              
131 0 0       0 if ( defined $response->{error} ) {
132 0         0 $log->warn( $response->{error_description} );
133             }
134             else {
135 0         0 my $granted_scopes = join q{ }, sort split( / /, $response->{scope} );
136 0         0 my $requested_scopes = join q{ }, sort @{ $self->scopes };
  0         0  
137              
138 0 0       0 croak $log->fatal('Granted and requested scopes do not match')
139             if $granted_scopes ne $requested_scopes;
140              
141 0         0 $self->access_token( $response->{access_token} );
142 0         0 $self->authorized( $response->{created_at} );
143             }
144              
145 0         0 return $self;
146             }
147              
148             # Authorize follow requests by account ID
149             sub authorize_follow {
150 0     0 1 0 my $self = shift;
151 0         0 state $check = compile( Int );
152 0         0 my ($id) = $check->(@_);
153 0         0 return $self->post( 'follow_requests/authorize' => { id => $id } );
154             }
155              
156             # Clears notifications
157             sub clear_notifications {
158 0     0 1 0 my $self = shift;
159 0         0 state $check = compile();
160 0         0 $check->(@_);
161              
162 0         0 return $self->post( 'notifications/clear' );
163             }
164              
165             # Delete a status by ID
166             sub delete_status {
167 0     0 1 0 my $self = shift;
168              
169 0         0 state $check = compile( Int );
170 0         0 my ($id) = $check->(@_);
171              
172 0         0 return $self->delete( "statuses/$id" );
173             }
174              
175             sub fetch_instance {
176 1     1 1 2601 my $self = shift;
177              
178             # Do not return from the instance attribute, since the user might have
179             # disabled coercions, and the attribute is always coerced
180 1         9 my $instance = $self->get( 'instance' );
181 1         87 $self->instance($instance);
182 1         125 return $instance;
183             }
184              
185             sub get_account {
186 4     4 1 1502 my $self = shift;
187 4         21 my $own = 'verify_credentials';
188              
189 4         74 state $check = compile( Optional [Int|HashRef], Optional [HashRef] );
190 4         3926 my ($id, $params) = $check->(@_);
191              
192 4 100       290 if (ref $id eq 'HASH') {
193 1         3 $params = $id;
194 1         68 $id = undef;
195             }
196              
197 4   66     25 $id //= $own;
198 4   100     26 $params //= {};
199              
200 4         31 my $data = $self->get( "accounts/$id", $params );
201              
202             # We fetched authenticated user account's data
203             # Update local reference
204 4 100       215 $self->account($data) if ($id eq $own);
205 4         169 return $data;
206             }
207              
208             # Get a single notification by ID
209             sub get_notification {
210 0     0 1 0 my $self = shift;
211 0         0 state $check = compile( Int, Optional [HashRef] );
212 0         0 my ($id, $params) = $check->(@_);
213              
214 0         0 return $self->get( "notifications/$id", $params );
215             }
216              
217             # Get a single status by ID
218             sub get_status {
219 0     0 1 0 my $self = shift;
220 0         0 state $check = compile( Int, Optional [HashRef] );
221 0         0 my ($id, $params) = $check->(@_);
222              
223 0         0 return $self->get( "statuses/$id", $params );
224             }
225              
226             # Post a status
227             sub post_status {
228 0     0 1 0 my $self = shift;
229 0         0 state $check = compile( Str|HashRef, Optional[HashRef]);
230 0         0 my ($text, $params) = $check->(@_);
231 0   0     0 $params //= {};
232              
233 0         0 my $payload;
234 0 0       0 if (ref $text eq 'HASH') {
235 0         0 $params = $text;
236             croak $log->fatal('Post must contain a (possibly empty) status text')
237 0 0       0 unless defined $params->{status};
238 0         0 $payload = $params;
239             }
240             else {
241 0         0 $payload = { status => $text, %{$params} };
  0         0  
242             }
243              
244 0         0 return $self->post( 'statuses', $payload);
245             }
246              
247             # Reblog a status by ID
248             sub reblog_status {
249 0     0 0 0 my $self = shift;
250              
251 0         0 state $check = compile( Int );
252 0         0 my ($id) = $check->(@_);
253              
254 0         0 return $self->post( "statuses/$id/reblog" );
255             }
256              
257             sub register {
258 0     0 1 0 my $self = shift;
259              
260 0 0 0     0 if ( $self->client_id && $self->client_secret ) {
261 0         0 $log->warn('Client is already registered');
262 0         0 return $self;
263             }
264              
265             state $check = compile(
266             slurpy Dict [
267 0     0   0 instance => Instance->plus_coercions( Undef, sub { $self->instance } ),
268             redirect_uris =>
269 0     0   0 Str->plus_coercions( Undef, sub { $self->redirect_uri } ),
270             scopes =>
271 0     0   0 ArrayRef->plus_coercions( Undef, sub { $self->scopes } ),
272 0     0   0 website => Str->plus_coercions( Undef, sub { $self->website } ),
  0         0  
273             ]
274             );
275 0         0 my ($params) = $check->(@_);
276              
277             my $response = $self->post('apps' => {
278             client_name => $self->name,
279             redirect_uris => $params->{redirect_uris},
280 0         0 scopes => join q{ }, sort( @{ $params->{scopes} } ),
  0         0  
281             });
282              
283 0         0 $self->client_id( $response->{client_id} );
284 0         0 $self->client_secret( $response->{client_secret} );
285              
286 0         0 return $self;
287             }
288              
289             sub statuses {
290 4     4 1 12176 my $self = shift;
291 4         15 state $check = compile( Optional [HashRef|Int], Optional [HashRef]);
292 4         3407 my ($id, $params) = $check->(@_);
293 4 100       229 if (ref $id) {
294 1         3 $params = $id;
295 1         4 $id = undef;
296             }
297 4   66     62 $id //= $self->account->{id};
298 4   100     52 $params //= {};
299              
300 4         26 return $self->get( "accounts/$id/statuses", $params );
301             }
302              
303             # Reject follow requsts by account ID
304             sub reject_follow {
305 0     0 1 0 my $self = shift;
306 0         0 state $check = compile( Int );
307 0         0 my ($id) = $check->(@_);
308 0         0 return $self->post( 'follow_requests/reject' => { id => $id } );
309             }
310              
311             # Follow a remote user by acct (username@instance)
312             sub remote_follow {
313 0     0 1 0 my $self = shift;
314 0         0 state $check = compile( Acct );
315 0         0 my ($acct) = $check->(@_);
316 0         0 return $self->post( 'follows' => { uri => $acct } );
317             }
318              
319             # Report a user account or list of statuses
320             sub report {
321 0     0 1 0 my $self = shift;
322             state $check = compile( slurpy Dict[
323             account_id => Optional[Int],
324 0     0   0 status_ids => Optional[ArrayRef->plus_coercions( Int, sub { [ $_ ] } ) ],
  0         0  
325             comment => Optional[Str],
326             ]);
327 0         0 my ($data) = $check->(@_);
328              
329             croak $log->fatal('Either account_id or status_ids are required for report')
330 0 0       0 unless join(q{ }, keys(%{$data})) =~ /\b(account_id|status_ids)\b/;
  0         0  
331              
332 0         0 return $self->post( 'reports' => $data );
333             }
334              
335             sub relationships {
336 5     5 1 11796 my $self = shift;
337              
338 5         16 state $check = compile( slurpy ArrayRef [Int|HashRef] );
339 5         7501 my ($ids) = $check->(@_);
340 5 100       323 my $params = (ref $ids->[-1] eq 'HASH') ? pop(@{$ids}) : {};
  2         6  
341              
342             croak $log->fatal('At least one ID number needed in relationships')
343 5 100       12 unless scalar @{$ids};
  5         44  
344              
345             $params = {
346             id => $ids,
347 3         9 %{$params},
  3         12  
348             };
349              
350 3         18 return $self->get( 'accounts/relationships', $params );
351             }
352              
353             sub search {
354 0     0 1 0 my $self = shift;
355              
356 0         0 state $check = compile( Str, Optional [HashRef] );
357 0         0 my ($query, $params) = $check->(@_);
358 0   0     0 $params //= {};
359              
360             $params = {
361             'q' => $query,
362 0         0 %{$params},
  0         0  
363             };
364              
365 0         0 return $self->get( 'search', $params );
366             }
367              
368             sub search_accounts {
369 5     5 1 17490 my $self = shift;
370              
371 5         17 state $check = compile( Str, Optional [HashRef] );
372 5         2326 my ($query, $params) = $check->(@_);
373 2   100     62 $params //= {};
374              
375             $params = {
376             'q' => $query,
377 2         6 %{$params},
  2         12  
378             };
379              
380 2         14 return $self->get( 'accounts/search', $params );
381             }
382              
383             sub stream {
384 0     0 1 0 my $self = shift;
385              
386 0         0 state $check = compile( NonEmptyStr );
387 0         0 my ($query) = $check->(@_);
388              
389 0 0       0 my $endpoint
390             = $self->instance->uri
391             . '/api/v'
392             . $self->api_version
393             . '/streaming/'
394             . (( $query =~ /^#/ )
395             ? ( 'hashtag?' . $query )
396             : $query
397             );
398              
399 3     3   6585 use Mastodon::Listener;
  3         9  
  3         1078  
400 0         0 return Mastodon::Listener->new(
401             url => $endpoint,
402             access_token => $self->access_token,
403             coerce_entities => $self->coerce_entities,
404             );
405             }
406              
407             sub timeline {
408 0     0 1 0 my $self = shift;
409              
410 0         0 state $check = compile( NonEmptyStr, Optional [HashRef] );
411 0         0 my ($query, $params) = $check->(@_);
412              
413 0 0       0 my $endpoint
414             = ( $query =~ /^#/ )
415             ? 'timelines/tag/' . $query
416             : 'timelines/' . $query;
417              
418 0         0 return $self->get($endpoint, $params);
419             }
420              
421             sub update_account {
422 0     0 1 0 my $self = shift;
423              
424 0         0 state $check = compile(
425             slurpy Dict [
426             display_name => Optional [Str],
427             note => Optional [Str],
428             avatar => Optional [Image],
429             header => Optional [Image],
430             ]
431             );
432 0         0 my ($data) = $check->(@_);
433              
434 0         0 return $self->patch( 'accounts/update_credentials' => $data );
435             }
436              
437             sub upload_media {
438 0     0 1 0 my $self = shift;
439              
440             state $check = compile(
441 0     0   0 File->plus_coercions( Str, sub { Path::Tiny::path($_) } )
442 0         0 );
443 0         0 my ($file) = $check->(@_);
444              
445 0         0 return $self->post( 'media' =>
446             { file => [ $file, undef ] },
447             headers => { Content_Type => 'form-data' },
448             );
449             }
450              
451             # POST requests with no data and a mandatory ID number
452             foreach my $pair ([
453             [ statuses => [qw( reblog unreblog favourite unfavourite )] ],
454             [ accounts => [qw( mute unmute block unblock follow unfollow )] ],
455             ]) {
456              
457             my ($base, $endpoints) = @{$pair};
458              
459             foreach my $endpoint (@{$endpoints}) {
460             my $method = ($base eq 'statuses') ? $endpoint . '_status' : $endpoint;
461              
462 3     3   26 no strict 'refs';
  3         6  
  3         380  
463             *{ __PACKAGE__ . '::' . $method } = sub {
464 0     0   0 my $self = shift;
465 0         0 state $check = compile( Int );
466 0         0 my ($id) = $check->(@_);
467              
468 0         0 return $self->post( "$base/$id/$endpoint" );
469             };
470             }
471             }
472              
473             # GET requests with no parameters but optional parameter hashref
474             for my $action (qw(
475             blocks favourites follow_requests mutes notifications reports
476             )) {
477              
478 3     3   18 no strict 'refs';
  3         10  
  3         292  
479             *{ __PACKAGE__ . '::' . $action } = sub {
480 0     0   0 my $self = shift;
481 0         0 state $check = compile(Optional [HashRef]);
482 0         0 my ($params) = $check->(@_);
483 0   0     0 $params //= {};
484              
485 0         0 return $self->get( $action, $params );
486             };
487             }
488              
489             # GET requests with optional ID and parameter hashref
490             # ID number defaults to authenticated account's ID
491             for my $action (qw( following followers )) {
492 3     3   17 no strict 'refs';
  3         7  
  3         449  
493             *{ __PACKAGE__ . '::' . $action } = sub {
494 8     8   19757 my $self = shift;
495 8         32 state $check = compile( Optional [Int|HashRef], Optional [HashRef] );
496 8         6582 my ($id, $params) = $check->(@_);
497              
498 8 100       616 if (ref $id eq 'HASH') {
499 2         6 $params = $id;
500 2         5 $id = undef;
501             }
502              
503 8   66     143 $id //= $self->account->{id};
504 8   100     89 $params //= {};
505              
506 8         60 return $self->get( "accounts/$id/$action", $params );
507             };
508             }
509              
510             # GET requests for status details
511             foreach my $pair ([
512             [ get_status_context => 'context' ],
513             [ get_status_card => 'card' ],
514             [ get_status_reblogs => 'reblogged_by' ],
515             [ get_status_favourites => 'favourited_by' ],
516             ]) {
517              
518             my ($method, $endpoint) = @{$pair};
519              
520 3     3   21 no strict 'refs';
  3         8  
  3         393  
521             *{ __PACKAGE__ . '::' . $method } = sub {
522 0     0     my $self = shift;
523 0           state $check = compile( Int, Optional [HashRef] );
524 0           my ($id, $params) = $check->(@_);
525              
526 0           return $self->get( "statuses/$id/$endpoint", $params );
527             };
528             }
529              
530             1;
531              
532             __END__
533              
534             =encoding utf8
535              
536             =head1 NAME
537              
538             Mastodon::Client - Talk to a Mastodon server
539              
540             =head1 SYNOPSIS
541              
542             use Mastodon::Client;
543              
544             my $client = Mastodon::Client->new(
545             instance => 'mastodon.social',
546             name => 'PerlBot',
547             client_id => $client_id,
548             client_secret => $client_secret,
549             access_token => $access_token,
550             coerce_entities => 1,
551             );
552              
553             $client->post_status('Posted to a Mastodon server!');
554             $client->post_status('And now in secret...',
555             { visibility => 'unlisted ' }
556             )
557              
558             # Streaming interface might change!
559             my $listener = $client->stream( 'public' );
560             $listener->on( update => sub {
561             my ($listener, $status) = @_;
562             printf "%s said: %s\n",
563             $status->account->display_name,
564             $status->content;
565             });
566             $listener->start;
567              
568             =head1 DESCRIPTION
569              
570             Mastodon::Client lets you talk to a Mastodon server to obtain authentication
571             credentials, read posts from timelines in both static or streaming mode, and
572             perform all the other operations exposed by the Mastodon API.
573              
574             Most of these are available through the convenience methods listed below, which
575             validate input parameters and are likely to provide more meaningful feedback in
576             case of errors.
577              
578             Alternatively, this distribution can be used via the low-level request methods
579             (B<post>, B<get>, etc), which allow direct access to the API endpoints. All
580             other methods call one of these at some point.
581              
582             =head1 ATTRIBUTES
583              
584             =over 4
585              
586             =item B<instance>
587              
588             A Mastodon::Entity::Instance object representing the instance to which this
589             client will speak. Defaults to C<mastodon.social>.
590              
591             =item B<api_version>
592              
593             An integer specifying the version of the API endpoints to use. Defaults to C<1>.
594              
595             =item B<redirect_uri>
596              
597             The URI to which authorization codes should be forwarded as part of the OAuth2
598             flow. Defaults to C<urn:ietf:wg:oauth:2.0:oob> (meaning no redirection).
599              
600             =item B<user_agent>
601              
602             The user agent to use for the requests. Defaults to an instance of
603             L<LWP::UserAgent>. It is expected to have a C<request> method that accepts
604             instances of L<HTTP::Request> objects.
605              
606             =item B<coerce_entities>
607              
608             A boolean value. Set to true if you want Mastodon::Client to internally coerce
609             all response entities to objects. This adds a level of validation, and can
610             make the objects easier to use.
611              
612             Although this does require some additional processing, the coercion is done by
613             L<Type::Tiny>, so the impact is negligible.
614              
615             For now, it defaults to B<false> (but this will likely change, so I recommend
616             you use it).
617              
618             =item B<access_token>
619              
620             The access token of your client. This is provided by the Mastodon API and is
621             used for the OAuth2 authentication required for most API calls.
622              
623             You can get this by calling B<authorize> with either an access code or your
624             account's username and password.
625              
626             =item B<authorized>
627              
628             Boolean. False is the client has no defined access_token. When an access token
629             is set, this is set to true or to a L<DateTime> object representing the time of
630             authorization if possible (as received from the server).
631              
632             =item B<client_id>
633              
634             =item B<client_secret>
635              
636             The client ID and secret are provided by the Mastodon API when you register
637             your client using the B<register> method. They are used to identify where your
638             calls are coming from, and are required before you can use the B<authorize>
639             method to get the access token.
640              
641             =item B<name>
642              
643             Your client's name. This is required when registering, but is otherwise seldom
644             used. If you are using the B<authorization_url> to get an access code from your
645             users, then they will see this name when they go to that page.
646              
647             =item B<account>
648              
649             Holds the authenticated account. It is set internally by the B<get_account>
650             method.
651              
652             =item B<scopes>
653              
654             This array reference holds the scopes set by you for the client. These are
655             required when registering your client with the Mastodon instance. Defaults to
656             C<read>.
657              
658             Mastodon::Client will internally make sure that the scopes you were provided
659             when calling B<authorize> match those that you requested. If this is not the
660             case, it will helpfully die.
661              
662             =item B<website>
663              
664             The URL of a human-readable website for the client. If made available, it
665             appears as a link in the "authorized applications" tab of the user preferences
666             in the default Mastodon web GUI. Defaults to the empty string.
667              
668             =back
669              
670             =head1 METHODS
671              
672             =head2 Authorizing an application
673              
674             Although not all of the API methods require authentication to be used, most of
675             them do. The authentication process involves a) registering an application with
676             a Mastodon server to obtain a client secret and ID; b) authorizing the
677             application by either providing a user's credentials, or by using an
678             authentication URL.
679              
680             The methods facilitating this process are detailed below:
681              
682             =over 4
683              
684             =item B<register()>
685              
686             =item B<register($data)>
687              
688             Obtain a client secret and ID from a given mastodon instance. Takes a single
689             hash reference as an argument, with the following possible keys:
690              
691             =over 4
692              
693             =item B<redirect_uris>
694              
695             The URL to which authorization codes should be forwarded after authorized by
696             the user. Defaults to the value of the B<redirect_uri> attribute.
697              
698             =item B<scopes>
699              
700             The scopes requested by this client. Defaults to the value of the B<scopes>
701             attribute.
702              
703             =item B<website>
704              
705             The client's website. Defaults to the value of the C<website> attribute.
706              
707             =back
708              
709             When successful, sets the C<client_secret> and C<client_id> attributes of
710             the Mastodon::Client object and returns the modified object.
711              
712             This should be called B<once> per client and its contents cached locally.
713              
714             =item B<authorization_url()>
715              
716             Generate an authorization URL for the given application. Accessing this URL
717             via a browser by a logged in user will allow that user to grant this
718             application access to the requested scopes. The scopes used are the ones in the
719             B<scopes> attribute at the time this method is called.
720              
721             =item B<authorize()>
722              
723             =item B<authorize( %data )>
724              
725             Grant the application access to the requested scopes for a given user. This
726             method takes a hash with either an access code or a user's login credentials to
727             grant authorization. Valid keys are:
728              
729             =over 4
730              
731             =item B<access_code>
732              
733             The access code obtained by visiting the URL generated by B<authorization_url>.
734              
735             =item B<username>
736              
737             =item B<password>
738              
739             The user's login credentials.
740              
741             =back
742              
743             When successful, the method automatically sets the client's B<authorized>
744             attribute to a true value and caches the B<access_token> for all future calls.
745              
746             =back
747              
748             The remaining methods listed here follow the order of those in the official API
749             documentation.
750              
751             =head2 Accounts
752              
753             =over 4
754              
755             =item B<get_account()>
756              
757             =item B<get_account($id)>
758              
759             =item B<get_account($params)>
760              
761             =item B<get_account($id, $params)>
762              
763             Fetches an account by ID. If no ID is provided, this defaults to the current
764             authenticated account. Global GET parameters are available for this method.
765              
766             Depending on the value of C<coerce_entities>, it returns a
767             Mastodon::Entity::Account object, or a plain hash reference.
768              
769             =item B<update_account($params)>
770              
771             Make changes to the authenticated account. Takes a hash reference with the
772             following possible keys:
773              
774             =over 4
775              
776             =item B<display_name>
777              
778             =item B<note>
779              
780             Strings
781              
782             =item B<avatar>
783              
784             =item B<header>
785              
786             A base64 encoded image, or the name of a file to be encoded.
787              
788             =back
789              
790             Depending on the value of C<coerce_entities>, returns the modified
791             Mastodon::Entity::Account object, or a plain hash reference.
792              
793             =item B<followers()>
794              
795             =item B<followers($id)>
796              
797             =item B<followers($params)>
798              
799             =item B<followers($id, $params)>
800              
801             Get the list of followers of an account by ID. If no ID is provided, the one
802             for the current authenticated account is used. Global GET parameters are
803             available for this method.
804              
805             Depending on the value of C<coerce_entities>, returns an array reference of
806             Mastodon::Entity::Account objects, or a plain array reference.
807              
808             =item B<following()>
809              
810             =item B<following($id)>
811              
812             =item B<following($params)>
813              
814             =item B<following($id, $params)>
815              
816             Get the list of accounts followed by the account specified by ID. If no ID is
817             provided, the one for the current authenticated account is used. Global GET
818             parameters are available for this method.
819              
820             Depending on the value of C<coerce_entities>, returns an array reference of
821             Mastodon::Entity::Account objects, or a plain array reference.
822              
823             =item B<statuses()>
824              
825             =item B<statuses($id)>
826              
827             =item B<statuses($params)>
828              
829             =item B<statuses($id, $params)>
830              
831             Get a list of statuses from the account specified by ID. If no ID is
832             provided, the one for the current authenticated account is used.
833              
834             In addition to the global GET parameters, this method accepts the following
835             parameters:
836              
837             =over 4
838              
839             =item B<only_media>
840              
841             =item B<exclude_replies>
842              
843             Both boolean.
844              
845             =back
846              
847             Depending on the value of C<coerce_entities>, returns an array reference of
848             Mastodon::Entity::Status objects, or a plain array reference.
849              
850             =item B<follow($id)>
851              
852             =item B<unfollow($id)>
853              
854             Follow or unfollow an account specified by ID. The ID argument is mandatory.
855              
856             Depending on the value of C<coerce_entities>, returns the new
857             Mastodon::Entity::Relationship object, or a plain hash reference.
858              
859             =item B<block($id)>
860              
861             =item B<unblock($id)>
862              
863             Block or unblock an account specified by ID. The ID argument is mandatory.
864              
865             Depending on the value of C<coerce_entities>, returns the new
866             Mastodon::Entity::Relationship object, or a plain hash reference.
867              
868             =item B<mute($id)>
869              
870             =item B<unmute($id)>
871              
872             Mute or unmute an account specified by ID. The ID argument is mandatory.
873              
874             Depending on the value of C<coerce_entities>, returns the new
875             Mastodon::Entity::Relationship object, or a plain hash reference.
876              
877             =item B<relationships(@ids)>
878              
879             =item B<relationships(@ids, $params)>
880              
881             Get the list of relationships of the current authenticated user with the
882             accounts specified by ID. At least one ID is required, but more can be passed
883             at once. Global GET parameters are available for this method, and can be passed
884             as an additional hash reference as a final argument.
885              
886             Depending on the value of C<coerce_entities>, returns an array reference of
887             Mastodon::Entity::Relationship objects, or a plain array reference.
888              
889             =item B<search_accounts($query)>
890              
891             =item B<search_accounts($query, $params)>
892              
893             Search for accounts. Takes a mandatory string argument to use as the search
894             query. If the search query is of the form C<username@domain>, the accounts
895             will be searched remotely.
896              
897             In addition to the global GET parameters, this method accepts the following
898             parameters:
899              
900             =over 4
901              
902             =item B<limit>
903              
904             The maximum number of matches. Defaults to 40.
905              
906             =back
907              
908             Depending on the value of C<coerce_entities>, returns an array reference of
909             Mastodon::Entity::Account objects, or a plain array reference.
910              
911             This method does not require authentication.
912              
913             =back
914              
915             =head2 Blocks
916              
917             =over 4
918              
919             =item B<blocks()>
920              
921             =item B<blocks($params)>
922              
923             Get the list of accounts blocked by the authenticated user. Global GET
924             parameters are available for this method.
925              
926             Depending on the value of C<coerce_entities>, returns an array reference of
927             Mastodon::Entity::Account objects, or a plain array reference.
928              
929             =back
930              
931             =head2 Favourites
932              
933             =over 4
934              
935             =item B<favourites()>
936              
937             =item B<favourites($params)>
938              
939             Get the list of statuses favourited by the authenticated user. Global GET
940             parameters are available for this method.
941              
942             Depending on the value of C<coerce_entities>, returns an array reference of
943             Mastodon::Entity::Status objects, or a plain array reference.
944              
945             =back
946              
947             =head2 Follow requests
948              
949             =over 4
950              
951             =item B<follow_requests()>
952              
953             =item B<follow_requests($params)>
954              
955             Get the list of accounts requesting to follow the the authenticated user.
956             Global GET parameters are available for this method.
957              
958             Depending on the value of C<coerce_entities>, returns an array reference of
959             Mastodon::Entity::Account objects, or a plain array reference.
960              
961             =item B<authorize_follow($id)>
962              
963             =item B<reject_follow($id)>
964              
965             Accept or reject the follow request by the account of the specified ID. The ID
966             argument is mandatory.
967              
968             Returns an empty object.
969              
970             =back
971              
972             =head2 Follows
973              
974             =over 4
975              
976             =item B<remote_follow($acct)>
977              
978             Follow a remote user by account string (ie. C<username@domain>). The argument
979             is mandatory.
980              
981             Depending on the value of C<coerce_entities>, returns an
982             Mastodon::Entity::Account object, or a plain hash reference with the local
983             representation of the specified account.
984              
985             =back
986              
987             =head2 Instances
988              
989             =over 4
990              
991             =item B<fetch_instance()>
992              
993             Fetches the latest information for the current instance the client is talking
994             to. When successful, this method updates the value of the C<instance>
995             attribute.
996              
997             Depending on the value of C<coerce_entities>, returns an
998             Mastodon::Entity::Instance object, or a plain hash reference.
999              
1000             This method does not require authentication.
1001              
1002             =back
1003              
1004             =head2 Media
1005              
1006             =over 4
1007              
1008             =item B<upload_media($file)>
1009              
1010             Upload a file as an attachment. Takes a single argument with the name of a
1011             local file to encode and upload. The argument is mandatory.
1012              
1013             Depending on the value of C<coerce_entities>, returns an
1014             Mastodon::Entity::Attachment object, or a plain hash reference.
1015              
1016             The returned object's ID can be passed to the B<post_status> to post it to a
1017             timeline.
1018              
1019             =back
1020              
1021             =head2 Mutes
1022              
1023             =over 4
1024              
1025             =item B<mutes()>
1026              
1027             =item B<mutes($params)>
1028              
1029             Get the list of accounts muted by the authenticated user. Global GET
1030             parameters are available for this method.
1031              
1032             Depending on the value of C<coerce_entities>, returns an array reference of
1033             Mastodon::Entity::Account objects, or a plain array reference.
1034              
1035             =back
1036              
1037             =head2 Notifications
1038              
1039             =over 4
1040              
1041             =item B<notifications()>
1042              
1043             =item B<notifications($params)>
1044              
1045             Get the list of notifications for the authenticated user. Global GET
1046             parameters are available for this method.
1047              
1048             Depending on the value of C<coerce_entities>, returns an array reference of
1049             Mastodon::Entity::Notification objects, or a plain array reference.
1050              
1051             =item B<get_notification($id)>
1052              
1053             Get a notification by ID. The argument is mandatory.
1054              
1055             Depending on the value of C<coerce_entities>, returns an
1056             Mastodon::Entity::Notification object, or a plain hash reference.
1057              
1058             =item B<clear_notifications()>
1059              
1060             Clears all notifications for the authenticated user.
1061              
1062             This method takes no arguments and returns an empty object.
1063              
1064             =back
1065              
1066             =head2 Reports
1067              
1068             =over 4
1069              
1070             =item B<reports()>
1071              
1072             =item B<reports($params)>
1073              
1074             Get a list of reports made by the authenticated user. Global GET
1075             parameters are available for this method.
1076              
1077             Depending on the value of C<coerce_entities>, returns an array reference of
1078             Mastodon::Entity::Report objects, or a plain array reference.
1079              
1080             =item B<report($params)>
1081              
1082             Report a user or status. Takes a mandatory hash with the following keys:
1083              
1084             =over 4
1085              
1086             =item B<account_id>
1087              
1088             The ID of a single account to report.
1089              
1090             =item B<status_ids>
1091              
1092             The ID of a single status to report, or an array reference of statuses to
1093             report.
1094              
1095             =item B<comment>
1096              
1097             An optional string.
1098              
1099             =back
1100              
1101             While the comment is always optional, either the B<account_id> or the list of
1102             B<status_ids> must be present.
1103              
1104             Depending on the value of C<coerce_entities>, returns the new
1105             Mastodon::Entity::Report object, or a plain hash reference.
1106              
1107             =back
1108              
1109             =head2 Search
1110              
1111             =over 4
1112              
1113             =item B<search($query)>
1114              
1115             =item B<search($query, $params)>
1116              
1117             Search for content. Takes a mandatory string argument to use as the search
1118             query. If the search query is a URL, Mastodon will attempt to fetch the
1119             provided account or status. Otherwise, it will do a local account and hashtag
1120             search.
1121              
1122             In addition to the global GET parameters, this method accepts the following
1123             parameters:
1124              
1125             =over 4
1126              
1127             =item B<resolve>
1128              
1129             Whether to resolve non-local accounts.
1130              
1131             =back
1132              
1133             =back
1134              
1135             =head2 Statuses
1136              
1137             =over 4
1138              
1139             =item B<get_status($id)>
1140              
1141             =item B<get_status($id, $params)>
1142              
1143             Fetches a status by ID. The ID argument is mandatory. Global GET parameters are available for this method as an additional hash reference.
1144              
1145             Depending on the value of C<coerce_entities>, it returns a
1146             Mastodon::Entity::Status object, or a plain hash reference.
1147              
1148             This method does not require authentication.
1149              
1150             =item B<get_status_context($id)>
1151              
1152             =item B<get_status_context($id, $params)>
1153              
1154             Fetches the context of a status by ID. The ID argument is mandatory. Global GET parameters are available for this method as an additional hash reference.
1155              
1156             Depending on the value of C<coerce_entities>, it returns a
1157             Mastodon::Entity::Context object, or a plain hash reference.
1158              
1159             This method does not require authentication.
1160              
1161             =item B<get_status_card($id)>
1162              
1163             =item B<get_status_card($id, $params)>
1164              
1165             Fetches a card associated to a status by ID. The ID argument is mandatory.
1166             Global GET parameters are available for this method as an additional hash
1167             reference.
1168              
1169             Depending on the value of C<coerce_entities>, it returns a
1170             Mastodon::Entity::Card object, or a plain hash reference.
1171              
1172             This method does not require authentication.
1173              
1174             =item B<get_status_reblogs($id)>
1175              
1176             =item B<get_status_reblogs($id, $params)>
1177              
1178             =item B<get_status_favourites($id)>
1179              
1180             =item B<get_status_favourites($id, $params)>
1181              
1182             Fetches a list of accounts who have reblogged or favourited a status by ID.
1183             The ID argument is mandatory. Global GET parameters are available for this
1184             method as an additional hash reference.
1185              
1186             Depending on the value of C<coerce_entities>, it returns an array reference of
1187             Mastodon::Entity::Account objects, or a plain array reference.
1188              
1189             This method does not require authentication.
1190              
1191             =item B<post_status($text)>
1192              
1193             =item B<post_status($text, $params)>
1194              
1195             Posts a new status. Takes a mandatory string as the content of the status
1196             (which can be the empty string), and an optional hash reference with the
1197             following additional parameters:
1198              
1199             =over 4
1200              
1201             =item B<status>
1202              
1203             The content of the status, as a string. Since this is already provided as the
1204             first argument of the method, this is not necessary. But if provided, this
1205             value will overwrite that of the first argument.
1206              
1207             =item B<in_reply_to_id>
1208              
1209             The optional ID of a status to reply to.
1210              
1211             =item B<media_ids>
1212              
1213             An array reference of up to four media IDs. These can be obtained as the result
1214             of a call to B<upload_media()>.
1215              
1216             =item B<sensitive>
1217              
1218             Boolean, to mark status content as NSFW.
1219              
1220             =item B<spoiler_text>
1221              
1222             A string, to be shown as a warning before the actual content.
1223              
1224             =item B<visibility>
1225              
1226             A string; one of C<direct>, C<private>, C<unlisted>, or C<public>.
1227              
1228             =back
1229              
1230             Depending on the value of C<coerce_entities>, it returns the new
1231             Mastodon::Entity::Status object, or a plain hash reference.
1232              
1233             =item B<delete_status($id)>
1234              
1235             Delete a status by ID. The ID is mandatory. Returns an empty object.
1236              
1237             =item B<reblog($id)>
1238              
1239             =item B<unreblog($id)>
1240              
1241             =item B<favourite($id)>
1242              
1243             =item B<unfavourite($id)>
1244              
1245             Reblog or favourite a status by ID, or revert this action. The ID argument is
1246             mandatory.
1247              
1248             Depending on the value of C<coerce_entities>, it returns the specified
1249             Mastodon::Entity::Status object, or a plain hash reference.
1250              
1251             =back
1252              
1253             =head2 Timelines
1254              
1255             =over 4
1256              
1257             =item B<timeline($query)>
1258              
1259             =item B<timeline($query, $params)>
1260              
1261             Retrieves a timeline. The first argument defines either the name of a timeline
1262             (which can be one of C<home> or C<public>), or a hashtag (if it begins with the
1263             C<#> character). This argument is mandatory.
1264              
1265             In addition to the global GET parameters, this method accepts the following
1266             parameters:
1267              
1268             Accessing the public and tag timelines does not require authentication.
1269              
1270             =over 4
1271              
1272             =item B<local>
1273              
1274             Boolean. If true, limits results only to those originating from the current
1275             instance. Only applies to public and tag timelines.
1276              
1277             =back
1278              
1279             Depending on the value of C<coerce_entities>, it returns an array of
1280             Mastodon::Entity::Status objects, or a plain array reference. The more recent
1281             statuses come first.
1282              
1283             =back
1284              
1285             =head1 STREAMING RESULTS
1286              
1287             Alternatively, it is possible to use the streaming API to get a constant stream
1288             of updates. To do this, there is the B<stream()> method.
1289              
1290             =over 4
1291              
1292             =item B<stream($query)>
1293              
1294             Creates a Mastodon::Listener object which will fetch a stream for the
1295             specified query. Possible values for the query are either C<user>, for events
1296             that are relevant to the authorized user; C<public>, for all public statuses;
1297             or a tag (if it begins with the C<#> character), for all public statuses for
1298             the particular tag.
1299              
1300             For more details on how to use this object, see the documentation for
1301             L<Mastodon::Listener>.
1302              
1303             Accessing streaming public timeline does not require authentication.
1304              
1305             =back
1306              
1307             =head1 REQUEST METHODS
1308              
1309             Mastodon::Client uses four lower-level request methods to contact the API
1310             with GET, POST, PATCH, and DELETE requests. These are left available in case
1311             one of the higher-level convenience methods are unsuitable or undesirable, but
1312             you use them at your own risk.
1313              
1314             They all take a URL as their first parameter, which can be a string with the
1315             API endpoint to contact, or a L<URI> object, which will be used as-is.
1316              
1317             If passed as a string, the methods expect one that contains only the variable
1318             parts of the endpoint (ie. not including the C<HOST/api/v1> part). The
1319             remaining parts will be filled-in appropriately internally.
1320              
1321             =over 4
1322              
1323             =item B<delete($url)>
1324              
1325             =item B<get($url)>
1326              
1327             =item B<get($url, $params)>
1328              
1329             Query parameters can be passed as part of the L<URI> object, but it is not
1330             recommended you do so, since Mastodon has expectations for array parameters
1331             that do not meet those of eg. L<URI::QueryParam>. It will be easier and safer
1332             if any additional parameters are passed as a hash reference, which will be
1333             added to the URL before the request is sent.
1334              
1335             =item B<post($url)>
1336              
1337             =item B<post($url, $data)>
1338              
1339             =item B<patch($url)>
1340              
1341             =item B<patch($url, $data)>
1342              
1343             the C<post> and C<patch> methods work similarly to C<get> and C<delete>, but
1344             the optional hash reference is sent in as form data, instead of processed as
1345             query parameters. The Mastodon API does not use query parameters on POST or
1346             PATCH endpoints.
1347              
1348             =back
1349              
1350             =head1 CONTRIBUTIONS AND BUG REPORTS
1351              
1352             Contributions of any kind are most welcome!
1353              
1354             The main repository for this distribution is on
1355             L<GitLab|https://gitlab.com/jjatria/Mastodon-Client>, which is where patches
1356             and bug reports are mainly tracked. The repository is also mirrored on
1357             L<Github|https://github.com/jjatria/Mastodon-Client>, in case that platform
1358             makes it easier to post contributions.
1359              
1360             If none of the above is acceptable, bug reports can also be sent through the
1361             CPAN RT system, or by mail directly to the developers at the address below,
1362             although these will not be as closely tracked.
1363              
1364             =head1 AUTHOR
1365              
1366             =over 4
1367              
1368             =item *
1369              
1370             José Joaquín Atria <jjatria@cpan.org>
1371              
1372             =back
1373              
1374             =head1 CONTRIBUTORS
1375              
1376             =over 4
1377              
1378             =item *
1379              
1380             Lance Wicks <lancew@cpan.org>
1381              
1382             =back
1383              
1384             =head1 COPYRIGHT AND LICENSE
1385              
1386             This software is copyright (c) 2017 by José Joaquín Atria.
1387              
1388             This is free software; you can redistribute it and/or modify it under
1389             the same terms as the Perl 5 programming language system itself.
1390              
1391             =cut