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:
+414
-66
@@ -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);
|
||||
¬ifytype_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;
|
||||
¬ifytype_dispatch('DM', &standarddm($dm_ref, 1), $dm_ref)
|
||||
¬ifytype_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;
|
||||
|
||||
Reference in New Issue
Block a user