Couple of bug fixes for /followers. Added /following.. Fixed the /follow command.
This commit is contained in:
+625
-79
@@ -146,6 +146,9 @@ BEGIN {
|
||||
# === DIRECT MESSAGES ===
|
||||
# dmpause=0 # DM refresh rate (0=use main pause)
|
||||
|
||||
# === NOTIFICATIONS ===
|
||||
# notificationpause=0 # Notification refresh rate (0=use main pause)
|
||||
|
||||
# === INTERACTION ===
|
||||
# mentions=0 # Show mentions in timeline
|
||||
# synch=0 # Synchronous mode (blocks on requests)
|
||||
@@ -237,7 +240,7 @@ EOF
|
||||
signals_use_posix dostream nostreamreplies streamallreplies
|
||||
nofilter
|
||||
); %opts_sync = map { $_ => 1 } qw(
|
||||
ansi pause dmpause ttytteristas verbose superverbose
|
||||
ansi pause dmpause notificationpause ttytteristas verbose superverbose
|
||||
url rlurl dmurl newline wrap notimeline lists dmidurl
|
||||
queryurl track colourprompt colourme notrack
|
||||
colourdm colourreply colourwarn coloursearch colourlist idurl
|
||||
@@ -247,6 +250,7 @@ EOF
|
||||
); %opts_urls = map {$_ => 1} qw(
|
||||
url dmurl uurl rurl wurl frurl rlurl update shoreblogurl
|
||||
apibase fediverseserver queryurl idurl delurl dmdelurl favsurl
|
||||
notificationurl markersurl
|
||||
favurl favdelurl followurl leaveurl
|
||||
dmupdate credurl blockurl blockdelurl friendsurl
|
||||
modifyliurl adduliurl delliurl getliurl getlisurl getfliurl
|
||||
@@ -265,7 +269,7 @@ EOF
|
||||
);
|
||||
|
||||
%opts_can_set = map { $_ => 1 } qw(
|
||||
url pause dmurl dmpause dmmarkread superverbose ansi verbose
|
||||
url pause dmurl dmpause notificationpause dmmarkread superverbose ansi verbose
|
||||
update uurl rurl wurl avatar ttytteristas frurl track
|
||||
rlurl noprompt shoreblogurl newline wrap verify autosplit
|
||||
notimeline queryurl fediverseserver colourprompt colourme
|
||||
@@ -672,15 +676,16 @@ if ($termrl && $termrl->ReadLine eq 'Term::ReadLine::Gnu') {
|
||||
/help /? /quit /q /bye /end /e /exit
|
||||
/refresh /r /thump /again /a
|
||||
/dm /dmr /dmrefresh /dms /dmsent /dmagain
|
||||
/notifications /notificationrefresh /nr
|
||||
/replies /re /reply /timeline /timelines
|
||||
/media /visibility /search /se
|
||||
/media /poll /mpoll /visibility /search /se
|
||||
/history /h /print /p /verbose /ve
|
||||
/ruler /ru /cls /clear /url /open
|
||||
/short /sh /rate /ratelimit
|
||||
/track /tron /troff /trends /woeids
|
||||
/notrack /set /unset /add /del
|
||||
/push /pop /list /lists /listfollowers
|
||||
/listfriends /dump /du /eval /ev
|
||||
/listfriends /followers /following /dump /du /eval /ev
|
||||
/version /update /versioncheck /updatecheck
|
||||
/thread /th /entities /ent /delete
|
||||
/deletelast /rtsof /vote /whois /w /me
|
||||
@@ -1344,6 +1349,9 @@ $dmupdate ||= "${apibase}/statuses"; # DMs are private statuses
|
||||
$dmdelurl ||= "${apibase}/statuses/%I";
|
||||
$dmidurl ||= "${apibase}/statuses/%I";
|
||||
|
||||
$notificationurl ||= "${apibase}/notifications";
|
||||
$markersurl ||= "${apibase}/markers";
|
||||
|
||||
$favsurl ||= "${apibase}/favourites";
|
||||
$favurl ||= "${apibase}/statuses/%I/favourite";
|
||||
$favdelurl ||= "${apibase}/statuses/%I/unfavourite";
|
||||
@@ -1396,6 +1404,9 @@ $dmpause = 4 if (!defined $dmpause); # NOT ||= ... zero is a VALID value!
|
||||
$dmpause = 0 if ($anonymous);
|
||||
$dmpause = 0 if ($pause eq '0');
|
||||
$dmmarkread = 1 if (!defined $dmmarkread); # Default to enabled
|
||||
$notificationpause = 6 if (!defined $notificationpause); # NOT ||= ... zero is a VALID value!
|
||||
$notificationpause = 0 if ($anonymous);
|
||||
$notificationpause = 0 if ($pause eq '0');
|
||||
$ansi = ($noansi) ? 0 :
|
||||
(($ansi || $ENV{'TERM'} eq 'ansi' || $ENV{'TERM'} eq 'xterm-color')
|
||||
? 1 : 0);
|
||||
@@ -1404,9 +1415,11 @@ $ansi = ($noansi) ? 0 :
|
||||
if ($synch) {
|
||||
$pause = 0;
|
||||
$dmpause = ($dmpause) ? 1 : 0;
|
||||
$notificationpause = ($notificationpause) ? 1 : 0;
|
||||
}
|
||||
|
||||
$dmcount = $dmpause;
|
||||
$notificationcount = $notificationpause;
|
||||
$lastshort = undef;
|
||||
|
||||
# ANSI sequences
|
||||
@@ -2008,7 +2021,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->{'username'} || $my_json_ref->{'acct'});
|
||||
$whoami = lc($my_json_ref->{'acct'} || $my_json_ref->{'username'});
|
||||
if (!length($whoami)) {
|
||||
print "FAILED!\nis your account suspended, or wrong token?\n";
|
||||
exit;
|
||||
@@ -2060,6 +2073,7 @@ if ($daemon) {
|
||||
}
|
||||
$parent = 0;
|
||||
$dmcount = 1 if ($dmpause); # force fetch
|
||||
$notificationcount = 1 if ($notificationpause); # force fetch
|
||||
$is_background = 1;
|
||||
DAEMONLOOP: for(;;) {
|
||||
my $snooze;
|
||||
@@ -2070,6 +2084,13 @@ if ($daemon) {
|
||||
&update_effpause;
|
||||
&refresh(0);
|
||||
$dont_refresh_first_time = 0;
|
||||
# Check notifications before DMs since DM refresh can return early
|
||||
if ($notificationpause) {
|
||||
if (!--$notificationcount) {
|
||||
¬ificationrefresh(0);
|
||||
$notificationcount = $notificationpause;
|
||||
}
|
||||
}
|
||||
# Move DM refresh after timeline refresh so timeline updates show even if no unread DMs
|
||||
if ($dmpause) {
|
||||
if (!--$dmcount) {
|
||||
@@ -3124,6 +3145,12 @@ Just type to talk!
|
||||
/media /path/to/file
|
||||
Upload media (images, video, audio) with accessibility features
|
||||
|
||||
/poll Your question here
|
||||
Create a single choice poll
|
||||
|
||||
/mpoll Your question here
|
||||
Create a multiple choice poll (users can select multiple options)
|
||||
|
||||
/quit
|
||||
Resumes your boring life.
|
||||
|
||||
@@ -3144,6 +3171,8 @@ USER COMMANDS:
|
||||
/wagain username - combines them all
|
||||
/follow username - follow a username
|
||||
/leave username - stop following a username
|
||||
/followers [username] - show who follows you (or username)
|
||||
/following [username] - show who you follow (or username follows)
|
||||
/dm username message - send a username a DM
|
||||
|
||||
POST AND DM SELECTION:
|
||||
@@ -3239,6 +3268,7 @@ EOF
|
||||
if ($dostream);
|
||||
&thump;
|
||||
&dmthump_no_skip if ($dmpause); # Also refresh DMs but don't skip timeline
|
||||
¬ificationthump if ($notificationpause); # Also refresh notifications
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -3247,6 +3277,24 @@ EOF
|
||||
my $file_path = $1;
|
||||
return &handle_media_upload($file_path);
|
||||
}
|
||||
|
||||
# Poll creation commands
|
||||
if (m#^/poll\s+(.+)$#) {
|
||||
my $poll_text = $1;
|
||||
return &handle_poll_creation($poll_text, 0); # Single choice
|
||||
}
|
||||
if (m#^/poll\s*$#) {
|
||||
print $stdout "-- ERROR: Poll requires question text. Usage: /poll Your question here\n";
|
||||
return 0;
|
||||
}
|
||||
if (m#^/mpoll\s+(.+)$#) {
|
||||
my $poll_text = $1;
|
||||
return &handle_poll_creation($poll_text, 1); # Multiple choice
|
||||
}
|
||||
if (m#^/mpoll\s*$#) {
|
||||
print $stdout "-- ERROR: Multiple choice poll requires question text. Usage: /mpoll Your question here\n";
|
||||
return 0;
|
||||
}
|
||||
if (m#^/a(gain)?(\s+\+\d+)?$#) { # the asynchronous form
|
||||
my $countmaybe = $2;
|
||||
$countmaybe =~ s/[^\d]//g if (length($countmaybe));
|
||||
@@ -3406,7 +3454,7 @@ EOF
|
||||
}
|
||||
|
||||
# this is dual-headed and supports both lists and regular followers.
|
||||
if(s#^/(frs|friends|fos|followers)(\s+\+\d+)?\s*##) {
|
||||
if(s#^/(frs|friends|following|fos|followers)(\s+\+\d+)?\s*##) {
|
||||
my $countmaybe = $2;
|
||||
my $mode = $1;
|
||||
my $arg = lc($_);
|
||||
@@ -3423,9 +3471,9 @@ EOF
|
||||
}
|
||||
$who ||= $whoami;
|
||||
if (!length($lname)) {
|
||||
$what = ($mode eq 'frs' || $mode eq 'friends')
|
||||
? "friends" : "followers";
|
||||
$mode = ($mode eq 'frs' || $mode eq 'friends')
|
||||
$what = ($mode eq 'frs' || $mode eq 'friends' || $mode eq 'following')
|
||||
? "following" : "followers";
|
||||
$mode = ($mode eq 'frs' || $mode eq 'friends' || $mode eq 'following')
|
||||
? $friendsurl : $followersurl;
|
||||
} else {
|
||||
# List members/followers - fediverse only supports list members, not subscribers
|
||||
@@ -3480,59 +3528,31 @@ EOF
|
||||
return 0;
|
||||
}
|
||||
|
||||
# Use proper Mastodon API endpoint with account ID
|
||||
# Use proper Mastodon API endpoint with account ID - much simpler than Twitter!
|
||||
my $followers_url = $mode;
|
||||
$followers_url =~ s/%I/$account_id/g;
|
||||
my $accounts_ref = &grabjson("$followers_url?limit=${countmaybe}", 0, 0, 0, undef, 1);
|
||||
|
||||
# Fediverse API directly returns account objects, no need for separate lookup
|
||||
my $limit = &min($countmaybe, 80); # Mastodon default max is 80
|
||||
my $accounts_ref = &grabjson("$followers_url?limit=$limit", 0, 0, 0, undef, 1);
|
||||
|
||||
print $stdout "-- DEBUG: API response type: " . (ref($accounts_ref) || 'not a reference') . "\n" if ($verbose);
|
||||
if (ref($accounts_ref) eq 'ARRAY') {
|
||||
print $stdout "-- DEBUG: Number of accounts in response: " . scalar(@{ $accounts_ref }) . "\n" if ($verbose);
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
my $dount = &min($countmaybe, scalar(@ids));
|
||||
my $swallow = &min(100, $dount);
|
||||
my @usarray = undef; shift(@usarray); # force underflow
|
||||
my $l_ref = undef;
|
||||
|
||||
# for each block of $countper, emit
|
||||
print $stdout "-- $what for $who:\n";
|
||||
my $printed = 0;
|
||||
|
||||
FFABIO: while ($dount--) {
|
||||
if (!scalar(@usarray)) {
|
||||
my @next_ids;
|
||||
|
||||
last FFABIO if (!scalar(@ids));
|
||||
|
||||
# if we asked for less than 100, get
|
||||
# that. otherwise,
|
||||
# get the top 100 off that list (or
|
||||
# the list itself, if 100 or less)
|
||||
if (scalar(@ids) <= $swallow) {
|
||||
@next_ids = @ids;
|
||||
@ids = ();
|
||||
} else {
|
||||
@next_ids =
|
||||
@ids[0..($swallow-1)];
|
||||
@ids = @ids[$swallow..$#ids];
|
||||
}
|
||||
|
||||
# turn it into a list to pass to
|
||||
# lookupidurl and get the list
|
||||
$l_ref = &postjson($lookupidurl,
|
||||
"user_id=".&url_oauth_sub(join(',', @next_ids)));
|
||||
last FFABIO if(ref($l_ref) ne 'ARRAY');
|
||||
@usarray = sort
|
||||
{ 0+($a->{'id'}) <=> 0+($b->{'id'}) }
|
||||
@{ $l_ref };
|
||||
last if (!scalar(@usarray));
|
||||
}
|
||||
&$userhandle(shift(@usarray));
|
||||
for my $account (@{ $accounts_ref }) {
|
||||
# Fediverse returns complete account objects - just display them
|
||||
print $stdout "-- DEBUG: Processing account: " . ($account->{'username'} || 'no-username') . " / " . ($account->{'acct'} || 'no-acct') . "\n" if ($verbose);
|
||||
&$userhandle($account);
|
||||
$printed++;
|
||||
}
|
||||
print $stdout "-- sorry, no $what found for $who.\n"
|
||||
if (!$printed);
|
||||
|
||||
print $stdout "-- sorry, no $what found for $who.\n" if (!$printed);
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -4065,14 +4085,33 @@ m#^/(un)?f(boost|a|av|ave|avorite|avourite)? ([zZ]?[a-zA-Z]?[0-9]+)$#) {
|
||||
}
|
||||
# Use Mastodon's acct field (includes @domain for remote users) or username for local users
|
||||
my $target;
|
||||
my $acct = &descape($post->{'user'}->{'acct'} || $post->{'user'}->{'username'});
|
||||
$target = $acct;
|
||||
my $acct = &descape($post->{'account'}->{'acct'} || $post->{'user'}->{'acct'} || $post->{'user'}->{'username'});
|
||||
|
||||
# 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) {
|
||||
# Always ensure we have the full @domain format for federation
|
||||
if ($acct =~ /\@/) {
|
||||
# Already has domain, use as-is
|
||||
$target = $acct;
|
||||
} else {
|
||||
# No domain - extract from post URL, account URI, or other sources
|
||||
my $domain;
|
||||
|
||||
# Try multiple sources for domain information
|
||||
if ($post->{'account'}->{'url'} && $post->{'account'}->{'url'} =~ m{^https?://([^/]+)/}) {
|
||||
$domain = $1;
|
||||
} elsif ($post->{'url'} && $post->{'url'} =~ m{^https?://([^/]+)/}) {
|
||||
$domain = $1;
|
||||
} elsif ($post->{'account'}->{'uri'} && $post->{'account'}->{'uri'} =~ m{^https?://([^/]+)/}) {
|
||||
$domain = $1;
|
||||
} elsif ($post->{'uri'} && $post->{'uri'} =~ m{^https?://([^/]+)/}) {
|
||||
$domain = $1;
|
||||
}
|
||||
|
||||
if ($domain) {
|
||||
$target = "$acct\@$domain";
|
||||
} else {
|
||||
# Last resort fallback - but this should not happen for federation
|
||||
print $stdout "-- WARNING: Could not determine domain for user $acct, using local server\n" if ($verbose);
|
||||
$target = "$acct\@$fediverseserver";
|
||||
}
|
||||
}
|
||||
print $stdout "-- DEBUG: Reply target acct='$post->{'user'}->{'acct'}', username='$post->{'user'}->{'username'}', url='$post->{'url'}', using='$target'\n" if ($verbose);
|
||||
@@ -4105,7 +4144,36 @@ 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->{'last_status'}->{'account'}->{'acct'} || $dm->{'last_status'}->{'account'}->{'username'});
|
||||
my $acct = &descape($dm->{'last_status'}->{'account'}->{'acct'} || $dm->{'last_status'}->{'account'}->{'username'});
|
||||
my $target;
|
||||
|
||||
# Always ensure we have the full @domain format for federation
|
||||
if ($acct =~ /\@/) {
|
||||
# Already has domain, use as-is
|
||||
$target = $acct;
|
||||
} else {
|
||||
# No domain - extract from DM account URL, URI, or other sources
|
||||
my $domain;
|
||||
|
||||
# Try multiple sources for domain information
|
||||
if ($dm->{'last_status'}->{'account'}->{'url'} && $dm->{'last_status'}->{'account'}->{'url'} =~ m{^https?://([^/]+)/}) {
|
||||
$domain = $1;
|
||||
} elsif ($dm->{'last_status'}->{'url'} && $dm->{'last_status'}->{'url'} =~ m{^https?://([^/]+)/}) {
|
||||
$domain = $1;
|
||||
} elsif ($dm->{'last_status'}->{'account'}->{'uri'} && $dm->{'last_status'}->{'account'}->{'uri'} =~ m{^https?://([^/]+)/}) {
|
||||
$domain = $1;
|
||||
} elsif ($dm->{'last_status'}->{'uri'} && $dm->{'last_status'}->{'uri'} =~ m{^https?://([^/]+)/}) {
|
||||
$domain = $1;
|
||||
}
|
||||
|
||||
if ($domain) {
|
||||
$target = "$acct\@$domain";
|
||||
} else {
|
||||
# Last resort fallback - but this should not happen for federation
|
||||
print $stdout "-- WARNING: Could not determine domain for DM user $acct, using local server\n" if ($verbose);
|
||||
$target = "$acct\@$fediverseserver";
|
||||
}
|
||||
}
|
||||
if ($termrl) {
|
||||
$readline_completion{'@'.lc($target)}++;
|
||||
&save_completion_cache;
|
||||
@@ -4128,14 +4196,33 @@ m#^/(un)?f(boost|a|av|ave|avorite|avourite)? ([zZ]?[a-zA-Z]?[0-9]+)$#) {
|
||||
}
|
||||
# Use Mastodon's acct field (includes @domain for remote users) or username for local users
|
||||
my $target;
|
||||
my $acct = &descape($post->{'user'}->{'acct'} || $post->{'user'}->{'username'});
|
||||
$target = $acct;
|
||||
my $acct = &descape($post->{'account'}->{'acct'} || $post->{'user'}->{'acct'} || $post->{'user'}->{'username'});
|
||||
|
||||
# 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) {
|
||||
# Always ensure we have the full @domain format for federation
|
||||
if ($acct =~ /\@/) {
|
||||
# Already has domain, use as-is
|
||||
$target = $acct;
|
||||
} else {
|
||||
# No domain - extract from post URL, account URI, or other sources
|
||||
my $domain;
|
||||
|
||||
# Try multiple sources for domain information
|
||||
if ($post->{'account'}->{'url'} && $post->{'account'}->{'url'} =~ m{^https?://([^/]+)/}) {
|
||||
$domain = $1;
|
||||
} elsif ($post->{'url'} && $post->{'url'} =~ m{^https?://([^/]+)/}) {
|
||||
$domain = $1;
|
||||
} elsif ($post->{'account'}->{'uri'} && $post->{'account'}->{'uri'} =~ m{^https?://([^/]+)/}) {
|
||||
$domain = $1;
|
||||
} elsif ($post->{'uri'} && $post->{'uri'} =~ m{^https?://([^/]+)/}) {
|
||||
$domain = $1;
|
||||
}
|
||||
|
||||
if ($domain) {
|
||||
$target = "$acct\@$domain";
|
||||
} else {
|
||||
# Last resort fallback - but this should not happen for federation
|
||||
print $stdout "-- WARNING: Could not determine domain for user $acct, using local server\n" if ($verbose);
|
||||
$target = "$acct\@$fediverseserver";
|
||||
}
|
||||
}
|
||||
my $text = $_;
|
||||
@@ -4284,6 +4371,12 @@ EOF
|
||||
&dmthump;
|
||||
return 0;
|
||||
}
|
||||
|
||||
# Notifications
|
||||
if ($_ eq '/notifications' || $_ eq '/notificationrefresh' || $_ eq '/nr') {
|
||||
¬ificationthump;
|
||||
return 0;
|
||||
}
|
||||
# /dmsent, /dmagain
|
||||
if (m#^/dm(s|sent|a|again)(\s+\+\d+)?$#) {
|
||||
my $mode = $1;
|
||||
@@ -4838,7 +4931,36 @@ 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'}->{'username'} || '';
|
||||
my $acct = $post_ref->{'account'}->{'acct'} || $post_ref->{'user'}->{'acct'} || $post_ref->{'user'}->{'username'} || '';
|
||||
my $post_author;
|
||||
|
||||
# Always ensure we have the full @domain format for federation
|
||||
if ($acct =~ /\@/) {
|
||||
# Already has domain, use as-is
|
||||
$post_author = $acct;
|
||||
} else {
|
||||
# No domain - extract from post URL, account URI, or other sources
|
||||
my $domain;
|
||||
|
||||
# Try multiple sources for domain information
|
||||
if ($post_ref->{'account'}->{'url'} && $post_ref->{'account'}->{'url'} =~ m{^https?://([^/]+)/}) {
|
||||
$domain = $1;
|
||||
} elsif ($post_ref->{'url'} && $post_ref->{'url'} =~ m{^https?://([^/]+)/}) {
|
||||
$domain = $1;
|
||||
} elsif ($post_ref->{'account'}->{'uri'} && $post_ref->{'account'}->{'uri'} =~ m{^https?://([^/]+)/}) {
|
||||
$domain = $1;
|
||||
} elsif ($post_ref->{'uri'} && $post_ref->{'uri'} =~ m{^https?://([^/]+)/}) {
|
||||
$domain = $1;
|
||||
}
|
||||
|
||||
if ($domain) {
|
||||
$post_author = "$acct\@$domain";
|
||||
} else {
|
||||
# Last resort fallback - but this should not happen for federation
|
||||
print $stdout "-- WARNING: Could not determine domain for user $acct in reply-to-all, using local server\n" if ($verbose);
|
||||
$post_author = "$acct\@$fediverseserver";
|
||||
}
|
||||
}
|
||||
|
||||
# Set up reply-to ID
|
||||
my $in_reply_to = $post_ref->{'id_str'} || $post_ref->{'id'};
|
||||
@@ -4953,6 +5075,7 @@ sub sub_helper {
|
||||
sub sync_console {
|
||||
&thump;
|
||||
&dmthump unless (!$dmpause);
|
||||
¬ificationthump unless (!$notificationpause);
|
||||
}
|
||||
sub sync_semaphore {
|
||||
if ($synch) {
|
||||
@@ -5044,6 +5167,12 @@ $stream_failure = 0;
|
||||
$dm_first_time = ($dmpause) ? 1 : 0;
|
||||
$dm_display_only = 0; # Flag to suppress notifications during user-initiated /dms commands
|
||||
$dm_notification_sent = 0; # Flag to prevent duplicate notifications per refresh cycle
|
||||
$notification_first_time = ($notificationpause) ? 1 : 0;
|
||||
$notification_display_only = 0; # Flag to suppress notifications during user-initiated notification commands
|
||||
$notification_notification_sent = 0; # Flag to prevent duplicate notifications per refresh cycle
|
||||
$last_notification_marker = ''; # Track last read notification ID via markers API
|
||||
# Notification tracking uses markers API for server-side read status
|
||||
%notification_seen = (); # Hash to track seen notification IDs locally
|
||||
# DM tracking now uses Mastodon's unread flag with local read tracking fallback
|
||||
%dm_seen_status = (); # Hash to track seen conversation_id:last_status_id pairs
|
||||
&load_dm_seen_status(); # Load persistent tracking data
|
||||
@@ -5062,6 +5191,21 @@ for(;;) {
|
||||
(!$effpause && !$interactive);
|
||||
$dont_refresh_first_time = 0;
|
||||
$previous_last_id = $last_id;
|
||||
if ($notificationpause && ($effpause || $synch)) {
|
||||
if ($notification_first_time) {
|
||||
¬ificationrefresh(0);
|
||||
$notificationcount = $notificationpause;
|
||||
} elsif (!$interactive) {
|
||||
print $stdout "-- DEBUG: Notification countdown: $notificationcount -> " . ($notificationcount - 1) . "\n" if ($verbose);
|
||||
if (!--$notificationcount) {
|
||||
print $stdout "-- DEBUG: Triggering background notification refresh\n" if ($verbose);
|
||||
¬ificationrefresh($interactive);
|
||||
$notificationcount = $notificationpause;
|
||||
}
|
||||
} else {
|
||||
print $stdout "-- DEBUG: Skipping notification countdown (interactive=$interactive)\n" if ($verbose);
|
||||
}
|
||||
}
|
||||
if ($dmpause && ($effpause || $synch)) {
|
||||
if ($dm_first_time) {
|
||||
&dmrefresh(0);
|
||||
@@ -5259,7 +5403,7 @@ EOF
|
||||
$key->{'tag'}->{'type'}. " ". # NO SPACES!
|
||||
unpack("${pack_magic}H*", $key->{'tag'}->{'payload'}). " ".
|
||||
($key->{'reblogs_count'} || "0") . " " .
|
||||
($key->{'user'}->{'username'} || $key->{'user'}->{'acct'})." $ds $src|".
|
||||
($key->{'user'}->{'username'} || $key->{'user'}->{'acct'})." ".($key->{'user'}->{'acct'} || $key->{'user'}->{'username'})." $ds $src|".
|
||||
unpack("${pack_magic}H*", $key->{'text'}).
|
||||
$space_pad), 0, 1024);
|
||||
print P $key;
|
||||
@@ -5414,6 +5558,11 @@ EOF
|
||||
&send_repaint if ($termrl);
|
||||
$dmcount = $dmpause;
|
||||
goto DONT_REFRESH;
|
||||
} elsif ($rout =~ /^notificationthump/) {
|
||||
¬ificationrefresh($interactive);
|
||||
&send_repaint if ($termrl);
|
||||
$notificationcount = $notificationpause;
|
||||
goto DONT_REFRESH;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
@@ -6661,7 +6810,23 @@ sub updatest {
|
||||
} else {
|
||||
if (length($user_name_dm)) {
|
||||
# For DMs: include mention in status and set visibility
|
||||
my $dm_status = "\@${user_name_dm} ${string}";
|
||||
# Ensure we have the full @domain format for federation
|
||||
my $dm_target = $user_name_dm;
|
||||
if ($dm_target !~ /\@/) {
|
||||
# No domain - try to find full format in completion cache
|
||||
my $found_full = undef;
|
||||
foreach my $cached_user (keys %readline_completion) {
|
||||
if ($cached_user =~ /^\@(.+)\@(.+)$/) {
|
||||
my ($cached_username) = ($1);
|
||||
if (lc($cached_username) eq lc($dm_target)) {
|
||||
$found_full = substr($cached_user, 1); # Remove @ prefix
|
||||
last;
|
||||
}
|
||||
}
|
||||
}
|
||||
$dm_target = $found_full || $dm_target;
|
||||
}
|
||||
my $dm_status = "\@${dm_target} ${string}";
|
||||
my $dm_urle = '';
|
||||
foreach my $char (unpack("${pack_magic}C*", $dm_status)) {
|
||||
my $k = chr($char);
|
||||
@@ -6746,6 +6911,171 @@ EOF
|
||||
return 0;
|
||||
}
|
||||
|
||||
# refresh for notifications
|
||||
sub notificationrefresh {
|
||||
my $interactive = shift;
|
||||
|
||||
# Reset notification flag for this refresh cycle
|
||||
$notification_notification_sent = 0;
|
||||
|
||||
if ($anonymous) {
|
||||
print $stdout
|
||||
"-- sorry, you can't read notifications if you're anonymous.\n"
|
||||
if ($interactive);
|
||||
return;
|
||||
}
|
||||
|
||||
# no point in doing this if we can't even get to our own timeline
|
||||
# (unless user specifically requested it, or our timeline is off)
|
||||
return if (!$interactive && !$last_id && !$notimeline);
|
||||
|
||||
# Get current markers to see what's been read
|
||||
my $markers_ref = &grabjson($markersurl . "?timeline[]=notifications",
|
||||
0, 0, 0, undef, 1);
|
||||
|
||||
my $since_id = 0;
|
||||
if (defined($markers_ref) && ref($markers_ref) eq 'HASH' &&
|
||||
$markers_ref->{'notifications'} &&
|
||||
$markers_ref->{'notifications'}->{'last_read_id'}) {
|
||||
$since_id = $markers_ref->{'notifications'}->{'last_read_id'};
|
||||
$last_notification_marker = $since_id;
|
||||
print $stdout "-- DEBUG: Got notification marker: $since_id\n" if ($verbose);
|
||||
}
|
||||
|
||||
# Fetch notifications since the marker
|
||||
my $my_json_ref = &grabjson($notificationurl, $since_id, 0,
|
||||
($interactive ? 20 : 50), undef, 1);
|
||||
|
||||
return if (!defined($my_json_ref) || ref($my_json_ref) ne 'ARRAY');
|
||||
|
||||
my $printed = 0;
|
||||
my $max_id = 0;
|
||||
my $disp_max = &min($print_max, scalar(@{ $my_json_ref }));
|
||||
|
||||
print $stdout "-- DEBUG: Notification response: " . scalar(@{ $my_json_ref }) . " notifications, disp_max=$disp_max\n" if ($verbose);
|
||||
|
||||
# For background refresh, check if there are any new notifications first
|
||||
if (!$interactive && $disp_max) {
|
||||
my $has_new = 0;
|
||||
for (my $check_i = 0; $check_i < $disp_max; $check_i++) {
|
||||
my $check_notif = $my_json_ref->[$check_i];
|
||||
next if (!$check_notif->{'id'});
|
||||
my $notif_id = $check_notif->{'id'};
|
||||
|
||||
# Skip if we've already seen this notification locally
|
||||
if ($notification_seen{$notif_id}) {
|
||||
print $stdout "-- DEBUG: Skipping notification $notif_id - already seen locally\n" if ($verbose);
|
||||
next;
|
||||
}
|
||||
|
||||
$has_new = 1;
|
||||
last;
|
||||
}
|
||||
if (!$has_new) {
|
||||
print $stdout "-- DEBUG: No new notifications found in background refresh, returning early\n" if ($verbose);
|
||||
return;
|
||||
}
|
||||
print $stdout "-- DEBUG: Found new notifications in background refresh, proceeding\n" if ($verbose);
|
||||
}
|
||||
|
||||
if ($disp_max) {
|
||||
if ($notification_first_time) {
|
||||
sleep 5 while ($suspend_output > 0);
|
||||
&send_removereadline if ($termrl);
|
||||
print $stdout
|
||||
"-- checking for most recent notifications:\n";
|
||||
$disp_max = 3;
|
||||
$interactive = 1;
|
||||
$notification_display_only = 1; # Suppress sounds during first-time load
|
||||
print $stdout "-- DEBUG: notification_first_time: disp_max reduced to $disp_max, sounds suppressed\n" if ($verbose);
|
||||
}
|
||||
|
||||
print $stdout "-- DEBUG: Starting notification display loop: $disp_max notifications\n" if ($verbose);
|
||||
for(my $i = $disp_max; $i > 0; $i--) {
|
||||
my $g = ($i-1);
|
||||
my $notif = $my_json_ref->[$g];
|
||||
print $stdout "-- DEBUG: Processing notification #$i (index $g)\n" if ($verbose);
|
||||
|
||||
# Skip if missing data
|
||||
if (!$notif->{'id'} || !$notif->{'type'}) {
|
||||
print $stdout "-- DEBUG: Skipping notification #$i - missing id or type\n" if ($verbose);
|
||||
next;
|
||||
}
|
||||
|
||||
my $notif_id = $notif->{'id'};
|
||||
|
||||
# For background refresh, skip notifications we've already seen locally
|
||||
if (!$interactive) {
|
||||
if ($notification_seen{$notif_id}) {
|
||||
print $stdout "-- DEBUG: Skipping notification #$i - already seen locally (id: $notif_id)\n" if ($verbose);
|
||||
next;
|
||||
}
|
||||
|
||||
# Mark as seen locally for this session
|
||||
$notification_seen{$notif_id} = 1;
|
||||
print $stdout "-- DEBUG: Marked notification $notif_id as seen locally\n" if ($verbose);
|
||||
}
|
||||
|
||||
# Track highest ID for marker update
|
||||
my $current_id = 0+$notif_id;
|
||||
$max_id = $current_id if ($current_id > $max_id);
|
||||
|
||||
# Process notification with proper type mapping for sounds
|
||||
my $type = $notif->{'type'} || 'default';
|
||||
my $sound_class = $type;
|
||||
$sound_class = 'mention' if ($type eq 'mention');
|
||||
$sound_class = 'boost' if ($type eq 'reblog');
|
||||
$sound_class = 'favourite' if ($type eq 'favourite');
|
||||
$sound_class = 'follow' if ($type eq 'follow');
|
||||
|
||||
print $stdout "-- DEBUG: Processing notification type '$type' as sound class '$sound_class'\n" if ($verbose);
|
||||
|
||||
# Use existing notification display handler
|
||||
if ($notification_display_only) {
|
||||
# During first-time load, display without sounds
|
||||
print $stdout "-- DEBUG: Displaying notification without sound (display_only mode)\n" if ($verbose);
|
||||
&$handle($notif, ''); # Empty class = no sound
|
||||
} else {
|
||||
¬ifications_tdisplay([ $notif ]);
|
||||
}
|
||||
$printed++;
|
||||
}
|
||||
}
|
||||
|
||||
sleep 5 while ($suspend_output > 0);
|
||||
if (($interactive || $verbose) && !$printed && !$notification_first_time) {
|
||||
&send_removereadline if ($termrl);
|
||||
print $stdout "-- no new notifications.\n";
|
||||
$wrapseq = 1;
|
||||
}
|
||||
|
||||
# Update marker to highest ID we processed (for background refresh or manual check)
|
||||
if (!$interactive || $notification_first_time || ($interactive && $max_id > 0)) {
|
||||
if ($max_id > 0) {
|
||||
my $marker_data = "notifications%5Blast_read_id%5D=$max_id";
|
||||
my $marker_result = &backticks($baseagent, '/dev/null', undef,
|
||||
$markersurl, $marker_data, 0, @wend);
|
||||
|
||||
if ($? == 0) {
|
||||
$last_notification_marker = $max_id;
|
||||
print $stdout "-- DEBUG: Updated notification marker to $max_id\n" if ($verbose);
|
||||
} else {
|
||||
print $stdout "-- DEBUG: Failed to update notification marker (exit code: $?)\n" if ($verbose);
|
||||
}
|
||||
}
|
||||
|
||||
print $stdout "-- DEBUG: Updated last_notification_marker to $last_notification_marker (interactive=$interactive, notification_first_time=$notification_first_time, max_id=$max_id)\n" if ($verbose);
|
||||
} else {
|
||||
print $stdout "-- DEBUG: NOT updating notification marker (interactive=$interactive, keeping last_notification_marker=$last_notification_marker)\n" if ($verbose);
|
||||
}
|
||||
|
||||
$notification_first_time = 0 if ($max_id || !scalar(@{ $my_json_ref }));
|
||||
$notification_display_only = 0; # Reset sound suppression flag
|
||||
print $stdout "-- notification bookmark is $last_notification_marker.\n" if ($verbose);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
# this dispatch routine replaces the common logic of deletest, deletedm,
|
||||
# follow, leave and the favourites system.
|
||||
# this is a modified, abridged version of &updatest.
|
||||
@@ -6834,10 +7164,48 @@ sub foruuser {
|
||||
return 1;
|
||||
}
|
||||
|
||||
# Substitute account ID into URL template
|
||||
my $api_url = $basef;
|
||||
$api_url =~ s/%I/$account_id/g;
|
||||
|
||||
print $stdout "-- DEBUG: Calling follow/unfollow API with account_id=$account_id, basef=$basef\n" if ($verbose);
|
||||
my ($en, $em) = ¢ral_cd_dispatch("id=$account_id",
|
||||
$interactive, $basef);
|
||||
print $stdout "-- ok, you have $verb following user $uname.\n"
|
||||
if ($interactive && !$en);
|
||||
$interactive, $api_url);
|
||||
print $stdout "-- DEBUG: Follow API returned - error code: $en, message length: " . length($em) . "\n" if ($verbose);
|
||||
print $stdout "-- DEBUG: Follow API response: $em\n" if ($verbose && $em);
|
||||
|
||||
# Check for HTTP error responses in the message
|
||||
if (!$en && $em && $em =~ /<title>(\d+)\s+([^<]+)<\/title>/) {
|
||||
my $http_code = $1;
|
||||
my $http_message = $2;
|
||||
print $stdout "-- ERROR: $verb follow failed - HTTP $http_code: $http_message\n" if ($interactive);
|
||||
return 1;
|
||||
}
|
||||
|
||||
# Parse JSON response to show accurate relationship status
|
||||
if (!$en && $interactive && $em) {
|
||||
my $response_data = &parsejson($em);
|
||||
if (ref($response_data) eq 'HASH') {
|
||||
if ($verb eq 'started') {
|
||||
if ($response_data->{'following'}) {
|
||||
print $stdout "-- ok, you are now following user $uname.\n";
|
||||
} elsif ($response_data->{'requested'}) {
|
||||
print $stdout "-- ok, follow request sent to user $uname (awaiting approval).\n";
|
||||
} else {
|
||||
print $stdout "-- ok, you have $verb following user $uname.\n";
|
||||
}
|
||||
} else {
|
||||
# For unfollow
|
||||
if (!$response_data->{'following'} && !$response_data->{'requested'}) {
|
||||
print $stdout "-- ok, you are no longer following user $uname.\n";
|
||||
} else {
|
||||
print $stdout "-- ok, you have $verb following user $uname.\n";
|
||||
}
|
||||
}
|
||||
} else {
|
||||
print $stdout "-- ok, you have $verb following user $uname.\n";
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -6855,8 +7223,12 @@ sub boruuser {
|
||||
return 1;
|
||||
}
|
||||
|
||||
# Substitute account ID into URL template
|
||||
my $api_url = $basef;
|
||||
$api_url =~ s/%I/$account_id/g;
|
||||
|
||||
my ($en, $em) = ¢ral_cd_dispatch("id=$account_id",
|
||||
$interactive, $basef);
|
||||
$interactive, $api_url);
|
||||
print $stdout "-- ok, you have $verb blocking user $uname.\n"
|
||||
if ($interactive && !$en);
|
||||
return 0;
|
||||
@@ -7818,17 +8190,42 @@ sub lookup_account_id {
|
||||
|
||||
print $stdout "-- DEBUG: Looking up account ID for username: $username\n" if ($verbose);
|
||||
|
||||
# Special case: if looking up our own account, use verify_credentials
|
||||
if (lc($username) eq lc($whoami)) {
|
||||
print $stdout "-- DEBUG: Looking up own account via verify_credentials\n" if ($verbose);
|
||||
my $creds_result = &grabjson($credurl, 0, 0, 0, undef, 1);
|
||||
if ($creds_result && $creds_result->{'id'}) {
|
||||
print $stdout "-- DEBUG: Found own account ID: " . $creds_result->{'id'} . "\n" if ($verbose);
|
||||
return $creds_result->{'id'};
|
||||
}
|
||||
print $stdout "-- DEBUG: Failed to get own account from verify_credentials\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);
|
||||
my $search_url = "${searchurl}?q=${username}&type=accounts&limit=1";
|
||||
print $stdout "-- DEBUG: Search URL: $search_url\n" if ($verbose);
|
||||
my $search_result = &grabjson($search_url, 0, 0, 0, undef, 1);
|
||||
|
||||
print $stdout "-- DEBUG: Search result: " . ($search_result ? "SUCCESS" : "FAILED") . "\n" if ($verbose);
|
||||
if ($search_result && ref($search_result) eq 'HASH') {
|
||||
print $stdout "-- DEBUG: Search result has accounts: " . ($search_result->{'accounts'} ? "YES" : "NO") . "\n" if ($verbose);
|
||||
if ($search_result->{'accounts'}) {
|
||||
print $stdout "-- DEBUG: Number of accounts found: " . scalar(@{$search_result->{'accounts'}}) . "\n" if ($verbose);
|
||||
}
|
||||
}
|
||||
|
||||
if ($search_result && $search_result->{'accounts'} && @{$search_result->{'accounts'}}) {
|
||||
my $account = $search_result->{'accounts'}->[0];
|
||||
my $found_username = $account->{'username'} || $account->{'acct'};
|
||||
|
||||
print $stdout "-- DEBUG: Found account - username: '" . ($account->{'username'} || 'null') . "', acct: '" . ($account->{'acct'} || 'null') . "'\n" if ($verbose);
|
||||
|
||||
# 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'};
|
||||
} else {
|
||||
print $stdout "-- DEBUG: Username mismatch - looking for '$username', found '$found_username'\n" if ($verbose);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7925,7 +8322,7 @@ sub defaultautocompletion {
|
||||
'/orepost', '/erepost', '/frepost', '/liston',
|
||||
'/listoff', '/dmsent', '/rtsof', '/rtson', '/rtsoff',
|
||||
'/lists', '/withlist', '/add', '/padd', '/push',
|
||||
'/pop', '/followers', '/friends', '/lfollow',
|
||||
'/pop', '/followers', '/following', '/friends', '/lfollow',
|
||||
'/lleave', '/listfollowers', '/listfriends',
|
||||
'/unset', '/verbose', '/short', '/follow', '/unfollow',
|
||||
'/doesfollow', '/search', '/tron', '/troff',
|
||||
@@ -8105,8 +8502,8 @@ sub get_post {
|
||||
$w->{'tag'}->{'type'},
|
||||
$w->{'tag'}->{'payload'},
|
||||
$w->{'reblogs_count'},
|
||||
$w->{'user'}->{'username'}, $w->{'created_at'},
|
||||
$l) = split(/\s/, $k, 17);
|
||||
$w->{'user'}->{'username'}, $w->{'user'}->{'acct'}, $w->{'created_at'},
|
||||
$l) = split(/\s/, $k, 18);
|
||||
($w->{'source'}, $k) = split(/\|/, $l, 2);
|
||||
$w->{'text'} = pack("H*", $k);
|
||||
$w->{'place'}->{'full_name'} = pack("H*",$w->{'place'}->{'full_name'});
|
||||
@@ -8220,6 +8617,7 @@ sub thump {
|
||||
}
|
||||
sub dmthump { print C "dmthump------------\n"; &sync_semaphore; }
|
||||
sub dmthump_no_skip { print C "dmthump_no_skip----\n"; &sync_semaphore; }
|
||||
sub notificationthump { print C "notificationthump--\n"; &sync_semaphore; }
|
||||
|
||||
sub sync_n_quit {
|
||||
# Save completion cache before exiting
|
||||
@@ -8663,6 +9061,154 @@ sub create_post_with_media {
|
||||
}
|
||||
}
|
||||
|
||||
##### Poll Creation Functions #####
|
||||
|
||||
sub handle_poll_creation {
|
||||
my $poll_text = shift;
|
||||
my $multiple_choice = shift || 0; # Default to single choice
|
||||
|
||||
my $poll_type = $multiple_choice ? "multiple choice" : "single choice";
|
||||
print $stdout "Creating $poll_type poll: $poll_text\n";
|
||||
|
||||
# Ask for poll duration first (default 24h)
|
||||
print $stdout "Poll duration (default 24h): ";
|
||||
my $duration_input = <STDIN>;
|
||||
chomp($duration_input);
|
||||
|
||||
my $expires_in = &parse_duration($duration_input);
|
||||
|
||||
# Collect poll options interactively
|
||||
my @options = ();
|
||||
my $option_num = 1;
|
||||
|
||||
print $stdout "Enter options (press Enter with no text to finish):\n";
|
||||
|
||||
while ($option_num <= 10) { # Allow up to 10 options (instance may limit to 4)
|
||||
print $stdout "Option $option_num: ";
|
||||
|
||||
# Read user input for option
|
||||
my $option = <STDIN>;
|
||||
chomp($option);
|
||||
|
||||
# If empty input, stop collecting options
|
||||
if ($option eq "") {
|
||||
last;
|
||||
}
|
||||
|
||||
# Validate option length (Mastodon limit is 50 characters)
|
||||
if (length($option) > 50) {
|
||||
print $stdout "-- ERROR: Option too long (max 50 characters): " . length($option) . " chars\n";
|
||||
print $stdout "-- Please enter a shorter option or press Enter to skip.\n";
|
||||
next; # Ask for this option again
|
||||
}
|
||||
|
||||
# Add the option to our list
|
||||
push @options, $option;
|
||||
$option_num++;
|
||||
}
|
||||
|
||||
# Validate we have at least 2 options
|
||||
if (scalar(@options) < 2) {
|
||||
print $stdout "-- ERROR: Poll cancelled. Need at least 2 options.\n";
|
||||
return 0;
|
||||
}
|
||||
|
||||
# Create and post the poll
|
||||
return &create_poll_post($poll_text, \@options, $expires_in, $multiple_choice);
|
||||
}
|
||||
|
||||
sub parse_duration {
|
||||
my $input = shift;
|
||||
|
||||
# Default to 24 hours if empty
|
||||
return 24 * 3600 if ($input eq "");
|
||||
|
||||
# Parse duration format: number + letter (h/d/m for hours/days/minutes)
|
||||
if ($input =~ /^(\d+)([hdm])$/i) {
|
||||
my ($num, $unit) = ($1, lc($2));
|
||||
my $hours;
|
||||
|
||||
if ($unit eq 'h') {
|
||||
$hours = $num;
|
||||
} elsif ($unit eq 'd') {
|
||||
$hours = $num * 24;
|
||||
} elsif ($unit eq 'm') {
|
||||
$hours = $num / 60.0;
|
||||
}
|
||||
|
||||
# Validate bounds
|
||||
if ($hours <= 0) {
|
||||
print $stdout "-- ERROR: Invalid duration. Using default 24 hours.\n";
|
||||
return 24 * 3600;
|
||||
}
|
||||
if ($hours > 8760) { # More than a year
|
||||
print $stdout "-- WARNING: Duration capped at 365 days.\n";
|
||||
$hours = 8760;
|
||||
}
|
||||
|
||||
return int($hours * 3600); # Convert to seconds
|
||||
} else {
|
||||
print $stdout "-- ERROR: Invalid format. Use format like 24h, 3d, 90m. Using default 24h.\n";
|
||||
return 24 * 3600;
|
||||
}
|
||||
}
|
||||
|
||||
sub create_poll_post {
|
||||
my ($poll_text, $options_ref, $expires_in, $multiple_choice) = @_;
|
||||
my @options = @$options_ref;
|
||||
$multiple_choice = $multiple_choice || 0; # Default to single choice
|
||||
|
||||
# Build the poll parameters for the API
|
||||
my $poll_params = '';
|
||||
for my $i (0 .. $#options) {
|
||||
$poll_params .= "&poll[options][]=" . &url_oauth_sub($options[$i]);
|
||||
}
|
||||
$poll_params .= "&poll[expires_in]=$expires_in";
|
||||
$poll_params .= "&poll[multiple]=" . ($multiple_choice ? "true" : "false");
|
||||
$poll_params .= "&poll[hide_totals]=false";
|
||||
|
||||
# Prepare the status post with poll
|
||||
my $status_param = &url_oauth_sub($poll_text);
|
||||
my $post_data = "status=$status_param$poll_params";
|
||||
|
||||
# Add visibility if set
|
||||
if (defined($visibility)) {
|
||||
$post_data .= "&visibility=$visibility";
|
||||
}
|
||||
|
||||
print $stdout "-- Posting poll with " . scalar(@options) . " options...\n" if ($verbose);
|
||||
|
||||
# Use the same authentication method as regular posts
|
||||
my $post_url = "${apibase}/statuses";
|
||||
print $stdout "-- DEBUG: Poll API call: $post_url\n" if ($verbose);
|
||||
print $stdout "-- DEBUG: Poll data: $post_data\n" if ($superverbose);
|
||||
|
||||
my $return = &backticks($baseagent, '/dev/null', undef, $post_url, $post_data, 0, @wend);
|
||||
|
||||
print $stdout "-- DEBUG: Poll API response length: " . length($return) . " bytes\n" if ($verbose);
|
||||
|
||||
if ($return =~ /"id":\s*"([^"]+)"/) {
|
||||
my $post_id = $1;
|
||||
print $stdout "-- Poll posted successfully (ID: $post_id)\n";
|
||||
print $stdout "-- DEBUG: Poll created with ID: $post_id\n" if ($verbose);
|
||||
return 1;
|
||||
} else {
|
||||
print $stdout "-- ERROR: Failed to create poll\n";
|
||||
if ($verbose) {
|
||||
print $stdout "-- DEBUG: Full poll response: $return\n";
|
||||
}
|
||||
|
||||
# Provide helpful error messages for common issues
|
||||
if ($return =~ /too many options/i || $return =~ /exceed.*options/i) {
|
||||
print $stdout "-- This instance may limit polls to 4 options or fewer.\n";
|
||||
} elsif ($return =~ /exceed max chars/i) {
|
||||
print $stdout "-- One or more options exceed the character limit (usually 50).\n";
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
##### optimizers -- these compile into an internal format #####
|
||||
|
||||
# utility routine for tquery support
|
||||
|
||||
Reference in New Issue
Block a user