App-bsky
view release on metacpan or search on metacpan
lib/App/bsky.pm view on Meta::CPAN
}
}
}
catch ($e) {
warn "CAR/CBOR decoding error for op on repo " . $body->{repo} . ": $e";
}
}
}
catch ($e) {
warn "Error processing firehose event: $e";
}
}
);
$fh->start();
};
$start_stream->();
Mojo::IOLoop->start unless Mojo::IOLoop->is_running;
}
method cmd_thread (@args) {
GetOptionsFromArray( \@args, 'json!' => \my $json, 'n=i' => \my $number );
$number //= ();
my ($id) = @args;
$id // return $self->cmd_help('thread');
my $res = $bsky->getPostThread( uri => $id, depth => $number, parentHeight => $number ); # $uri, depth, $parentHeight
return unless $res->{thread};
return $self->say( JSON::Tiny::to_json $res->{thread} ) if $json;
$self->_dump_post( 0, $res->{thread} );
}
method cmd_post ($text) {
my $res = $bsky->createPost( text => $text );
defined $res ? $self->say( $res->{uri} ) : 0;
}
method cmd_delete ($uri) {
$uri = At::Protocol::URI->new($uri) unless builtin::blessed $uri;
$bsky->at->delete_record( $uri->collection, $uri->rkey );
}
# TODO
method cmd_like ( $uri, @args ) { # can take the post uri
GetOptionsFromArray( \@args, 'json!' => \my $json, 'cid=s' => \my $cid );
my $res = $bsky->like( $uri, $cid );
$res || $res->throw;
$self->say( $json ? JSON::Tiny::to_json($res) : sprintf 'Liked! [id:%s]', $res->{uri}->as_string );
}
# TODO
method cmd_unlike ( $uri, @args ) { # can take the post uri or the like uri
GetOptionsFromArray( \@args, 'json!' => \my $json, 'cid=s' => \my $cid );
my $res = $bsky->deleteLike($uri);
$res || $res->throw;
$self->say( $json ? JSON::Tiny::to_json($res) : sprintf 'Removed like!' );
}
# TODO
method cmd_likes ( $uri, @args ) {
GetOptionsFromArray( \@args, 'json!' => \my $json );
my @likes;
my $cursor = ();
do {
my $likes = $bsky->at->get( 'app.bsky.feed.getLikes', { uri => $uri, limit => 100, cursor => $cursor } );
push @likes, @{ $likes->{likes} };
$cursor = $likes->{cursor};
} while ($cursor);
if ($json) {
$self->say( JSON::Tiny::to_json \@likes );
}
else {
$self->say(
'%s%s%s%s (%s)',
color('red'), $_->{actor}{handle},
color('reset'), defined $_->{actor}{displayName} ? ' [' . $_->{actor}{displayName} . ']' : '',
$_->{createdAt}
) for @likes;
}
scalar @likes;
}
# TODO
method cmd_repost ($uri) {
my $res = $bsky->repost($uri);
$res // return;
$self->say( $res->{uri}->as_string );
}
# TODO
method cmd_reposts ( $uri, @args ) {
GetOptionsFromArray( \@args, 'json!' => \my $json );
my @reposts;
my $cursor = ();
do {
my $reposts = $bsky->at->get( 'app.bsky.feed.getRepostedBy', { uri => $uri, limit => 100, cursor => $cursor } );
push @reposts, @{ $reposts->{repostedBy} };
$cursor = $reposts->{cursor};
} while ($cursor);
if ($json) {
$self->say( JSON::Tiny::to_json \@reposts );
}
else {
$self->say( '%s%s%s%s', color('red'), $_->{handle}, color('reset'), defined $_->{displayName} ? ' [' . $_->{displayName} . ']' : '' )
for @reposts;
}
scalar @reposts;
}
# TODO
method cmd_follow ($actor) { # takes handle or did
my $res = $bsky->follow($actor);
$res || $res->throw;
$self->say( $res->{uri}->as_string );
}
# TODO
method cmd_unfollow ($actor) { # takes handle or did
my $profile = $bsky->getProfile($actor);
my $uri = $profile->{viewer}{following} // return $self->err("You are not following $actor");
$bsky->deleteFollow($uri);
$self->say("Unfollowed $actor");
}
# TODO
method cmd_follows (@args) {
GetOptionsFromArray( \@args, 'json!' => \my $json, 'handle|H=s' => \my $handle );
my @follows;
my $cursor = ();
do {
my $follows = $bsky->at->get( 'app.bsky.graph.getFollows',
{ actor => $handle // $config->{session}{handle}, limit => 100, cursor => $cursor } );
push @follows, @{ $follows->{follows} };
$cursor = $follows->{cursor};
} while ($cursor);
if ($json) {
$self->say( JSON::Tiny::to_json \@follows );
}
else {
for my $follow (@follows) {
$self->say(
sprintf '%s%s%s%s %s%s%s',
color('red'), $follow->{handle}, color('reset'), defined $follow->{displayName} ? ' [' . $follow->{displayName} . ']' : '',
color('blue'), $follow->{did}, color('reset')
);
}
}
return scalar @follows;
}
method cmd_followers (@args) {
GetOptionsFromArray( \@args, 'json!' => \my $json, 'handle|H=s' => \my $handle );
my @followers;
my $cursor = ();
do {
my $followers = $bsky->at->get( 'app.bsky.graph.getFollowers',
{ actor => $handle // $config->{session}{handle}, limit => 100, cursor => $cursor } );
$followers // last;
if ( defined $followers->{followers} ) {
push @followers, @{ $followers->{followers} };
$cursor = $followers->{cursor};
}
} while ($cursor);
if ($json) {
$self->say( JSON::Tiny::to_json [ map {$_} @followers ] );
}
else {
my $len1 = my $len2 = 0;
for (@followers) {
$len1 = length( $_->{handle} ) if length( $_->{handle} ) > $len1;
$len2 = length( $_->{displayName} ) if length( $_->{displayName} ) > $len2;
}
for my $follower (@followers) {
$self->say(
sprintf '%s%-' . ($len1) . 's %s%-' . ($len2) . 's %s%s%s',
color('red'), $follower->{handle}, color('reset'), $follower->{displayName} // '',
color('blue'), $follower->{did}, color('reset')
);
}
}
scalar @followers;
}
# TODO
method cmd_block ($actor) { # takes handle or did
my $res = $bsky->block($actor);
$res || $res->throw;
$self->say( $res->{uri}->as_string );
}
# TODO
method cmd_unblock ($actor) { # takes handle or did
my $profile = $bsky->getProfile($actor);
my $uri = $profile->{viewer}{blocking} // return $self->err("You are not blocking $actor");
$bsky->deleteBlock($uri);
$self->say("Unblocked $actor");
}
# TODO
method cmd_blocks (@args) {
GetOptionsFromArray( \@args, 'json!' => \my $json );
my @blocks;
my $cursor = ();
do {
my $follows = $bsky->at->get( 'app.bsky.graph.getBlocks', { limit => 100, cursor => $cursor } );
push @blocks, @{ $follows->{blocks} };
$cursor = $follows->{cursor};
} while ($cursor);
if ($json) {
$self->say( JSON::Tiny::to_json \@blocks );
}
else {
for my $follow (@blocks) {
$self->say(
sprintf '%s%s%s%s %s%s%s',
color('red'), $follow->{handle}, color('reset'), defined $follow->{displayName} ? ' [' . $follow->{displayName} . ']' : '',
color('blue'), $follow->{did}, color('reset')
);
}
}
return scalar @blocks;
}
method cmd_login ( $ident, $password, @args ) {
GetOptionsFromArray( \@args, 'host=s' => \my $host );
$bsky = Bluesky->new( defined $host ? ( service => $host ) : () );
unless ( $bsky->login( $ident, $password ) ) {
return $self->err( 'Failed to log in as ' . $ident, 1 );
}
$config->{resume} = $bsky->session->_raw;
$config->{session} = $bsky->session->_raw;
$self->put_config;
$self->say( 'Logged in' . ( $host ? ' at ' . $host : '' ) . ' as ' . color('red') . $ident . color('reset') . ' [' . $bsky->did . ']' );
}
method cmd_notifications (@args) {
GetOptionsFromArray( \@args, 'all|a' => \my $all, 'json!' => \my $json );
if ( !$all ) {
my $notification_count = $bsky->at->get('app.bsky.notification.getUnreadCount');
$notification_count || $notification_count->throw;
return $self->say( $json ? '[]' : 'No unread notifications' ) unless $notification_count->{count};
}
my @notes;
my $cursor = ();
do {
my $notes = $bsky->at->get( 'app.bsky.notification.listNotifications', { limit => 100, cursor => $cursor } );
$notes || $notes->throw;
push @notes, @{ $notes->{notifications} };
$cursor = $all && $notes->{cursor} ? $notes->{cursor} : ();
} while ($cursor);
return $self->say( JSON::Tiny::to_json [ map {$_} @notes ] ) if $json;
return $self->say('No notifications.') unless @notes;
for my $note (@notes) {
$self->say(
'%s%s%s%s %s', color('red'), $note->{author}{handle},
color('reset'),
defined $note->{author}{displayName} ? ' [' . $note->{author}{displayName} . ']' : '',
$note->{author}{did}
);
$self->say(
' %s',
$note->{reason} eq 'like' ? 'liked ' . $note->{record}{subject}{uri} :
$note->{reason} eq 'repost' ? 'reposted ' . $note->{record}{subject}{uri} :
$note->{reason} eq 'follow' ? 'followed you' :
$note->{reason} eq 'mention' ? 'mentioned you at ' . $note->{record}{subject}{uri} :
$note->{reason} eq 'reply' ? 'replied at ' . $note->{record}{subject}{uri} :
$note->{reason} eq 'quote' ? 'quoted you at ' . $note->{record}{subject}{uri} :
'unknown notification: ' . $note->{reason}
);
}
scalar @notes;
}
method cmd_notif (@args) {
$self->cmd_notifications(@args);
}
method cmd_listapppasswords (@args) {
GetOptionsFromArray( \@args, 'json!' => \my $json );
my $passwords = $bsky->at->get('com.atproto.server.listAppPasswords');
$passwords || $passwords->throw;
my @passwords = @{ $passwords->{passwords} };
if ($json) {
$self->say( JSON::Tiny::to_json [ map {$_} @passwords ] );
}
elsif (@passwords) {
$self->say( '%s%s (%s)', $_->{privileged} ? '*' : ' ', $_->{name}, $_->{createdAt} ) for @passwords;
}
else {
$self->say('No app passwords found');
}
scalar @passwords;
}
method cmd_addapppassword ($name) {
my $res = $bsky->at->post( 'com.atproto.server.createAppPassword', { name => $name } );
$res || $res->throw;
if ( $res->{appPassword} ) {
$self->say( 'App name: %s', $res->{appPassword}{name} );
$self->say( 'Password: %s', $res->{appPassword}{password} );
}
1;
}
method cmd_revokeapppassword ($name) {
$bsky->at->post( 'com.atproto.server.revokeAppPassword', { name => $name } ) ? 1 : 0;
}
method cmd_config ( $field //= (), $value //= () ) {
unless ( defined $field ) {
( run in 1.379 second using v1.01-cache-2.11-cpan-cdf2f3d4e48 )