Mastodon-Client-0.017/0000775000175000017500000000000013646430665014372 5ustar jjatriajjatriaMastodon-Client-0.017/cpanfile0000644000175000017500000000217713646430665016103 0ustar jjatriajjatriarequires 'Class::Load', '0.25'; requires 'DateTime', '1.51'; requires 'DateTime::Format::Strptime', '1.73'; requires 'Future', '0.35'; requires 'HTTP::Request::Common', '6.18'; requires 'HTTP::Thin', '0.006'; requires 'IO::Async', '0.66'; requires 'Image::Info', '1.41'; requires 'JSON::MaybeXS', '1.003009'; requires 'List::Util', '1.47'; requires 'Log::Any', '1.049'; requires 'Moo', '2.003002'; requires 'Net::Async::HTTP', '0.41'; requires 'Role::EventEmitter', '0.002'; requires 'Try::Tiny', '0.28'; requires 'Type::Params', '1.000006'; requires 'Types::Common::String', '1.000006'; requires 'Types::Path::Tiny', '0.005'; requires 'Types::Standard', '1.003003'; requires 'URI', '1.71'; on test => sub { requires 'Plack', '1.0043'; requires 'Test::Pod', '1.51'; requires 'Test::TCP', '2.17'; requires 'Test2::V0', '0.000126'; }; Mastodon-Client-0.017/t/0000775000175000017500000000000013646430665014635 5ustar jjatriajjatriaMastodon-Client-0.017/t/01_entities.t0000644000175000017500000000306313646430665017146 0ustar jjatriajjatriause Test2::V0; # Test data uses only minimum arguments for entity constructors # These were set as required only to aid in automatic detection, # which makes blind coercion possible # my $samples = { Account => { acct => 'username', avatar => 'https://example.tld/image.png', }, Application => { website => 'https://website.xyz', }, Attachment => { preview_url => 'https://example.tld/image.png', }, Card => { description => 'A card', url => 'https://website.xyz', }, Context => { ancestors => [], descendants => [], }, Error => { error => 'An error', }, Instance => { uri => 'botsin.space', }, Mention => { acct => 'username@instance.xyz', username => 'tester', }, Relationship => { muting => 0, }, Report => { action_taken => 0, }, Results => { hashtags => [ 'tag '], }, Tag => { url => 'https://website.xyz', } }; my ($account, $status); test($_) foreach qw( Account Instance Application Attachment Card Context Mention Relationship Report Results Error Tag ); $samples->{Status} = { account => $account, visibility => 'public', favourites_count => 123, }; test('Status'); $samples->{Notification} = { status => $status, }; test('Notification'); sub test { my $name = shift; eval "use Mastodon::Entity::$name"; ok my $e = "Mastodon::Entity::$name"->new($samples->{$name}), "$name constructor succeeds"; $account = $e if $name eq 'Account'; $status = $e if $name eq 'Status'; isa_ok $e, "Mastodon::Entity::$name"; } done_testing(); Mastodon-Client-0.017/t/02_entity_coercion.t0000644000175000017500000000325713646430665020525 0ustar jjatriajjatriause Test2::V0; use Mastodon::Types qw( to_Entity to_Instance ); # Test data uses only minimum arguments for entity constructors # These were set as required only to aid in automatic detection, # which makes blind coercion possible # my $samples = { Account => { acct => 'username', avatar => 'https://example.tld/image.png', }, Application => { website => 'https://website.xyz', }, Attachment => { preview_url => 'https://example.tld/image.png', }, Card => { description => 'A card', url => 'https://website.xyz', }, Context => { ancestors => [], descendants => [], }, Error => { error => 'An error', }, Instance => { uri => 'mastodon.social', }, Mention => { acct => 'username@instance.xyz', username => 'tester', }, Relationship => { muting => 0, }, Report => { action_taken => 0, }, Results => { hashtags => [ 'tag '], }, Tag => { url => 'https://website.xyz', } }; $samples->{Status} = { account => $samples->{Account}, visibility => 'public', favourites_count => 123, application => $samples->{Application}, media_attachments => [ $samples->{Attachment}, $samples->{Attachment}, ], mentions => [ $samples->{Mention}, $samples->{Mention}, ], }; $samples->{Notification} = { status => $samples->{Status}, }; foreach my $name (keys %{$samples}) { ok my $e = to_Entity($samples->{$name}), 'Coercion succeeds'; isa_ok $e, "Mastodon::Entity::$name"; } ok my $e = to_Instance('mastodon.social'), 'Coercion of instance from string succeeds'; isa_ok $e, 'Mastodon::Entity::Instance'; like $e->uri, qr/https/, 'Instance defaults to HTTPS'; done_testing(); Mastodon-Client-0.017/t/lib/0000775000175000017500000000000013646430665015403 5ustar jjatriajjatriaMastodon-Client-0.017/t/lib/Plack/0000775000175000017500000000000013646430665016435 5ustar jjatriajjatriaMastodon-Client-0.017/t/lib/Plack/App/0000775000175000017500000000000013646430665017155 5ustar jjatriajjatriaMastodon-Client-0.017/t/lib/Plack/App/Mastodon/0000775000175000017500000000000013646430665020741 5ustar jjatriajjatriaMastodon-Client-0.017/t/lib/Plack/App/Mastodon/MockServer/0000775000175000017500000000000013646430665023021 5ustar jjatriajjatriaMastodon-Client-0.017/t/lib/Plack/App/Mastodon/MockServer/v1.pm0000644000175000017500000001711713646430665023712 0ustar jjatriajjatriapackage Plack::App::Mastodon::MockServer::v1; use strict; use warnings; use parent qw( Plack::Component ); use Plack::Util; use JSON::MaybeXS qw( encode_json ); my $samples = { Account => { a => { acct => 'a', avatar => 'https://perl.test/path/to/image.png', avatar_static => 'https://perl.test/path/to/image.png', created_at => '2017-04-12T11:24:56.416Z', display_name => 'Ada', followers_count => 2, following_count => 1, header => '/headers/original/missing.png', header_static => '/headers/original/missing.png', id => 1, locked => 0, note => '', statuses_count => 2, url => 'https://perl.test/@a', username => 'a' }, b => { acct => 'b', avatar => 'https://perl.test/path/to/image.png', avatar_static => 'https://perl.test/path/to/image.png', created_at => '2015-04-12T11:24:56.416Z', display_name => 'Bob', followers_count => 0, following_count => 1, header => '/headers/original/missing.png', header_static => '/headers/original/missing.png', id => 2, locked => 0, note => '', statuses_count => 0, url => 'https://perl.test/@b', username => 'b' }, c => { acct => 'c', avatar => 'https://perl.test/path/to/image.png', avatar_static => 'https://perl.test/path/to/image.png', created_at => '2016-04-12T11:24:56.416Z', display_name => 'Cid', followers_count => 1, following_count => 1, header => '/headers/original/missing.png', header_static => '/headers/original/missing.png', id => 2, locked => 0, note => '', statuses_count => 0, url => 'https://perl.test/@c', username => 'c' }, }, Relationship => { a => { b => { id => 2, blocking => 0, followed_by => 1, following => 0, muting => 0, requested => 0 }, c => { id => 3, blocking => 0, followed_by => 1, following => 1, muting => 0, requested => 0 }, }, b => { a => { id => 1, blocking => 0, followed_by => 0, following => 1, muting => 0, requested => 0 }, c => { id => 3, blocking => 0, followed_by => 0, following => 0, muting => 0, requested => 0 }, }, c => { a => { id => 1, blocking => 0, followed_by => 1, following => 1, muting => 0, requested => 0 }, b => { id => 2, blocking => 0, followed_by => 0, following => 0, muting => 0, requested => 0 }, }, }, Instance => { description => 'This is not a real instance', email => 'admin@perl.test', title => 'perl.test', uri => 'https://perl.test' }, Status => { a => [ { id => 100, uri => 'tag:perl.test,2017-04-17:objectId=100:objectType=Status', url => 'https://perl.test/@a/100', reblog => undef, account => undef, # {Account}{a} content => '

A #tag

', mentions => [], reblogged => undef, sensitive => 0, visibility => 'public', created_at => '2017-04-17T17:32:29.772Z', favourited => undef, application => undef, spoiler_text => '', reblogs_count => 0, in_reply_to_id => undef, favourites_count => 0, media_attachments => [], in_reply_to_account_id => undef, tags => [{ url => 'https://perl.test/tags/tag', name => 'tag' }], }, { id => 101, uri => 'tag:perl.test,2017-04-17:objectId=101:objectType=Status', url => 'https://perl.test/@a/101', tags => [], reblog => undef, content => '

Hello, @c!

