Huge chunk of refactoring completed.

This commit is contained in:
Storm Dragon
2025-07-27 22:39:55 -04:00
parent fc736cfc9a
commit efe2c13c9d
+160 -109
View File
@@ -1717,7 +1717,7 @@ exit(0) if (length($status));
if (length($credentials)) {
print "-- processing credentials: ";
$my_json_ref = &map_mastodon_fields(&parsejson($credentials));
$whoami = lc($my_json_ref->{'screen_name'});
$whoami = lc($my_json_ref->{'username'} || $my_json_ref->{'acct'});
if (!length($whoami)) {
print "FAILED!\nis your account suspended, or wrong token?\n";
exit;
@@ -2239,7 +2239,7 @@ print $stdout "*** invalid UTF-8: partial delete of a wide character?\n";
my $sn;
my $id;
my @superfields = (
[ "user", "screen_name" ], # must always be first
[ "user", "username" ], # must always be first
[ "reblog", "id_str" ],
[ "user", "geo_enabled" ],
[ "place", "id" ],
@@ -2293,7 +2293,7 @@ print $stdout "*** invalid UTF-8: partial delete of a wide character?\n";
my $sn;
my $id;
my @superfields = (
[ "sender", "screen_name" ], # must always be first
[ "sender", "username" ], # must always be first
);
if (!defined($dm)) {
@@ -2941,9 +2941,18 @@ EOF
print $stdout
"-- synchronous /again command for $uname ($countmaybe)\n"
if ($verbose);
my $my_json_ref =
&grabjson("${uurl}?screen_name=${uname}&include_rts=true",
0, 0, $countmaybe, undef, 1);
# Look up account ID for the username
my $account_id = &lookup_account_id($uname);
if (!$account_id) {
print $stdout "-- ERROR: Could not find account for user: $uname\n";
return 0;
}
# Use proper Mastodon API endpoint with account ID
my $user_statuses_url = $uurl;
$user_statuses_url =~ s/%I/$account_id/g;
my $my_json_ref = &grabjson($user_statuses_url, 0, 0, $countmaybe, undef, 1);
&dt_tdisplay($my_json_ref, 'again');
unless ($mode eq 'w' || $mode eq 'wf') {
return 0;
@@ -2955,11 +2964,21 @@ EOF
$readline_completion{'@'.$uname}++ if ($termrl);
print $stdout "-- synchronous /whois command for $uname\n"
if ($verbose);
my $my_json_ref =
&grabjson("${wurl}?screen_name=${uname}", 0, 0, 0, undef, 1);
# Look up account ID for the username
my $account_id = &lookup_account_id($uname);
if (!$account_id) {
print $stdout "-- ERROR: Could not find account for user: $uname\n";
return 0;
}
# Use proper Mastodon API endpoint with account ID
my $user_info_url = $wurl;
$user_info_url =~ s/%I/$account_id/g;
my $my_json_ref = &grabjson($user_info_url, 0, 0, 0, undef, 1);
if (defined($my_json_ref) && ref($my_json_ref) eq 'HASH' &&
length($my_json_ref->{'screen_name'})) {
length($my_json_ref->{'username'} || $my_json_ref->{'acct'})) {
my $sturl = undef;
my $purl =
&descape($my_json_ref->{'profile_image_url'});
@@ -2999,18 +3018,18 @@ ${EM}Picture:${OFF}\t@{[ &descape($my_json_ref->{'profile_image_url'}) ]}
EOF
unless ($anonymous || $whoami eq $uname) {
my $g = &grabjson(
"$frurl?source_screen_name=$whoami&target_screen_name=$uname", 0, 0, 0,
undef, 1);
print $streamout &wwrap(
"${EM}Do you follow${OFF} this user? ... ${EM}$g->{'relationship'}->{'target'}->{'followed_by'}${OFF}\n")
if (ref($g) eq 'HASH');
my $g = &grabjson(
"$frurl?source_screen_name=$uname&target_screen_name=$whoami", 0, 0, 0,
undef, 1);
print $streamout &wwrap(
"${EM}Does this user follow${OFF} you? ... ${EM}$g->{'relationship'}->{'target'}->{'followed_by'}${OFF}\n")
if (ref($g) eq 'HASH');
# Use Mastodon relationships API with account ID
my $relationship_url = $blockingurl;
$relationship_url =~ s/%I/$account_id/g;
my $g = &grabjson($relationship_url, 0, 0, 0, undef, 1);
if (ref($g) eq 'ARRAY' && @$g > 0) {
my $rel = $g->[0]; # relationships API returns array
print $streamout &wwrap(
"${EM}Do you follow${OFF} this user? ... ${EM}$rel->{'following'}${OFF}\n");
print $streamout &wwrap(
"${EM}Does this user follow${OFF} you? ... ${EM}$rel->{'followed_by'}${OFF}\n");
}
print $streamout "\n";
}
print $stdout &wwrap(
@@ -3036,21 +3055,11 @@ EOF
print $stdout "--- sorry, this won't work on lists.\n";
return 0;
}
my $g = &grabjson(
"${frurl}?source_screen_name=${user_a}&target_screen_name=${user_b}", 0, 0, 0,
undef, 1);
if ($msg = &is_json_error($g)) {
print $stdout <<"EOF";
${MAGENTA}*** warning: server error message received
*** "$ec"${OFF}
EOF
} elsif ($g->{'relationship'}->{'target'}) {
print $stdout "--- does $user_a follow ${user_b}? => ";
print $streamout "$g->{'relationship'}->{'target'}->{'followed_by'}\n"
} else {
print $stdout
"-- sorry, bogus server response, try again later.\n";
}
# Note: Fediverse APIs don't support checking arbitrary user relationships
# You can only check your own relationships via /api/v1/accounts/relationships
print $stdout "-- Sorry, /doesfollow is not supported in fediverse (privacy feature)\n";
print $stdout "-- You can only check your own relationships with /whois \@username\n";
return 0;
return 0;
}
@@ -3104,15 +3113,21 @@ EOF
return 0;
}
# grab all the IDs
my $ids_ref = &grabjson(
"$mode?limit=${countmaybe}&screen_name=${who}",
0, 0, 0, undef, 1);
return 0 if (!$ids_ref || ref($ids_ref) ne 'HASH' ||
!$ids_ref->{'ids'});
$ids_ref = $ids_ref->{'ids'};
return 0 if (ref($ids_ref) ne 'ARRAY');
my @ids = @{ $ids_ref };
# Look up account ID for the username
my $account_id = &lookup_account_id($who);
if (!$account_id) {
print $stdout "-- ERROR: Could not find account for user: $who\n";
return 0;
}
# Use proper Mastodon API endpoint with account ID
my $followers_url = $mode;
$followers_url =~ s/%I/$account_id/g;
my $accounts_ref = &grabjson("$followers_url?limit=${countmaybe}", 0, 0, 0, undef, 1);
return 0 if (!$accounts_ref || ref($accounts_ref) ne 'ARRAY');
# Fediverse (Mastodon/Pleroma/etc) returns array of account objects, extract IDs
my @ids = map { $_->{'id'} } @{ $accounts_ref };
@ids = sort { 0+$a <=> 0+$b } @ids;
# make it somewhat deterministic
@@ -3407,8 +3422,16 @@ EOF
$countmaybe += 0;
if (length) {
$my_json_ref = &grabjson("${favsurl}?screen_name=$_",
0, 0, $countmaybe, undef, 1);
# Look up account ID for the username
my $account_id = &lookup_account_id($_);
if (!$account_id) {
print $stdout "-- ERROR: Could not find account for user: $_\n";
return 0;
}
# Use fediverse accounts/{id}/favourites endpoint
$my_json_ref = &grabjson("${apibase}/accounts/${account_id}/favourites?limit=${countmaybe}",
0, 0, 0, undef, 1);
} else {
if ($anonymous) {
print $stdout
@@ -3488,7 +3511,7 @@ m#^/(un)?f(boost|a|av|ave|avorite|avourite)? ([zZ]?[a-zA-Z]?[0-9]+)$#) {
}
# we can't or user requested /ert /ort
$repost = "boost @" .
&descape($post->{'user'}->{'acct'} || $post->{'user'}->{'screen_name'}) .
&descape($post->{'user'}->{'acct'} || $post->{'user'}->{'username'}) .
": " . &descape($post->{'text'});
if ($mode eq 'e') {
&add_history($repost);
@@ -3565,7 +3588,7 @@ m#^/(un)?f(boost|a|av|ave|avorite|avourite)? ([zZ]?[a-zA-Z]?[0-9]+)$#) {
print $stdout "-- no such post (yet?): $code\n";
return 0;
}
if (lc(&descape($post->{'user'}->{'screen_name'}))
if (lc(&descape($post->{'user'}->{'username'} || $post->{'user'}->{'acct'}))
ne lc($whoami)) {
print $stdout
"-- not allowed to delete somebody's else's posts\n";
@@ -3641,29 +3664,19 @@ m#^/(un)?f(boost|a|av|ave|avorite|avourite)? ([zZ]?[a-zA-Z]?[0-9]+)$#) {
print $stdout "-- no such post (yet?): $code\n";
return 0;
}
# Use acct field if available, otherwise construct from screen_name and server
# Use Mastodon's acct field (includes @domain for remote users) or username for local users
my $target;
if ($post->{'user'}->{'acct'} && $post->{'user'}->{'acct'} =~ /\@/) {
# Full acct field with domain
$target = &descape($post->{'user'}->{'acct'});
} elsif ($post->{'user'}->{'screen_name'}) {
# If no domain in acct, try to get it from the post URL or fallback to screen_name
my $screen_name = &descape($post->{'user'}->{'screen_name'});
if ($post->{'url'} && $post->{'url'} =~ m{^https?://([^/]+)/}) {
my $domain = $1;
# Don't add domain if it's the same as our server
if ($domain ne $fediverseserver) {
$target = "$screen_name\@$domain";
} else {
$target = $screen_name;
}
} else {
$target = $screen_name;
my $acct = &descape($post->{'user'}->{'acct'} || $post->{'user'}->{'username'});
$target = $acct;
# If acct doesn't include @domain and this is a remote post, construct it
if ($acct !~ /\@/ && $post->{'url'} && $post->{'url'} =~ m{^https?://([^/]+)/}) {
my $domain = $1;
if ($domain ne $fediverseserver) {
$target = "$acct\@$domain";
}
} else {
$target = &descape($post->{'user'}->{'acct'} || $post->{'user'}->{'screen_name'});
}
print $stdout "-- DEBUG: Reply target acct='$post->{'user'}->{'acct'}', screen_name='$post->{'user'}->{'screen_name'}', url='$post->{'url'}', using='$target'\n" if ($verbose);
print $stdout "-- DEBUG: Reply target acct='$post->{'user'}->{'acct'}', username='$post->{'user'}->{'username'}', url='$post->{'url'}', using='$target'\n" if ($verbose);
$_ = '@' . $target . " $_";
unless ($mode eq 'v') {
$in_reply_to = $post->{'id_str'};
@@ -3708,27 +3721,17 @@ m#^/(un)?f(boost|a|av|ave|avorite|avourite)? ([zZ]?[a-zA-Z]?[0-9]+)$#) {
print $stdout "-- no such post (yet?): $code\n";
return 0;
}
# Use acct field if available, otherwise construct from screen_name and server
# Use Mastodon's acct field (includes @domain for remote users) or username for local users
my $target;
if ($post->{'user'}->{'acct'} && $post->{'user'}->{'acct'} =~ /\@/) {
# Full acct field with domain
$target = &descape($post->{'user'}->{'acct'});
} elsif ($post->{'user'}->{'screen_name'}) {
# If no domain in acct, try to get it from the post URL or fallback to screen_name
my $screen_name = &descape($post->{'user'}->{'screen_name'});
if ($post->{'url'} && $post->{'url'} =~ m{^https?://([^/]+)/}) {
my $domain = $1;
# Don't add domain if it's the same as our server
if ($domain ne $fediverseserver) {
$target = "$screen_name\@$domain";
} else {
$target = $screen_name;
}
} else {
$target = $screen_name;
my $acct = &descape($post->{'user'}->{'acct'} || $post->{'user'}->{'username'});
$target = $acct;
# If acct doesn't include @domain and this is a remote post, construct it
if ($acct !~ /\@/ && $post->{'url'} && $post->{'url'} =~ m{^https?://([^/]+)/}) {
my $domain = $1;
if ($domain ne $fediverseserver) {
$target = "$acct\@$domain";
}
} else {
$target = &descape($post->{'user'}->{'acct'} || $post->{'user'}->{'screen_name'});
}
my $text = $_;
$_ = '@' . $target;
@@ -4175,11 +4178,11 @@ EOF
# ones they subscribe to, different from 1.0.
# right now we just deal with that.
#next if ($uname ne
# $list_ref->{'user'}->{'screen_name'});
# $list_ref->{'user'}->{'username'} || $list_ref->{'user'}->{'acct'});
# listhandle?
my $list_name =
"\@$list_ref->{'user'}->{'screen_name'}/@{[ &descape($list_ref->{'slug'}) ]}";
"\@".($list_ref->{'user'}->{'username'} || $list_ref->{'user'}->{'acct'})."/@{[ &descape($list_ref->{'slug'}) ]}";
my $list_full_name =
(length($list_ref->{'name'})) ?
&descape($list_ref->{'name'})."${OFF} ($list_name)" : $list_name;
@@ -4382,7 +4385,7 @@ sub reply_to_all {
# Get the post content and author
my $post_content = $post_ref->{'text'} || '';
my $post_author = $post_ref->{'user'}->{'acct'} || $post_ref->{'user'}->{'screen_name'} || '';
my $post_author = $post_ref->{'user'}->{'acct'} || $post_ref->{'user'}->{'username'} || '';
# Set up reply-to ID
my $in_reply_to = $post_ref->{'id_str'} || $post_ref->{'id'};
@@ -4801,7 +4804,7 @@ EOF
$key->{'tag'}->{'type'}. " ". # NO SPACES!
unpack("${pack_magic}H*", $key->{'tag'}->{'payload'}). " ".
($key->{'reblogs_count'} || "0") . " " .
$key->{'user'}->{'screen_name'}." $ds $src|".
($key->{'user'}->{'username'} || $key->{'user'}->{'acct'})." $ds $src|".
unpack("${pack_magic}H*", $key->{'text'}).
$space_pad), 0, 1024);
print P $key;
@@ -5318,7 +5321,7 @@ sub handle_fediverse_stream_event {
# this so the user can interpose custom logic.
if ($nostreamreplies) {
my $sn = &descape(
$payload->{'user'}->{'screen_name'});
$payload->{'user'}->{'username'} || $payload->{'user'}->{'acct'});
my $text = &descape($payload->{'text'});
return if (&$posttype($payload, $sn, $text) eq
'reply');
@@ -5350,7 +5353,7 @@ sub handle_fediverse_stream_event {
elsif (!$notimeline) {
$w = $w->{'payload'};
my $sou_sn =
&descape($w->{'source'}->{'screen_name'});
&descape($w->{'source'}->{'username'} || $w->{'source'}->{'acct'});
if (!length($sou_sn) || !$filterusers_sub ||
!&$filterusers_sub($sou_sn)) {
&send_removereadline if ($termrl);
@@ -5381,7 +5384,7 @@ sub handle_mastodon_stream_event {
# Filter replies if requested
if ($nostreamreplies) {
my $sn = &descape($payload->{'user'}->{'screen_name'});
my $sn = &descape($payload->{'user'}->{'username'} || $payload->{'user'}->{'acct'});
my $text = &descape($payload->{'text'});
return if (&$posttype($payload, $sn, $text) eq 'reply');
}
@@ -5717,7 +5720,7 @@ sub tdisplay { # used by both synchronous /again and asynchronous refreshes
my $g = ($i-1);
$j = $my_json_ref->[$g];
my $id = $j->{'id_str'};
my $sn = $j->{'user'}->{'screen_name'};
my $sn = $j->{'user'}->{'username'} || $j->{'user'}->{'acct'};
next if (!length($sn));
$sn = lc(&descape($sn));
@@ -5748,7 +5751,7 @@ sub tdisplay { # used by both synchronous /again and asynchronous refreshes
$filterusers_sub &&
&$filterusers_sub(lc(&descape($j->
{'reblog'}->
{'user'}->{'screen_name'}))));
{'user'}->{'username'} || $j->{'user'}->{'acct'}))));
# second, filterrts. this is almost as fast.
(&killtw($j), next) if
@@ -6307,7 +6310,14 @@ sub foruuser {
my $basef = shift;
my $verb = shift;
my ($en, $em) = &central_cd_dispatch("screen_name=$uname",
# Look up account ID for the username
my $account_id = &lookup_account_id($uname);
if (!$account_id) {
print $stdout "-- ERROR: Could not find account for user: $uname\n" if ($interactive);
return 1;
}
my ($en, $em) = &central_cd_dispatch("id=$account_id",
$interactive, $basef);
print $stdout "-- ok, you have $verb following user $uname.\n"
if ($interactive && !$en);
@@ -6321,7 +6331,14 @@ sub boruuser {
my $basef = shift;
my $verb = shift;
my ($en, $em) = &central_cd_dispatch("screen_name=$uname",
# Look up account ID for the username
my $account_id = &lookup_account_id($uname);
if (!$account_id) {
print $stdout "-- ERROR: Could not find account for user: $uname\n" if ($interactive);
return 1;
}
my ($en, $em) = &central_cd_dispatch("id=$account_id",
$interactive, $basef);
print $stdout "-- ok, you have $verb blocking user $uname.\n"
if ($interactive && !$en);
@@ -6336,8 +6353,15 @@ sub rtsonoffuser {
my $verb = ($selection) ? 'enabled' : 'disabled';
my $tval = ($selection) ? 'true' : 'false';
# Look up account ID for the username
my $account_id = &lookup_account_id($uname);
if (!$account_id) {
print $stdout "-- ERROR: Could not find account for user: $uname\n" if ($interactive);
return 1;
}
my ($en, $em) = &central_cd_dispatch(
"reposts=${tval}&screen_name=${uname}",
"reposts=${tval}&id=${account_id}",
$interactive, $frupdurl);
print $stdout "-- ok, you have ${verb} boosts for user $uname.\n"
if ($interactive && !$en);
@@ -6362,7 +6386,7 @@ sub standardpost {
my $ref = shift;
my $nocolour = shift;
my $sn = &descape($ref->{'user'}->{'acct'} || $ref->{'user'}->{'screen_name'});
my $sn = &descape($ref->{'user'}->{'acct'} || $ref->{'user'}->{'username'});
my $post = &descape($ref->{'text'});
# Debug boost display
@@ -6683,8 +6707,8 @@ sub standardevent {
# ActivityPub streaming API messages
if (length($verb)) { # see below for server-level events
my $tar_sn = '@'.&descape($ref->{'target'}->{'acct'} || $ref->{'target'}->{'screen_name'});
my $sou_sn = '@'.&descape($ref->{'source'}->{'acct'} || $ref->{'source'}->{'screen_name'});
my $tar_sn = '@'.&descape($ref->{'target'}->{'acct'} || $ref->{'target'}->{'username'});
my $sou_sn = '@'.&descape($ref->{'source'}->{'acct'} || $ref->{'source'}->{'username'});
my $tar_list_name = '';
my $tar_list_desc = '';
@@ -7030,7 +7054,7 @@ sub defaulthandle {
my $post_ref = shift;
my $class = shift;
my $dclass = ($verbose) ? "{$class,$post_ref->{'id_str'}} " : '';
my $sn = &descape($post_ref->{'user'}->{'acct'} || $post_ref->{'user'}->{'screen_name'});
my $sn = &descape($post_ref->{'user'}->{'acct'} || $post_ref->{'user'}->{'username'});
my $post = &descape($post_ref->{'text'});
# Debug: Check what data defaulthandle receives for boost posts
@@ -7074,7 +7098,7 @@ sub userline { # used by both $userhandle and /whois
($my_json_ref->{'protected'} eq 'true') ?
"${EM}(Protected)${OFF} " : '';
print $fh <<"EOF";
${CCprompt}@{[ &descape($my_json_ref->{'name'}) ]}${OFF} (@{[ &descape($my_json_ref->{'screen_name'}) ]}) (f:$my_json_ref->{'friends_count'}/$my_json_ref->{'followers_count'}) (u:$my_json_ref->{'statuses_count'}) ${verified}${protected}
${CCprompt}@{[ &descape($my_json_ref->{'name'}) ]}${OFF} (@{[ &descape($my_json_ref->{'username'} || $my_json_ref->{'acct'}) ]}) (f:$my_json_ref->{'friends_count'}/$my_json_ref->{'followers_count'}) (u:$my_json_ref->{'statuses_count'}) ${verified}${protected}
EOF
return;
}
@@ -7082,7 +7106,7 @@ sub sendnotifies { # this is a default subroutine of a sort, right?
my $post_ref = shift;
my $class = shift;
my $sn = &descape($post_ref->{'user'}->{'acct'} || $post_ref->{'user'}->{'screen_name'});
my $sn = &descape($post_ref->{'user'}->{'acct'} || $post_ref->{'user'}->{'username'});
my $post = &descape($post_ref->{'text'});
# Debug: Show what we received
@@ -7193,6 +7217,33 @@ sub mark_conversation_read {
}
}
sub lookup_account_id {
my $username = shift;
return unless ($username);
# Remove @ prefix if present
$username =~ s/^@//;
print $stdout "-- DEBUG: Looking up account ID for username: $username\n" if ($verbose);
# Use Mastodon search API to find account
my $search_result = &grabjson("${searchurl}?q=${username}&type=accounts&limit=1", 0, 0, 0, undef, 1);
if ($search_result && $search_result->{'accounts'} && @{$search_result->{'accounts'}}) {
my $account = $search_result->{'accounts'}->[0];
my $found_username = $account->{'username'} || $account->{'acct'};
# Verify we found the right account (case-insensitive match)
if (lc($found_username) eq lc($username) || lc($account->{'acct'}) eq lc($username)) {
print $stdout "-- DEBUG: Found account ID: " . $account->{'id'} . " for $username\n" if ($verbose);
return $account->{'id'};
}
}
print $stdout "-- DEBUG: Could not find account ID for username: $username\n" if ($verbose);
return undef;
}
sub defaulteventhandle {
(&flag_default_call, return) if ($multi_module_context);
my $event_ref = shift;
@@ -8551,17 +8602,17 @@ sub normalizejson {
$i->{'reblog'} = $rt;
# Get original author and content
my $original_author = $rt->{'user'}->{'acct'} || $rt->{'user'}->{'screen_name'} || 'unknown_user';
my $original_author = $rt->{'user'}->{'acct'} || $rt->{'user'}->{'username'} || 'unknown_user';
my $content = $rt->{'text'} || '';
# Get booster (who shared this)
my $booster = $i->{'user'}->{'acct'} || $i->{'user'}->{'screen_name'} || 'unknown_booster';
my $booster = $i->{'user'}->{'acct'} || $i->{'user'}->{'username'} || 'unknown_booster';
print $stdout "-- DEBUG: Boost - original: '$original_author', booster: '$booster', content: '$content'\n" if ($verbose);
# Store boost data to apply after destroy_all_tco
$boost_content = $content;
my $original_acct = $rt->{'user'}->{'acct'} || $rt->{'user'}->{'screen_name'} || $original_author;
my $original_acct = $rt->{'user'}->{'acct'} || $rt->{'user'}->{'username'} || $original_author;
$boost_attribution = $original_acct;
# Set booster as the main user (who performed the boost action)