Mostly a bug fix push. Extensions working (for the most part), visibility shownb on the line with the code, that line is pretty much an information line, code, date, client, etc now.

This commit is contained in:
Storm Dragon
2025-07-27 13:46:32 -04:00
parent b92fc9fe47
commit 6bbd2ab263
+414 -66
View File
@@ -48,13 +48,22 @@ BEGIN {
$my_version_string = ($TTYverse_PATCH_VERSION) ? "${TTYverse_VERSION}.${TTYverse_PATCH_VERSION}" : "${TTYverse_VERSION}";
(warn ("$my_version_string\n"), exit) if ($version);
# Set up XDG config directory early for -create-rc
# Set up XDG directories early for -create-rc and extensions
our $config = ($ENV{'XDG_CONFIG_HOME'} || "$ENV{'HOME'}/.config") . '/ttyverse';
# Check if the directory exists; if not, create it
our $data = ($ENV{'XDG_DATA_HOME'} || "$ENV{'HOME'}/.local/share") . '/ttyverse';
# Check if directories exist; if not, create them
unless (-d $config) {
eval { require File::Path; File::Path::make_path($config) };
if ($@) {
die "Failed to create directory: $@";
die "Failed to create config directory: $@";
}
}
unless (-d $data) {
eval { require File::Path; File::Path::make_path("$data/extensions", "$data/sounds/default") };
if ($@) {
die "Failed to create data directory: $@";
}
}
@@ -82,24 +91,40 @@ BEGIN {
# fediverseserver=mastodon.social # Your fediverse server
# ssl=1 # Use SSL/HTTPS (recommended)
# authtype=oauth2 # Authentication type (oauth2 for fediverse)
# apibase= # Custom API base URL (auto-detected)
# oauthbase= # Custom OAuth base URL (auto-detected)
# === AUTHENTICATION ===
# keyfile=~/.config/ttyverse/key # Path to OAuth key file
# anonymous=0 # Anonymous mode (not supported for fediverse)
# === TIMELINE SETTINGS ===
# pause=auto # Auto-refresh rate (seconds, or 'auto')
# backload=30 # Number of posts to load initially
# wrap=120 # Text wrapping width
# timestamp=0 # Show timestamps on posts (0=off, 1=relative, 2=both)
# timestamp=0 # Show timestamps (0=off, 1=on, format string for custom)
# noreblogs=0 # Hide boost/reblog posts
# notimeline=0 # Disable timeline display
# searchhits=20 # Number of search results to show
# === DISPLAY SETTINGS ===
# ansi=1 # Use ANSI colors
# noansi=0 # Force disable colors
# verbose=0 # Show debug information
# superverbose=0 # Even more debug information
# silent=0 # Reduce output messages
# readline=1 # Use readline for input (if available)
# readlinerepaint=0 # Repaint readline on signals
# vcheck=1 # Check for updates on startup
# noprompt=0 # Disable interactive prompts
# newline=0 # Add extra newlines
# === POST SETTINGS ===
# post_visibility=public # Default post visibility (public, unlisted, private, direct)
# linelength=5000 # Maximum post length
# autosplit=0 # Auto-split long posts
# slowpost=0 # Slower posting for rate limiting
# verify=0 # Verify posts before sending
# === COLORS ===
# colourprompt=CYAN # Prompt color
@@ -111,25 +136,75 @@ BEGIN {
# colourlist=OFF # List posts color
# colourdefault=OFF # Default text color
# === ADVANCED SETTINGS ===
# === STREAMING API ===
# dostream=0 # Enable streaming API (real-time updates)
# synch=0 # Synchronous mode (blocks on requests)
# maxhist=10 # Command history size
# location=0 # Include location in posts
# notimeline=0 # Disable timeline display
# daemon=0 # Run as daemon (background)
# nostreamreplies=0 # Don't stream replies
# streamallreplies=0 # Stream all replies (not just to you)
# eventbuf=0 # Event buffer size for streaming
# === NOTIFICATION SETTINGS ===
# === DIRECT MESSAGES ===
# dmpause=0 # DM refresh rate (0=use main pause)
# === INTERACTION ===
# mentions=0 # Show mentions in timeline
# synch=0 # Synchronous mode (blocks on requests)
# maxhist=19 # Command history size
# hold=0 # Hold mode for piped input
# daemon=0 # Run as daemon (background)
# script=0 # Script mode (non-interactive)
# === LOCATION ===
# location=0 # Include location in posts
# lat= # Latitude for location
# long= # Longitude for location
# === NOTIFICATIONS ===
# notifies= # Comma-separated list of notification types
# notifyquiet=0 # Quiet notifications
# notifytype= # Type of notifications to send
# === FILTER SETTINGS ===
# track= # Track keywords (comma-separated)
# === FILTERING ===
# track= # Track keywords (space-separated)
# filter= # Filter out posts containing these terms
# notrack=0 # Disable tracking
# filterusers= # Filter posts from specific users
# filterats= # Filter posts with specific @mentions
# filterrts= # Filter retweets/boosts
# filteratonly= # Only show posts with @mentions
# filterflags= # Filter flags
# nofilter=0 # Disable all filtering
# === LISTS ===
# lists= # Comma-separated list of lists to follow
# === URL HANDLING ===
# urlopen=echo %U # Command to open URLs (%U = URL)
# shoreblogurl=http://is.gd/api.php?longurl= # URL shortening service
# === SYSTEM SETTINGS ===
# seven=0 # 7-bit mode for older terminals
# oldperl=0 # Compatibility mode for old Perl
# signals_use_posix=0 # Use POSIX signals (auto-detected)
# nocounter=0 # Disable character counter
# exception_is_maskable=0 # Allow masking exceptions
# simplestart=0 # Simple startup mode
# noratelimit=0 # Ignore rate limiting
# notco=0 # Disable t.co URL expansion
# === ADVANCED/TECHNICAL ===
# runcommand= # Command to run on startup
# twarg= # Legacy Twitter argument (unused)
# user= # Username override
# leader= # Command leader character
# === EXTENSION SETTINGS ===
# Any setting starting with 'extpref_' is for extensions
# extpref_example_setting=value
# Extensions can be loaded with the -exts option
# Extension preferences use the extpref_ prefix:
# extpref_sound_command=paplay # Sound system command
# extpref_tts_synthesizer=espeak # Text-to-speech engine
# extpref_tts_language=en-US # TTS language
# extpref_tts_rate=175 # TTS speaking rate
# extpref_tts_variant= # TTS voice variant
EOF
close($rc_fh);
@@ -327,16 +402,44 @@ EOF
# XDG config directory already set up earlier
# Check for deprecated -keyf parameter and error out
if (defined($keyf)) {
die("** Error: -keyf is deprecated. Use -keyfile instead.\n** Example: ./ttyverse.pl -keyfile test -oauthwizard\n");
}
# Handle command line argument parsing for keyfile
# Perl's -s flag makes -keyfile without = set $keyfile=1, so fix this
if (defined($keyfile) && $keyfile eq '1' && @ARGV && $ARGV[0] !~ /^-/) {
$keyfile = shift @ARGV; # Take the next argument as the keyfile path
# If it's a relative path, put it in the config directory
if ($keyfile !~ m|^/|) { # Not an absolute path
$keyfile = "$config/$keyfile";
}
# Process remaining flags that Perl's -s might have missed
while (@ARGV && $ARGV[0] =~ /^-(\w+)$/) {
my $flag = $1;
shift @ARGV;
if ($flag eq 'oauthwizard') { $oauthwizard = 1; }
elsif ($flag eq 'retoke') { $retoke = 1; }
elsif ($flag eq 'verbose') { $verbose = 1; }
# Add other flags as needed
}
}
# try to find an OAuth keyfile if we haven't specified key+secret
# no worries if this fails; we could be Basic Auth, after all
$whine = (length($keyf)) ? 1 : 0;
$keyf ||= "$config/key";
$attempted_keyf = $keyf;
my $user_specified_keyfile = length($keyfile) ? 1 : 0;
# Only set default keyfile path if none specified
if (!$keyfile) {
$keyfile = "$config/key";
}
$whine = $user_specified_keyfile;
$attempted_keyfile = $keyfile;
if (!length($oauthkey) && !length($oauthsecret) # set later
&& !length($tokenkey)
&& !length($tokensecret) && !$oauthwizard) {
my $keybuf = '';
if(open(W, $keyf)) {
if(open(W, $keyfile)) {
while(<W>) {
chomp;
s/\s+//g;
@@ -383,16 +486,16 @@ EOF
$authtype = 'oauth2';
}
}
die("** tried to load OAuth tokens from $keyf\n".
die("** tried to load OAuth tokens from $keyfile\n".
" but it seems corrupt or incomplete. please see the documentation,\n".
" or delete the file so that we can try making your keyfile again.\n")
unless ($oauth_valid);
} else {
die("** couldn't open keyfile $keyf: $!\n".
die("** couldn't open keyfile $keyfile: $!\n".
"if you want to run the OAuth wizard to create this file, add ".
"-oauthwizard\n")
if ($whine);
$keyf = ''; # i.e., we loaded nothing from a key file
$keyfile = ''; # i.e., we loaded nothing from a key file
}
}
@@ -612,6 +715,35 @@ if ($script) {
### now instantiate the TTYverse dynamic API ###
### based off the defaults later in script. ####
# resolve extension path using XDG directory only
sub resolve_extension_path {
my $name = shift;
# If absolute path, check if it exists
if ($name =~ m{^/}) {
return $name if (-r $name);
return undef;
}
# If relative path (./... or ../...), use as-is if readable
if ($name =~ m{^\.\.?/}) {
return $name if (-r $name);
return undef;
}
# Only location: XDG user extensions directory
my $ext_path = "$data/extensions/$name";
return $ext_path if (-r $ext_path);
# If name doesn't end in .pl, try adding it
unless ($name =~ /\.pl$/) {
my $pl_path = "$data/extensions/$name.pl";
return $pl_path if (-r $pl_path);
}
return undef; # Not found
}
# first we need to load any extensions specified by -exts.
if (length($exts) && $exts ne '0') {
$multi_module_mode = -1; # mark as loader stage
@@ -626,20 +758,26 @@ if (length($exts) && $exts ne '0') {
#TODO
# wildcards?
$file =~ s/$xstring/,/g;
print "** loading $file\n" unless ($silent);
my $original_file = $file;
# Resolve extension path using XDG + submodule locations
my $extension_path = &resolve_extension_path($file);
die("** extension '$original_file' not found in any search path\n")
unless ($extension_path);
print "** loading $extension_path\n" unless ($silent);
die("** sorry, you cannot load the same extension twice.\n")
if ($master_store->{$file}->{'loaded'});
if ($master_store->{$original_file}->{'loaded'});
# prepare its working space in $store and load the module
$master_store->{$file} = { 'loaded' => 1 };
$store = \%{ $master_store->{$file} };
$master_store->{$original_file} = { 'loaded' => 1 };
$store = \%{ $master_store->{$original_file} };
$EM_DONT_CARE = 0;
$EM_SCRIPT_ON = 1;
$EM_SCRIPT_OFF = -1;
$extension_mode = $EM_DONT_CARE;
die("** $file not found: $!\n") if (! -r "$file");
require $file; # and die if bad
require $extension_path; # and die if bad
die("** $file failed to load: $@\n") if ($@);
die("** consistency failure: reference failure on $file\n")
if (!$store->{'loaded'});
@@ -688,6 +826,19 @@ if (length($exts) && $exts ne '0') {
}
}
}
# Show summary of loaded extensions
unless ($silent) {
my @loaded_extensions = ();
foreach my $ext_name (keys %$master_store) {
push @loaded_extensions, $ext_name if $master_store->{$ext_name}->{'loaded'};
}
if (@loaded_extensions) {
my $ext_list = join(', ', @loaded_extensions);
print "** loaded extensions: $ext_list\n";
}
}
# success! enable multi-module support in the TTYverse API and then
# dispatch calls through the multi-module system instead.
$multi_module_mode = 1; # mark as completed loader
@@ -748,6 +899,9 @@ $fetch_id = $last_id || 0;
# we can't do this in BEGIN, because it may not be instantiated yet,
# and we have to do it after loading modules because it might be in one.
@notifytypes = ();
# Initialize flag to suppress notifications during initial timeline load
$initial_load_in_progress = 1;
if (length($notifytype) && $notifytype ne '0' &&
$notifytype ne '1' && !$status) {
# NOT $script! scripts have a use case for notifiers!
@@ -1186,7 +1340,7 @@ if ($authtype eq 'basic') {
$tokensecret = undef;
}
# but if we are using OAuth, we can request one, unless we are in script
elsif (($authtype eq 'oauth' || $authtype eq 'oauth2') && (!length($keyf) || $oauthwizard)) {
elsif (($authtype eq 'oauth' || $authtype eq 'oauth2') && (!length($keyfile) || $oauthwizard)) {
if (length($oauthkey) && length($oauthsecret) &&
!length($tokenkey) && !length($tokensecret)) {
# we have a key, we don't have the user token
@@ -1200,7 +1354,7 @@ EOF
exit;
}
# run the wizard, which writes a keyfile for us
$keyf ||= $attempted_keyf;
# keyfile is already set correctly from command line or default
print $stdout <<"EOF";
+----------------------------------------------------------------------------+
@@ -1215,10 +1369,10 @@ access tokens. This needs to be done JUST ONCE. You can take this keyfile with
you to other systems. If you revoke TTYverse's access, you must remove the
keyfile and start again with a new token. You need to do this once per account
you use with TTYverse; only one account token can be stored per keyfile. If you
have multiple accounts, use -keyf=... to specify different keyfiles. KEEP THESE
have multiple accounts, use -keyfile=... to specify different keyfiles. KEEP THESE
FILES SECRET.
** This wizard will overwrite $keyf
** This wizard will overwrite $keyfile
Press RETURN/ENTER to continue or CTRL-C NOW! to abort.
EOF
$j = <STDIN>;
@@ -1265,20 +1419,20 @@ EOF
$oauthkey = "X";
$oauthsecret = "X";
open(W, ">$keyf") ||
die("Failed to write keyfile $keyf: $!\n");
open(W, ">$keyfile") ||
die("Failed to write keyfile $keyfile: $!\n");
print W <<"EOF";
ck=${oauthkey}&cs=${oauthsecret}&at=${tokenkey}&ats=${tokensecret}
EOF
close(W);
chmod(0600, $keyf) || print $stdout
"Warning: could not change permissions on $keyf : $!\n";
chmod(0600, $keyfile) || print $stdout
"Warning: could not change permissions on $keyfile : $!\n";
print $stdout <<"EOF";
Written keyfile $keyf
Written keyfile $keyfile
Now, restart TTYverse to use this keyfile.
(To choose between multiple keyfiles other than the default .ttyversekey,
tell TTYverse where the key is using -keyf=... .)
tell TTYverse where the key is using -keyfile=... .)
EOF
exit;
@@ -1296,7 +1450,7 @@ EOF
print $streamout <<"EOF";
you are missing portions of the OAuth sequence. either create a keyfile
and point to it with -keyf=... or add these missing pieces:
and point to it with -keyfile=... or add these missing pieces:
$error
then restart TTYverse, or use -authtype=basic.
EOF
@@ -1304,7 +1458,7 @@ EOF
}
} # end OAuth 1.0a else block
}
} elsif ($retoke && length($keyf)) {
} elsif ($retoke && length($keyfile)) {
# start the "re-toke" wizard to convert DM-less cloned app keys.
# dup STDIN for systems that can only "close" it once
open(STDIN2, "<&STDIN") || die("couldn't dup STDIN: $!\n");
@@ -1329,8 +1483,8 @@ You SHOULD NOT need this wizard if your app key was cloned after 1 June 2011.
However, you can still use it if you experience this specific issue with DMs,
or need to rebuild your keyfile for any other reason.
** This wizard will overwrite the key at $keyf
** To change this, restart TTYverse with -retoke -keyf=/path/to/keyfile
** This wizard will overwrite the key at $keyfile
** To change this, restart TTYverse with -retoke -keyfile=/path/to/keyfile
Press RETURN/ENTER to continue, or CTRL-C NOW! to abort.
EOF
@@ -1441,13 +1595,13 @@ Access token =========> $at
Access token secret ==> $ats
EOF
open(W, ">$keyf") || (print $stdout ("Unable to write to $keyf: $!\n"),
open(W, ">$keyfile") || (print $stdout ("Unable to write to $keyfile: $!\n"),
exit);
print W "ck=$ck&cs=$cs&at=$at&ats=$ats\n";
close(W);
chmod(0600, $keyf) || print $stdout
"Warning: could not change permissions on $keyf : $!\n";
print $stdout "Keys written to regenerated keyfile $keyf\n";
chmod(0600, $keyfile) || print $stdout
"Warning: could not change permissions on $keyfile : $!\n";
print $stdout "Keys written to regenerated keyfile $keyfile\n";
print $stdout "Now restart TTYverse.\n";
exit;
}
@@ -1911,6 +2065,48 @@ exit;
sub prinput {
my $i;
local($_) = shift; # bleh
# Paste protection - detect multi-line input
if (!$script && $_ =~ /\n/) {
my @lines = split(/\n/, $_);
my $line_count = scalar(@lines);
if ($line_count > 3) {
print $stdout "-- PASTE PROTECTION: Detected $line_count lines of input!\n";
print $stdout "-- This looks like an accidental paste.\n";
print $stdout "-- First few lines:\n";
# Show first 3 lines as preview
for my $j (0..($line_count > 3 ? 2 : $line_count-1)) {
my $preview = substr($lines[$j], 0, 60);
$preview .= "..." if length($lines[$j]) > 60;
print $stdout " " . ($j+1) . ": $preview\n";
}
print $stdout "-- Type 'paste' to continue or anything else to cancel: ";
my $response = <STDIN>;
chomp($response);
if (lc($response) ne 'paste') {
print $stdout "-- Multi-line input cancelled.\n";
return 0;
}
print $stdout "-- Processing multi-line input...\n";
}
# Process each line separately with paste protection
my $processed = 0;
for my $line (@lines) {
next if $line =~ /^\s*$/; # Skip empty lines
my $result = &prinput($line);
if ($result < 0) {
return $result; # Propagate quit command (-1)
}
$processed++;
}
return $processed;
}
# validate this string if we are in UTF-8 mode
unless ($seven) {
@@ -3444,7 +3640,29 @@ 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;
}
my $target = &descape($post->{'user'}->{'acct'} || $post->{'user'}->{'screen_name'});
# Use acct field if available, otherwise construct from screen_name and server
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;
}
} 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);
$_ = '@' . $target . " $_";
unless ($mode eq 'v') {
$in_reply_to = $post->{'id_str'};
@@ -3489,7 +3707,28 @@ 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;
}
my $target = &descape($post->{'user'}->{'acct'} || $post->{'user'}->{'screen_name'});
# Use acct field if available, otherwise construct from screen_name and server
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;
}
} else {
$target = &descape($post->{'user'}->{'acct'} || $post->{'user'}->{'screen_name'});
}
my $text = $_;
$_ = '@' . $target;
unless ($mode eq 'v') {
@@ -5405,6 +5644,13 @@ sub refresh {
"-- last_id $last_id, fetch_id $fetch_id, rollback $relative_last_id\n".
"-- (@{[ scalar(keys %id_cache) ]} cached)\n"
if ($verbose);
# Clear initial load flag after first timeline display
if ($initial_load_in_progress) {
$initial_load_in_progress = 0;
print $stdout "-- DEBUG: Initial timeline load complete, notifications now enabled\n" if ($verbose);
}
&send_removereadline if ($termrl);
&$conclude;
$wrapseq = 1;
@@ -5685,22 +5931,59 @@ sub updatest {
return 99;
}
# "the pastebrake"
# "the pastebrake" - enhanced paste protection
if (!$slowpost && !$verify && !$script) {
if ((time() - $postbreak_time) < 5) {
my $current_time = time();
# Check if posting too fast (more than 2 posts in 3 seconds)
if (($current_time - $postbreak_time) < 3) {
$postbreak_count++;
if ($postbreak_count == 3) {
# First warning after 2 rapid posts
if ($postbreak_count == 2) {
print $stdout
"-- you're posting pretty fast. did you mean to do that?\n".
"-- waiting three seconds before taking the next set of posts\n".
"-- hit CTRL-C NOW! to kill TTYverse if you accidentally pasted in this window\n";
sleep 3;
$postbreak_count = 0;
"-- PASTE PROTECTION: You're posting very fast!\n".
"-- This might be an accidental paste. Press CTRL-C to abort!\n".
"-- Waiting 5 seconds... (posts will be ignored during this time)\n";
# Install signal handler for interrupt
local $SIG{INT} = sub {
print $stdout "\n-- PASTE ABORTED by user! No more posts will be sent.\n";
$postbreak_count = 999; # Block further posts
die "User aborted paste sequence\n";
};
# Wait with interrupt checking
for my $i (1..5) {
print $stdout "-- $i... ";
sleep 1;
}
print $stdout "\n-- Continuing (press CTRL-C quickly to abort next posts)\n";
}
# After 4 posts, require confirmation
if ($postbreak_count >= 4) {
print $stdout
"-- PASTE PROTECTION: Blocking rapid posts!\n".
"-- You've posted $postbreak_count times in quick succession.\n".
"-- Type 'continue' to keep posting, or CTRL-C to abort: ";
my $response = <STDIN>;
chomp($response);
if (lc($response) ne 'continue') {
print $stdout "-- Paste sequence aborted by user.\n";
return 98; # Return without posting
}
print $stdout "-- Continuing paste sequence...\n";
$postbreak_count = 0; # Reset after confirmation
}
} else {
# Reset counter if enough time has passed
$postbreak_count = 0;
}
$postbreak_time = time();
$postbreak_time = $current_time;
}
my $payload = 'status'; # Always use 'status' for Mastodon
@@ -6018,6 +6301,48 @@ sub standardpost {
$info_line .= " ($relative_time)" if ($relative_time);
$info_line .= " via $client_info" if ($client_info);
# Add visibility indicator
my $visibility = $ref->{'visibility'} || 'public'; # Default to public if not specified
my $vis_display = '';
my $vis_color = '';
if ($visibility eq 'public') {
$vis_display = '[Public]';
$vis_color = $CYAN; # Subtle cyan for public
} elsif ($visibility eq 'unlisted') {
$vis_display = '[Unlisted]';
$vis_color = $MAGENTA; # Purple for unlisted
} elsif ($visibility eq 'private') {
$vis_display = '[Followers]';
$vis_color = $YELLOW; # Yellow for followers-only
} elsif ($visibility eq 'direct') {
$vis_display = '[Direct]';
$vis_color = $RED; # Red for direct messages
}
if ($vis_display) {
if ($nocolour) {
$info_line .= " $vis_display";
} else {
$info_line .= " ${vis_color}${vis_display}${OFF}";
}
}
# Add content warning/title if present
my $cw_text = '';
if (exists($ref->{'reblog'}) && $ref->{'reblog'} && exists($ref->{'reblog'}->{'spoiler_text'})) {
# For boost posts, check original post's content warning
$cw_text = $ref->{'reblog'}->{'spoiler_text'};
} elsif (exists($ref->{'spoiler_text'})) {
# Regular post content warning
$cw_text = $ref->{'spoiler_text'};
}
if ($cw_text && length($cw_text)) {
$cw_text = &descape($cw_text);
$info_line .= " [$cw_text]";
}
# fediverse doesn't always do this right.
$h = $ref->{'reblogs_count'}; $h += 0; #$h = "${h}+" if ($h >= 100);
# fediverse doesn't always handle single reposts right. good f'n grief.
@@ -6600,6 +6925,7 @@ sub defaulthandle {
: '';
print $streamout $menu_select . $dclass . $spost;
print $stdout "-- DEBUG: defaulthandle about to call sendnotifies with class='$class'\n" if ($verbose);
&sendnotifies($post_ref, $class);
return 1;
}
@@ -6637,12 +6963,29 @@ sub sendnotifies { # this is a default subroutine of a sort, right?
my $sn = &descape($post_ref->{'user'}->{'acct'} || $post_ref->{'user'}->{'screen_name'});
my $post = &descape($post_ref->{'text'});
# interactive? first time?
unless (length($class) || !$last_id || !length($post)) {
# Debug: Show what we received
print $stdout "-- DEBUG: sendnotifies called with class='$class', sn='$sn'\n" if ($verbose);
# If no class provided, determine it from post content
if (!length($class) && length($post)) {
$class = scalar(&$posttype($post_ref, $sn, $post));
print $stdout "-- DEBUG: sendnotifies determined class='$class'\n" if ($verbose);
}
# Debug: Show notify_list status
my $notify_enabled = $notify_list{$class} ? 'YES' : 'NO';
print $stdout "-- DEBUG: notify_list{$class} = $notify_enabled\n" if ($verbose);
# Send notification if we have a valid class, it's enabled, AND initial load is complete
if (length($class) && $notify_list{$class} && !$initial_load_in_progress) {
print $stdout "-- DEBUG: Calling notifytype_dispatch for class='$class'\n" if ($verbose);
&notifytype_dispatch($class,
&standardpost($post_ref, 1), $post_ref)
if ($notify_list{$class});
&standardpost($post_ref, 1), $post_ref);
} else {
my $reason = !length($class) ? "no class" :
!$notify_list{$class} ? "disabled" :
$initial_load_in_progress ? "initial load" : "unknown";
print $stdout "-- DEBUG: NOT calling notifytype_dispatch - class='$class', reason='$reason'\n" if ($verbose);
}
}
@@ -6692,7 +7035,7 @@ sub defaultdmhandle {
sub senddmnotifies {
my $dm_ref = shift;
&notifytype_dispatch('DM', &standarddm($dm_ref, 1), $dm_ref)
&notifytype_dispatch('dm', &standarddm($dm_ref, 1), $dm_ref)
if ($notify_list{'dm'} && $last_dm);
}
@@ -7316,8 +7659,12 @@ sub tracktags_compile {
# notification multidispatch
sub notifytype_dispatch {
print $stdout "-- DEBUG: notifytype_dispatch called with " . scalar(@notifytypes) . " notifiers\n" if ($verbose);
return if (!scalar(@notifytypes));
my $nt; foreach $nt (@notifytypes) { &$nt(@_); }
my $nt; foreach $nt (@notifytypes) {
print $stdout "-- DEBUG: Calling notifier function: $nt\n" if ($verbose);
&$nt(@_);
}
}
# notifications compiler
@@ -7330,6 +7677,7 @@ sub notify_compile {
$notify_list{$w} = 1;
}
$notifies = join(',', keys %notify_list);
print $stdout "-- DEBUG: notify_list compiled: " . join(',', keys %notify_list) . "\n" if ($verbose);
}
}
@@ -9141,12 +9489,12 @@ sub oauth2_wizard {
# Step 4: Save credentials to keyfile
my $keyfile_content = "client_id=${client_id}&client_secret=${client_secret}&access_token=${access_token}&server=${fediverseserver}";
open(W, ">$keyf") || die("couldn't write keyfile $keyf: $!\n");
chmod 0600, $keyf;
open(W, ">$keyfile") || die("couldn't write keyfile $keyfile: $!\n");
chmod 0600, $keyfile;
print W $keyfile_content;
close(W);
print $stdout "\nKeyfile $keyf written successfully!\n";
print $stdout "\nKeyfile $keyfile written successfully!\n";
print $stdout "You can now use TTYverse with your $fediverseserver account.\n";
exit;