From d2208e0ddda2a9c7057af9becf6fd0079109a71c Mon Sep 17 00:00:00 2001 From: Storm Dragon Date: Fri, 25 Jul 2025 18:05:29 -0400 Subject: [PATCH] Fixes to direct messaging. Removed some more Twitter terminology. --- ttyverse.pl | 140 ++++++++++++++++++++++++++++++++-------------------- 1 file changed, 86 insertions(+), 54 deletions(-) diff --git a/ttyverse.pl b/ttyverse.pl index 67ec175..ce7036d 100755 --- a/ttyverse.pl +++ b/ttyverse.pl @@ -230,7 +230,7 @@ EOF $favurl = "${apibase}/statuses/%I/favourite"; $favdelurl = "${apibase}/statuses/%I/unfavourite"; $blockingurl = "${apibase}/accounts/relationships?id[]=%I"; - $dmurl = "${apibase}/statuses"; + $dmurl = "${apibase}/conversations"; $directurl = "${apibase}/conversations"; $listurl = "${apibase}/lists/%I/accounts"; $murl = "${apibase}/timelines/home"; @@ -1869,9 +1869,9 @@ print $stdout "*** invalid UTF-8: partial delete of a wide character?\n"; "*** to send this as a command, type /%%\n"; } else { print $stdout - "*** did you really mean to tweet \"$_\"?\n"; + "*** did you really mean to post \"$_\"?\n"; } - print $stdout "*** to tweet it anyway, type %%\n"; + print $stdout "*** to post it anyway, type %%\n"; return 0; } @@ -1994,8 +1994,8 @@ print $stdout "*** invalid UTF-8: partial delete of a wide character?\n"; " " . &descape($tweet->{$k}) . "\n"; } # include a URL to the tweet per @augmentedfourth - $urlshort = - "${http_proto}://twitter.com/$sn/statuses/$tweet->{'id_str'}"; + # URL construction removed for fediverse compatibility + $urlshort = ""; print $stdout "-- %URL% is now $urlshort (/short to shorten)\n"; return 0; @@ -3241,7 +3241,7 @@ m#^/(un)?f(boost|a|av|ave|avorite|avourite)? ([zZ]?[a-zA-Z]?[0-9]+)$#) { $id = $tweet->{'retweeted_status'}->{'id_str'} || $tweet->{'id_str'}; if (!$id) { - print $stdout "-- hmmm, that tweet is major bogus.\n"; + print $stdout "-- hmmm, that post is major bogus.\n"; return 0; } my $url = $rtsbyurl; @@ -3251,7 +3251,7 @@ m#^/(un)?f(boost|a|av|ave|avorite|avourite)? ([zZ]?[a-zA-Z]?[0-9]+)$#) { my $k = scalar(@{ $users_ref }); if (!$k) { print $stdout - "-- no known retweeters, or they're private.\n"; + "-- no known boosters, or they're private.\n"; return 0; } my $j; @@ -3279,7 +3279,7 @@ m#^/(un)?f(boost|a|av|ave|avorite|avourite)? ([zZ]?[a-zA-Z]?[0-9]+)$#) { if (lc(&descape($tweet->{'user'}->{'screen_name'})) ne lc($whoami)) { print $stdout - "-- not allowed to delete somebody's else's tweets\n"; + "-- not allowed to delete somebody's else's posts\n"; return 0; } print $stdout &wwrap( @@ -3288,7 +3288,7 @@ m#^/(un)?f(boost|a|av|ave|avorite|avourite)? ([zZ]?[a-zA-Z]?[0-9]+)$#) { $answer = lc(&linein( "-- sure you want to delete? (only y or Y is affirmative):")); if ($answer ne 'y') { - print $stdout "-- ok, tweet is NOT deleted.\n"; + print $stdout "-- ok, post is NOT deleted.\n"; return 0; } $lastpostid = -1 if ($tweet->{'id_str'} == $lastpostid); @@ -3304,10 +3304,11 @@ m#^/(un)?f(boost|a|av|ave|avorite|avourite)? ([zZ]?[a-zA-Z]?[0-9]+)$#) { print $stdout "-- no such DM (yet?): $code\n"; return 0; } + my $sender = &descape($dm->{'last_status'}->{'account'}->{'username'} || $dm->{'last_status'}->{'account'}->{'acct'}); + my $text = &descape(&html_to_text($dm->{'last_status'}->{'content'})); print $stdout &wwrap( "-- verify you want to delete: " . - "(from @{[ &descape($dm->{'sender'}->{'screen_name'}) ]}) ". - "\"@{[ &descape($dm->{'text'}) ]}\""); + "(from \@${sender}) \"${text}\""); print $stdout "\n"; $answer = lc(&linein( "-- sure you want to delete? (only y or Y is affirmative):")); @@ -3334,7 +3335,7 @@ m#^/(un)?f(boost|a|av|ave|avorite|avourite)? ([zZ]?[a-zA-Z]?[0-9]+)$#) { $answer = lc(&linein( "-- sure you want to delete? (only y or Y is affirmative):")); if ($answer ne 'y') { - print $stdout "-- ok, tweet is NOT deleted.\n"; + print $stdout "-- ok, post is NOT deleted.\n"; return 0; } &deletest($lastpostid, 1); @@ -3378,7 +3379,7 @@ m#^/(un)?f(boost|a|av|ave|avorite|avourite)? ([zZ]?[a-zA-Z]?[0-9]+)$#) { return 0; } # in the future, add DM in_reply_to here - my $target = &descape($dm->{'sender'}->{'acct'} || $dm->{'sender'}->{'screen_name'}); + my $target = &descape($dm->{'last_status'}->{'account'}->{'acct'} || $dm->{'last_status'}->{'account'}->{'username'}); $readline_completion{'@'.lc($target)}++ if ($termrl); $_ = "/dm $target $_"; print $stdout &wwrap("(expanded to \"$_\")"); @@ -3879,7 +3880,7 @@ EOF if ($slash_first) { if (!m#^//#) { print $stdout "*** invalid command\n"; - print $stdout "*** to pass as a tweet, type /%%\n"; + print $stdout "*** to pass as a post, type /%%\n"; return 0; } s#^/##; # leave the second slash on @@ -4300,11 +4301,13 @@ EOF } elsif ($rout =~ /^piped (..)/) { my $key = $dm_store_hash{$1}; my $ms = $key->{'menu_select'} || 'XX'; - my $ds = $key->{'created_at'} || 'argh, no created_at'; + my $ds = $key->{'last_status'}->{'created_at'} || 'argh, no created_at'; $ds =~ s/\s/_/g; - $key = substr(( "$ms ".($key->{'id_str'})." ". - $key->{'sender'}->{'screen_name'}." $ds ". - unpack("${pack_magic}H*", $key->{'text'}). + my $sender = $key->{'last_status'}->{'account'}->{'acct'} || $key->{'last_status'}->{'account'}->{'username'}; + my $content = $key->{'last_status'}->{'content'}; + $key = substr(( "$ms ".($key->{'id'})." ". + $sender." $ds ". + unpack("${pack_magic}H*", $content). $space_pad), 0, 1024); print P $key; goto RESTART_SELECT; @@ -5312,9 +5315,9 @@ sub dmrefresh { for($i = $disp_max; $i > 0; $i--) { $g = ($i-1); my $j = $my_json_ref->[$g]; - next if (!$sent_dm && $j->{'id_str'} <= $last_dm); - next if (!length($j->{'sender'}->{'screen_name'}) || - !length($j->{'recipient'}->{'screen_name'})); + next if (!$sent_dm && $j->{'id'} <= $last_dm); + next if (!$j->{'accounts'} || !@{$j->{'accounts'}} || + !$j->{'last_status'}); $key = substr($alphabet, $dm_counter/10, 1) . $dm_counter % 10; @@ -5330,7 +5333,7 @@ sub dmrefresh { $printed += scalar(&$dmhandle($j)); } - $max = $my_json_ref->[0]->{'id_str'}; + $max = $my_json_ref->[0]->{'id'}; } sleep 5 while ($suspend_output > 0); if (($interactive || $verbose) && !$printed && !$dm_first_time) { @@ -5390,8 +5393,8 @@ sub updatest { $postbreak_time = time(); } - my $payload = (length($user_name_dm)) ? 'text' : 'status'; - $string = &$prepost($string) unless ($user_name_dm || $rt_id); + my $payload = 'status'; # Always use 'status' for Mastodon + $string = &$prepost($string) unless ($rt_id); # YES, you *can* verify and slowpost. I thought about this and I # think I want to allow it. @@ -5421,8 +5424,7 @@ sub updatest { } } - $user_name_dm = (length($user_name_dm)) ? - "&user=$user_name_dm" : ''; + # Keep $user_name_dm as-is for Mastodon DM handling my $i = ''; $i .= "source=TTYverse&" if ($authtype eq 'basic'); @@ -5434,9 +5436,29 @@ sub updatest { print $stdout "-- warning: incomplete location ($lat, $long) ignored\n"; } - $i .= "${payload}=${urle}${user_name_dm}" unless ($rt_id); - $i .= "id=$rt_id" if ($rt_id); - $i .= "visibility=${post_visibility}&" unless ($rt_id || length($user_name_dm)); + if ($rt_id) { + $i .= "id=$rt_id"; + } else { + if (length($user_name_dm)) { + # For DMs: include mention in status and set visibility + my $dm_status = "\@${user_name_dm} ${string}"; + my $dm_urle = ''; + foreach my $char (unpack("${pack_magic}C*", $dm_status)) { + my $k = chr($char); + if ($k =~ /[-._~a-zA-Z0-9]/) { + $dm_urle .= $k; + } else { + $k = sprintf("%02X", $char); + $dm_urle .= "%$k"; + } + } + $i .= "${payload}=${dm_urle}&"; + $i .= "visibility=direct&"; + } else { + $i .= "${payload}=${urle}&"; + $i .= "visibility=${post_visibility}&"; + } + } $slowpost += 0; if ($slowpost && !$script && !$status && !$silent) { if($pid = open(SLOWPOST, '-|')) { # pause background so that it doesn't kill itself @@ -5461,9 +5483,8 @@ sub updatest { } } my $return = &backticks($baseagent, '/dev/null', undef, - (length($user_name_dm)) ? $dmupdate : - ($rt_id) ? "$rturl/${rt_id}.json" : - $update, $i, 0, @wend); + ($rt_id) ? "$rturl/${rt_id}.json" : $update, + $i, 0, @wend); print $stdout "-- return --\n$return\n-- return --\n" if ($superverbose); if ($? > 0) { @@ -5488,7 +5509,7 @@ EOF $return =~ /^/i || $return =~ /^<\??xml\s+/) { print $stdout <<"EOF" if ($interactive); -${MAGENTA}*** warning: Twitter Fail Whale${OFF} +${MAGENTA}*** warning: Server temporarily unavailable${OFF} EOF return 98; } @@ -5613,7 +5634,7 @@ sub rtsonoffuser { my ($en, $em) = ¢ral_cd_dispatch( "retweets=${tval}&screen_name=${uname}", $interactive, $frupdurl); - print $stdout "-- ok, you have ${verb} retweets for user $uname.\n" + print $stdout "-- ok, you have ${verb} boosts for user $uname.\n" if ($interactive && !$en); return 0; } @@ -5711,12 +5732,9 @@ sub standarddm { my $ref = shift; my $nocolour = shift; - my ($time, $ts) = &$wraptime($ref->{'created_at'}); - my $text = &descape($ref->{'text'}); - my $sns = &descape($ref->{'sender'}->{'screen_name'}); - if ($sns eq $whoami) { - $sns = "->" . &descape($ref->{'recipient'}->{'screen_name'}); - } + my ($time, $ts) = &$wraptime($ref->{'last_status'}->{'created_at'}); + my $text = &descape(&html_to_text($ref->{'last_status'}->{'content'})); + my $sns = &descape($ref->{'last_status'}->{'account'}->{'username'} || $ref->{'last_status'}->{'account'}->{'acct'}); my $g = &wwrap("[DM d$ref->{'menu_select'}]". "[$sns/$ts] $text", ($wrapseq <= 1) ? ((&$prompt(1))[1]) : 0); @@ -6183,7 +6201,7 @@ sub defaultconclude { sub defaultdmhandle { (&flag_default_call, return) if ($multi_module_context); my $dm_ref = shift; - my $sns = &descape($dm_ref->{'sender'}->{'screen_name'}); + my $sns = &descape($dm_ref->{'last_status'}->{'account'}->{'username'} || $dm_ref->{'last_status'}->{'account'}->{'acct'}); print $streamout &standarddm($dm_ref); &senddmnotifies($dm_ref) if ($sns ne $whoami); @@ -6450,6 +6468,11 @@ sub get_dm { my $w = {'sender' => {}}; return undef if (length($code) < 3 || $code !~ s/^d//); + # Handle foreground/background like get_tweet does + if ($is_background) { + return $dm_store_hash{$code}; + } + # this is the aforementioned "similar code" (see get_tweet). # optimization: I doubt ANY of us can get DMIDs less than 9. return &grabjson("${dmidurl}?id=$code", 0, 0, 0, undef, 1) @@ -6466,12 +6489,21 @@ sub get_dm { return undef if ($k !~ /[^\s]/); $k =~ s/\s+$//; # remove trailing spaces print $stdout "-- background store fetch: $k\n" if ($verbose); - ($w->{'menu_select'}, $w->{'id_str'}, - $w->{'sender'}->{'screen_name'}, $w->{'created_at'}, - $l) = split(/\s/, $k, 5); - $w->{'text'} = pack("H*", $l); - return undef if (!length($w->{'text'})); # not possible - $w->{'created_at'} =~ s/_/ /g; + my ($menu_select, $id, $sender, $created_at, $hex_content) = split(/\s/, $k, 5); + + # Reconstruct Mastodon conversation format + $w->{'menu_select'} = $menu_select; + $w->{'id'} = $id; + $w->{'last_status'} = { + 'created_at' => $created_at, + 'content' => pack("H*", $hex_content), + 'account' => { + 'username' => $sender, + 'acct' => $sender + } + }; + return undef if (!length($w->{'last_status'}->{'content'})); # not possible + $w->{'last_status'}->{'created_at'} =~ s/_/ /g; return $w; } @@ -7253,10 +7285,10 @@ sub postjson { if ($data =~ /^\[?\]?/i || $data =~ /^<\??xml\s+/) { print $stdout $data if ($superverbose); if (&is_fail_whale($data)) { - &$exception(2, "*** warning: Twitter Fail Whale\n"); + &$exception(2, "*** warning: Server temporarily unavailable\n"); } else { - &$exception(2, "*** warning: Twitter error message received\n" . - (($data =~ /Twitter:\s*([^<]+)</) ? + &$exception(2, "*** warning: server error message received\n" . + (($data =~ /<title>([^<]+)</) ? "*** \"$1\"\n" : '')); } return undef; @@ -7359,10 +7391,10 @@ sub grabjson { if ($data =~ /^\[?\]?<!DOCTYPE\s+html/i || $data =~ /^(Status:\s*)?50[0-9]\s/ || $data =~ /^<html>/i || $data =~ /^<\??xml\s+/) { print $stdout $data if ($superverbose); if (&is_fail_whale($data)) { - &$exception(2, "*** warning: Twitter Fail Whale\n"); + &$exception(2, "*** warning: Server temporarily unavailable\n"); } else { - &$exception(2, "*** warning: Twitter error message received\n" . - (($data =~ /<title>Twitter:\s*([^<]+)</) ? + &$exception(2, "*** warning: server error message received\n" . + (($data =~ /<title>([^<]+)</) ? "*** \"$1\"\n" : '')); } return undef; @@ -7825,7 +7857,7 @@ sub fix_geo_api_data { sub is_fail_whale { # is this actually the dump from a fail whale? my $data = shift; - return ($data =~ m#<title>Twitter.+Over.+capacity.*#i || + return ($data =~ m#.*Over.+capacity.*#i || $data =~ m#[\r\l\n\s]*DB_DataObject Error: Connect failed#s); }