', account => undef, # {Account}{a} reblogged => undef, sensitive => 0, visibility => 'public', created_at => '2017-04-17T18:23:59.781Z', favourited => undef, application => undef, spoiler_text => '', reblogs_count => 0, in_reply_to_id => undef, favourites_count => 0, media_attachments => [], in_reply_to_account_id => undef, mentions => [{ username => 'c', acct => 'c', id => 3, url => 'https://perl.test/@c' }], }, ], }, }; $samples->{Status}{a}[0]{account} = $samples->{Account}{a}; $samples->{Status}{a}[1]{account} = $samples->{Account}{a}; my $routes = { GET => { 'instance' => [ 200, [ 'Content-Type' => 'application/json' ], [ encode_json $samples->{Instance} ], ], 'accounts/2' => [ 200, [ 'Content-Type' => 'application/json' ], [ encode_json $samples->{Account}{b} ], ], 'accounts/verify_credentials' => [ 200, [ 'Content-Type' => 'application/json' ], [ encode_json $samples->{Account}{a} ], ], 'accounts/1/followers' => [ 200, [ 'Content-Type' => 'application/json' ], [ encode_json [ $samples->{Account}{b}, $samples->{Account}{c} ] ], ], 'accounts/2/followers' => [ 200, [ 'Content-Type' => 'application/json' ], [ encode_json [] ], ], 'accounts/1/following' => [ 200, [ 'Content-Type' => 'application/json' ], [ encode_json [ $samples->{Account}{c} ] ], ], 'accounts/2/following' => [ 200, [ 'Content-Type' => 'application/json' ], [ encode_json [ $samples->{Account}{a} ] ], ], 'accounts/1/statuses' => [ 200, [ 'Content-Type' => 'application/json' ], [ encode_json $samples->{Status}{a} ], ], 'accounts/2/statuses' => [ 200, [ 'Content-Type' => 'application/json' ], [ encode_json [] ], ], 'accounts/relationships?id[]=2' => [ 200, [ 'Content-Type' => 'application/json' ], [ encode_json [ $samples->{Relationship}{a}{b} ] ], ], 'accounts/relationships?id[]=2&id[]=3' => [ 200, [ 'Content-Type' => 'application/json' ], [ encode_json [ $samples->{Relationship}{a}{b}, $samples->{Relationship}{a}{c} ] ], ], 'accounts/search?q=a' => [ 200, [ 'Content-Type' => 'application/json' ], [ encode_json [ $samples->{Account}{a} ] ], ], }, }; sub call { my ($self, $env) = @_; my $uri = $env->{REQUEST_URI}; my $endpoint = $uri; $endpoint =~ s%^/api/v1/%%; $endpoint =~ s|%5B%5D|[]|g; my $return = $routes->{$env->{REQUEST_METHOD}}{$endpoint} // [ 404, [ 'Content-Type' => 'text/plain' ], [ '' ], ]; return $return; } 1; Mastodon-Client-0.017/t/05_authorize.t0000644000175000017500000000065713646430665017346 0ustar jjatriajjatriause Test2::V0; use Mastodon::Client; my $client = Mastodon::Client->new(); like dies { $client->authorize; }, qr/cannot authorize client without/i, 'Cannot authorize a client without ID and secret'; $client = Mastodon::Client->new( client_id => 'id', client_secret => 'secret', access_token => 'token', ); # ok warning { $client->authorize; }, 'Warns if access_token already exists'; done_testing(); Mastodon-Client-0.017/t/10_tcp.t0000644000175000017500000001575013646430665016116 0ustar jjatriajjatriause Test2::V0; use Test::TCP; use Mastodon::Client; use Try::Tiny; use lib 't/lib'; use Plack::Runner; use Plack::App::Mastodon::MockServer::v1; # use Log::Any::Adapter; # Log::Any::Adapter->set( 'Stderr', # category => 'Mastodon', # log_level => 'debug', # ); my $host = '127.0.0.1'; my $server = try { Test::TCP->new( host => $host, max_wait => 3, # seconds code => sub { my $port = shift; my $runner = Plack::Runner->new; $runner->parse_options( '--host' => $host, '--port' => $port, '--env' => 'test', '--server' => 'HTTP::Server::PSGI' ); $runner->run(Plack::App::Mastodon::MockServer::v1->new->to_app); } ); } catch { plan skip_all => $_; }; my $url = "http://$host:" . $server->port; my $client = Mastodon::Client->new( instance => $url, coerce_entities => 1, ); ok my $instance = $client->fetch_instance, 'fetch_instance succeeds'; isa_ok $instance, 'Mastodon::Entity::Instance'; isa_ok $client->instance, 'Mastodon::Entity::Instance'; # Override internal base URL $client->instance->{uri} = $url; # get_account { my $response; ok $response = $client->get_account(); isa_ok $response, 'Mastodon::Entity::Account'; like $response->username, qr/a/i, 'Fetches self'; isa_ok $client->latest_response, 'HTTP::Response'; like $client->account, { acct => $response->acct }, 'Cache self account'; ok $response = $client->get_account(2); isa_ok $response, 'Mastodon::Entity::Account'; like $response->username, qr/b/i, 'Fetches other'; ok $response = $client->get_account({}); isa_ok $response, 'Mastodon::Entity::Account'; like $response->username, qr/a/i, 'Fetches self'; ok $response = $client->get_account(2, {}); isa_ok $response, 'Mastodon::Entity::Account'; like $response->username, qr/b/i, 'Fetches other'; } # followers { use Mastodon::Types qw( is_Account ); use List::Util qw( all ); my $response; ok $response = $client->followers(), 'followers()'; ref_ok $response, 'ARRAY'; is scalar(@{$response}), 2, 'a has two followers'; ok( (all { is_Account($_) } @{$response}), 'List of Account'); like $response->[0]->username, qr/b/i, 'Followed by b'; like $response->[1]->username, qr/c/i, 'Followed by c'; ok $response = $client->followers(2), 'followers(Int)'; ref_ok $response, 'ARRAY'; is scalar(@{$response}), 0, 'b has no followers'; ok $response = $client->followers({}), 'followers(HashRef)'; ref_ok $response, 'ARRAY'; is scalar(@{$response}), 2, 'a has two followers'; ok( (all { is_Account($_) } @{$response}), 'List of Account'); like $response->[0]->username, qr/b/i, 'a followed by b'; like $response->[1]->username, qr/c/i, 'a followed by c'; ok $response = $client->followers(2, {}), 'followers(Int, HashRef)'; ref_ok $response, 'ARRAY'; is scalar(@{$response}), 0, 'b has no followers'; } # following { use Mastodon::Types qw( is_Account ); use List::Util qw( all ); my $response; ok $response = $client->following(), 'following()'; ref_ok $response, 'ARRAY'; ok( (all { is_Account($_) } @{$response}), 'List of Account'); like $response->[0]->username, qr/c/i, 'a follows c'; ok $response = $client->following(2), 'following(Int)'; ref_ok $response, 'ARRAY'; ok( (all { is_Account($_) } @{$response}), 'List of Account'); like $response->[0]->username, qr/a/i, 'b follows a'; ok $response = $client->following({}), 'following(HashRef)'; ref_ok $response, 'ARRAY'; ok( (all { is_Account($_) } @{$response}), 'List of Account'); like $response->[0]->username, qr/c/i, 'a follows c'; ok $response = $client->following(2, {}), 'following(Int, HashRef)'; ref_ok $response, 'ARRAY'; ok( (all { is_Account($_) } @{$response}), 'List of Account'); like $response->[0]->username, qr/a/i, 'b follows a'; } # statuses { use Mastodon::Types qw( is_Status ); use List::Util qw( all ); my $response; ok $response = $client->statuses(), 'statuses()'; ref_ok $response, 'ARRAY'; ok( (all { is_Status($_) } @{$response}), 'List of Status'); like $response->[0]->content, qr/#/i, 'Recent status has tag'; like $response->[1]->content, qr/@/i, 'Recent status has mention'; ok $response = $client->statuses(2), 'statuses(Int)'; ref_ok $response, 'ARRAY'; is scalar(@{$response}), 0, 'b has no statuses'; ok $response = $client->statuses({}), 'statuses(HashRef)'; ref_ok $response, 'ARRAY'; ok( (all { is_Status($_) } @{$response}), 'List of Status'); like $response->[0]->content, qr/#/i, 'Recent status has tag'; like $response->[1]->content, qr/@/i, 'Recent status has mention'; ok $response = $client->statuses(2, {}), 'statuses(Int, HashRef)'; ref_ok $response, 'ARRAY'; is scalar(@{$response}), 0, 'b has no statuses'; } # relationships { use Mastodon::Types qw( is_Relationship ); use List::Util qw( all ); my $response; like dies { $client->relationships() }, qr/at least one id number needed/i, 'relationships() dies'; like dies { $client->relationships({}) }, qr/at least one id number needed/i, 'relationships(HashRef) dies'; ok $response = $client->relationships(2), 'relationships(Int)'; ref_ok $response, 'ARRAY'; ok( (all { is_Relationship($_) } @{$response}), 'List of Relationship'); is scalar(@{$response}), 1, 'Requested one relationship'; ok !$response->[0]->following, 'Not followed by b'; ok $response = $client->relationships(2, 3), 'relationships(Int, Int)'; ref_ok $response, 'ARRAY'; ok( (all { is_Relationship($_) } @{$response}), 'List of Relationship'); is scalar(@{$response}), 2, 'Requested two relationships'; ok !$response->[0]->following, 'Not followed by b'; ok $response->[1]->following, 'Followed by c'; ok $response = $client->relationships(2, {}), 'relationships(Int, HashRef)'; ref_ok $response, 'ARRAY'; ok( (all { is_Relationship($_) } @{$response}), 'List of Relationship'); is scalar(@{$response}), 1, 'Requested one relationship'; ok !$response->[0]->following, 'Not followed by b'; } # search_accounts { use Mastodon::Types qw( is_Account ); use List::Util qw( all ); my $response; like dies { $client->search_accounts() }, qr/wrong number of parameters/i, 'search_accounts() dies'; like dies { $client->search_accounts({}) }, qr/did not pass type constraint/i, 'search_accounts(HashRef) dies'; like dies { $client->search_accounts('a', 'b') }, qr/did not pass type constraint/i, 'search_accounts(Str, Str) dies'; ok $response = $client->search_accounts('a'), 'search_accounts(Str)'; ref_ok $response, 'ARRAY'; ok( (all { is_Account($_) } @{$response}), 'List of Account'); is scalar(@{$response}), 1, 'Requested one relationship'; like $response->[0]->username, qr/a/, 'Found self'; ok $response = $client->search_accounts('a', {}), 'search_accounts(Str, HashRef)'; ref_ok $response, 'ARRAY'; ok( (all { is_Account($_) } @{$response}), 'List of Account'); is scalar(@{$response}), 1, 'Requested one relationship'; like $response->[0]->username, qr/a/, 'Found self'; } undef $server; done_testing(); Mastodon-Client-0.017/t/data_leak.t0000644000175000017500000000320113646430665016721 0ustar jjatriajjatriause Test2::V0; use Test2::Tools::Spec; use Mastodon::Client; # Incorrect use of Type::Params coercions for setting defaults caused # some methods to leak data across instances. This regression test is # here to make sure that is never again the case. describe 'Data leaks' => sub { tests 'authorization_url' => sub { my $alpha = Mastodon::Client->new( instance => 'alpha.botsin.space', client_id => 'alpha_id', client_secret => 'alpha_secret', ); my $beta = Mastodon::Client->new( instance => 'beta.botsin.space', client_id => 'beta_id', client_secret => 'beta_secret', ); is $alpha->authorization_url->host, $alpha->instance->uri->host; is $beta->authorization_url->host, $beta->instance->uri->host; }; tests 'authorization_url' => sub { my $data; my $alpha = Mastodon::Client->new( name => 'alpha', instance => 'alpha.botsin.space', ); my $beta = Mastodon::Client->new( name => 'beta', instance => 'beta.botsin.space', ); my $mock = mock 'Mastodon::Client', override => [ post => sub { ( undef, undef, $data ) = @_; return { client_id => 'id', client_secret => 'secret' }; }, ]; $alpha->register; is $data, { client_name => $alpha->name, redirect_uris => $alpha->redirect_uri, scopes => join( ' ', sort @{ $alpha->scopes } ), }; $beta->register; is $data, { client_name => $beta->name, redirect_uris => $beta->redirect_uri, scopes => join( ' ', sort @{ $beta->scopes } ), }; }; }; done_testing(); Mastodon-Client-0.017/t/Dockerfile0000644000175000017500000000031013646430665016617 0ustar jjatriajjatriaFROM perl:5.30-slim RUN apt-get update && apt-get install --yes \ build-essential openssl zlib1g-dev libssl-dev COPY cpanfile cpanfile.snapshot ./ RUN cpanm -nq --installdeps . CMD prove -lvr Mastodon-Client-0.017/t/00_basic.t0000644000175000017500000000123713646430665016403 0ustar jjatriajjatriause Test2::V0; use Mastodon::Client; my $client = Mastodon::Client->new( instance => 'mastodon.cloud', name => 'JJ', client_id => 'id', client_secret => 'secret', access_token => 'token', scopes => [qw( read write )], ); isa_ok( $client, 'Mastodon::Client' ); is $client->name, 'JJ', 'Correct name'; is $client->scopes, [qw( read write )], 'Correct scopes'; $client = Mastodon::Client->new; is $client->name, undef, 'Name has no default'; is $client->scopes, ['read'], 'Scopes default to read only'; like $client->instance->uri, qr/mastodon\.social/, 'Instance defaults to mastodon.social'; done_testing(); Mastodon-Client-0.017/t/authorize.t0000644000175000017500000001132513646430665017034 0ustar jjatriajjatriause Test2::V0; use Test2::Tools::Spec; use Log::Any::Test; use Log::Any qw( $log ); use Mastodon::Client; describe 'authorize' => sub { before_each 'Clear log' => sub { $log->clear; }; describe 'Needs a client_id and client_secret' => sub { my %params; case 'No client_id' => sub { %params = ( client_secret => 'secret' ); }; case 'No client_secret' => sub { %params = ( client_id => 'id' ); }; case 'Neither' => sub { %params = (); }; it 'Dies' => sub { my $client = Mastodon::Client->new( %params, instance => 'botsin.space', ); ok dies { $client->authorize }, 'It dies'; is $log->msgs, [ { level => 'critical', message => match(qr/without client_id and client_secret/), category => 'Mastodon', }, ], 'Logged dying message'; }; }; describe 'Warnings' => sub { my ( %client_params, $post_response, $warning ); after_each 'Reset' => sub { %client_params = (); undef $post_response; }; case 'Already authorized' => sub { %client_params = ( access_token => 'foo' ); $warning = match qr/already authorized/; }; case 'It receives an error' => sub { $post_response = { error => 1, error_description => 'this is a test' }; $warning = 'this is a test'; }; it 'Warns' => sub { my $mock = mock 'Mastodon::Client', override => [ post => sub { return $post_response }, ]; my $client = Mastodon::Client->new( instance => 'botsin.space', client_id => 'id', client_secret => 'secret', %client_params, ); is $client->authorize, $client, 'Method returns $self'; is $log->msgs, [ { message => $warning, level => 'warning', category => 'Mastodon', }, ], 'Raises expected warning'; }; }; it q{Dies if granted scopes don't match} => sub { my $client = Mastodon::Client->new( instance => 'botsin.space', client_id => 'id', client_secret => 'secret', ); my $mock = mock 'Mastodon::Client', override => [ post => sub { return { scope => $_[2]->{scope} . ' extra' }; }, ]; ok dies { $client->authorize }, 'It dies'; is $log->msgs, [ { message => match(qr/scopes do not match/), level => 'critical', category => 'Mastodon', }, ]; }; it 'Sets access token and authorization time' => sub { my $client = Mastodon::Client->new( instance => 'botsin.space', client_id => 'id', client_secret => 'secret', ); my $mock = mock 'Mastodon::Client', override => [ post => sub { return { scope => $_[2]->{scope}, created_at => '2018-12-16T12:20:40.123Z', access_token => 'a token', }; }, ]; is $client->authorize, object { call access_token => 'a token'; call authorized => '2018-12-16T12:20:40'; etc; }, 'Method sets attributes'; is $log->msgs, [], 'Nothing logged'; }; describe 'Accepts different credentials' => sub { my ( %check, %params, $data ); before_each 'Clear data' => sub { undef $data }; case 'No params' => sub { note 'This is legacy behaviour, we should probably die instead'; %params = (); %check = ( username => '', password => '', grant_type => 'password' ); }; case 'Access code' => sub { %params = ( access_code => 'access_code' ); %check = ( grant_type => 'authorization_code', code => 'access_code' ); }; case 'Username and password' => sub { %check = %params = ( username => 'username', password => 'password' ); $check{grant_type} = 'password'; }; it 'Works' => sub { my $mock = mock 'Mastodon::Client', override => [ post => sub { ( undef, undef, $data ) = @_; return { created_at => 1, scope => $data->{scope} // '', access_token => 'mocked_token', }; }, ]; my $client = Mastodon::Client->new( instance => 'botsin.space', client_id => 'id', client_secret => 'secret', ); $client->authorize( %params ); is $data, hash { field $_ => $check{$_} for keys %check; field client_id => $client->client_id; field client_secret => $client->client_secret; field redirect_uri => T; field scope => T; end; }, 'Posted correct data payload'; is $log->msgs, [], 'Nothing logged'; }; }; }; done_testing(); Mastodon-Client-0.017/t/examples/0000775000175000017500000000000013646430665016453 5ustar jjatriajjatriaMastodon-Client-0.017/t/examples/public_stream.pl0000644000175000017500000000405613646430665021644 0ustar jjatriajjatria#!/usr/bin/env perl binmode STDOUT, ':utf8'; use warnings; use strict; use diagnostics; use Term::ANSIColor qw(:constants); use Mastodon::Client; use Config::Tiny; # use Log::Any::Adapter; # my $log = Log::Any::Adapter->set( 'Stderr', # category => 'Mastodon', # log_level => 'debug', # ); unless (scalar @ARGV) { print " Missing arguments USAGE: $0 should be an INI file with a valid 'client_secret', 'client_id', and 'access_token', and an 'instance' key with the URL to a Mastodon instance.\n"; exit(1); } my ($configfile, $stream) = @ARGV; my $config = (defined $configfile) ? Config::Tiny->read( $configfile )->{_} : {}; my $app = Mastodon::Client->new({ %{$config}, coerce_entities => 1, }); my $listener = $app->stream( $stream // 'public' ); # Counter for statuses my $n = 0; $listener->on( update => sub { my ($listener, $data) = @_; # Only print 10 first statuses $listener->stop if ++$n >= 10; use HTML::FormatText::WithLinks; my $f = HTML::FormatText::WithLinks->new; local $Term::ANSIColor::AUTORESET = 1; my $name = $data->account->display_name; $name = $data->account->acct if !defined $name or $name eq q{}; if ($data->reblog) { print BLUE sprintf("%s reblogged a status:\n", $name); $listener->emit( update => $data->reblog ) } else { print BOLD BLUE sprintf("%s:\n", $name); print $f->parse($data->content); } }); $listener->on( delete => sub { my ($listener, $data) = @_; print BOLD RED sprintf("Status %s was deleted!\n\n", $data); }); $listener->on( heartbeat => sub { my ($listener) = @_; print BOLD RED " -- THUMP -- \n\n"; }); $listener->on( notification => sub { my ($listener, $data) = @_; use Lingua::EN::Inflexion; my $line = $data->account->acct . ' ' . verb($data->type)->past . ' you'; if ($data->type =~ /(blog|fav)/) { $line .= 'r status'; } print BOLD GREEN "$line!\n"; }); $listener->on( error => sub { my ($listener, $fatal, $message, $data) = @_; $fatal //= 0; print BOLD YELLOW "ERROR: $message\n\n"; }); $listener->start; Mastodon-Client-0.017/t/examples/getter.pl0000644000175000017500000000131613646430665020301 0ustar jjatriajjatria#!/usr/bin/env perl binmode STDOUT, ':utf8'; use warnings; use strict; use diagnostics; use Mastodon::Client; use Config::Tiny; use Log::Any::Adapter; my $log = Log::Any::Adapter->set( 'Stderr', category => 'Mastodon', log_level => 'warn', ); unless (scalar @ARGV) { print " Missing arguments USAGE: $0 should be an INI file with a valid 'client_secret', 'client_id', and 'access_token', and an 'instance' key with the URL to a Mastodon instance.\n"; exit(1); } my $config = (scalar @ARGV) ? Config::Tiny->read( $ARGV[0] )->{_} : {}; my $client = Mastodon::Client->new({ %{$config}, coerce_entities => 0, }); use Data::Dumper; print Dumper($client->get( $ARGV[1] )), "\n"; Mastodon-Client-0.017/t/author-pod-syntax.t0000644000175000017500000000045413646430665020431 0ustar jjatriajjatria#!perl BEGIN { unless ($ENV{AUTHOR_TESTING}) { print qq{1..0 # SKIP these tests are for testing by the author\n}; exit } } # This file was automatically generated by Dist::Zilla::Plugin::PodSyntaxTests. use strict; use warnings; use Test::More; use Test::Pod 1.41; all_pod_files_ok(); Mastodon-Client-0.017/META.yml0000644000175000017500000000620013646430665015637 0ustar jjatriajjatria--- abstract: 'Talk to a Mastodon server' author: - 'José Joaquín Atria ' build_requires: Plack: '1.0043' Test2::V0: '0.000126' Test::Pod: '1.51' Test::TCP: '2.17' perl: '5.010' configure_requires: ExtUtils::MakeMaker: '0' perl: v5.10.0 dynamic_config: 0 generated_by: 'Dist::Zilla version 6.012, CPAN::Meta::Converter version 2.150010' license: perl meta-spec: url: http://module-build.sourceforge.net/META-spec-v1.4.html version: '1.4' name: Mastodon-Client provides: Mastodon::Client: file: lib/Mastodon/Client.pm version: '0.017' Mastodon::Entity::Account: file: lib/Mastodon/Entity/Account.pm version: '0.017' Mastodon::Entity::Application: file: lib/Mastodon/Entity/Application.pm version: '0.017' Mastodon::Entity::Attachment: file: lib/Mastodon/Entity/Attachment.pm version: '0.017' Mastodon::Entity::Card: file: lib/Mastodon/Entity/Card.pm version: '0.017' Mastodon::Entity::Context: file: lib/Mastodon/Entity/Context.pm version: '0.017' Mastodon::Entity::Error: file: lib/Mastodon/Entity/Error.pm version: '0.017' Mastodon::Entity::Instance: file: lib/Mastodon/Entity/Instance.pm version: '0.017' Mastodon::Entity::Mention: file: lib/Mastodon/Entity/Mention.pm version: '0.017' Mastodon::Entity::Notification: file: lib/Mastodon/Entity/Notification.pm version: '0.017' Mastodon::Entity::Relationship: file: lib/Mastodon/Entity/Relationship.pm version: '0.017' Mastodon::Entity::Report: file: lib/Mastodon/Entity/Report.pm version: '0.017' Mastodon::Entity::Results: file: lib/Mastodon/Entity/Results.pm version: '0.017' Mastodon::Entity::Status: file: lib/Mastodon/Entity/Status.pm version: '0.017' Mastodon::Entity::Tag: file: lib/Mastodon/Entity/Tag.pm version: '0.017' Mastodon::Listener: file: lib/Mastodon/Listener.pm version: '0.017' Mastodon::Role::Entity: file: lib/Mastodon/Role/Entity.pm version: '0.017' Mastodon::Role::UserAgent: file: lib/Mastodon/Role/UserAgent.pm version: '0.017' Mastodon::Types: file: lib/Mastodon/Types.pm version: '0.017' requires: Class::Load: '0.25' DateTime: '1.51' DateTime::Format::Strptime: '1.73' Future: '0.35' HTTP::Request::Common: '6.18' HTTP::Thin: '0.006' IO::Async: '0.66' Image::Info: '1.41' JSON::MaybeXS: '1.003009' List::Util: '1.47' Log::Any: '1.049' Moo: '2.003002' Net::Async::HTTP: '0.41' Role::EventEmitter: '0.002' Try::Tiny: '0.28' Type::Params: '1.000006' Types::Common::String: '1.000006' Types::Path::Tiny: '0.005' Types::Standard: '1.003003' URI: '1.71' perl: '5.010' resources: bugtracker: https://gitlab.com/jjatria/Mastodon-Client/issues repository: git://gitlab.com/jjatria/Mastodon-Client.git version: '0.017' x_contributors: - 'Al Beano ' - 'Alexander Zaitsev ' - 'Eric Prestemon ' - 'Florian Obser ' - 'Lance Wicks ' - 'Luc Didry ' x_generated_by_perl: v5.30.0 x_serialization_backend: 'YAML::Tiny version 1.73' Mastodon-Client-0.017/Makefile.PL0000644000175000017500000000453213646430665016346 0ustar jjatriajjatria# This file was automatically generated by Dist::Zilla::Plugin::MakeMaker v6.012. use strict; use warnings; use 5.010; use ExtUtils::MakeMaker; my %WriteMakefileArgs = ( "ABSTRACT" => "Talk to a Mastodon server", "AUTHOR" => "Jos\x{e9} Joaqu\x{ed}n Atria ", "CONFIGURE_REQUIRES" => { "ExtUtils::MakeMaker" => 0 }, "DISTNAME" => "Mastodon-Client", "LICENSE" => "perl", "MIN_PERL_VERSION" => "5.010", "NAME" => "Mastodon::Client", "PREREQ_PM" => { "Class::Load" => "0.25", "DateTime" => "1.51", "DateTime::Format::Strptime" => "1.73", "Future" => "0.35", "HTTP::Request::Common" => "6.18", "HTTP::Thin" => "0.006", "IO::Async" => "0.66", "Image::Info" => "1.41", "JSON::MaybeXS" => "1.003009", "List::Util" => "1.47", "Log::Any" => "1.049", "Moo" => "2.003002", "Net::Async::HTTP" => "0.41", "Role::EventEmitter" => "0.002", "Try::Tiny" => "0.28", "Type::Params" => "1.000006", "Types::Common::String" => "1.000006", "Types::Path::Tiny" => "0.005", "Types::Standard" => "1.003003", "URI" => "1.71" }, "TEST_REQUIRES" => { "Plack" => "1.0043", "Test2::V0" => "0.000126", "Test::Pod" => "1.51", "Test::TCP" => "2.17" }, "VERSION" => "0.017", "test" => { "TESTS" => "t/*.t" } ); my %FallbackPrereqs = ( "Class::Load" => "0.25", "DateTime" => "1.51", "DateTime::Format::Strptime" => "1.73", "Future" => "0.35", "HTTP::Request::Common" => "6.18", "HTTP::Thin" => "0.006", "IO::Async" => "0.66", "Image::Info" => "1.41", "JSON::MaybeXS" => "1.003009", "List::Util" => "1.47", "Log::Any" => "1.049", "Moo" => "2.003002", "Net::Async::HTTP" => "0.41", "Plack" => "1.0043", "Role::EventEmitter" => "0.002", "Test2::V0" => "0.000126", "Test::Pod" => "1.51", "Test::TCP" => "2.17", "Try::Tiny" => "0.28", "Type::Params" => "1.000006", "Types::Common::String" => "1.000006", "Types::Path::Tiny" => "0.005", "Types::Standard" => "1.003003", "URI" => "1.71" ); unless ( eval { ExtUtils::MakeMaker->VERSION(6.63_03) } ) { delete $WriteMakefileArgs{TEST_REQUIRES}; delete $WriteMakefileArgs{BUILD_REQUIRES}; $WriteMakefileArgs{PREREQ_PM} = \%FallbackPrereqs; } delete $WriteMakefileArgs{CONFIGURE_REQUIRES} unless eval { ExtUtils::MakeMaker->VERSION(6.52) }; WriteMakefile(%WriteMakefileArgs); Mastodon-Client-0.017/Changes0000644000175000017500000001365513646430665015675 0ustar jjatriajjatriaRevision history for Mastodon::Client 0.017 2020-04-17 23:46:11+01:00 Europe/London * New: * Added an example script to illustrate the use of Mastodon::Listener. * Fixes: * Stream parser in Mastodon::Listener can now handle long messages that did not fit in a single chunk (thanks Alexander Zaitsev!) * Corrected the minimum required version of Types::Standard 0.016 2019-10-13 12:23:24+01:00 Europe/London * New: * A new 'latest_response' accessor makes it possible to retrieve the most recent response obtained from the server, to assist in error handling. * Add caption and focus settings support to upload_media (thanks Eric Prestemon!). * We now ship a cpanfile specifying the distribution's dependencies and a Dockerfile (in 't') that can be used to run tests in an isolated environment. * Changes: * The default user agent is now an instance of HTTP::Thin instead of LWP::UserAgent. * When coercing the Image type from a filename, we now use Path::Tiny's 'slurp_raw' instead of the plain 'slurp'. * Fixes: * Fixed an issue with parameter validation closures that meant having multiple clients would leak data across them. * Fixed an issue that caused a failure when trying to update the 'scopes' attribute, which is now read-write (thanks Luc Didry!) * Fixed an issue caused by a missing coercion for Bool types. 0.015 2018-04-22 02:36:42+01:00 Europe/London * New: * Status entities now have a placeholder `emojis` attribute. This will likely need to be populated with a new entity. * Changes: * A successful call to `register` will overwrite the value of the client's `scopes` attribute to that passed as an argument (since those are the scopes that were requested from the server; thanks Shura0!). * Fixes: * Fixed an issue with authorisation of clients that used scopes other than `read` (thanks Shura0!) * Corrected an error in the documentation that stated that `register` accepted a hash reference instead of a hash. * Coercing attachment entities without `remote_url`s no longer results in an error. 0.014 2018-01-23 19:27:43+00:00 Europe/London * Changes: * Allowed warnings in tests because of an open issue with Net::Async::HTTP 0.013 2018-01-21 22:29:25+00:00 Europe/London * Changes: * [internal] Switched from AnyEvent to IO::Async. * Fixes: * Fixed a typo in the synopsis that could cause a server error when run (++Florian Obser). 0.012 2017-06-09 12:58:08+01:00 Europe/London * Changes: * Improved documentation, including a section with details about the OAuth2 process for obtaining IDs, secrets, and tokens 0.011 2017-05-10 15:59:57+01:00 Europe/London * Fixes: * Fix an issue with data preparation, which made authentication impossible (thanks @whiteisthenewblack!) 0.010 2017-05-08 00:15:59+01:00 Europe/London * Fixes: * Added AutoPrereq plugin for automatic dependency detection 0.009 2017-05-06 23:50:20+01:00 Europe/London * New: * Added tidyall tests * Changes: * Switch to JSON::MaybeXS * Use Class::Load to programmatically load classes * Improved robustness of stream parsing * Simplified method to perform requests * Fixes: * Fixed syntax errors in POD * Fixed a missing import in Application entities * Reblog status method no longer calls DELETE 0.008 2017-04-22 02:45:33+01:00 Europe/London * New: * Added documentation for entity and listener objects * Status and Account objects have some convenience methods * Changes: * Re-wrote Mastodon::Listener to use AnyEvent::HTTP * Added more attributes to Mastodon::Listener, making it more usable as a standalone class * Fixes: * Fixed an issue with entity coercions, that could cause some tests to fail * TCP tests are skipped if unable to establish a connection * Added more missing dependencies * Never coerce server responses for app registration 0.007 2017-04-18 16:12:15+01:00 Europe/London * Fixes: * Corrected list of provided packages (fixed broken release) * Add readme to distribution 0.006 2017-04-18 02:28:45+01:00 Europe/London * New: * Added tests for more methods * Entity objects have experimental method shortcuts * Fixes: * Some of the GET methods did not correctly parse arguments * Changes: * The `uri` attribute for Status entities is not of type URI 0.005 2017-04-17 15:20:15+01:00 Europe/London * Fixes: * Fixed wrong links in POD 0.004 2017-04-17 15:14:15+01:00 Europe/London * New: * Added some tests for GET requests, using Test::TCP * Added an example script to dump responses to GET requests, mainly for debug * Fixes: * Corrected some inconsistencies in the method signatures with what was documented * Do not try to coerce non-entity responses (ie. from OAuth2 flow) * Respect the value of coerce_entities in more methods * Changes: * The `authorized` attribute is now never undefined 0.003 2017-04-17 00:12:15+01:00 Europe/London * New: * Added Travis CI configuration * Fixes: * Added some missing dependencies to dist.ini 0.002 2017-04-16 23:06:52+01:00 Europe/London * New: * Added convenience methods for all API endpoints * Added a `coerce_entities` option to turn JSON responses from Mastodon into Perl objects * Added more tests (suite is still far from complete!) * Added methods for sending DELETE and PATCH requests * Added this change log. :) * Several changes to dist.ini add repository data, META.json, etc. * Added documentation * Changes: * Interface of `timeline()` and `stream()` methods now more closely mirrors that of the rest of the distribution * Fixes: * Fixed several issues with passing arguments to requst functions 0.001 * Initial release Mastodon-Client-0.017/README.md0000644000175000017500000006001513646430665015651 0ustar jjatriajjatria# NAME Mastodon::Client - Talk to a Mastodon server # SYNOPSIS use Mastodon::Client; my $client = Mastodon::Client->new( instance => 'mastodon.social', name => 'PerlBot', client_id => $client_id, client_secret => $client_secret, access_token => $access_token, coerce_entities => 1, ); $client->post_status('Posted to a Mastodon server!'); $client->post_status('And now in secret...', { visibility => 'unlisted' } ) # Streaming interface might change! my $listener = $client->stream( 'public' ); $listener->on( update => sub { my ($listener, $status) = @_; printf "%s said: %s\n", $status->account->display_name, $status->content; }); $listener->start; # DESCRIPTION Mastodon::Client lets you talk to a Mastodon server to obtain authentication credentials, read posts from timelines in both static or streaming mode, and perform all the other operations exposed by the Mastodon API. Most of these are available through the convenience methods listed below, which validate input parameters and are likely to provide more meaningful feedback in case of errors. Alternatively, this distribution can be used via the low-level request methods (**post**, **get**, etc), which allow direct access to the API endpoints. All other methods call one of these at some point. # VERSION NOTICE This distribution currently **only supports version 1 of the Mastodon API**. Help implementing support for any missing features in version 1, and for the new features in version 2, would be greatfully appreciated. # ATTRIBUTES - **instance** A Mastodon::Entity::Instance object representing the instance to which this client will speak. Defaults to `mastodon.social`. - **api\_version** An integer specifying the version of the API endpoints to use. Defaults to `1`. - **redirect\_uri** The URI to which authorization codes should be forwarded as part of the OAuth2 flow. Defaults to `urn:ietf:wg:oauth:2.0:oob` (meaning no redirection). - **user\_agent** The user agent to use for the requests. Defaults to an instance of [HTTP::Thin](https://metacpan.org/pod/HTTP::Thin). It is expected to have a `request` method that accepts [HTTP::Request](https://metacpan.org/pod/HTTP::Request) objects. - **coerce\_entities** A boolean value. Set to true if you want Mastodon::Client to internally coerce all response entities to objects. This adds a level of validation, and can make the objects easier to use. Although this does require some additional processing, the coercion is done by [Type::Tiny](https://metacpan.org/pod/Type::Tiny), so the impact is negligible. For now, it defaults to **false** (but this will likely change, so I recommend you use it). - **access\_token** The access token of your client. This is provided by the Mastodon API and is used for the OAuth2 authentication required for most API calls. You can get this by calling **authorize** with either an access code or your account's username and password. - **authorized** Boolean. False is the client has no defined access\_token. When an access token is set, this is set to true or to a [DateTime](https://metacpan.org/pod/DateTime) object representing the time of authorization if possible (as received from the server). - **client\_id** - **client\_secret** The client ID and secret are provided by the Mastodon API when you register your client using the **register** method. They are used to identify where your calls are coming from, and are required before you can use the **authorize** method to get the access token. - **name** Your client's name. This is required when registering, but is otherwise seldom used. If you are using the **authorization\_url** to get an access code from your users, then they will see this name when they go to that page. - **account** Holds the authenticated account. It is set internally by the **get\_account** method. - **scopes** This array reference holds the scopes set by you for the client. These are required when registering your client with the Mastodon instance. Defaults to `read`. Mastodon::Client will internally make sure that the scopes you were provided when calling **authorize** match those that you requested. If this is not the case, it will helpfully die. - **website** The URL of a human-readable website for the client. If made available, it appears as a link in the "authorized applications" tab of the user preferences in the default Mastodon web GUI. Defaults to the empty string. # METHODS ## Authorizing an application Although not all of the API methods require authentication to be used, most of them do. The authentication process involves a) registering an application with a Mastodon server to obtain a client secret and ID; b) authorizing the application by either providing a user's credentials, or by using an authentication URL. The methods facilitating this process are detailed below: - **register()** - **register(%data)** Obtain a client secret and ID from a given mastodon instance. Takes a single hash as an argument, with the following possible keys: - **redirect\_uris** The URL to which authorization codes should be forwarded after authorized by the user. Defaults to the value of the **redirect\_uri** attribute. - **scopes** The scopes requested by this client. Defaults to the value of the **scopes** attribute. - **website** The client's website. Defaults to the value of the `website` attribute. When successful, sets the `client_secret`, `scopes`, and `client_id` attributes of the Mastodon::Client object and returns the modified object. This should be called **once** per client and its contents cached locally. - **authorization\_url()** Generate an authorization URL for the given application. Accessing this URL via a browser by a logged in user will allow that user to grant this application access to the requested scopes. The scopes used are the ones in the **scopes** attribute at the time this method is called. - **authorize()** - **authorize( %data )** Grant the application access to the requested scopes for a given user. This method takes a hash with either an access code or a user's login credentials to grant authorization. Valid keys are: - **access\_code** The access code obtained by visiting the URL generated by **authorization\_url**. - **username** - **password** The user's login credentials. When successful, the method automatically sets the client's **authorized** attribute to a true value and caches the **access\_token** for all future calls. ## Error handling Methods that make requests to the server will `die` whenever an error is encountered, or the data that was received from the server is not what is expected. The error string will, when possible, come from the response's status line, but this will likely not be enough to fully diagnose what went wrong. - **latest\_response** To make this easier, the client will cache the server's response after each request has been made, and expose it through the `latest_response` accessor. Note that, as its name implies, _this will only store the most recent response_. If called before any request has been made, it will return an undefined value. The remaining methods listed here follow the order of those in the official API documentation. ## Accounts - **get\_account()** - **get\_account($id)** - **get\_account($params)** - **get\_account($id, $params)** Fetches an account by ID. If no ID is provided, this defaults to the current authenticated account. Global GET parameters are available for this method. Depending on the value of `coerce_entities`, it returns a Mastodon::Entity::Account object, or a plain hash reference. - **update\_account($params)** Make changes to the authenticated account. Takes a hash reference with the following possible keys: - **display\_name** - **note** Strings - **avatar** - **header** A base64 encoded image, or the name of a file to be encoded. Depending on the value of `coerce_entities`, returns the modified Mastodon::Entity::Account object, or a plain hash reference. - **followers()** - **followers($id)** - **followers($params)** - **followers($id, $params)** Get the list of followers of an account by ID. If no ID is provided, the one for the current authenticated account is used. Global GET parameters are available for this method. Depending on the value of `coerce_entities`, returns an array reference of Mastodon::Entity::Account objects, or a plain array reference. - **following()** - **following($id)** - **following($params)** - **following($id, $params)** Get the list of accounts followed by the account specified by ID. If no ID is provided, the one for the current authenticated account is used. Global GET parameters are available for this method. Depending on the value of `coerce_entities`, returns an array reference of Mastodon::Entity::Account objects, or a plain array reference. - **statuses()** - **statuses($id)** - **statuses($params)** - **statuses($id, $params)** Get a list of statuses from the account specified by ID. If no ID is provided, the one for the current authenticated account is used. In addition to the global GET parameters, this method accepts the following parameters: - **only\_media** - **exclude\_replies** Both boolean. Depending on the value of `coerce_entities`, returns an array reference of Mastodon::Entity::Status objects, or a plain array reference. - **follow($id)** - **unfollow($id)** Follow or unfollow an account specified by ID. The ID argument is mandatory. Depending on the value of `coerce_entities`, returns the new Mastodon::Entity::Relationship object, or a plain hash reference. - **block($id)** - **unblock($id)** Block or unblock an account specified by ID. The ID argument is mandatory. Depending on the value of `coerce_entities`, returns the new Mastodon::Entity::Relationship object, or a plain hash reference. - **mute($id)** - **unmute($id)** Mute or unmute an account specified by ID. The ID argument is mandatory. Depending on the value of `coerce_entities`, returns the new Mastodon::Entity::Relationship object, or a plain hash reference. - **relationships(@ids)** - **relationships(@ids, $params)** Get the list of relationships of the current authenticated user with the accounts specified by ID. At least one ID is required, but more can be passed at once. Global GET parameters are available for this method, and can be passed as an additional hash reference as a final argument. Depending on the value of `coerce_entities`, returns an array reference of Mastodon::Entity::Relationship objects, or a plain array reference. - **search\_accounts($query)** - **search\_accounts($query, $params)** Search for accounts. Takes a mandatory string argument to use as the search query. If the search query is of the form `username@domain`, the accounts will be searched remotely. In addition to the global GET parameters, this method accepts the following parameters: - **limit** The maximum number of matches. Defaults to 40. Depending on the value of `coerce_entities`, returns an array reference of Mastodon::Entity::Account objects, or a plain array reference. This method does not require authentication. ## Blocks - **blocks()** - **blocks($params)** Get the list of accounts blocked by the authenticated user. Global GET parameters are available for this method. Depending on the value of `coerce_entities`, returns an array reference of Mastodon::Entity::Account objects, or a plain array reference. ## Favourites - **favourites()** - **favourites($params)** Get the list of statuses favourited by the authenticated user. Global GET parameters are available for this method. Depending on the value of `coerce_entities`, returns an array reference of Mastodon::Entity::Status objects, or a plain array reference. ## Follow requests - **follow\_requests()** - **follow\_requests($params)** Get the list of accounts requesting to follow the the authenticated user. Global GET parameters are available for this method. Depending on the value of `coerce_entities`, returns an array reference of Mastodon::Entity::Account objects, or a plain array reference. - **authorize\_follow($id)** - **reject\_follow($id)** Accept or reject the follow request by the account of the specified ID. The ID argument is mandatory. Returns an empty object. ## Follows - **remote\_follow($acct)** Follow a remote user by account string (ie. `username@domain`). The argument is mandatory. Depending on the value of `coerce_entities`, returns an Mastodon::Entity::Account object, or a plain hash reference with the local representation of the specified account. ## Instances - **fetch\_instance()** Fetches the latest information for the current instance the client is talking to. When successful, this method updates the value of the `instance` attribute. Depending on the value of `coerce_entities`, returns an Mastodon::Entity::Instance object, or a plain hash reference. This method does not require authentication. ## Media - **upload\_media($file)** - **upload\_media($file, $params)** Upload a file as an attachment. Takes a mandatory argument with the name of a local file to encode and upload, and an optional hash reference with the following additional parameters: - **description** A plain-text description of the media, for accessibility, as a string. - **focus** An array reference of two floating point values, to be used as the x and y focus values. These inform clients which point in the image is the most important one to show in a cropped view. The value of a coordinate is a real number between -1 and +1, where 0 is the center. x:-1 indicates the left edge of the image, x:1 the right edge. For the y axis, y:1 is the top edge and y:-1 is the bottom. Depending on the value of `coerce_entities`, returns an Mastodon::Entity::Attachment object, or a plain hash reference. The returned object's ID can be passed to the **post\_status** to post it to a timeline. ## Mutes - **mutes()** - **mutes($params)** Get the list of accounts muted by the authenticated user. Global GET parameters are available for this method. Depending on the value of `coerce_entities`, returns an array reference of Mastodon::Entity::Account objects, or a plain array reference. ## Notifications - **notifications()** - **notifications($params)** Get the list of notifications for the authenticated user. Global GET parameters are available for this method. Depending on the value of `coerce_entities`, returns an array reference of Mastodon::Entity::Notification objects, or a plain array reference. - **get\_notification($id)** Get a notification by ID. The argument is mandatory. Depending on the value of `coerce_entities`, returns an Mastodon::Entity::Notification object, or a plain hash reference. - **clear\_notifications()** Clears all notifications for the authenticated user. This method takes no arguments and returns an empty object. ## Reports - **reports()** - **reports($params)** Get a list of reports made by the authenticated user. Global GET parameters are available for this method. Depending on the value of `coerce_entities`, returns an array reference of Mastodon::Entity::Report objects, or a plain array reference. - **report($params)** Report a user or status. Takes a mandatory hash with the following keys: - **account\_id** The ID of a single account to report. - **status\_ids** The ID of a single status to report, or an array reference of statuses to report. - **comment** An optional string. While the comment is always optional, either the **account\_id** or the list of **status\_ids** must be present. Depending on the value of `coerce_entities`, returns the new Mastodon::Entity::Report object, or a plain hash reference. ## Search - **search($query)** - **search($query, $params)** Search for content. Takes a mandatory string argument to use as the search query. If the search query is a URL, Mastodon will attempt to fetch the provided account or status. Otherwise, it will do a local account and hashtag search. In addition to the global GET parameters, this method accepts the following parameters: - **resolve** Whether to resolve non-local accounts. ## Statuses - **get\_status($id)** - **get\_status($id, $params)** Fetches a status by ID. The ID argument is mandatory. Global GET parameters are available for this method as an additional hash reference. Depending on the value of `coerce_entities`, it returns a Mastodon::Entity::Status object, or a plain hash reference. This method does not require authentication. - **get\_status\_context($id)** - **get\_status\_context($id, $params)** 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. Depending on the value of `coerce_entities`, it returns a Mastodon::Entity::Context object, or a plain hash reference. This method does not require authentication. - **get\_status\_card($id)** - **get\_status\_card($id, $params)** Fetches a card associated to a status by ID. The ID argument is mandatory. Global GET parameters are available for this method as an additional hash reference. Depending on the value of `coerce_entities`, it returns a Mastodon::Entity::Card object, or a plain hash reference. This method does not require authentication. - **get\_status\_reblogs($id)** - **get\_status\_reblogs($id, $params)** - **get\_status\_favourites($id)** - **get\_status\_favourites($id, $params)** Fetches a list of accounts who have reblogged or favourited a status by ID. The ID argument is mandatory. Global GET parameters are available for this method as an additional hash reference. Depending on the value of `coerce_entities`, it returns an array reference of Mastodon::Entity::Account objects, or a plain array reference. This method does not require authentication. - **post\_status($text)** - **post\_status($text, $params)** Posts a new status. Takes a mandatory string as the content of the status (which can be the empty string), and an optional hash reference with the following additional parameters: - **status** The content of the status, as a string. Since this is already provided as the first argument of the method, this is not necessary. But if provided, this value will overwrite that of the first argument. - **in\_reply\_to\_id** The optional ID of a status to reply to. - **media\_ids** An array reference of up to four media IDs. These can be obtained as the result of a call to **upload\_media()**. - **sensitive** Boolean, to mark status content as NSFW. - **spoiler\_text** A string, to be shown as a warning before the actual content. - **visibility** A string; one of `direct`, `private`, `unlisted`, or `public`. Depending on the value of `coerce_entities`, it returns the new Mastodon::Entity::Status object, or a plain hash reference. - **delete\_status($id)** Delete a status by ID. The ID is mandatory. Returns an empty object. - **reblog($id)** - **unreblog($id)** - **favourite($id)** - **unfavourite($id)** Reblog or favourite a status by ID, or revert this action. The ID argument is mandatory. Depending on the value of `coerce_entities`, it returns the specified Mastodon::Entity::Status object, or a plain hash reference. ## Timelines - **timeline($query)** - **timeline($query, $params)** Retrieves a timeline. The first argument defines either the name of a timeline (which can be one of `home` or `public`), or a hashtag (if it begins with the `#` character). This argument is mandatory. In addition to the global GET parameters, this method accepts the following parameters: Accessing the public and tag timelines does not require authentication. - **local** Boolean. If true, limits results only to those originating from the current instance. Only applies to public and tag timelines. Depending on the value of `coerce_entities`, it returns an array of Mastodon::Entity::Status objects, or a plain array reference. The more recent statuses come first. # STREAMING RESULTS Alternatively, it is possible to use the streaming API to get a constant stream of updates. To do this, there is the **stream()** method. - **stream($query)** Creates a Mastodon::Listener object which will fetch a stream for the specified query. Possible values for the query are either `user`, for events that are relevant to the authorized user; `public`, for all public statuses; or a tag (if it begins with the `#` character), for all public statuses for the particular tag. For more details on how to use this object, see the documentation for [Mastodon::Listener](https://metacpan.org/pod/Mastodon::Listener). Accessing streaming public timeline does not require authentication. # REQUEST METHODS Mastodon::Client uses four lower-level request methods to contact the API with GET, POST, PATCH, and DELETE requests. These are left available in case one of the higher-level convenience methods are unsuitable or undesirable, but you use them at your own risk. They all take a URL as their first parameter, which can be a string with the API endpoint to contact, or a [URI](https://metacpan.org/pod/URI) object, which will be used as-is. If passed as a string, the methods expect one that contains only the variable parts of the endpoint (ie. not including the `HOST/api/v1` part). The remaining parts will be filled-in appropriately internally. - **delete($url)** - **get($url)** - **get($url, $params)** Query parameters can be passed as part of the [URI](https://metacpan.org/pod/URI) object, but it is not recommended you do so, since Mastodon has expectations for array parameters that do not meet those of eg. [URI::QueryParam](https://metacpan.org/pod/URI::QueryParam). It will be easier and safer if any additional parameters are passed as a hash reference, which will be added to the URL before the request is sent. - **post($url)** - **post($url, $data)** - **patch($url)** - **patch($url, $data)** the `post` and `patch` methods work similarly to `get` and `delete`, but the optional hash reference is sent in as form data, instead of processed as query parameters. The Mastodon API does not use query parameters on POST or PATCH endpoints. # CONTRIBUTIONS AND BUG REPORTS Contributions of any kind are most welcome! The main repository for this distribution is on [GitLab](https://gitlab.com/jjatria/Mastodon-Client), which is where patches and bug reports are mainly tracked. The repository is also mirrored on [Github](https://github.com/jjatria/Mastodon-Client), in case that platform makes it easier to post contributions. If none of the above is acceptable, bug reports can also be sent through the CPAN RT system, or by mail directly to the developers at the address below, although these will not be as closely tracked. # AUTHOR - José Joaquín Atria # CONTRIBUTORS - Lance Wicks # COPYRIGHT AND LICENSE This software is copyright (c) 2017 by José Joaquín Atria. This is free software; you can redistribute it and/or modify it under the same terms as the Perl 5 programming language system itself. Mastodon-Client-0.017/lib/0000775000175000017500000000000013646430665015140 5ustar jjatriajjatriaMastodon-Client-0.017/lib/Mastodon/0000775000175000017500000000000013646430665016724 5ustar jjatriajjatriaMastodon-Client-0.017/lib/Mastodon/Client.pm0000644000175000017500000010752113646430665020504 0ustar jjatriajjatria# ABSTRACT: Talk to a Mastodon server package Mastodon::Client; use strict; use warnings; use v5.10.0; our $VERSION = '0.017'; use Carp; use Mastodon::Types qw( Acct Account DateTime Image URI Instance ); use Moo; use Types::Common::String qw( NonEmptyStr ); use Types::Standard qw( Any Int Str Bool Undef HashRef ArrayRef Dict Tuple StrictNum slurpy Maybe Optional ); use Types::Path::Tiny qw( File ); use Type::Params qw( compile validate ); use Log::Any; my $log = Log::Any->get_logger(category => 'Mastodon'); with 'Mastodon::Role::UserAgent'; has coerce_entities => ( is => 'rw', isa => Bool, lazy => 1, default => 0, ); has access_token => ( is => 'rw', isa => NonEmptyStr, lazy => 1, predicate => '_has_access_token', ); has authorized => ( is => 'rw', isa => DateTime|Bool, lazy => 1, default => sub { $_[0]->_has_access_token }, coerce => 1, ); has client_id => ( is => 'rw', isa => NonEmptyStr, lazy => 1, ); has client_secret => ( is => 'rw', isa => NonEmptyStr, lazy => 1, ); has name => ( is => 'ro', isa => NonEmptyStr, ); has website => ( is => 'ro', isa => Str, lazy => 1, default => q{}, ); has account => ( is => 'rw', isa => HashRef|Account, init_arg => undef, lazy => 1, default => sub { $_[0]->get_account; }, ); has scopes => ( is => 'rw', isa => ArrayRef->plus_coercions( Str, sub { [ split / /, $_ ] } ), lazy => 1, default => sub { [ 'read' ] }, coerce => 1, ); after access_token => sub { $_[0]->authorized(1) unless $_[0]->authorized; }; sub authorize { my $self = shift; unless ( $self->client_id and $self->client_secret ) { croak $log->fatal( 'Cannot authorize client without client_id and client_secret'); } if ( $self->access_token ) { $log->warn('Client is already authorized'); return $self; } state $check = compile( slurpy Dict [ access_code => Optional [Str], username => Optional [Str], password => Optional [Str], ], ); my ($params) = $check->(@_); my $data = { client_id => $self->client_id, client_secret => $self->client_secret, redirect_uri => $self->redirect_uri, }; if ( $params->{access_code} ) { $data->{grant_type} = 'authorization_code'; $data->{code} = $params->{access_code}; } else { $data->{grant_type} = 'password'; $data->{username} = $params->{username} // ''; $data->{password} = $params->{password} // ''; } $data->{scope} = join q{ }, sort @{ $self->scopes }; my $response = $self->post( 'oauth/token' => $data ); if ( defined $response->{error} ) { $log->warn( $response->{error_description} ); } else { my $granted_scopes = join q{ }, sort split( / /, $response->{scope} ); my $requested_scopes = join q{ }, sort @{ $self->scopes }; croak $log->fatal('Granted and requested scopes do not match') if $granted_scopes ne $requested_scopes; $self->access_token( $response->{access_token} ); $self->authorized( $response->{created_at} ); } return $self; } # Authorize follow requests by account ID sub authorize_follow { my $self = shift; state $check = compile( Int ); my ($id) = $check->(@_); return $self->post( 'follow_requests/authorize' => { id => $id } ); } # Clears notifications sub clear_notifications { my $self = shift; state $check = compile(); $check->(@_); return $self->post( 'notifications/clear' ); } # Delete a status by ID sub delete_status { my $self = shift; state $check = compile( Int ); my ($id) = $check->(@_); return $self->delete( "statuses/$id" ); } sub fetch_instance { my $self = shift; # Do not return from the instance attribute, since the user might have # disabled coercions, and the attribute is always coerced my $instance = $self->get( 'instance' ); $self->instance($instance); return $instance; } sub get_account { my $self = shift; my $own = 'verify_credentials'; state $check = compile( Optional [Int|HashRef], Optional [HashRef] ); my ($id, $params) = $check->(@_); if (ref $id eq 'HASH') { $params = $id; $id = undef; } $id //= $own; $params //= {}; my $data = $self->get( "accounts/$id", $params ); # We fetched authenticated user account's data # Update local reference $self->account($data) if ($id eq $own); return $data; } # Get a single notification by ID sub get_notification { my $self = shift; state $check = compile( Int, Optional [HashRef] ); my ($id, $params) = $check->(@_); return $self->get( "notifications/$id", $params ); } # Get a single status by ID sub get_status { my $self = shift; state $check = compile( Int, Optional [HashRef] ); my ($id, $params) = $check->(@_); return $self->get( "statuses/$id", $params ); } # Post a status sub post_status { my $self = shift; state $check = compile( Str|HashRef, Optional[HashRef]); my ($text, $params) = $check->(@_); $params //= {}; my $payload; if (ref $text eq 'HASH') { $params = $text; croak $log->fatal('Post must contain a (possibly empty) status text') unless defined $params->{status}; $payload = $params; } else { $payload = { status => $text, %{$params} }; } return $self->post( 'statuses', $payload); } # Reblog a status by ID sub reblog_status { my $self = shift; state $check = compile( Int ); my ($id) = $check->(@_); return $self->post( "statuses/$id/reblog" ); } sub register { my $self = shift; if ( $self->client_id && $self->client_secret ) { $log->warn('Client is already registered'); return $self; } state $check = compile( slurpy Dict [ redirect_uris => Optional [ Str ], scopes => Optional [ ArrayRef [ Str ] ], website => Optional [ Str ], instance => Optional [ Any ], ] ); my ($params) = $check->(@_); # We used to accept instance by mistake, we want to turn it off warn 'Deprecation notice: do not pass an instance parameter to register' if $params->{instance}; my $website = $params->{website} // $self->website; my $scopes = $params->{scopes} // $self->scopes; my $response = $self->post( 'apps' => { client_name => $self->name, redirect_uris => $params->{redirect_uris} // $self->redirect_uri, scopes => join( ' ', sort @{$scopes} ), $website ? ( website => $website ) : (), }, ); $self->client_id( $response->{client_id} ); $self->client_secret( $response->{client_secret} ); $self->scopes($scopes); return $self; } sub statuses { my $self = shift; state $check = compile( Optional [HashRef|Int], Optional [HashRef]); my ($id, $params) = $check->(@_); if (ref $id) { $params = $id; $id = undef; } $id //= $self->account->{id}; $params //= {}; return $self->get( "accounts/$id/statuses", $params ); } # Reject follow requests by account ID sub reject_follow { my $self = shift; state $check = compile( Int ); my ($id) = $check->(@_); return $self->post( 'follow_requests/reject' => { id => $id } ); } # Follow a remote user by acct (username@instance) sub remote_follow { my $self = shift; state $check = compile( Acct ); my ($acct) = $check->(@_); return $self->post( 'follows' => { uri => $acct } ); } # Report a user account or list of statuses sub report { my $self = shift; state $check = compile( slurpy Dict[ account_id => Optional[Int], status_ids => Optional[ArrayRef->plus_coercions( Int, sub { [ $_ ] } ) ], comment => Optional[Str], ]); my ($data) = $check->(@_); croak $log->fatal('Either account_id or status_ids are required for report') unless join(q{ }, keys(%{$data})) =~ /\b(account_id|status_ids)\b/; return $self->post( 'reports' => $data ); } sub relationships { my $self = shift; state $check = compile( slurpy ArrayRef [Int|HashRef] ); my ($ids) = $check->(@_); my $params = (ref $ids->[-1] eq 'HASH') ? pop(@{$ids}) : {}; croak $log->fatal('At least one ID number needed in relationships') unless scalar @{$ids}; $params = { id => $ids, %{$params}, }; return $self->get( 'accounts/relationships', $params ); } sub search { my $self = shift; state $check = compile( Str, Optional [HashRef] ); my ($query, $params) = $check->(@_); $params //= {}; $params = { 'q' => $query, %{$params}, }; return $self->get( 'search', $params ); } sub search_accounts { my $self = shift; state $check = compile( Str, Optional [HashRef] ); my ($query, $params) = $check->(@_); $params //= {}; $params = { 'q' => $query, %{$params}, }; return $self->get( 'accounts/search', $params ); } sub stream { my $self = shift; state $check = compile( NonEmptyStr ); my ($query) = $check->(@_); my $endpoint = $self->instance->uri . '/api/v' . $self->api_version . '/streaming/' . (( $query =~ /^#/ ) ? ( 'hashtag?' . $query ) : $query ); use Mastodon::Listener; return Mastodon::Listener->new( url => $endpoint, access_token => $self->access_token, coerce_entities => $self->coerce_entities, ); } sub timeline { my $self = shift; state $check = compile( NonEmptyStr, Optional [HashRef] ); my ($query, $params) = $check->(@_); my $endpoint = ( $query =~ /^#/ ) ? 'timelines/tag/' . $query : 'timelines/' . $query; return $self->get($endpoint, $params); } sub update_account { my $self = shift; state $check = compile( slurpy Dict [ display_name => Optional [Str], note => Optional [Str], avatar => Optional [Image], header => Optional [Image], ] ); my ($data) = $check->(@_); return $self->patch( 'accounts/update_credentials' => $data ); } sub upload_media { my $self = shift; state $check = compile( File->plus_coercions( Str, sub { Path::Tiny::path($_) } ), Optional [ Dict[ description => Optional[Str], focus => Optional[Tuple[StrictNum, StrictNum]], ]] ); my ($file, $params) = $check->(@_); $params //= {}; if (exists $params->{focus}) { my ($x,$y) = @{$params->{focus}}; if ($x >= -1 && $x <= 1 && $y >= -1 && $y <= 1) { $params->{focus} = "$x,$y"; } else { delete $params->{focus}; } } return $self->post( 'media' => { file => [ $file, undef ], %$params }, headers => { Content_Type => 'form-data' }, ); } # POST requests with no data and a mandatory ID number foreach my $pair ([ [ statuses => [qw( reblog unreblog favourite unfavourite )] ], [ accounts => [qw( mute unmute block unblock follow unfollow )] ], ]) { my ($base, $endpoints) = @{$pair}; foreach my $endpoint (@{$endpoints}) { my $method = ($base eq 'statuses') ? $endpoint . '_status' : $endpoint; no strict 'refs'; *{ __PACKAGE__ . '::' . $method } = sub { my $self = shift; state $check = compile( Int ); my ($id) = $check->(@_); return $self->post( "$base/$id/$endpoint" ); }; } } # GET requests with no parameters but optional parameter hashref for my $action (qw( blocks favourites follow_requests mutes notifications reports )) { no strict 'refs'; *{ __PACKAGE__ . '::' . $action } = sub { my $self = shift; state $check = compile(Optional [HashRef]); my ($params) = $check->(@_); $params //= {}; return $self->get( $action, $params ); }; } # GET requests with optional ID and parameter hashref # ID number defaults to authenticated account's ID for my $action (qw( following followers )) { no strict 'refs'; *{ __PACKAGE__ . '::' . $action } = sub { my $self = shift; state $check = compile( Optional [Int|HashRef], Optional [HashRef] ); my ($id, $params) = $check->(@_); if (ref $id eq 'HASH') { $params = $id; $id = undef; } $id //= $self->account->{id}; $params //= {}; return $self->get( "accounts/$id/$action", $params ); }; } # GET requests for status details foreach my $pair ([ [ get_status_context => 'context' ], [ get_status_card => 'card' ], [ get_status_reblogs => 'reblogged_by' ], [ get_status_favourites => 'favourited_by' ], ]) { my ($method, $endpoint) = @{$pair}; no strict 'refs'; *{ __PACKAGE__ . '::' . $method } = sub { my $self = shift; state $check = compile( Int, Optional [HashRef] ); my ($id, $params) = $check->(@_); return $self->get( "statuses/$id/$endpoint", $params ); }; } 1; __END__ =encoding utf8 =head1 NAME Mastodon::Client - Talk to a Mastodon server =head1 SYNOPSIS use Mastodon::Client; my $client = Mastodon::Client->new( instance => 'mastodon.social', name => 'PerlBot', client_id => $client_id, client_secret => $client_secret, access_token => $access_token, coerce_entities => 1, ); $client->post_status('Posted to a Mastodon server!'); $client->post_status('And now in secret...', { visibility => 'unlisted' } ) # Streaming interface might change! my $listener = $client->stream( 'public' ); $listener->on( update => sub { my ($listener, $status) = @_; printf "%s said: %s\n", $status->account->display_name, $status->content; }); $listener->start; =head1 DESCRIPTION Mastodon::Client lets you talk to a Mastodon server to obtain authentication credentials, read posts from timelines in both static or streaming mode, and perform all the other operations exposed by the Mastodon API. Most of these are available through the convenience methods listed below, which validate input parameters and are likely to provide more meaningful feedback in case of errors. Alternatively, this distribution can be used via the low-level request methods (B, B, etc), which allow direct access to the API endpoints. All other methods call one of these at some point. =head1 VERSION NOTICE This distribution currently B. Help implementing support for any missing features in version 1, and for the new features in version 2, would be greatfully appreciated. =head1 ATTRIBUTES =over 4 =item B A Mastodon::Entity::Instance object representing the instance to which this client will speak. Defaults to C. =item B An integer specifying the version of the API endpoints to use. Defaults to C<1>. =item B The URI to which authorization codes should be forwarded as part of the OAuth2 flow. Defaults to C (meaning no redirection). =item B The user agent to use for the requests. Defaults to an instance of L. It is expected to have a C method that accepts L objects. =item B A boolean value. Set to true if you want Mastodon::Client to internally coerce all response entities to objects. This adds a level of validation, and can make the objects easier to use. Although this does require some additional processing, the coercion is done by L, so the impact is negligible. For now, it defaults to B (but this will likely change, so I recommend you use it). =item B The access token of your client. This is provided by the Mastodon API and is used for the OAuth2 authentication required for most API calls. You can get this by calling B with either an access code or your account's username and password. =item B Boolean. False is the client has no defined access_token. When an access token is set, this is set to true or to a L object representing the time of authorization if possible (as received from the server). =item B =item B The client ID and secret are provided by the Mastodon API when you register your client using the B method. They are used to identify where your calls are coming from, and are required before you can use the B method to get the access token. =item B Your client's name. This is required when registering, but is otherwise seldom used. If you are using the B to get an access code from your users, then they will see this name when they go to that page. =item B Holds the authenticated account. It is set internally by the B method. =item B This array reference holds the scopes set by you for the client. These are required when registering your client with the Mastodon instance. Defaults to C. Mastodon::Client will internally make sure that the scopes you were provided when calling B match those that you requested. If this is not the case, it will helpfully die. =item B The URL of a human-readable website for the client. If made available, it appears as a link in the "authorized applications" tab of the user preferences in the default Mastodon web GUI. Defaults to the empty string. =back =head1 METHODS =head2 Authorizing an application Although not all of the API methods require authentication to be used, most of them do. The authentication process involves a) registering an application with a Mastodon server to obtain a client secret and ID; b) authorizing the application by either providing a user's credentials, or by using an authentication URL. The methods facilitating this process are detailed below: =over 4 =item B =item B Obtain a client secret and ID from a given mastodon instance. Takes a single hash as an argument, with the following possible keys: =over 4 =item B The URL to which authorization codes should be forwarded after authorized by the user. Defaults to the value of the B attribute. =item B The scopes requested by this client. Defaults to the value of the B attribute. =item B The client's website. Defaults to the value of the C attribute. =back When successful, sets the C, C, and C attributes of the Mastodon::Client object and returns the modified object. This should be called B per client and its contents cached locally. =item B Generate an authorization URL for the given application. Accessing this URL via a browser by a logged in user will allow that user to grant this application access to the requested scopes. The scopes used are the ones in the B attribute at the time this method is called. =item B =item B Grant the application access to the requested scopes for a given user. This method takes a hash with either an access code or a user's login credentials to grant authorization. Valid keys are: =over 4 =item B The access code obtained by visiting the URL generated by B. =item B =item B The user's login credentials. =back When successful, the method automatically sets the client's B attribute to a true value and caches the B for all future calls. =back =head2 Error handling Methods that make requests to the server will C whenever an error is encountered, or the data that was received from the server is not what is expected. The error string will, when possible, come from the response's status line, but this will likely not be enough to fully diagnose what went wrong. =over 4 =item B To make this easier, the client will cache the server's response after each request has been made, and expose it through the C accessor. Note that, as its name implies, I. If called before any request has been made, it will return an undefined value. =back The remaining methods listed here follow the order of those in the official API documentation. =head2 Accounts =over 4 =item B =item B =item B =item B Fetches an account by ID. If no ID is provided, this defaults to the current authenticated account. Global GET parameters are available for this method. Depending on the value of C, it returns a Mastodon::Entity::Account object, or a plain hash reference. =item B Make changes to the authenticated account. Takes a hash reference with the following possible keys: =over 4 =item B =item B Strings =item B =item B
A base64 encoded image, or the name of a file to be encoded. =back Depending on the value of C, returns the modified Mastodon::Entity::Account object, or a plain hash reference. =item B =item B =item B =item B Get the list of followers of an account by ID. If no ID is provided, the one for the current authenticated account is used. Global GET parameters are available for this method. Depending on the value of C, returns an array reference of Mastodon::Entity::Account objects, or a plain array reference. =item B =item B =item B =item B Get the list of accounts followed by the account specified by ID. If no ID is provided, the one for the current authenticated account is used. Global GET parameters are available for this method. Depending on the value of C, returns an array reference of Mastodon::Entity::Account objects, or a plain array reference. =item B =item B =item B =item B Get a list of statuses from the account specified by ID. If no ID is provided, the one for the current authenticated account is used. In addition to the global GET parameters, this method accepts the following parameters: =over 4 =item B =item B Both boolean. =back Depending on the value of C, returns an array reference of Mastodon::Entity::Status objects, or a plain array reference. =item B =item B Follow or unfollow an account specified by ID. The ID argument is mandatory. Depending on the value of C, returns the new Mastodon::Entity::Relationship object, or a plain hash reference. =item B =item B Block or unblock an account specified by ID. The ID argument is mandatory. Depending on the value of C, returns the new Mastodon::Entity::Relationship object, or a plain hash reference. =item B =item B Mute or unmute an account specified by ID. The ID argument is mandatory. Depending on the value of C, returns the new Mastodon::Entity::Relationship object, or a plain hash reference. =item B =item B Get the list of relationships of the current authenticated user with the accounts specified by ID. At least one ID is required, but more can be passed at once. Global GET parameters are available for this method, and can be passed as an additional hash reference as a final argument. Depending on the value of C, returns an array reference of Mastodon::Entity::Relationship objects, or a plain array reference. =item B =item B Search for accounts. Takes a mandatory string argument to use as the search query. If the search query is of the form C, the accounts will be searched remotely. In addition to the global GET parameters, this method accepts the following parameters: =over 4 =item B The maximum number of matches. Defaults to 40. =back Depending on the value of C, returns an array reference of Mastodon::Entity::Account objects, or a plain array reference. This method does not require authentication. =back =head2 Blocks =over 4 =item B =item B Get the list of accounts blocked by the authenticated user. Global GET parameters are available for this method. Depending on the value of C, returns an array reference of Mastodon::Entity::Account objects, or a plain array reference. =back =head2 Favourites =over 4 =item B =item B Get the list of statuses favourited by the authenticated user. Global GET parameters are available for this method. Depending on the value of C, returns an array reference of Mastodon::Entity::Status objects, or a plain array reference. =back =head2 Follow requests =over 4 =item B =item B Get the list of accounts requesting to follow the the authenticated user. Global GET parameters are available for this method. Depending on the value of C, returns an array reference of Mastodon::Entity::Account objects, or a plain array reference. =item B =item B Accept or reject the follow request by the account of the specified ID. The ID argument is mandatory. Returns an empty object. =back =head2 Follows =over 4 =item B Follow a remote user by account string (ie. C). The argument is mandatory. Depending on the value of C, returns an Mastodon::Entity::Account object, or a plain hash reference with the local representation of the specified account. =back =head2 Instances =over 4 =item B Fetches the latest information for the current instance the client is talking to. When successful, this method updates the value of the C attribute. Depending on the value of C, returns an Mastodon::Entity::Instance object, or a plain hash reference. This method does not require authentication. =back =head2 Media =over 4 =item B =item B Upload a file as an attachment. Takes a mandatory argument with the name of a local file to encode and upload, and an optional hash reference with the following additional parameters: =over 4 =item B A plain-text description of the media, for accessibility, as a string. =item B An array reference of two floating point values, to be used as the x and y focus values. These inform clients which point in the image is the most important one to show in a cropped view. The value of a coordinate is a real number between -1 and +1, where 0 is the center. x:-1 indicates the left edge of the image, x:1 the right edge. For the y axis, y:1 is the top edge and y:-1 is the bottom. =back Depending on the value of C, returns an Mastodon::Entity::Attachment object, or a plain hash reference. The returned object's ID can be passed to the B to post it to a timeline. =back =head2 Mutes =over 4 =item B =item B Get the list of accounts muted by the authenticated user. Global GET parameters are available for this method. Depending on the value of C, returns an array reference of Mastodon::Entity::Account objects, or a plain array reference. =back =head2 Notifications =over 4 =item B =item B Get the list of notifications for the authenticated user. Global GET parameters are available for this method. Depending on the value of C, returns an array reference of Mastodon::Entity::Notification objects, or a plain array reference. =item B Get a notification by ID. The argument is mandatory. Depending on the value of C, returns an Mastodon::Entity::Notification object, or a plain hash reference. =item B Clears all notifications for the authenticated user. This method takes no arguments and returns an empty object. =back =head2 Reports =over 4 =item B =item B Get a list of reports made by the authenticated user. Global GET parameters are available for this method. Depending on the value of C, returns an array reference of Mastodon::Entity::Report objects, or a plain array reference. =item B Report a user or status. Takes a mandatory hash with the following keys: =over 4 =item B The ID of a single account to report. =item B The ID of a single status to report, or an array reference of statuses to report. =item B An optional string. =back While the comment is always optional, either the B or the list of B must be present. Depending on the value of C, returns the new Mastodon::Entity::Report object, or a plain hash reference. =back =head2 Search =over 4 =item B =item B Search for content. Takes a mandatory string argument to use as the search query. If the search query is a URL, Mastodon will attempt to fetch the provided account or status. Otherwise, it will do a local account and hashtag search. In addition to the global GET parameters, this method accepts the following parameters: =over 4 =item B Whether to resolve non-local accounts. =back =back =head2 Statuses =over 4 =item B =item B Fetches a status by ID. The ID argument is mandatory. Global GET parameters are available for this method as an additional hash reference. Depending on the value of C, it returns a Mastodon::Entity::Status object, or a plain hash reference. This method does not require authentication. =item B =item B 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. Depending on the value of C, it returns a Mastodon::Entity::Context object, or a plain hash reference. This method does not require authentication. =item B =item B Fetches a card associated to a status by ID. The ID argument is mandatory. Global GET parameters are available for this method as an additional hash reference. Depending on the value of C, it returns a Mastodon::Entity::Card object, or a plain hash reference. This method does not require authentication. =item B =item B =item B =item B Fetches a list of accounts who have reblogged or favourited a status by ID. The ID argument is mandatory. Global GET parameters are available for this method as an additional hash reference. Depending on the value of C, it returns an array reference of Mastodon::Entity::Account objects, or a plain array reference. This method does not require authentication. =item B =item B Posts a new status. Takes a mandatory string as the content of the status (which can be the empty string), and an optional hash reference with the following additional parameters: =over 4 =item B The content of the status, as a string. Since this is already provided as the first argument of the method, this is not necessary. But if provided, this value will overwrite that of the first argument. =item B The optional ID of a status to reply to. =item B An array reference of up to four media IDs. These can be obtained as the result of a call to B. =item B Boolean, to mark status content as NSFW. =item B A string, to be shown as a warning before the actual content. =item B A string; one of C, C, C, or C. =back Depending on the value of C, it returns the new Mastodon::Entity::Status object, or a plain hash reference. =item B Delete a status by ID. The ID is mandatory. Returns an empty object. =item B =item B =item B =item B Reblog or favourite a status by ID, or revert this action. The ID argument is mandatory. Depending on the value of C, it returns the specified Mastodon::Entity::Status object, or a plain hash reference. =back =head2 Timelines =over 4 =item B =item B Retrieves a timeline. The first argument defines either the name of a timeline (which can be one of C or C), or a hashtag (if it begins with the C<#> character). This argument is mandatory. In addition to the global GET parameters, this method accepts the following parameters: Accessing the public and tag timelines does not require authentication. =over 4 =item B Boolean. If true, limits results only to those originating from the current instance. Only applies to public and tag timelines. =back Depending on the value of C, it returns an array of Mastodon::Entity::Status objects, or a plain array reference. The more recent statuses come first. =back =head1 STREAMING RESULTS Alternatively, it is possible to use the streaming API to get a constant stream of updates. To do this, there is the B method. =over 4 =item B Creates a Mastodon::Listener object which will fetch a stream for the specified query. Possible values for the query are either C, for events that are relevant to the authorized user; C, for all public statuses; or a tag (if it begins with the C<#> character), for all public statuses for the particular tag. For more details on how to use this object, see the documentation for L. Accessing streaming public timeline does not require authentication. =back =head1 REQUEST METHODS Mastodon::Client uses four lower-level request methods to contact the API with GET, POST, PATCH, and DELETE requests. These are left available in case one of the higher-level convenience methods are unsuitable or undesirable, but you use them at your own risk. They all take a URL as their first parameter, which can be a string with the API endpoint to contact, or a L object, which will be used as-is. If passed as a string, the methods expect one that contains only the variable parts of the endpoint (ie. not including the C part). The remaining parts will be filled-in appropriately internally. =over 4 =item B =item B =item B Query parameters can be passed as part of the L object, but it is not recommended you do so, since Mastodon has expectations for array parameters that do not meet those of eg. L. It will be easier and safer if any additional parameters are passed as a hash reference, which will be added to the URL before the request is sent. =item B =item B =item B =item B the C and C methods work similarly to C and C, but the optional hash reference is sent in as form data, instead of processed as query parameters. The Mastodon API does not use query parameters on POST or PATCH endpoints. =back =head1 CONTRIBUTIONS AND BUG REPORTS Contributions of any kind are most welcome! The main repository for this distribution is on L, which is where patches and bug reports are mainly tracked. The repository is also mirrored on L, in case that platform makes it easier to post contributions. If none of the above is acceptable, bug reports can also be sent through the CPAN RT system, or by mail directly to the developers at the address below, although these will not be as closely tracked. =head1 AUTHOR =over 4 =item * José Joaquín Atria =back =head1 CONTRIBUTORS =over 4 =item * Lance Wicks =back =head1 COPYRIGHT AND LICENSE This software is copyright (c) 2017 by José Joaquín Atria. This is free software; you can redistribute it and/or modify it under the same terms as the Perl 5 programming language system itself. =cut Mastodon-Client-0.017/lib/Mastodon/Role/0000775000175000017500000000000013646430665017625 5ustar jjatriajjatriaMastodon-Client-0.017/lib/Mastodon/Role/Entity.pm0000644000175000017500000000022413646430665021433 0ustar jjatriajjatriapackage Mastodon::Role::Entity; use strict; use warnings; our $VERSION = '0.017'; use Moo::Role; has _client => ( is => 'rw', weaken => 1); 1; Mastodon-Client-0.017/lib/Mastodon/Role/UserAgent.pm0000644000175000017500000001222013646430665022053 0ustar jjatriajjatriapackage Mastodon::Role::UserAgent; use strict; use warnings; our $VERSION = '0.017'; use v5.10.0; use Moo::Role; use Log::Any; my $log = Log::Any->get_logger( category => 'Mastodon' ); use URI::QueryParam; use List::Util qw( any ); use Types::Standard qw( ArrayRef Dict HashRef Maybe Num Optional Str Undef slurpy ); use Mastodon::Types qw( HTTPResponse Instance URI UserAgent to_Entity ); use Type::Params qw( compile ); use Carp; has instance => ( is => 'rw', isa => Instance, default => 'https://mastodon.social', coerce => 1, ); has api_version => ( is => 'ro', isa => Num, default => 1, ); has redirect_uri => ( is => 'ro', isa => Str, lazy => 1, default => 'urn:ietf:wg:oauth:2.0:oob', ); has user_agent => ( is => 'ro', isa => UserAgent, default => sub { require HTTP::Thin; HTTP::Thin->new; }, ); has latest_response => ( is => 'ro', isa => Maybe[HTTPResponse], init_args => undef, ); sub authorization_url { my $self = shift; unless ($self->client_id and $self->client_secret) { croak $log->fatal( 'Cannot get authorization URL without client_id and client_secret' ); } state $check = compile( slurpy Dict [ access_code => Optional [Instance] ] ); my ($params) = $check->(@_); $params->{instance} //= $self->instance; my $uri = URI->new('/oauth/authorize')->abs($params->{instance}->uri); $uri->query_param(redirect_uri => $self->redirect_uri); $uri->query_param(response_type => 'code'); $uri->query_param(client_id => $self->client_id); $uri->query_param(scope => join q{ }, sort(@{$self->scopes})); return $uri; } sub post { shift->_request( post => shift, data => shift, @_ ) } sub patch { shift->_request( patch => shift, data => shift, @_ ) } sub get { shift->_request( get => shift, params => shift, @_ ) } sub delete { shift->_request( delete => shift, params => shift, @_ ) } sub _request { my $self = shift; my $method = shift; my $url = shift; my $args = { @_ }; my $headers = $args->{headers} // {}; my $data = $self->_prepare_data($args->{data}); $url = $self->_prepare_params($url, $args->{params}); $method = uc($method); if ($self->can('access_token') and $self->access_token) { $headers = { Authorization => 'Bearer ' . $self->access_token, %{$headers}, }; } if ($log->is_trace) { require Data::Dumper; $log->debugf('Method: %s', $method); $log->debugf('URL: %s', $url); $log->debugf('Headers: %s', Data::Dumper::Dumper( $headers )); $log->debugf('Data: %s', Data::Dumper::Dumper( $data )); } use Try::Tiny; return try { my @args = $url; push @args, [%{$data}] unless $method eq 'GET'; @args = (@args, %{$headers}); require HTTP::Request::Common; my $type = ($method eq 'PATCH') ? 'POST' : $method; my $request = HTTP::Request::Common->can($type)->( @args ); $request->method($method); my $response = $self->user_agent->request( $request ); use JSON::MaybeXS qw( decode_json ); use Encode qw( encode ); # We want to be able to set it, but do not want the user to do so $self->{latest_response} = $response; die $response->status_line unless $response->is_success; my $payload = decode_json encode('utf8', $response->decoded_content); # Some API calls return empty objects, which cannot be coerced if ($response->decoded_content ne '{}') { if ($url !~ /(?:apps|oauth)/ and $self->coerce_entities) { $payload = (ref $payload eq 'ARRAY') ? [ map { to_Entity({ %{$_}, _client => $self }) } @{$payload} ] : to_Entity({ %{$payload}, _client => $self }); } } if (ref $payload eq 'ARRAY') { die $payload->{error} if any { defined $_->{error} } @{$payload}; } elsif (ref $payload eq 'HASH') { die $payload->{error} if defined $payload->{error}; } return $payload; } catch { my $msg = sprintf 'Could not complete request: %s', $_; $log->fatal($msg); croak $msg; }; } sub _prepare_data { my ($self, $data) = @_; $data //= {}; foreach my $key (keys %{$data}) { # Array parameters to the API need keys that are marked with [] # However, HTTP::Request::Common expects an arrayref to encode files # for transfer, even though the API does not expect that to be an array # So we need to manually skip it, unless we come up with another solution. next if $key eq 'file'; my $val = $data->{$key}; $data->{$key . '[]'} = delete($data->{$key}) if ref $val eq 'ARRAY'; } return $data; } sub _prepare_params { my ($self, $url, $params) = @_; $params //= {}; croak 'Cannot make a request without a URL' unless $url; unless (ref $url eq 'URI') { my $base = $url =~ m{^/oauth/} ? '/' : '/api/v' . $self->api_version . '/'; $url = URI->new( $self->instance->uri . $base . $url ); } # Adjust query param format to be Ruby-compliant foreach my $key (keys %{$params}) { my $val = $params->{$key}; if (ref $val eq 'ARRAY') { $url->query_param($key . '[]' => @{$val}) } else { $url->query_param($key => $val) } } return $url; } 1; Mastodon-Client-0.017/lib/Mastodon/Listener.pm0000644000175000017500000001534513646430665021055 0ustar jjatriajjatriapackage Mastodon::Listener; use strict; use warnings; our $VERSION = '0.017'; use Moo; with 'Role::EventEmitter'; use Types::Standard qw( Int Str Bool ); use Mastodon::Types qw( Instance to_Status to_Notification ); use IO::Async::Loop; use Net::Async::HTTP; use Try::Tiny; use JSON::MaybeXS qw( decode_json ); use Log::Any; my $log = Log::Any->get_logger(category => 'Mastodon'); has instance => ( is => 'ro', isa => Instance, coerce => 1, default => 'mastodon.cloud', ); has api_version => ( is => 'ro', isa => Int, default => 1, ); has url => ( is => 'ro', lazy => 1, default => sub { $_[0]->instance . '/api/v' . $_[0]->api_version . '/streaming/' . $_[0]->stream; }, ); has stream => ( is => 'ro', lazy => 1, default => 'public', ); has access_token => ( is => 'ro', required => 1, ); has _ua => ( is => 'rw', init_arg => undef, default => sub { Net::Async::HTTP->new }, ); has _future => ( is => 'rw', init_arg => undef, lazy => 1, default => sub { Future->new }, ); has coerce_entities => ( is => 'rw', isa => Bool, lazy => 1, default => 0, ); sub BUILD { my ($self, $arg) = @_; IO::Async::Loop->new->add($self->_ua); } sub start { my $self = shift; my $on_error = sub { $self->emit( error => shift, shift, \@_ ) }; $self->_future( $self->_ua->do_request( uri => $self->url, headers => { Authorization => 'Bearer ' . $self->access_token, }, on_error => sub { $on_error->( 1, shift, \@_ ) }, on_header => sub { my $response = shift; $on_error->( 1, $response->message, $response ) unless $response->is_success; my $current_event; my $buffer = ''; return sub { my $chunk = shift; # We do not have enough data yet, add it to the buffer unless ( $chunk =~ /\n$/m ) { $buffer .= $chunk; return; } $chunk = $buffer . $chunk; if ( $chunk =~ /^(:thump|event: (\w+))$/m ) { my $line = $1; my $event = $2; unless ( defined $event ) { # Heartbeats have no data $self->emit( 'heartbeat' ); return; } $current_event = $event; $chunk =~ s/$line\n//; } if ( $chunk =~ /^data:/ ) { $chunk =~ s/^data:\s+//; if ( $current_event eq 'delete' ) { # The payload for delete is a single integer $self->emit( delete => $chunk ); } else { try { my $payload = decode_json $chunk; $self->emit( $current_event => $payload ); } catch { $self->emit( error => 0, "Error decoding JSON payload: $_", $chunk ); }; } undef $current_event; } $buffer = ''; return; } }, ) ); $self->_future->get; } sub stop { my $self = shift; $self->_future->done(@_) unless $self->_future->is_ready; return $self; } sub reset { my $self = shift; $self->stop->start; } around emit => sub { my $orig = shift; my $self = shift; my ($event, $data, @rest) = @_; if ($event =~ /(update|notification)/ and $self->coerce_entities) { $data = to_Notification($data) if $event eq 'notification'; $data = to_Status($data) if $event eq 'update'; } $self->$orig($event, $data, @rest); }; 1; __END__ =encoding utf8 =head1 NAME Mastodon::Listener - Access the streaming API of a Mastodon server =head1 SYNOPSIS # From Mastodon::Client my $listener = $client->stream( 'public' ); # Or use it directly my $listener = Mastodon::Listener->new( url => 'https://mastodon.cloud/api/v1/streaming/public', access_token => $token, coerce_entities => 1, ) $listener->on( update => sub { my ($listener, $status) = @_; printf "%s said: %s\n", $status->account->display_name, $status->content; }); $listener->start; =head1 DESCRIPTION A Mastodon::Listener object is created by calling the B method from a L, and it exists for the sole purpose of parsing a stream of events from a Mastodon server. Mastodon::Listener objects inherit from L. Please refer to its documentation for details on how to register callbacks for the different events. Once callbacks have been registered, the listener can be set in motion by calling its B method, which takes no arguments and never returns. The B method can be called from within callbacks to disconnect from the stream. =head1 ATTRIBUTES =over 4 =item B The OAuth2 access token of your application, if authorization is needed. This is not needed for streaming from public timelines. =item B The API version to use. Defaults to C<1>. =item B Whether JSON responses should be coerced into Mastodon::Entity objects. Currently defaults to false (but this will likely change in v0.01). =item B The instance to use, as a L object. Will be coerced from a URL, and defaults to C. =item B The stream to use. Current valid streams are C, C, and tag timelines. To access a tag timeline, the argument to this value should begin with a hash character (C<#>). =item B The full streaming URL to use. By default, it is constructed from the values in the B, B, and B attributes. =back =head1 EVENTS =over 4 =item B A new status has appeared. Callback will be called with the listener and the new status. =item B A new notification has appeared. Callback will be called with the listener and the new notification. =item B A status has been deleted. Callback will be called with the listener and the ID of the deleted status. =item B A new C<:thump> has been received from the server. This is mostly for debugging purposes. =item B Inherited from L, will be emitted when an error was found. The callback will be called with a fatal flag, an error message, and any relevant data as a single third arghument. If the error event is triggered in response to a 4xx or 5xx error, the data payload will be an array reference with the response and request objects as received from L. =back =head1 AUTHOR =over 4 =item * José Joaquín Atria =back =head1 COPYRIGHT AND LICENSE This software is copyright (c) 2017 by José Joaquín Atria. This is free software; you can redistribute it and/or modify it under the same terms as the Perl 5 programming language system itself. =cut Mastodon-Client-0.017/lib/Mastodon/Types.pm0000644000175000017500000000515313646430665020370 0ustar jjatriajjatriapackage Mastodon::Types; use strict; use warnings; our $VERSION = '0.017'; use Type::Library -base; use Type::Utils -all; use Types::Standard qw( Str HashRef Num ); use Types::Path::Tiny qw( File to_File); use URI; use DateTime; use MIME::Base64; use Class::Load qw( load_class ); duck_type 'UserAgent', [qw( get post delete )]; class_type 'URI', { class => 'URI' }; coerce 'URI', from Str, via { s{^/+}{}g; my $uri = URI->new((m{^https?://} ? q{} : 'https://') . $_); $uri->scheme('https') unless $uri->scheme; return $uri; }; # We provide our own DateTime type because the Types::DateTime distribution # is currently undermaintained class_type 'DateTime', { class => 'DateTime' }; class_type 'HTTPResponse', { class => 'HTTP::Response' }; coerce 'DateTime', from Num, via { 'DateTime'->from_epoch( epoch => $_ ) } from Str, via { require DateTime::Format::Strptime; DateTime::Format::Strptime->new( pattern => '%FT%T.%3N%Z', on_error => 'croak', )->parse_datetime($_); }; # Validation here could be improved # It is either a username if a local account, or a username@instance.tld # but what characters are valid? declare 'Acct', as Str; declare 'Image', as Str, where { m{^data:image/(?:png|jpeg);base64,[a-zA-Z0-9/+=\n]+$} }; coerce File, from Str, via { require Path::Tiny; return Path::Tiny::path( $_ ); }; coerce 'Image', from File->coercibles, via { my $file = to_File($_); require Image::Info; require MIME::Base64; my $type = lc Image::Info::image_type( $file->stringify )->{file_type}; my $img = "data:image/$type;base64," . MIME::Base64::encode_base64( $file->slurp_raw ); return $img; }; # Entity types my @entities = qw( Status Account Instance Attachment Card Context Mention Notification Relationship Report Results Error Tag Application ); foreach my $name (@entities) { class_type $name, { class => "Mastodon::Entity::$name" }; coerce $name, from HashRef, via { load_class "Mastodon::Entity::$name"; "Mastodon::Entity::$name"->new($_); }; } role_type 'Entity', { role => 'Mastodon::Role::Entity' }; coerce 'Instance', from Str, via { require Mastodon::Entity::Instance; Mastodon::Entity::Instance->new({ uri => $_, }); }; coerce 'Entity', from HashRef, via { my $hash = $_; my $entity; use Try::Tiny; foreach my $name (@entities) { $entity = try { load_class "Mastodon::Entity::$name"; "Mastodon::Entity::$name"->new($hash); }; last if defined $entity; } return $entity; }; 1; Mastodon-Client-0.017/lib/Mastodon/Entity/0000775000175000017500000000000013646430665020200 5ustar jjatriajjatriaMastodon-Client-0.017/lib/Mastodon/Entity/Notification.pm0000644000175000017500000000325713646430665023171 0ustar jjatriajjatriapackage Mastodon::Entity::Notification; use strict; use warnings; our $VERSION = '0.017'; use Moo; with 'Mastodon::Role::Entity'; use Types::Standard qw( Int Enum ); use Mastodon::Types qw( Status DateTime Account ); has account => ( is => 'ro', isa => Account, ); has created_at => ( is => 'ro', isa => DateTime, ); has id => ( is => 'ro', isa => Int, ); has status => ( is => 'ro', isa => Status, required => 1, coerce => 1, ); has type => ( is => 'ro', isa => Enum[qw( mention reblog favourite follow )], ); 1; =encoding utf8 =head1 NAME Mastodon::Entity::Notification - A Mastodon notification =head1 DESCRIPTION This object should not be manually created. It is intended to be generated from the data received from a Mastodon server using the coercions in L. For current information, see the L =head1 ATTRIBUTES =over 4 =item B The notification ID. =item B One of: "mention", "reblog", "favourite", "follow". =item B The time the notification was created. =item B The Account sending the notification to the user as a L object. =item B The Status associated with the notification, if applicable. As a L object. =back =head1 AUTHOR =over 4 =item * José Joaquín Atria =back =head1 COPYRIGHT AND LICENSE This software is copyright (c) 2017 by José Joaquín Atria. This is free software; you can redistribute it and/or modify it under the same terms as the Perl 5 programming language system itself. =cut Mastodon-Client-0.017/lib/Mastodon/Entity/Card.pm0000644000175000017500000000257713646430665021420 0ustar jjatriajjatriapackage Mastodon::Entity::Card; use strict; use warnings; our $VERSION = '0.017'; use Moo; with 'Mastodon::Role::Entity'; use Types::Standard qw( Any Str ); use Mastodon::Types qw( URI ); has description => ( is => 'ro', isa => Str, required => 1, ); has image => ( is => 'ro', isa => Any, ); # What type of data is this? has title => ( is => 'ro', isa => Str, ); has url => ( is => 'ro', isa => URI, coerce => 1, required => 1, ); 1; =encoding utf8 =head1 NAME Mastodon::Entity::Card - A Mastodon card =head1 DESCRIPTION This object should not be manually created. It is intended to be generated from the data received from a Mastodon server using the coercions in L. For current information, see the L =head1 ATTRIBUTES =over 4 =item B The url associated with the card. =item B The title of the card. =item B<description> The card description. =item B<image> The image associated with the card, if any. =back =head1 AUTHOR =over 4 =item * José Joaquín Atria <jjatria@cpan.org> =back =head1 COPYRIGHT AND LICENSE This software is copyright (c) 2017 by José Joaquín Atria. This is free software; you can redistribute it and/or modify it under the same terms as the Perl 5 programming language system itself. =cut ���������������������������������������������������������������������������������������������������������������������������������Mastodon-Client-0.017/lib/Mastodon/Entity/Context.pm������������������������������������������������0000644�0001750�0001750�00000002521�13646430665�022160� 0����������������������������������������������������������������������������������������������������ustar �jjatria�������������������������jjatria����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������package Mastodon::Entity::Context; use strict; use warnings; our $VERSION = '0.017'; use Moo; with 'Mastodon::Role::Entity'; use Types::Standard qw( ArrayRef ); use Mastodon::Types qw( Status ); has ancestors => ( is => 'ro', isa => ArrayRef [Status], required => 1, ); has descendants => ( is => 'ro', isa => ArrayRef [Status], ); 1; =encoding utf8 =head1 NAME Mastodon::Entity::Context - The context of a Mastodon status =head1 DESCRIPTION This object should not be manually created. It is intended to be generated from the data received from a Mastodon server using the coercions in L<Mastodon::Types>. For current information, see the L<Mastodon API documentation|https://github.com/tootsuite/documentation/blob/master/Using-the-API/API.md#context> =head1 ATTRIBUTES =over 4 =item B<ancestors> The ancestors of the status in the conversation, as a list of L<Mastodon::Entity::Status> objects. =item B<descendants> The descendants of the status in the conversation, as a list of L<Mastodon::Entity::Status> objects. =back =head1 AUTHOR =over 4 =item * José Joaquín Atria <jjatria@cpan.org> =back =head1 COPYRIGHT AND LICENSE This software is copyright (c) 2017 by José Joaquín Atria. This is free software; you can redistribute it and/or modify it under the same terms as the Perl 5 programming language system itself. =cut �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������Mastodon-Client-0.017/lib/Mastodon/Entity/Results.pm������������������������������������������������0000644�0001750�0001750�00000002571�13646430665�022202� 0����������������������������������������������������������������������������������������������������ustar �jjatria�������������������������jjatria����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������package Mastodon::Entity::Results; use strict; use warnings; our $VERSION = '0.017'; use Moo; with 'Mastodon::Role::Entity'; use Types::Standard qw( Str ArrayRef ); use Mastodon::Types qw( Account Status ); has accounts => ( is => 'ro', isa => ArrayRef [Account], ); has hashtags => ( is => 'ro', isa => ArrayRef [Str], required => 1, ); # Not Tag! has statuses => ( is => 'ro', isa => ArrayRef [Status], ); 1; =encoding utf8 =head1 NAME Mastodon::Entity::Results - A Mastodon search result =head1 DESCRIPTION This object should not be manually created. It is intended to be generated from the data received from a Mastodon server using the coercions in L<Mastodon::Types>. For current information, see the L<Mastodon API documentation|https://github.com/tootsuite/documentation/blob/master/Using-the-API/API.md#results> =head1 ATTRIBUTES =over 4 =item B<accounts> An array of matched L<Mastodon::Entity::Account> objects. =item B<statuses> An array of matchhed L<Mastodon::Entity::Status> objects. =item B<hashtags> An array of matched hashtags, as strings. =back =head1 AUTHOR =over 4 =item * José Joaquín Atria <jjatria@cpan.org> =back =head1 COPYRIGHT AND LICENSE This software is copyright (c) 2017 by José Joaquín Atria. This is free software; you can redistribute it and/or modify it under the same terms as the Perl 5 programming language system itself. =cut ���������������������������������������������������������������������������������������������������������������������������������������Mastodon-Client-0.017/lib/Mastodon/Entity/Relationship.pm�������������������������������������������0000644�0001750�0001750�00000003301�13646430665�023172� 0����������������������������������������������������������������������������������������������������ustar �jjatria�������������������������jjatria����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������package Mastodon::Entity::Relationship; use strict; use warnings; our $VERSION = '0.017'; use Moo; with 'Mastodon::Role::Entity'; use Types::Standard qw( Int Bool ); has id => ( is => 'ro', isa => Int, ); has blocking => ( is => 'ro', isa => Bool, coerce => 1, ); has followed_by => ( is => 'ro', isa => Bool, coerce => 1, ); has following => ( is => 'ro', isa => Bool, coerce => 1, ); has muting => ( is => 'ro', isa => Bool, coerce => 1, required => 1, ); has requested => ( is => 'ro', isa => Bool, coerce => 1, ); 1; =encoding utf8 =head1 NAME Mastodon::Entity::Relationship - A Mastodon relationship =head1 DESCRIPTION This object should not be manually created. It is intended to be generated from the data received from a Mastodon server using the coercions in L<Mastodon::Types>. For current information, see the L<Mastodon API documentation|https://github.com/tootsuite/documentation/blob/master/Using-the-API/API.md#relationship> =head1 ATTRIBUTES =over 4 =item B<id> Target account id. =item B<following> Whether the user is currently following the account. =item B<followed_by> Whether the user is currently being followed by the account. =item B<blocking> Whether the user is currently blocking the account. =item B<muting> Whether the user is currently muting the account. =item B<requested> Whether the user has requested to follow the account. =back =head1 AUTHOR =over 4 =item * José Joaquín Atria <jjatria@cpan.org> =back =head1 COPYRIGHT AND LICENSE This software is copyright (c) 2017 by José Joaquín Atria. This is free software; you can redistribute it and/or modify it under the same terms as the Perl 5 programming language system itself. =cut �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������Mastodon-Client-0.017/lib/Mastodon/Entity/Account.pm������������������������������������������������0000644�0001750�0001750�00000011553�13646430665�022135� 0����������������������������������������������������������������������������������������������������ustar �jjatria�������������������������jjatria����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������package Mastodon::Entity::Account; use strict; use warnings; our $VERSION = '0.017'; use Moo; with 'Mastodon::Role::Entity'; use Types::Standard qw( Int Str Bool ); use Mastodon::Types qw( Acct URI DateTime ); use Log::Any; my $log = Log::Any->get_logger( category => 'Mastodon' ); has acct => ( is => 'ro', isa => Acct, required => 1, ); has avatar => ( is => 'ro', isa => URI, coerce => 1, required => 1, ); has avatar_static => ( is => 'ro', isa => URI, coerce => 1, ); has created_at => ( is => 'ro', isa => DateTime, coerce => 1, ); has display_name => ( is => 'ro', isa => Str, ); has followers_count => ( is => 'ro', isa => Int, ); has following_count => ( is => 'ro', isa => Int, ); has header => ( is => 'ro', isa => URI, coerce => 1, ); has header_static => ( is => 'ro', isa => URI, coerce => 1, ); has id => ( is => 'ro', isa => Int, ); has locked => ( is => 'ro', isa => Bool, coerce => 1, ); has note => ( is => 'ro', isa => Str, ); has statuses_count => ( is => 'ro', isa => Int, ); has url => ( is => 'ro', isa => URI, coerce => 1, ); has username => ( is => 'ro', isa => Str, ); foreach my $pair ( [ fetch => 'get_account' ], [ followers => undef ], [ following => undef ], [ statuses => undef ], [ follow => undef ], [ unfollow => undef ], [ block => undef ], [ unblock => undef ], [ mute => undef ], [ unmute => undef ], [ relationship => 'relationships' ], [ authorize => 'authorize_follow' ], [ reject => 'reject_follow' ], ) { my ($name, $method) = @{$pair}; $method //= $name; no strict 'refs'; *{ __PACKAGE__ . '::' . $name } = sub { my $self = shift; croak $log->fatal(qq{Cannot call '$name' without client}) unless $self->_client; $self->_client->$method($self->id, @_); }; } sub remote_follow { my $self = shift; croak $log->fatal(q{Cannot call 'remote_follow' without client}) unless $self->_client; $self->_client->remote_follow($self->acct, @_); } sub report { my ($self, $params) = @_; croak $log->fatal(q{Cannot call 'report' without client}) unless $self->_client; $self->_client->report({ %{$params}, account_id => $self->id, }); } 1; =encoding utf8 =head1 NAME Mastodon::Entity::Account - A Mastodon user account =head1 DESCRIPTION This object should not be manually created. It is intended to be generated from the data received from a Mastodon server using the coercions in L<Mastodon::Types>. For current information, see the L<Mastodon API documentation|https://github.com/tootsuite/documentation/blob/master/Using-the-API/API.md#account> =head1 ATTRIBUTES =over 4 =item B<id> The ID of the account =item B<username> The username of the account =item B<acct> Equals C<username> for local users, includes C<@domain> for remote ones =item B<display_name> The account's display name =item B<locked> Boolean for when the account cannot be followed without waiting for approval first =item B<created_at> The time the account was created =item B<followers_count> The number of followers for the account =item B<following_count> The number of accounts the given account is following =item B<statuses_count> The number of statuses the account has made =item B<note> Biography of user =item B<url> URL of the user's profile page (can be remote) =item B<avatar> URL to the avatar image =item B<avatar_static> URL to the avatar static image (gif) =item B<header> URL to the header image =item B<header_static> URL to the header static image (gif) =back =head1 METHODS This class provides the following convenience methods. They act as a shortcut, passing the appropriate identifier of the current object as the first argument to the corresponding methods in L<Mastodon::Client>. =over 4 =item B<fetch> A shortcut to C<get_account>. =item B<followers> A shortcut to C<followers>. =item B<following> A shortcut to C<following>. =item B<statuses> A shortcut to C<statuses>. =item B<follow> A shortcut to C<follow>. =item B<unfollow> A shortcut to C<unfollow>. =item B<remote_follow> A shortcut to C<remote_follow>. =item B<report> A shortcut to C<report>. =item B<block> A shortcut to C<block>. =item B<unblock> A shortcut to C<unblock>. =item B<mute> A shortcut to C<mute>. =item B<unmute> A shortcut to C<unmute>. =item B<relationship> A shortcut to C<relationships>. =item B<authorize> A shortcut to C<authorize_follow>. =item B<reject> A shortcut to C<reject_follow>. =back =head1 AUTHOR =over 4 =item * José Joaquín Atria <jjatria@cpan.org> =back =head1 COPYRIGHT AND LICENSE This software is copyright (c) 2017 by José Joaquín Atria. This is free software; you can redistribute it and/or modify it under the same terms as the Perl 5 programming language system itself. =cut �����������������������������������������������������������������������������������������������������������������������������������������������������Mastodon-Client-0.017/lib/Mastodon/Entity/Tag.pm����������������������������������������������������0000644�0001750�0001750�00000002200�13646430665�021241� 0����������������������������������������������������������������������������������������������������ustar �jjatria�������������������������jjatria����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������package Mastodon::Entity::Tag; use strict; use warnings; our $VERSION = '0.017'; use Moo; with 'Mastodon::Role::Entity'; use Types::Standard qw( Str ); use Mastodon::Types qw( URI ); has name => ( is => 'ro', isa => Str ); has url => ( is => 'ro', isa => URI, coerce => 1, required => 1); 1; =encoding utf8 =head1 NAME Mastodon::Entity::Tag - A tag in Mastodon =head1 DESCRIPTION This object should not be manually created. It is intended to be generated from the data received from a Mastodon server using the coercions in L<Mastodon::Types>. For current information, see the L<Mastodon API documentation|https://github.com/tootsuite/documentation/blob/master/Using-the-API/API.md#tag> =head1 ATTRIBUTES =over 4 =item B<name> The hashtag, not including the preceding C<#>. =item B<url> The URL of the hashtag. =back =head1 AUTHOR =over 4 =item * José Joaquín Atria <jjatria@cpan.org> =back =head1 COPYRIGHT AND LICENSE This software is copyright (c) 2017 by José Joaquín Atria. This is free software; you can redistribute it and/or modify it under the same terms as the Perl 5 programming language system itself. =cut ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������Mastodon-Client-0.017/lib/Mastodon/Entity/Attachment.pm���������������������������������������������0000644�0001750�0001750�00000003340�13646430665�022624� 0����������������������������������������������������������������������������������������������������ustar �jjatria�������������������������jjatria����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������package Mastodon::Entity::Attachment; use strict; use warnings; our $VERSION = '0.017'; use Moo; with 'Mastodon::Role::Entity'; use Types::Standard qw( Maybe Enum Int ); use Mastodon::Types qw( URI ); has id => ( is => 'ro', isa => Int, ); has preview_url => ( is => 'ro', isa => URI, coerce => 1, required => 1, ); has remote_url => ( is => 'ro', isa => Maybe [URI], coerce => 1, ); has text_url => ( is => 'ro', isa => Maybe [URI], coerce => 1, ); has url => ( is => 'ro', isa => URI, coerce => 1, ); has type => ( is => 'ro', isa => Enum[qw( image video gifv )], ); 1; =encoding utf8 =head1 NAME Mastodon::Entity::Attachment - A Mastodon media attachment =head1 DESCRIPTION This object should not be manually created. It is intended to be generated from the data received from a Mastodon server using the coercions in L<Mastodon::Types>. For current information, see the L<Mastodon API documentation|https://github.com/tootsuite/documentation/blob/master/Using-the-API/API.md#attachment> =head1 ATTRIBUTES =over 4 =item B<id> ID of the attachment. =item B<type> One of: "image", "video", "gifv". =item B<url> URL of the locally hosted version of the image. =item B<remote_url> For remote images, the remote URL of the original image. =item B<preview_url> URL of the preview image =item B<text_url> Shorter URL for the image, for insertion into text (only present on local images). =back =head1 AUTHOR =over 4 =item * José Joaquín Atria <jjatria@cpan.org> =back =head1 COPYRIGHT AND LICENSE This software is copyright (c) 2017 by José Joaquín Atria. This is free software; you can redistribute it and/or modify it under the same terms as the Perl 5 programming language system itself. =cut ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������Mastodon-Client-0.017/lib/Mastodon/Entity/Report.pm�������������������������������������������������0000644�0001750�0001750�00000002207�13646430665�022010� 0����������������������������������������������������������������������������������������������������ustar �jjatria�������������������������jjatria����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������package Mastodon::Entity::Report; use strict; use warnings; our $VERSION = '0.017'; use Moo; with 'Mastodon::Role::Entity'; use Types::Standard qw( Int Bool ); has id => ( is => 'ro', isa => Int, ); has action_taken => ( is => 'ro', isa => Bool, coerce => 1, required => 1, ); 1; =encoding utf8 =head1 NAME Mastodon::Entity::Report- A Mastodon report =head1 DESCRIPTION This object should not be manually created. It is intended to be generated from the data received from a Mastodon server using the coercions in L<Mastodon::Types>. For current information, see the L<Mastodon API documentation|https://github.com/tootsuite/documentation/blob/master/Using-the-API/API.md#report> =head1 ATTRIBUTES =over 4 =item B<id> Target id of the report. =item B<action_taken> The action taken in response to the report. =back =head1 AUTHOR =over 4 =item * José Joaquín Atria <jjatria@cpan.org> =back =head1 COPYRIGHT AND LICENSE This software is copyright (c) 2017 by José Joaquín Atria. This is free software; you can redistribute it and/or modify it under the same terms as the Perl 5 programming language system itself. =cut �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������Mastodon-Client-0.017/lib/Mastodon/Entity/Instance.pm�����������������������������������������������0000644�0001750�0001750�00000002601�13646430665�022277� 0����������������������������������������������������������������������������������������������������ustar �jjatria�������������������������jjatria����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������package Mastodon::Entity::Instance; use strict; use warnings; our $VERSION = '0.017'; use Moo; with 'Mastodon::Role::Entity'; use Types::Standard qw( Str ); use Mastodon::Types qw( URI ); has email => ( is => 'ro', isa => Str, ); has description => ( is => 'ro', isa => Str, ); has title => ( is => 'ro', isa => Str, ); has uri => ( is => 'ro', isa => URI, coerce => 1, required => 1, ); 1; =encoding utf8 =head1 NAME Mastodon::Entity::Instance - A Mastodon instance =head1 DESCRIPTION This object should not be manually created. It is intended to be generated from the data received from a Mastodon server using the coercions in L<Mastodon::Types>. For current information, see the L<Mastodon API documentation|https://github.com/tootsuite/documentation/blob/master/Using-the-API/API.md#instance> =head1 ATTRIBUTES =over 4 =item B<uri> URI of the current instance. =item B<title> The instance's title. =item B<description> A description for the instance. =item B<email> An email address which can be used to contact the instance administrator. =back =head1 AUTHOR =over 4 =item * José Joaquín Atria <jjatria@cpan.org> =back =head1 COPYRIGHT AND LICENSE This software is copyright (c) 2017 by José Joaquín Atria. This is free software; you can redistribute it and/or modify it under the same terms as the Perl 5 programming language system itself. =cut �������������������������������������������������������������������������������������������������������������������������������Mastodon-Client-0.017/lib/Mastodon/Entity/Mention.pm������������������������������������������������0000644�0001750�0001750�00000002623�13646430665�022150� 0����������������������������������������������������������������������������������������������������ustar �jjatria�������������������������jjatria����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������package Mastodon::Entity::Mention; use strict; use warnings; our $VERSION = '0.017'; use Moo; with 'Mastodon::Role::Entity'; use Types::Standard qw( Str Int ); use Mastodon::Types qw( URI Acct ); has acct => ( is => 'ro', isa => Acct, coerce => 1, required => 1, ); has id => ( is => 'ro', isa => Int, ); has url => ( is => 'ro', isa => URI, coerce => 1, ); has username => ( is => 'ro', isa => Str, required => 1, ); 1; =encoding utf8 =head1 NAME Mastodon::Entity::Mention - A mention in Mastodon =head1 DESCRIPTION This object should not be manually created. It is intended to be generated from the data received from a Mastodon server using the coercions in L<Mastodon::Types>. For current information, see the L<Mastodon API documentation|https://github.com/tootsuite/documentation/blob/master/Using-the-API/API.md#mention> =head1 ATTRIBUTES =over 4 =item B<url> URL of user's profile (can be remote). =item B<username> The C<username> of the account. =item B<acct> Equals C<username> for local users, includes C<@domain> for remote ones. =item B<id> Account ID. =back =head1 AUTHOR =over 4 =item * José Joaquín Atria <jjatria@cpan.org> =back =head1 COPYRIGHT AND LICENSE This software is copyright (c) 2017 by José Joaquín Atria. This is free software; you can redistribute it and/or modify it under the same terms as the Perl 5 programming language system itself. =cut �������������������������������������������������������������������������������������������������������������Mastodon-Client-0.017/lib/Mastodon/Entity/Application.pm��������������������������������������������0000644�0001750�0001750�00000002233�13646430665�022777� 0����������������������������������������������������������������������������������������������������ustar �jjatria�������������������������jjatria����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������package Mastodon::Entity::Application; use strict; use warnings; our $VERSION = '0.017'; use Moo; with 'Mastodon::Role::Entity'; use Types::Standard qw( Str Maybe ); use Mastodon::Types qw( URI ); has name => ( is => 'ro', isa => Str, ); has website => ( is => 'ro', isa => Maybe[URI], coerce => 1, required => 1,); 1; =encoding utf8 =head1 NAME Mastodon::Entity::Application - A Mastodon application =head1 DESCRIPTION This object should not be manually created. It is intended to be generated from the data received from a Mastodon server using the coercions in L<Mastodon::Types>. For current information, see the L<Mastodon API documentation|https://github.com/tootsuite/documentation/blob/master/Using-the-API/API.md#application> =head1 ATTRIBUTES =over 4 =item B<name> Name of the app. =item B<website> Homepage URL of the app. =back =head1 AUTHOR =over 4 =item * José Joaquín Atria <jjatria@cpan.org> =back =head1 COPYRIGHT AND LICENSE This software is copyright (c) 2017 by José Joaquín Atria. This is free software; you can redistribute it and/or modify it under the same terms as the Perl 5 programming language system itself. =cut ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������Mastodon-Client-0.017/lib/Mastodon/Entity/Status.pm�������������������������������������������������0000644�0001750�0001750�00000012604�13646430665�022022� 0����������������������������������������������������������������������������������������������������ustar �jjatria�������������������������jjatria����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������package Mastodon::Entity::Status; use strict; use warnings; our $VERSION = '0.017'; use Moo; with 'Mastodon::Role::Entity'; use Types::Standard qw( Maybe Int Str Bool ArrayRef Enum ); use Mastodon::Types qw( URI Account Status DateTime Attachment Mention Tag Application ); use Log::Any; my $log = Log::Any->get_logger( category => 'Mastodon' ); has account => ( is => 'ro', isa => Account, coerce => 1, required => 1, ); has application => ( is => 'ro', isa => Maybe [Application], coerce => 1, ); has content => ( is => 'ro', isa => Str, ); has created_at => ( is => 'ro', isa => DateTime, coerce => 1, ); has emojis => ( is => 'ro', isa => ArrayRef, ); has favourited => ( is => 'ro', isa => Bool, coerce => 1, ); has favourites_count => ( is => 'ro', isa => Int, required => 1, ); has id => ( is => 'ro', isa => Int, ); has in_reply_to_account_id => ( is => 'ro', isa => Maybe [Int], ); has in_reply_to_id => ( is => 'ro', isa => Maybe [Int], ); has media_attachments => ( is => 'ro', isa => ArrayRef [Attachment], coerce => 1, ); has mentions => ( is => 'ro', isa => ArrayRef [Mention], coerce => 1, ); has reblog => ( is => 'ro', isa => Maybe [Status], coerce => 1, ); has reblogged => ( is => 'ro', isa => Bool, coerce => 1, ); has reblogs_count => ( is => 'ro', isa => Int, ); has sensitive => ( is => 'ro', isa => Bool, coerce => 1, ); has spoiler_text => ( is => 'ro', isa => Str, ); has tags => ( is => 'ro', isa => ArrayRef [Tag], coerce => 1, ); has uri => ( is => 'ro', isa => Str, ); has url => ( is => 'ro', isa => URI, coerce => 1, ); has visibility => ( is => 'ro', isa => Enum[qw( public unlisted private direct )], required => 1, ); foreach my $pair ( [ fetch => 'get_status' ], [ fetch_context => 'get_status_context' ], [ fetch_card => 'get_status_card' ], [ fetch_reblogs => 'get_status_reblogs' ], [ fetch_favourites => 'get_status_favourites' ], [ delete => 'delete_status' ], [ boost => 'reblog' ], [ unboost => 'unreblog' ], [ favourite => undef ], [ unfavourite => undef ], ) { my ($name, $method) = @{$pair}; $method //= $name; no strict 'refs'; *{ __PACKAGE__ . '::' . $name } = sub { my $self = shift; croak $log->fatal(qq{Cannot call '$name' without client}) unless $self->_client; $self->_client->$method($self->id, @_); }; } 1; =encoding utf8 =head1 NAME Mastodon::Entity::Status - A Mastodon status =head1 DESCRIPTION This object should not be manually created. It is intended to be generated from the data received from a Mastodon server using the coercions in L<Mastodon::Types>. For current information, see the L<Mastodon API documentation|https://github.com/tootsuite/documentation/blob/master/Using-the-API/API.md#status> =head1 ATTRIBUTES =over 4 =item B<id> The ID of the status. =item B<uri> A Fediverse-unique resource ID. =item B<url> URL to the status page (can be remote). =item B<account> The L<Mastodon::Entity::Account> which posted the status. =item B<in_reply_to_id> C<undef> or the ID of the status it replies to. =item B<in_reply_to_account_id> C<undef> or the ID of the account it replies to. =item B<reblog> C<undef> or the reblogged L<Mastodon::Entity::Status>. =item B<content> Body of the status; this will contain HTML (remote HTML already sanitized). =item B<created_at> The time the status was created as a L<DateTime> object. =item B<reblogs_count> The number of reblogs for the status. =item B<favourites_count> The number of favourites for the status. =item B<reblogged> Whether the authenticated user has reblogged the status. =item B<favourited> Whether the authenticated user has favourited the status. =item B<sensitive> Whether media attachments should be hidden by default. =item B<spoiler_text> If not empty, warning text that should be displayed before the actual content. =item B<visibility> One of: C<public>, C<unlisted>, C<private>, C<direct>. =item B<media_attachments> An array of L<Mastodon::Entity::Attachment> objects. =item B<mentions> An array of L<Mastodon::Entity::Mention> objects. =item B<tags> An array of L<Mastodon::Entity::Tag> objects. =item B<application> Application from which the status was posted, as a L<Mastodon::Entity::Application> object. =back =head1 METHODS This class provides the following convenience methods. They act as a shortcut, passing the appropriate identifier of the current object as the first argument to the corresponding methods in L<Mastodon::Client>. =over 4 =item B<fetch> A shortcut to C<get_status>. =item B<fetch_context> A shortcut to C<get_status_context>. =item B<fetch_card> A shortcut to C<get_status_card>. =item B<fetch_reblogs> A shortcut to C<get_status_reblogs>. =item B<fetch_favourites> A shortcut to C<get_status_favourites>. =item B<delete> A shortcut to C<delete_status>. =item B<boost> A shortcut to C<reblog>. =item B<unboost> A shortcut to C<unreblog>. =item B<favourite> A shortcut to C<favourite>. =item B<unfavourite> A shortcut to C<unfavourite>. =back =head1 AUTHOR =over 4 =item * José Joaquín Atria <jjatria@cpan.org> =back =head1 COPYRIGHT AND LICENSE This software is copyright (c) 2017 by José Joaquín Atria. This is free software; you can redistribute it and/or modify it under the same terms as the Perl 5 programming language system itself. =cut ����������������������������������������������������������������������������������������������������������������������������Mastodon-Client-0.017/lib/Mastodon/Entity/Error.pm��������������������������������������������������0000644�0001750�0001750�00000002007�13646430665�021624� 0����������������������������������������������������������������������������������������������������ustar �jjatria�������������������������jjatria����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������package Mastodon::Entity::Error; use strict; use warnings; our $VERSION = '0.017'; use Moo; with 'Mastodon::Role::Entity'; use Types::Standard qw( Str ); has error => ( is => 'ro', isa => Str, required => 1, ); 1; =encoding utf8 =head1 NAME Mastodon::Entity::Error - An error in Mastodon =head1 DESCRIPTION This object should not be manually created. It is intended to be generated from the data received from a Mastodon server using the coercions in L<Mastodon::Types>. For current information, see the L<Mastodon API documentation|https://github.com/tootsuite/documentation/blob/master/Using-the-API/API.md#error> =head1 ATTRIBUTES =over 4 =item B<error> A textual description of the error. =back =head1 AUTHOR =over 4 =item * José Joaquín Atria <jjatria@cpan.org> =back =head1 COPYRIGHT AND LICENSE This software is copyright (c) 2017 by José Joaquín Atria. This is free software; you can redistribute it and/or modify it under the same terms as the Perl 5 programming language system itself. =cut �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������Mastodon-Client-0.017/dist.ini����������������������������������������������������������������������0000644�0001750�0001750�00000001545�13646430665�016041� 0����������������������������������������������������������������������������������������������������ustar �jjatria�������������������������jjatria����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������name = Mastodon-Client author = José Joaquín Atria <jjatria@cpan.org> license = Perl_5 copyright_holder = José Joaquín Atria copyright_year = 2017 [Git::GatherDir] [ReadmeAnyFromPod / MarkdownInBuild] filename = README.md [CopyFilesFromBuild] copy = README.md [@Filter] -bundle = @Basic -remove = GatherDir -remove = Readme [PodSyntaxTests] [NextRelease] [Repository] repository = git://gitlab.com/jjatria/Mastodon-Client.git web = https://gitlab.com/jjatria/Mastodon-Client [Bugtracker] web = https://gitlab.com/jjatria/Mastodon-Client/issues mailto = jjatria at cpan.org [MinimumPerl] [MetaJSON] [MetaProvides::Package] [RewriteVersion] [BumpVersionAfterRelease] [Git::Contributors] [Git::CommitBuild] release_branch = builds branch = [Git::Tag] branch = builds [Git::Tag / devel] branch = master tag_format = v%v-devel [Prereqs::FromCPANfile] �����������������������������������������������������������������������������������������������������������������������������������������������������������Mastodon-Client-0.017/examples/���������������������������������������������������������������������0000775�0001750�0001750�00000000000�13646430665�016210� 5����������������������������������������������������������������������������������������������������ustar �jjatria�������������������������jjatria����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������Mastodon-Client-0.017/examples/listener�������������������������������������������������������������0000755�0001750�0001750�00000001167�13646430665�017766� 0����������������������������������������������������������������������������������������������������ustar �jjatria�������������������������jjatria����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������#!/usr/bin/env perl use strict; use warnings; use open ':std', ':encoding(UTF-8)'; use Mastodon::Listener; my $access_token = shift or die "You must pass an access token as the first argument\n"; my $listener = Mastodon::Listener->new( url => 'https://botsin.space/api/v1/streaming/public', access_token => $ARGV[0], coerce_entities => 1, ); $listener->on( error => sub { my ( undef, undef, $msg ) = @_; warn $msg; }); $listener->on( update => sub { my ( undef, $status ) = @_; printf "%s said: %s\n\n", $status->account->display_name, $status->content; }); $listener->start; ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������Mastodon-Client-0.017/LICENSE�����������������������������������������������������������������������0000644�0001750�0001750�00000043705�13646430665�015406� 0����������������������������������������������������������������������������������������������������ustar �jjatria�������������������������jjatria����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������This software is copyright (c) 2017 by José Joaquín Atria. This is free software; you can redistribute it and/or modify it under the same terms as the Perl 5 programming language system itself. Terms of the Perl programming language system itself a) the GNU General Public License as published by the Free Software Foundation; either version 1, or (at your option) any later version, or b) the "Artistic License" --- The GNU General Public License, Version 1, February 1989 --- This software is Copyright (c) 2017 by José Joaquín Atria. This is free software, licensed under: The GNU General Public License, Version 1, February 1989 GNU GENERAL PUBLIC LICENSE Version 1, February 1989 Copyright (C) 1989 Free Software Foundation, Inc. 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The license agreements of most software companies try to keep users at the mercy of those companies. By contrast, our General Public License is intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users. The General Public License applies to the Free Software Foundation's software and to any other program whose authors commit to using it. You can use it for your programs, too. When we speak of free software, we are referring to freedom, not price. Specifically, the General Public License is designed to make sure that you have the freedom to give away or sell copies of free software, that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs; and that you know you can do these things. To protect your rights, we need to make restrictions that forbid anyone to deny you these rights or to ask you to surrender the rights. These restrictions translate to certain responsibilities for you if you distribute copies of the software, or if you modify it. For example, if you distribute copies of a such a program, whether gratis or for a fee, you must give the recipients all the rights that you have. You must make sure that they, too, receive or can get the source code. And you must tell them their rights. We protect your rights with two steps: (1) copyright the software, and (2) offer you this license which gives you legal permission to copy, distribute and/or modify the software. Also, for each author's protection and ours, we want to make certain that everyone understands that there is no warranty for this free software. If the software is modified by someone else and passed on, we want its recipients to know that what they have is not the original, so that any problems introduced by others will not reflect on the original authors' reputations. The precise terms and conditions for copying, distribution and modification follow. GNU GENERAL PUBLIC LICENSE TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 0. This License Agreement applies to any program or other work which contains a notice placed by the copyright holder saying it may be distributed under the terms of this General Public License. The "Program", below, refers to any such program or work, and a "work based on the Program" means either the Program or any work containing the Program or a portion of it, either verbatim or with modifications. Each licensee is addressed as "you". 1. You may copy and distribute verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice and disclaimer of warranty; keep intact all the notices that refer to this General Public License and to the absence of any warranty; and give any other recipients of the Program a copy of this General Public License along with the Program. You may charge a fee for the physical act of transferring a copy. 2. You may modify your copy or copies of the Program or any portion of it, and copy and distribute such modifications under the terms of Paragraph 1 above, provided that you also do the following: a) cause the modified files to carry prominent notices stating that you changed the files and the date of any change; and b) cause the whole of any work that you distribute or publish, that in whole or in part contains the Program or any part thereof, either with or without modifications, to be licensed at no charge to all third parties under the terms of this General Public License (except that you may choose to grant warranty protection to some or all third parties, at your option). c) If the modified program normally reads commands interactively when run, you must cause it, when started running for such interactive use in the simplest and most usual way, to print or display an announcement including an appropriate copyright notice and a notice that there is no warranty (or else, saying that you provide a warranty) and that users may redistribute the program under these conditions, and telling the user how to view a copy of this General Public License. d) You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee. Mere aggregation of another independent work with the Program (or its derivative) on a volume of a storage or distribution medium does not bring the other work under the scope of these terms. 3. You may copy and distribute the Program (or a portion or derivative of it, under Paragraph 2) in object code or executable form under the terms of Paragraphs 1 and 2 above provided that you also do one of the following: a) accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Paragraphs 1 and 2 above; or, b) accompany it with a written offer, valid for at least three years, to give any third party free (except for a nominal charge for the cost of distribution) a complete machine-readable copy of the corresponding source code, to be distributed under the terms of Paragraphs 1 and 2 above; or, c) accompany it with the information you received as to where the corresponding source code may be obtained. (This alternative is allowed only for noncommercial distribution and only if you received the program in object code or executable form alone.) Source code for a work means the preferred form of the work for making modifications to it. For an executable file, complete source code means all the source code for all modules it contains; but, as a special exception, it need not include source code for modules which are standard libraries that accompany the operating system on which the executable file runs, or for standard header files or definitions files that accompany that operating system. 4. You may not copy, modify, sublicense, distribute or transfer the Program except as expressly provided under this General Public License. Any attempt otherwise to copy, modify, sublicense, distribute or transfer the Program is void, and will automatically terminate your rights to use the Program under this License. However, parties who have received copies, or rights to use copies, from you under this General Public License will not have their licenses terminated so long as such parties remain in full compliance. 5. By copying, distributing or modifying the Program (or any work based on the Program) you indicate your acceptance of this license to do so, and all its terms and conditions. 6. Each time you redistribute the Program (or any work based on the Program), the recipient automatically receives a license from the original licensor to copy, distribute or modify the Program subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. 7. The Free Software Foundation may publish revised and/or new versions of the General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Program specifies a version number of the license which applies to it and "any later version", you have the option of following the terms and conditions either of that version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of the license, you may choose any version ever published by the Free Software Foundation. 8. If you wish to incorporate parts of the Program into other free programs whose distribution conditions are different, write to the author to ask for permission. For software which is copyrighted by the Free Software Foundation, write to the Free Software Foundation; we sometimes make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally. NO WARRANTY 9. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 10. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. END OF TERMS AND CONDITIONS Appendix: How to Apply These Terms to Your New Programs If you develop a new program, and you want it to be of the greatest possible use to humanity, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively convey the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. <one line to give the program's name and a brief idea of what it does.> Copyright (C) 19yy <name of author> This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 1, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301 USA Also add information on how to contact you by electronic and paper mail. If the program is interactive, make it output a short notice like this when it starts in an interactive mode: Gnomovision version 69, Copyright (C) 19xx name of author Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, the commands you use may be called something other than `show w' and `show c'; they could even be mouse-clicks or menu items--whatever suits your program. You should also get your employer (if you work as a programmer) or your school, if any, to sign a "copyright disclaimer" for the program, if necessary. Here a sample; alter the names: Yoyodyne, Inc., hereby disclaims all copyright interest in the program `Gnomovision' (a program to direct compilers to make passes at assemblers) written by James Hacker. <signature of Ty Coon>, 1 April 1989 Ty Coon, President of Vice That's all there is to it! --- The Artistic License 1.0 --- This software is Copyright (c) 2017 by José Joaquín Atria. This is free software, licensed under: The Artistic License 1.0 The Artistic License Preamble The intent of this document is to state the conditions under which a Package may be copied, such that the Copyright Holder maintains some semblance of artistic control over the development of the package, while giving the users of the package the right to use and distribute the Package in a more-or-less customary fashion, plus the right to make reasonable modifications. Definitions: - "Package" refers to the collection of files distributed by the Copyright Holder, and derivatives of that collection of files created through textual modification. - "Standard Version" refers to such a Package if it has not been modified, or has been modified in accordance with the wishes of the Copyright Holder. - "Copyright Holder" is whoever is named in the copyright or copyrights for the package. - "You" is you, if you're thinking about copying or distributing this Package. - "Reasonable copying fee" is whatever you can justify on the basis of media cost, duplication charges, time of people involved, and so on. (You will not be required to justify it to the Copyright Holder, but only to the computing community at large as a market that must bear the fee.) - "Freely Available" means that no fee is charged for the item itself, though there may be fees involved in handling the item. It also means that recipients of the item may redistribute it under the same conditions they received it. 1. You may make and give away verbatim copies of the source form of the Standard Version of this Package without restriction, provided that you duplicate all of the original copyright notices and associated disclaimers. 2. You may apply bug fixes, portability fixes and other modifications derived from the Public Domain or from the Copyright Holder. A Package modified in such a way shall still be considered the Standard Version. 3. You may otherwise modify your copy of this Package in any way, provided that you insert a prominent notice in each changed file stating how and when you changed that file, and provided that you do at least ONE of the following: a) place your modifications in the Public Domain or otherwise make them Freely Available, such as by posting said modifications to Usenet or an equivalent medium, or placing the modifications on a major archive site such as ftp.uu.net, or by allowing the Copyright Holder to include your modifications in the Standard Version of the Package. b) use the modified Package only within your corporation or organization. c) rename any non-standard executables so the names do not conflict with standard executables, which must also be provided, and provide a separate manual page for each non-standard executable that clearly documents how it differs from the Standard Version. d) make other distribution arrangements with the Copyright Holder. 4. You may distribute the programs of this Package in object code or executable form, provided that you do at least ONE of the following: a) distribute a Standard Version of the executables and library files, together with instructions (in the manual page or equivalent) on where to get the Standard Version. b) accompany the distribution with the machine-readable source of the Package with your modifications. c) accompany any non-standard executables with their corresponding Standard Version executables, giving the non-standard executables non-standard names, and clearly documenting the differences in manual pages (or equivalent), together with instructions on where to get the Standard Version. d) make other distribution arrangements with the Copyright Holder. 5. You may charge a reasonable copying fee for any distribution of this Package. You may charge any fee you choose for support of this Package. You may not charge a fee for this Package itself. However, you may distribute this Package in aggregate with other (possibly commercial) programs as part of a larger (possibly commercial) software distribution provided that you do not advertise this Package as a product of your own. 6. The scripts and library files supplied as input to or produced as output from the programs of this Package do not automatically fall under the copyright of this Package, but belong to whomever generated them, and may be sold commercially, and may be aggregated with this Package. 7. C or perl subroutines supplied by you and linked into this Package shall not be considered part of this Package. 8. The name of the Copyright Holder may not be used to endorse or promote products derived from this software without specific prior written permission. 9. THIS PACKAGE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE. The End �����������������������������������������������������������Mastodon-Client-0.017/META.json���������������������������������������������������������������������0000644�0001750�0001750�00000011562�13646430665�016016� 0����������������������������������������������������������������������������������������������������ustar �jjatria�������������������������jjatria����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������{ "abstract" : "Talk to a Mastodon server", "author" : [ "Jos\u00e9 Joaqu\u00edn Atria <jjatria@cpan.org>" ], "dynamic_config" : 0, "generated_by" : "Dist::Zilla version 6.012, CPAN::Meta::Converter version 2.150010", "license" : [ "perl_5" ], "meta-spec" : { "url" : "http://search.cpan.org/perldoc?CPAN::Meta::Spec", "version" : 2 }, "name" : "Mastodon-Client", "prereqs" : { "configure" : { "requires" : { "ExtUtils::MakeMaker" : "0", "perl" : "v5.10.0" } }, "develop" : { "requires" : { "Test::Pod" : "1.41" } }, "runtime" : { "requires" : { "Class::Load" : "0.25", "DateTime" : "1.51", "DateTime::Format::Strptime" : "1.73", "Future" : "0.35", "HTTP::Request::Common" : "6.18", "HTTP::Thin" : "0.006", "IO::Async" : "0.66", "Image::Info" : "1.41", "JSON::MaybeXS" : "1.003009", "List::Util" : "1.47", "Log::Any" : "1.049", "Moo" : "2.003002", "Net::Async::HTTP" : "0.41", "Role::EventEmitter" : "0.002", "Try::Tiny" : "0.28", "Type::Params" : "1.000006", "Types::Common::String" : "1.000006", "Types::Path::Tiny" : "0.005", "Types::Standard" : "1.003003", "URI" : "1.71", "perl" : "5.010" } }, "test" : { "requires" : { "Plack" : "1.0043", "Test2::V0" : "0.000126", "Test::Pod" : "1.51", "Test::TCP" : "2.17", "perl" : "5.010" } } }, "provides" : { "Mastodon::Client" : { "file" : "lib/Mastodon/Client.pm", "version" : "0.017" }, "Mastodon::Entity::Account" : { "file" : "lib/Mastodon/Entity/Account.pm", "version" : "0.017" }, "Mastodon::Entity::Application" : { "file" : "lib/Mastodon/Entity/Application.pm", "version" : "0.017" }, "Mastodon::Entity::Attachment" : { "file" : "lib/Mastodon/Entity/Attachment.pm", "version" : "0.017" }, "Mastodon::Entity::Card" : { "file" : "lib/Mastodon/Entity/Card.pm", "version" : "0.017" }, "Mastodon::Entity::Context" : { "file" : "lib/Mastodon/Entity/Context.pm", "version" : "0.017" }, "Mastodon::Entity::Error" : { "file" : "lib/Mastodon/Entity/Error.pm", "version" : "0.017" }, "Mastodon::Entity::Instance" : { "file" : "lib/Mastodon/Entity/Instance.pm", "version" : "0.017" }, "Mastodon::Entity::Mention" : { "file" : "lib/Mastodon/Entity/Mention.pm", "version" : "0.017" }, "Mastodon::Entity::Notification" : { "file" : "lib/Mastodon/Entity/Notification.pm", "version" : "0.017" }, "Mastodon::Entity::Relationship" : { "file" : "lib/Mastodon/Entity/Relationship.pm", "version" : "0.017" }, "Mastodon::Entity::Report" : { "file" : "lib/Mastodon/Entity/Report.pm", "version" : "0.017" }, "Mastodon::Entity::Results" : { "file" : "lib/Mastodon/Entity/Results.pm", "version" : "0.017" }, "Mastodon::Entity::Status" : { "file" : "lib/Mastodon/Entity/Status.pm", "version" : "0.017" }, "Mastodon::Entity::Tag" : { "file" : "lib/Mastodon/Entity/Tag.pm", "version" : "0.017" }, "Mastodon::Listener" : { "file" : "lib/Mastodon/Listener.pm", "version" : "0.017" }, "Mastodon::Role::Entity" : { "file" : "lib/Mastodon/Role/Entity.pm", "version" : "0.017" }, "Mastodon::Role::UserAgent" : { "file" : "lib/Mastodon/Role/UserAgent.pm", "version" : "0.017" }, "Mastodon::Types" : { "file" : "lib/Mastodon/Types.pm", "version" : "0.017" } }, "release_status" : "stable", "resources" : { "bugtracker" : { "mailto" : "jjatria at cpan.org", "web" : "https://gitlab.com/jjatria/Mastodon-Client/issues" }, "repository" : { "type" : "git", "url" : "git://gitlab.com/jjatria/Mastodon-Client.git", "web" : "https://gitlab.com/jjatria/Mastodon-Client" } }, "version" : "0.017", "x_contributors" : [ "Al Beano <albino@autistici.org>", "Alexander Zaitsev <shura0@yandex.ru>", "Eric Prestemon <ecp@prestemon.com>", "Florian Obser <florian+gitlab@narrans.de>", "Lance Wicks <lancew@cpan.org>", "Luc Didry <luc@didry.org>" ], "x_generated_by_perl" : "v5.30.0", "x_serialization_backend" : "Cpanel::JSON::XS version 4.12" } ����������������������������������������������������������������������������������������������������������������������������������������������Mastodon-Client-0.017/MANIFEST����������������������������������������������������������������������0000644�0001750�0001750�00000001733�13646430665�015525� 0����������������������������������������������������������������������������������������������������ustar �jjatria�������������������������jjatria����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������# This file was automatically generated by Dist::Zilla::Plugin::Manifest v6.012. Changes LICENSE MANIFEST META.json META.yml Makefile.PL README.md cpanfile dist.ini examples/listener lib/Mastodon/Client.pm lib/Mastodon/Entity/Account.pm lib/Mastodon/Entity/Application.pm lib/Mastodon/Entity/Attachment.pm lib/Mastodon/Entity/Card.pm lib/Mastodon/Entity/Context.pm lib/Mastodon/Entity/Error.pm lib/Mastodon/Entity/Instance.pm lib/Mastodon/Entity/Mention.pm lib/Mastodon/Entity/Notification.pm lib/Mastodon/Entity/Relationship.pm lib/Mastodon/Entity/Report.pm lib/Mastodon/Entity/Results.pm lib/Mastodon/Entity/Status.pm lib/Mastodon/Entity/Tag.pm lib/Mastodon/Listener.pm lib/Mastodon/Role/Entity.pm lib/Mastodon/Role/UserAgent.pm lib/Mastodon/Types.pm t/00_basic.t t/01_entities.t t/02_entity_coercion.t t/05_authorize.t t/10_tcp.t t/Dockerfile t/author-pod-syntax.t t/authorize.t t/data_leak.t t/examples/getter.pl t/examples/public_stream.pl t/lib/Plack/App/Mastodon/MockServer/v1.pm ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������