aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSilvio Rhatto <rhatto@riseup.net>2014-01-27 16:40:54 -0200
committerSilvio Rhatto <rhatto@riseup.net>2014-01-27 16:40:54 -0200
commit74d5f3ab1c1a7f53b6f66905d0d3304dd7b31de0 (patch)
treefed7c8be7453915810194e1c05443f7ef8927a1e
parent379e2842c4eaf005ed9a799ce9840d6735a339b2 (diff)
downloadscripts-74d5f3ab1c1a7f53b6f66905d0d3304dd7b31de0.tar.gz
scripts-74d5f3ab1c1a7f53b6f66905d0d3304dd7b31de0.tar.bz2
Removing ttytter (already on wheezy)
-rwxr-xr-xttytter7989
1 files changed, 0 insertions, 7989 deletions
diff --git a/ttytter b/ttytter
deleted file mode 100755
index eda7a1b..0000000
--- a/ttytter
+++ /dev/null
@@ -1,7989 +0,0 @@
-#!/usr/bin/perl -s
-#########################################################################
-#
-# TTYtter v2.1 (c)2007-2012 cameron kaiser (and contributors).
-# all rights reserved.
-# http://www.floodgap.com/software/ttytter/
-#
-# distributed under the floodgap free software license
-# http://www.floodgap.com/software/ffsl/
-#
-# After all, we're flesh and blood. -- Oingo Boingo
-# If someone writes an app and no one uses it, does his code run? -- me
-#
-#########################################################################
-
-require 5.005;
-
-BEGIN {
- # ONLY STUFF THAT MUST RUN BEFORE INITIALIZATION GOES HERE!
- # THIS FUNCTION HAS GOTTEN TOO DAMN CLUTTERED!
-
-# @INC = (); # wreck intentionally for testing
- # dynamically changing PERL_SIGNALS doesn't work in Perl 5.14+ (bug
- # 92246). we deal with this by forcing -signals_use_posix if the
- # environment variable wasn't already set.
- if ($] >= 5.014000 && $ENV{'PERL_SIGNALS'} ne 'unsafe') {
- $signals_use_posix = 1;
- } else {
- $ENV{'PERL_SIGNALS'} = 'unsafe';
- }
-
- $command_line = $0; $0 = "TTYtter";
- $TTYtter_VERSION = "2.1";
- $TTYtter_PATCH_VERSION = 0;
- $TTYtter_RC_NUMBER = 0; # non-zero for release candidate
- # this is kludgy, yes.
- $LANG = $ENV{'LANG'} || $ENV{'GDM_LANG'} || $ENV{'LC_CTYPE'} ||
- $ENV{'ALL'};
- $my_version_string = "${TTYtter_VERSION}.${TTYtter_PATCH_VERSION}";
- (warn ("$my_version_string\n"), exit) if ($version);
-
- $space_pad = " " x 1024;
- $background_is_ready = 0;
-
- # for multi-module extension handling
- $multi_module_mode = 0;
- $multi_module_context = 0;
- $muffle_server_messages = 0;
- undef $master_store;
- undef %push_stack;
-
- $padded_patch_version = substr($TTYtter_PATCH_VERSION . " ", 0, 2);
-
- %opts_boolean = map { $_ => 1 } qw(
- ansi noansi verbose superverbose ttytteristas noprompt
- seven silent hold daemon script anonymous readline ssl
- newline vcheck verify noratelimit notrack nonewrts notimeline
- synch exception_is_maskable mentions simplestart
- location readlinerepaint nocounter notifyquiet
- signals_use_posix dostream nostreamreplies streamallreplies
- nofilter
- ); %opts_sync = map { $_ => 1 } qw(
- ansi pause dmpause ttytteristas verbose superverbose
- url rlurl dmurl newline wrap notimeline lists dmidurl
- queryurl track colourprompt colourme notrack
- colourdm colourreply colourwarn coloursearch colourlist idurl
- notifies filter colourdefault backload searchhits dmsenturl
- nostreamreplies mentions wtrendurl atrendurl filterusers
- filterats filterrts filteratonly filterflags nofilter
- ); %opts_urls = map {$_ => 1} qw(
- url dmurl uurl rurl wurl frurl rlurl update shorturl
- apibase queryurl idurl delurl dmdelurl favsurl
- favurl favdelurl followurl leaveurl
- dmupdate credurl blockurl blockdelurl friendsurl
- modifyliurl adduliurl delliurl getliurl getlisurl getfliurl
- creliurl delliurl deluliurl crefliurl delfliurl
- getuliurl getufliurl dmsenturl rturl rtsbyurl dmidurl
- statusliurl followliurl leaveliurl followersurl
- oauthurl oauthauthurl oauthaccurl oauthbase wtrendurl
- atrendurl frupdurl lookupidurl rtsofmeurl
- ); %opts_secret = map { $_ => 1} qw(
- superverbose ttytteristas
- ); %opts_comma_delimit = map { $_ => 1 } qw(
- lists notifytype notifies filterflags filterrts filterats
- filterusers filteratonly
- ); %opts_space_delimit = map { $_ => 1 } qw(
- track
- );
-
- %opts_can_set = map { $_ => 1 } qw(
- url pause dmurl dmpause superverbose ansi verbose
- update uurl rurl wurl avatar ttytteristas frurl track
- rlurl noprompt shorturl newline wrap verify autosplit
- notimeline queryurl colourprompt colourme
- colourdm colourreply colourwarn coloursearch colourlist idurl
- urlopen delurl notrack dmdelurl favsurl
- favurl favdelurl slowpost notifies filter colourdefault
- followurl leaveurl dmupdate mentions backload
- lat long location searchhits blockurl blockdelurl woeid
- nocounter linelength friendsurl followersurl lists
- modifyliurl adduliurl delliurl getliurl getlisurl getfliurl
- creliurl delliurl deluliurl crefliurl delfliurl atrendurl
- getuliurl getufliurl dmsenturl rturl rtsbyurl wtrendurl
- statusliurl followliurl leaveliurl dmidurl nostreamreplies
- frupdurl filterusers filterats filterrts filterflags
- filteratonly nofilter rtsofmeurl
- ); %opts_others = map { $_ => 1 } qw(
- lynx curl seven silent maxhist noansi hold status
- daemon timestamp twarg user anonymous script readline
- leader ssl rc norc vcheck apibase notifytype exts
- nonewrts synch runcommand authtype oauthkey oauthsecret
- tokenkey tokensecret credurl keyf readlinerepaint
- simplestart exception_is_maskable oldperl notco
- notify_tool_path oauthurl oauthauthurl oauthaccurl oauthbase
- signals_use_posix dostream eventbuf streamallreplies
- ); %valid = (%opts_can_set, %opts_others);
- $rc = (defined($rc) && length($rc)) ? $rc : "";
- unless ($norc) {
- my $rcf =
- ($rc =~ m#^/#) ? $rc : "$ENV{'HOME'}/.ttytterrc${rc}";
- if (open(W, $rcf)) {
- # 5.14 sets this lazily, so this gives us a way out
- eval 'binmode(W, ":utf8")' unless ($seven);
- while(<W>) {
- chomp;
- next if (/^\s*$/ || /^#/);
- s/^-//;
- ($key, $value) = split(/\=/, $_, 2);
- if ($key eq 'rc') {
- warn "** that's stupid, setting rc in an rc file\n";
- } elsif ($key eq 'norc') {
- warn "** that's dumb, using norc in an rc file\n";
- } elsif (length $$key) {
- ; # carry on
- } elsif ($valid{$key} && !length($$key)) {
- $$key = $value;
- } elsif ($key =~ /^extpref_/) {
- $$key = $value;
- } elsif (!$valid{$key}) {
- warn "** setting $key not supported in this version\n";
- }
- }
- close(W);
- } elsif (length($rc)) {
- die("couldn't access rc file $rcf: $!\n".
- "to use defaults, use -norc or don't specify the -rc option.\n\n");
- }
- }
- warn "** -twarg is deprecated\n" if (length($twarg));
- $seven ||= 0;
- $oldperl ||= 0;
- $parent = $$;
- $script = 1 if (length($runcommand));
- $supreturnto = $verbose + 0;
- $postbreak_time = 0;
- $postbreak_count = 0;
-
- # our minimum official support is now 5.8.6.
- if ($] < 5.008006 && !$oldperl) {
- die(<<"EOF");
-
-*** you are using a version of Perl in "extended" support: $] ***
-the minimum tested version of Perl now required by TTYtter is 5.8.6.
-
-Perl 5.005 thru 5.8.5 probably can still run TTYtter, but they are not
-tested with it. if you want to suppress this warning, specify -oldperl on
-the command line, or put oldperl=1 in your .ttytterrc. bug patches will
-still be accepted for older Perls; see the TTYtter home page for info.
-
-for Perl 5.005, remember to also specify -seven.
-
-EOF
- }
-
- # defaults that our extensions can override
- $last_id = 0;
- $last_dm = 0;
- # a correct fix for -daemon would make this unlimited, but this
- # is good enough for now.
- $print_max ||= ($daemon) ? 999999 : 250; # shiver
-
- $suspend_output = -1;
-
- # 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 ||= "$ENV{'HOME'}/.ttytterkey";
- $keyf = "$ENV{'HOME'}/.ttytterkey${keyf}" if ($keyf !~ m#/#);
- $attempted_keyf = $keyf;
- if (!length($oauthkey) && !length($oauthsecret) # set later
- && !length($tokenkey)
- && !length($tokensecret) && !$oauthwizard) {
- my $keybuf = '';
- if(open(W, $keyf)) {
- while(<W>) {
- chomp;
- s/\s+//g;
- $keybuf .= $_;
- }
- close(W);
- my (@pairs) = split(/\&/, $keybuf);
- foreach(@pairs) {
- my (@pair) = split(/\=/, $_, 2);
- $oauthkey = $pair[1]
- if ($pair[0] eq 'ck');
- $oauthsecret = $pair[1]
- if ($pair[0] eq 'cs');
- $tokenkey = $pair[1]
- if ($pair[0] eq 'at');
- $tokensecret = $pair[1]
- if ($pair[0] eq 'ats');
- }
- die("** tried to load OAuth tokens from $keyf\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")
- if ((!length($oauthkey) ||
- !length($oauthsecret) ||
- !length($tokenkey) ||
- !length($tokensecret)));
- } else {
- die("** couldn't open keyfile $keyf: $!\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
- }
- }
-
- # try to init Term::ReadLine if it was requested
- # (shakes fist at @br3nda, it's all her fault)
- %readline_completion = ();
- if ($readline && !$silent && !$script) {
- $ENV{"PERL_RL"} = "TTYtter" if (!length($ENV{'PERL_RL'}));
- eval
-'use Term::ReadLine; $termrl = new Term::ReadLine ("TTYtter", \*STDIN, \*STDOUT)'
- || die(
- "$@\nthis perl doesn't have ReadLine. don't use -readline.\n");
- $stdout = $termrl->OUT || \*STDOUT;
- $stdin = $termrl->IN || \*STDIN;
- $readline = '' if ($readline eq '1');
- $readline =~ s/^"//; # for optimizer
- $readline =~ s/"$//;
- #$termrl->Attribs()->{'autohistory'} = undef; # not yet
- (%readline_completion) = map {$_ => 1} split(/\s+/, $readline);
- %original_readline = %readline_completion;
- # readline repaint can't be tested here. we cache our
- # result later.
- } else {
- $stdout = \*STDOUT;
- $stdin = \*STDIN;
- }
- $wrapseq = 0;
- $lastlinelength = -1;
-
- print $stdout "$leader\n" if (length($leader));
-
- # state information
- $lasttwit = '';
- $lastpostid = 0;
-
- # stub namespace for multimodules and (eventually) state saving
- undef %store;
- $store = \%store;
-
- $pack_magic = ($] < 5.006) ? '' : "U0";
- $utf8_encode = sub { ; };
- $utf8_decode = sub { ; };
- unless ($seven) {
- eval
-'use utf8;binmode($stdin,":utf8");binmode($stdout,":utf8");return 1' ||
- die("$@\nthis perl doesn't fully support UTF-8. use -seven.\n");
-
- # this is for the prinput utf8 validator.
- # adapted from http://mail.nl.linux.org/linux-utf8/2003-03/msg00087.html
- # eventually this will be removed when 5.6.x support is removed,
- # and Perl will do the UTF-8 validation for us.
- $badutf8='[\x00-\x7f][\x80-\xbf]+|^[\x80-\xbf]+|'.
- '[\xc0-\xdf][\x00-\x7f\xc0-\xff]|'.
- '[\xc0-\xdf][\x80-\xbf]{2}|'.
- '[\xe0-\xef][\x80-\xbf]{0,1}[\x00-\x7f\xc0-\xff]|'.
- '[\xe0-\xef][\x80-\xbf]{3}|'.
- '[\xf0-\xf7][\x80-\xbf]{0,2}[\x00-\x7f\xc0-\xff]|'.
- '[\xf0-\xf7][\x80-\xbf]{4}|'.
- '[\xf8-\xfb][\x80-\xbf]{0,3}[\x00-\x7f\xc0-\xff]|'.
- '[\xf8-\xfb][\x80-\xbf]{5}|'.
- '[\xfc-\xfd][\x80-\xbf]{0,4}[\x00-\x7f\xc0-\xff]|'.
- '\xed[\xa0-\xbf][\x80-\xbf]|'.
- '\xef\xbf[\xbe-\xbf]|'.
- '[\xf0-\xf7][\x8f,\x9f,\xaf,\xbf]\xbf[\xbe-\xbf]|'.
- '\xfe|\xff|'.
- '[\xc0-\xc1][\x80-\xbf]|'.
- '\xe0[\x80-\x9f][\x80-\xbf]|'.
- '\xf0[\x80-\x8f][\x80-\xbf]{2}|'.
- '\xf8[\x80-\x87][\x80-\xbf]{3}|'.
- '\xfc[\x80-\x83][\x80-\xbf]{4}'; # gah!
-
- eval <<'EOF';
- $utf8_encode = sub { utf8::encode(shift); };
- $utf8_decode = sub { utf8::decode(shift); };
-EOF
- }
- $wraptime = sub { my $x = shift; return ($x, $x); };
- if ($timestamp) {
- my $fail = "-- can't use custom timestamps.\nspecify -timestamp by itself to use Twitter's without module.\n";
- if (length($timestamp) > 1) { # pattern specified
- eval 'use Date::Parse;return 1' ||
- die("$@\nno Date::Parse $fail");
- eval 'use Date::Format;return 1' ||
- die("$@\nno Date::Format $fail");
- $timestamp = "%Y-%m-%d %k:%M:%S"
- if ($timestamp eq "default" ||
- $timestamp eq "def");
- $wraptime = sub {
- my $time = str2time(shift);
- my $stime = time2str($timestamp, $time);
- return ($time, $stime);
- };
- }
- }
-}
-END {
- &killkid unless ($in_backticks || $in_buffer); # this is disgusting
-}
-
-#### COMMON STARTUP ####
-
-# if we requested POSIX signals, or we NEED posix signals (5.14+), we
-# must check if we have POSIX signals actually
-if ($signals_use_posix) {
- eval 'use POSIX';
- # God help the system that doesn't have SIGTERM
- $j = eval 'return POSIX::SIGTERM' ;
- die(<<"EOF") if (!(0+$j));
-*** death permeates me ***
-your configuration requires using POSIX signalling (either Perl 5.14+ or
-you specifically asked with -signals_use_posix). however, either you don't
-have POSIX.pm, or it doesn't work.
-
-TTYtter requires 'unsafe' Perl signals (which are of course for its
-purposes perfectly safe). unfortunately, due to Perl bug 92246 5.14+ must
-use POSIX.pm, or have the switch set before starting TTYtter. run one of
-
-export PERL_SIGNALS=unsafe # sh, bash, ksh, etc.
-setenv PERL_SIGNALS unsafe # csh, tcsh, etc.
-
-and restart TTYtter, or use Perl 5.12 or earlier (without specifying
--signals_use_posix).
-EOF
-}
-
-# do we have POSIX::Termios? (usually we do)
-eval 'use POSIX; $termios = new POSIX::Termios;';
-print $stdout "-- termios test: $termios\n" if ($verbose);
-
-# check the TRLT version. versions < 1.3 won't work with 2.0.
-if ($termrl && $termrl->ReadLine eq 'Term::ReadLine::TTYtter') {
- eval '$trlv = $termrl->Version;';
- die (<<"EOF") if (length($trlv) && 0+$trlv < 1.3);
-*** death permeates me ***
-you need to upgrade your Term::ReadLine::TTYtter to at least version 1.3
-to use TTYtter 2.x, or bad things will happen such as signal mismatches,
-unexpected quits, and dogs and cats living peacefully in the same house.
-
-EOF
- print $stdout "** t.co support needs Term::ReadLine:TTYtter 1.4+ (-notco to ignore)\n"
- if (length($trlv) && !$notco && 0+$trlv < 1.4);
-}
-
-# try to get signal numbers for SIG* from POSIX. use internals if failed.
-eval 'use POSIX; $SIGUSR1 = POSIX::SIGUSR1; $SIGUSR2 = POSIX::SIGUSR2; $SIGHUP = POSIX::SIGHUP; $SIGTERM = POSIX::SIGTERM';
-# from <sys/signal.h>
-$SIGHUP ||= 1;
-$SIGTERM ||= 15;
-$SIGUSR1 ||= 30;
-$SIGUSR2 ||= 31;
-
-# wrap warning
-die(
-"** dude, what the hell kind of terminal can't handle a 5 character line?\n")
- if ($wrap > 1 && $wrap < 5);
-print $stdout "** warning: prompts not wrapped for wrap < 70\n"
- if ($wrap > 1 && $wrap < 70);
-
-# reject stupid combinations
-die("you can't use automatic ratelimits with -noratelimit.\nuse -pause=#sec\n")
- if ($noratelimit && $pause eq 'auto');
-die("you can't use -synch with -script or -daemon.\n")
- if ($synch && ($script || $daemon));
-die("-script and -daemon cannot be used together.\n")
- if ($script && $daemon);
-
-# set up menu codes and caches
-$is_background = 0;
-$alphabet = "abcdefghijkLmnopqrstuvwxyz";
-%store_hash = ();
-$mini_split = 250; # i.e., 10 tweets for the mini-menu (/th)
-# leaving 50 tweets for the foreground temporary menus
-$tweet_counter = 0;
-%dm_store_hash = ();
-$dm_counter = 0;
-%id_cache = ();
-%filter_next = ();
-
-# set up threading management
-$in_reply_to = 0;
-$expected_tweet_ref = undef;
-
-# interpret -script at this level
-if ($script) {
- $noansi = $noprompt = 1;
- $silent = ($verbose) ? 0 : 1;
- $pause = $vcheck = $slowpost = $verify = 0;
-}
-
-### now instantiate the TTYtter dynamic API ###
-### based off the defaults later in script. ####
-
-# first we need to load any extensions specified by -exts.
-if (length($exts) && $exts ne '0') {
- $multi_module_mode = -1; # mark as loader stage
-
- print "** attempting to load extensions\n" unless ($silent);
- # unescape \,
- $j=0; $xstring = "ESCAPED_STRING";
- while($exts =~ /$xstring$j/) { $j++; }
- $xstring .= $j;
- $exts =~ s/\\,/$xstring/g;
- foreach $file (split(/,/, $exts)) {
-#TODO
-# wildcards?
- $file =~ s/$xstring/,/g;
- print "** loading $file\n" unless ($silent);
-
- die("** sorry, you cannot load the same extension twice.\n")
- if ($master_store->{$file}->{'loaded'});
-
- # prepare its working space in $store and load the module
- $master_store->{$file} = { 'loaded' => 1 };
- $store = \%{ $master_store->{$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
- die("** $file failed to load: $@\n") if ($@);
- die("** consistency failure: reference failure on $file\n")
- if (!$store->{'loaded'});
-
- # check type of extension (interactive or non-interactive). if
- # we are in the wrong mode, bail out.
- if ($extension_mode) {
- die(
-"** this extension requires -script. this may conflict with other extensions\n".
-" you are loading, which may have their own requirements.\n")
- if ($extension_mode == $EM_SCRIPT_ON && !$script);
- die(
-"** this extension cannot work with -script. this may conflict with other\n".
-" extensions you are loading, which may have their own requirements.\n")
- if ($extension_mode == $EM_SCRIPT_OFF && $script);
- }
-
- # pick off all the subroutine references it makes for storage
- # in an array to iterate and chain over later.
-
- # these methods are multi-module safe
- foreach $arry (qw(
- handle exception tweettype conclude dmhandle dmconclude
- heartbeat precommand prepost postpost addaction
- eventhandle listhandle userhandle shutdown)) {
- if (defined($$arry)) {
- $aarry = "m_$arry";
- push(@$aarry, [ $file, $$arry ]);
- undef $$arry;
- }
- }
- # these methods are NOT multi-module safe
- # if a extension already hooked one of
- # these and another extension tries to hook it, fatal error.
- foreach $arry (qw(
- getpassword prompt main autocompletion)) {
- if (defined($$arry)) {
- $sarry = "l_$arry";
- if (defined($$sarry)) {
- die(
-"** double hook of unsafe method \"$arry\" -- you cannot use this extension\n".
-" with the other extensions you are loading. see the documentation.\n");
- }
- $$sarry = $$arry;
- undef $$arry;
- }
- }
- }
- # success! enable multi-module support in the TTYtter API and then
- # dispatch calls through the multi-module system instead.
- $multi_module_mode = 1; # mark as completed loader
-
- $handle = \&multihandle;
- $exception = \&multiexception;
- $tweettype = \&multitweettype;
- $conclude = \&multiconclude;
- $dmhandle = \&multidmhandle;
- $dmconclude = \&multidmconclude;
- $heartbeat = \&multiheartbeat;
- $precommand = \&multiprecommand;
- $prepost = \&multiprepost;
- $postpost = \&multipostpost;
- $addaction = \&multiaddaction;
- $shutdown = \&multishutdown;
- $userhandle = \&multiuserhandle;
- $listhandle = \&multilisthandle;
- $eventhandle = \&multieventhandle;
-
-} else {
- # the old API single-end-point system
-
- $multi_module_mode = 0; # not executing multi module endpoints
-
- $handle = \&defaulthandle;
- $exception = \&defaultexception;
- $tweettype = \&defaulttweettype;
- $conclude = \&defaultconclude;
- $dmhandle = \&defaultdmhandle;
- $dmconclude = \&defaultdmconclude;
- $heartbeat = \&defaultheartbeat;
- $precommand = \&defaultprecommand;
- $prepost = \&defaultprepost;
- $postpost = \&defaultpostpost;
- $addaction = \&defaultaddaction;
- $shutdown = \&defaultshutdown;
- $userhandle = \&defaultuserhandle;
- $listhandle = \&defaultlisthandle;
- $eventhandle = \&defaulteventhandle;
-}
-
-# unsafe methods use the single-end-point
-$prompt = $l_prompt || \&defaultprompt;
-$main = $l_main || \&defaultmain;
-$getpassword = $l_getpassword || \&defaultgetpassword;
-
-# $autocompletion is special:
-if ($termrl) {
- $termrl->Attribs()->{'completion_function'} =
- $l_autocompletion || \&defaultautocompletion;
-}
-
-# fetch_id is based off last_id, if an extension set it
-$fetch_id = $last_id || 0;
-
-# validate the notify method the user chose, if any.
-# 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 = ();
-if (length($notifytype) && $notifytype ne '0' &&
- $notifytype ne '1' && !$status) {
- # NOT $script! scripts have a use case for notifiers!
-
- %dupenet = ();
- foreach $nt (split(/\s*,\s*/, $notifytype)) {
- $fnt="notifier_${nt}";
- (warn("** duplicate notification $nt was ignored\n"), next)
- if ($dupenet{$fnt});
- eval 'return &$fnt(undef)' ||
- die("** invalid notification framework $nt: $@\n");
- $dupenet{$fnt}=1;
- }
- @notifytypes = keys %dupenet;
- $notifytype = join(',', @notifytypes);
- # warning if someone didn't tell us what notifies they wanted.
- warn "-- warning: you specified -notifytype, but no -notifies\n"
- if (!$silent && !length($notifies));
-}
-
-# set up track tags
-if (length($tquery) && $tquery ne '0') {
- my $xtquery = &tracktags_tqueryurlify($tquery);
- die("** custom tquery is over 140 length: $xtquery\n")
- if (length($xtquery) > 139);
- @trackstrings = ($xtquery);
-} else {
- &tracktags_makearray;
-}
-
-# compile filterflags
-&filterflags_compile;
-
-# compile filters
-exit(1) if (!&filter_compile);
-$filterusers_sub = &filteruserlist_compile(undef, $filterusers);
-$filterrts_sub = &filteruserlist_compile(undef, $filterrts);
-$filteratonly_sub = &filteruserlist_compile(undef, $filteratonly);
-exit(1) if (!&filterats_compile);
-
-# compile lists
-exit(1) if (!&list_compile);
-
-# finally, compile notifies. we do this regardless of notifytype, so that
-# an extension can look at it if it wants to.
-&notify_compile;
-
-# check that we are using a sensible authtype, based on our guessed user agent
-$authtype ||= "oauth";
-die("** supported authtypes are basic or oauth only.\n")
-if ($authtype ne 'basic' && $authtype ne 'oauth');
-
-if ($termrl) {
- $streamout = $stdout; # this is just simpler instead of dupping
- warn(<<"EOF") if ($] < 5.006);
-***********************************************************
-** -readline may not function correctly on Perls < 5.6.0 **
-***********************************************************
-EOF
- print $stdout "-- readline using ".$termrl->ReadLine."\n";
-} else {
- # dup $stdout for benefit of various other scripts
- open(DUPSTDOUT, ">&STDOUT") ||
- warn("** warning: could not dup $stdout: $!\n");
- binmode(DUPSTDOUT, ":utf8") unless ($seven);
- $streamout = \*DUPSTDOUT;
-}
-if ($silent) {
- close($stdout);
- open($stdout, ">>/dev/null"); # KLUUUUUUUDGE
-}
-
-# after this point, die() may cause problems
-
-# initialize our route back out so background can talk to foreground
-pipe(W, P) || die("pipe() error [or your Perl doesn't support it]: $!\n");
-select(P); $|++;
-binmode(P, ":utf8") unless ($seven);
-binmode(W, ":utf8") unless ($seven);
-
-# default command line options
-
-$anonymous ||= 0;
-die("** -anonymous is no longer supported with Twitter (you must use -apibase also)\n")
- if ($anonymous && !length($apibase));
-undef $user if ($anonymous);
-print $stdout "-- using SSL for default URLs.\n" if ($ssl);
-$http_proto = ($ssl) ? 'https' : 'http';
-
-$lat ||= undef;
-$long ||= undef;
-$location ||= 0;
-$linelength ||= 140;
-$oauthbase ||= $apibase || "${http_proto}://api.twitter.com";
-# this needs to be AFTER oauthbase so that apibase can set oauthbase.
-$apibase ||= "${http_proto}://api.twitter.com/1.1";
-$nonewrts ||= 0;
-
-# special case: if we explicitly refuse backload, don't load initially.
-$backload = 30 if (!defined($backload)); # zero is valid!
-$dont_refresh_first_time = 1 if (!$backload);
-
-$searchhits ||= 20;
-$url ||= "${apibase}/statuses/home_timeline.json";
-
-$oauthurl ||= "${oauthbase}/oauth/request_token";
-$oauthauthurl ||= "${oauthbase}/oauth/authorize";
-$oauthaccurl ||= "${oauthbase}/oauth/access_token";
-
-$credurl ||= "${apibase}/account/verify_credentials.json";
-$update ||= "${apibase}/statuses/update.json";
-$rurl ||= "${apibase}/statuses/mentions_timeline.json";
-$uurl ||= "${apibase}/statuses/user_timeline.json";
-$idurl ||= "${apibase}/statuses/show.json";
-$delurl ||= "${apibase}/statuses/destroy/%I.json";
-
-$rturl ||= "${apibase}/statuses/retweet";
-$rtsbyurl ||= "${apibase}/statuses/retweets/%I.json";
-$rtsofmeurl ||= "${apibase}/statuses/retweets_of_me.json";
-
-$wurl ||= "${apibase}/users/show.json";
-
-$frurl ||= "${apibase}/friendships/show.json";
-$followurl ||= "${apibase}/friendships/create.json";
-$leaveurl ||= "${apibase}/friendships/destroy.json";
-$blockurl ||= "${apibase}/blocks/create.json";
-$blockdelurl ||= "${apibase}/blocks/destroy.json";
-$friendsurl ||= "${apibase}/friends/ids.json";
-$followersurl ||= "${apibase}/followers/ids.json";
-$frupdurl ||= "${apibase}/friendships/update.json";
-$lookupidurl ||= "${apibase}/users/lookup.json";
-
-$rlurl ||= "${apibase}/application/rate_limit_status.json";
-
-$dmurl ||= "${apibase}/direct_messages.json";
-$dmsenturl ||= "${apibase}/direct_messages/sent.json";
-$dmupdate ||= "${apibase}/direct_messages/new.json";
-$dmdelurl ||= "${apibase}/direct_messages/destroy.json";
-$dmidurl ||= "${apibase}/direct_messages/show.json";
-
-$favsurl ||= "${apibase}/favorites/list.json";
-$favurl ||= "${apibase}/favorites/create.json";
-$favdelurl ||= "${apibase}/favorites/destroy.json";
-
-$getlisurl ||= "${apibase}/lists/list.json";
-$creliurl ||= "${apibase}/lists/create.json";
-$delliurl ||= "${apibase}/lists/destroy.json";
-$modifyliurl ||= "${apibase}/lists/update.json";
-$deluliurl ||= "${apibase}/lists/members/destroy_all.json";
-$adduliurl ||= "${apibase}/lists/members/create_all.json";
-$getuliurl ||= "${apibase}/lists/memberships.json";
-$getufliurl ||= "${apibase}/lists/subscriptions.json";
-$delfliurl ||= "${apibase}/lists/subscribers/destroy.json";
-$crefliurl ||= "${apibase}/lists/subscribers/create.json";
-$getfliurl ||= "${apibase}/lists/subscribers.json";
-$getliurl ||= "${apibase}/lists/members.json";
-$statusliurl ||= "${apibase}/lists/statuses.json";
-
-$streamurl ||= "https://userstream.twitter.com/2/user.json";
-$dostream ||= 0;
-$eventbuf ||= 0;
-
-$queryurl ||= "${apibase}/search/tweets.json";
-# no more $trendurl in 2.1.
-$wtrendurl ||= "${apibase}/trends/place.json";
-$atrendurl ||= "${apibase}/trends/closest.json";
-
-# pick ONE!
-#$shorturl ||= "http://api.tr.im/v1/trim_simple?url=";
-$shorturl ||= "http://is.gd/api.php?longurl=";
-
-# figure out the domain to stop shortener loops
-&generate_shortdomain;
-
-$pause = (($anonymous) ? 120 : "auto") if (!defined $pause);
- # NOT ||= ... zero is a VALID value!
-$superverbose ||= 0;
-$avatar ||= "";
-$urlopen ||= 'echo %U';
-$hold ||= 0;
-$daemon ||= 0;
-$maxhist ||= 19;
-undef $shadow_history;
-$timestamp ||= 0;
-$noprompt ||= 0;
-$slowpost ||= 0;
-$twarg ||= undef;
-
-$verbose ||= $superverbose;
-$dmpause = 4 if (!defined $dmpause); # NOT ||= ... zero is a VALID value!
-$dmpause = 0 if ($anonymous);
-$dmpause = 0 if ($pause eq '0');
-$ansi = ($noansi) ? 0 :
- (($ansi || $ENV{'TERM'} eq 'ansi' || $ENV{'TERM'} eq 'xterm-color')
- ? 1 : 0);
-
-# synch overrides these options.
-if ($synch) {
- $pause = 0;
- $dmpause = ($dmpause) ? 1 : 0;
-}
-
-$dmcount = $dmpause;
-$lastshort = undef;
-
-# ANSI sequences
-$colourprompt ||= "CYAN";
-$colourme ||= "YELLOW";
-$colourdm ||= "GREEN";
-$colourreply ||= "RED";
-$colourwarn ||= "MAGENTA";
-$coloursearch ||= "CYAN";
-$colourlist ||= "OFF";
-$colourdefault ||= "OFF";
-$ESC = pack("C", 27);
-$BEL = pack("C", 7);
-&generate_ansi;
-
-# to force unambiguous bareword interpretation
-$true = 'true';
-sub true { return 'true'; }
-$false = 'false';
-sub false { return 'false'; }
-$null = undef;
-sub null { return undef; }
-
-select($stdout); $|++;
-
-# figure out what our user agent should be
-if ($lynx) {
- if (length($lynx) > 1 && -x "/$lynx") {
- $wend = $lynx;
- print $stdout "Lynx forced to $wend\n";
- } else {
- $wend = &wherecheck("trying to find Lynx", "lynx",
-"specify -curl to use curl instead, or just let TTYtter autodetect stuff.\n");
- }
-} else {
- if (length($curl) > 1 && -x "/$curl") {
- $wend = $curl;
- print $stdout "cURL forced to $wend\n";
- } else {
- $wend = (($curl) ? &wherecheck("trying to find cURL", "curl",
-"specify -lynx to use Lynx instead, or just let TTYtter autodetect stuff.\n")
- : &wherecheck("trying to find cURL", "curl"));
- if (!$curl && !length($wend)) {
- $wend = &wherecheck("failed. trying to find Lynx",
- "lynx",
- "you must have either Lynx or cURL installed to use TTYtter.\n")
- if (!length($wend));
- $lynx = 1;
- } else {
- $curl = 1;
- }
- }
-}
-$baseagent = $wend;
-
-# whoops, no Lynx here if we are not using Basic Auth
- die(
-"sorry, OAuth is not currently supported with Lynx.\n".
-"you must use SSL cURL, or specify -authtype=basic.\n")
- if ($lynx && $authtype ne 'basic' && !$anonymous);
-
-# streaming API has multiple prereqs. not fatal; we just fall back on the
-# REST API if not there.
-unless($status) {
-if (!$dostream || $authtype eq 'basic' || !$ssl || $script || $anonymous || $synch) {
- $reason = (!$dostream) ? "(no -dostream)"
- : ($script) ? "(-script)"
- : (!$ssl) ? "(no SSL)"
- : ($anonymous) ? "(-anonymous)"
- : ($synch) ? "(-synch)"
- : ($authtype eq 'basic') ? "(no OAuth)"
- : "(it's funkatron's fault)";
- print $stdout
- "-- Streaming API disabled $reason (TTYtter will use REST API only)\n";
- $dostream = 0;
- } else {
- print $stdout "-- Streaming API enabled\n";
-
- # streams change mentions behaviour; we get them automatically.
- # warn the user if the current settings are suboptimal.
- if ($mentions) {
- if ($nostreamreplies) {
- print $stdout
-"** warning: -mentions and -nostreamreplies are very inefficient together\n";
- } else {
- print $stdout
-"** warning: -mentions not generally needed in Streaming mode\n";
- }
- }
- }
-} else { $dostream = 0; } # -status suppresses streaming
-if (!$dostream && $streamallreplies) {
- print $stdout
-"** warning: -streamallreplies only works in Streaming mode\n";
-}
-
-# create and cache the logic for our selected user agent
-if ($lynx) {
- $simple_agent = "$baseagent -nostatus -source";
-
- @wend = ('-nostatus');
- @wind = (@wend, '-source'); # GET agent
- @wend = (@wend, '-post_data'); # POST agent
- # we don't need to have the request signed by Lynx right now;
- # it doesn't know how to pass custom headers. so this is simpler.
- $stringify_args = sub {
- my $basecom = shift;
- my $resource = shift;
- my $data = shift;
- my $dont_do_auth = shift;
- my $k = join("\n", @_);
-
- # if resource is an arrayref, then it's a GET with URL
- # and args (mostly generated by &grabjson)
- $resource = join('?', @{ $resource })
- if (ref($resource) eq 'ARRAY');
- die("wow, we have a bug: Lynx only works with Basic Auth\n")
- if ($authtype ne 'basic' && !$dont_do_auth);
- $k = "-auth=".$mytoken.':'.$mytokensecret."\n".$k
- unless ($dont_do_auth);
- $k .= "\n";
- $basecom = "$basecom \"$resource\" -";
- return ($basecom, $k, $data);
- };
-} else {
- $simple_agent = "$baseagent -s -m 20";
-
- @wend = ('-s', '-m', '20', '-A', "TTYtter/$TTYtter_VERSION",
- '-H', 'Expect:');
- @wind = @wend;
- $stringify_args = sub {
- my $basecom = shift;
- my $resource = shift;
- my $data = shift;
- my $dont_do_auth = shift;
- my $p;
- my $l = '';
-
- foreach $p (@_) {
- if ($p =~ /^-/) {
- $l .= "\n" if (length($l));
- $l .= "$p ";
- next;
- }
- $l .= $p;
- }
- $l .= "\n";
-
- # sign our request (Basic Auth or oAuth)
- unless ($dont_do_auth) {
- if ($authtype eq 'basic') {
- $l .= "-u ".$mytoken.":".$mytokensecret."\n";
- } else {
- my $nonce;
- my $timestamp;
- my $sig;
- my $verifier = '';
- my $header;
- my $ttoken = (length($mytoken) ?
- (' oauth_token=\\"'.$mytoken.'\\",') :
- '');
-
- ($timestamp, $nonce, $sig, $verifier) =
- &signrequest($resource, $data);
- $header = <<"EOF";
--H "Authorization: OAuth oauth_nonce=\\"$nonce\\", oauth_signature_method=\\"HMAC-SHA1\\", oauth_timestamp=\\"$timestamp\\", oauth_consumer_key=\\"$oauthkey\\", oauth_signature=\\"$sig\\",${ttoken}${verifier} oauth_version=\\"1.0\\""
-EOF
- print $stdout $header if ($superverbose);
- $l .= $header;
- }
- }
-
- # if resource is an arrayref, then it's a GET with URL
- # and args (mostly generated by &grabjson)
- $resource = join('?', @{ $resource })
- if (ref($resource) eq 'ARRAY');
- $l .= "url = \"$resource\"\n";
- $l .= "data = \"$data\"\n" if length($data);
- return ("$basecom -K -", $l, undef);
- };
-}
-
-# update check
-if ($vcheck && !length($status)) {
- $vs = &updatecheck(0);
-} else {
- $vs =
-"-- no version check performed (use /vcheck, or -vcheck to check on startup)\n"
- unless ($script || $status);
-}
-print $stdout $vs; # and then again when client starts up
-
-## make sure we have all the authentication pieces we need for the
-## chosen method (authtoken handles this for Basic Auth;
-## this is where we validate OAuth)
-
-# if we use OAuth, then don't use any Basic Auth credentials we gave
-# unless we specifically say -authtype=basic
-if ($authtype eq 'oauth' && length($user)) {
- print "** warning: -user is ignored when -authtype=oauth (default)\n";
- $user = undef;
-}
-$whoami = (split(/\:/, $user, 2))[0] unless ($anonymous || !length($user));
-
-# yes, this is plaintext. obfuscation would be ludicrously easy to crack,
-# and there is no way to hide them effectively or fully in a Perl script.
-# so be a good neighbour and leave this the fark alone, okay? stealing
-# credentials is mean and inconvenient to users. this is blessed by
-# arrangement with Twitter. don't be a d*ck. thanks for your cooperation.
-$oauthkey = (!length($oauthkey) || $oauthkey eq 'X') ?
- "XtbRXaQpPdfssFwdUmeYw" : $oauthkey;
-$oauthsecret = (!length($oauthsecret) || $oauthsecret eq 'X') ?
- "csmjfTQPE8ZZ5wWuzgPJPOBR9dyvOBEtHT5cJeVVmAA" : $oauthsecret;
-
-unless ($anonymous) {
-# if we are using Basic Auth, ignore any user token we may have in
-# our keyfile
-if ($authtype eq 'basic') {
- $tokenkey = undef;
- $tokensecret = undef;
-}
-# but if we are using OAuth, we can request one, unless we are in script
-elsif ($authtype eq 'oauth' && (!length($keyf) || $oauthwizard)) {
- if (length($oauthkey) && length($oauthsecret) &&
- !length($tokenkey) && !length($tokensecret)) {
- # we have a key, we don't have the user token
- # but we can't get that with -script
- if ($script) {
- print $streamout <<"EOF";
-AUTHENTICATION FAILURE
-YOU NEED TO GET AN OAuth KEY, or use -authtype=basic
-(run TTYtter without -script or -runcommand for help)
-EOF
- exit;
- }
- # run the wizard, which writes a keyfile for us
- $keyf ||= $attempted_keyf;
- print $stdout <<"EOF";
-
-+----------------------------------------------------------------------------+
-|| WELCOME TO TTYtter: Authorize TTYtter by signing into Twitter with OAuth ||
-+----------------------------------------------------------------------------+
-Looks like you're starting TTYtter for the first time, and/or creating a
-keyfile. Welcome to the most user-hostile, highly obfuscated, spaghetti code
-infested and obscenely obscure Twitter client that's out there. You'll love it.
-
-TTYtter generates a keyfile that contains credentials for you, including your
-access tokens. This needs to be done JUST ONCE. You can take this keyfile with
-you to other systems. If you revoke TTYtter'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 TTYtter; only one account token can be stored per keyfile. If you
-have multiple accounts, use -keyf=... to specify different keyfiles. KEEP THESE
-FILES SECRET.
-
-** This wizard will overwrite $keyf
-Press RETURN/ENTER to continue or CTRL-C NOW! to abort.
-EOF
- $j = <STDIN>;
- print $stdout "\nRequest from $oauthurl ...";
- ($tokenkey, $tokensecret) = &tryhardfortoken($oauthurl,
- "oauth_callback=oob");
- $mytoken = $tokenkey;
- $mytokensecret = $tokensecret; # needs to be in both places
- # kludge in case user does not specify SSL and this is
- # Twitter: we know Twitter supports SSL
- ($oauthauthurl =~ /twitter/) &&
- ($oauthauthurl =~ s/^http:/https:/);
- print $stdout <<"EOF";
-
-1. Visit, in your browser, ALL ON ONE LINE,
-
-${oauthauthurl}?oauth_token=$mytoken
-
-2. If you are not already signed in, fill in your username and password.
-
-3. Verify that TTYtter is the requesting application, and that its permissions
-are as you expect (read your timeline, see who you follow and follow new
-people, update your profile, post tweets on your behalf and access your
-direct messages). IF THIS IS NOT CORRECT, PRESS CTRL-C NOW!
-
-4. Click Authorize app.
-
-5. A PIN will appear. Enter it below.
-
-EOF
- $j = '';
- while(!(0+$j)) {
- print $stdout "Enter PIN> ";
- chomp($j = <STDIN>);
- }
- print $stdout "\nRequest from $oauthaccurl ...";
- ($tokenkey, $tokensecret) = &tryhardfortoken($oauthaccurl,
- "oauth_verifier=$j");
-
- $oauthkey = "X";
- $oauthsecret = "X";
- open(W, ">$keyf") ||
- die("Failed to write keyfile $keyf: $!\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";
- print $stdout <<"EOF";
-Written keyfile $keyf
-
-Now, restart TTYtter to use this keyfile.
-(To choose between multiple keyfiles other than the default .ttytterkey,
- tell TTYtter where the key is using -keyf=... .)
-
-EOF
- exit;
- }
- # if we get three of the four, this must have been command line
- if (length($oauthkey) && length($oauthsecret) &&
- (!length($tokenkey) || !length($tokensecret))) {
- my $error = undef;
- my $k;
- foreach $k (qw(oauthkey oauthsecret tokenkey tokensecret)) {
- $error .= "** you need to specify -$k\n"
- if (!length($$k));
- }
- if (length($error)) {
- 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:
-$error
-then restart TTYtter, or use -authtype=basic.
-EOF
- exit;
- }
- }
-} elsif ($retoke && length($keyf)) {
- # 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");
- print $stdout <<"EOF";
-
-+-------------------------------------------------------------------------+
-|| The Re-Toke Wizard: Generate a new TTYtter keyfile for your app/token ||
-+-------------------------------------------------------------------------+
-Twitter is requiring tokens to now have specific permissions to READ
-direct messages. This will be enforced by 1 July 2011. If you find you are
-unable to READ direct messages, you will need this wizard. DO NOT use this
-wizard if you are NOT using a cloned app key (1.2 and on) -- use -oauthwizard.
-
-This wizard will create a new keyfile for you from your app/user keys/tokens.
-You do NOT need this wizard if you are using TTYtter for a purpose that does
-not require direct message access. For example, if TTYtter is acting as
-your command line posting agent, or you are only using it to read your
-timeline, you do NOT need a new token. You also do not need a new token to
-SEND a direct message, only to READ ones this account has received.
-
-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 TTYtter with -retoke -keyf=/path/to/keyfile
-Press RETURN/ENTER to continue, or CTRL-C NOW! to abort.
-EOF
-
- $j = <STDIN>;
- print $stdout <<"EOF";
-
-First: let's get your API key, consumer key and consumer secret.
-Start your browser.
-1. Log into https://twitter.com/ with your desired account.
-2. Go to this URL. You must be logged into Twitter FIRST!
-
-https://dev.twitter.com/apps
-
-3. Click the TTYtter cloned app key you need to regenerate or upgrade.
-4. Click Edit Application Settings.
-5. Make sure Read, Write & Private Message is selected, and click the
- "Save application" button.
-6. Select All (CTRL/Command-A) on the next screen, copy (CTRL/Command-C) it,
- and paste (CTRL/Command-V) it into this window. (You can also cut and
- paste a smaller section if I can't understand your browser's layout.)
-7. Press ENTER/RETURN and CTRL-D when you have pasted the window contents.
-EOF
-
- $q = $/;
- PASTE1LOOP: for(;;) {
- print $stdout <<"EOF";
-
--- Press ENTER and CTRL-D AFTER you have pasted the window contents! ---------
-Go ahead:
-EOF
- undef $/;
- $j = <STDIN2>;
- print $stdout <<"EOF";
-
--- EOF -----------------------------------------------------------------------
-Processing ...
-
-EOF
- $j =~ s/[\r\n]/ /sg;
-
- # process this. as a checksum, API key should == consumer key.
- $ck = '';
- $cs = '';
- ($j =~ /Consumer key\s+([-a-zA-Z0-9_]{10,})\s+/) && ($ck = $1);
- ($j =~ /Consumer secret\s+([-a-zA-Z0-9_]{10,})\s+/) &&
- ($cs = $1);
-
- if (!length($ck) || !length($cs)) {
- # escape hatch
- print $stdout <<"EOF";
-Something's wrong: I could not find your consumer key or consumer
-secret in that text. If this was a misfired paste, please restart the wizard.
-Otherwise, bug me at \@ttytter or ckaiser\@floodgap.com. Please don't send
-keys or secrets to either address.
-
-EOF
- exit;
- }
- last PASTE1LOOP;
- }
- # this part is similar to the retoke.
- $oauthkey = $ck;
- $oauthsecret = $cs;
- print $stdout "\nI'm testing this key to see if it works.\n";
- print $stdout "Request from $oauthurl ...";
- ($tokenkey, $tokensecret) = &tryhardfortoken($oauthurl,
- "oauth_callback=oob");
- $mytoken = $tokenkey;
- $mytokensecret = $tokensecret;
- # kludge in case user does not specify SSL and this is
- # Twitter: we know Twitter supports SSL
- ($oauthauthurl =~ /twitter/) && ($oauthauthurl =~ s/^http:/https:/);
- $/ = $q;
- print $stdout <<"EOF";
-
-Okay, your consumer key is ==> $ck
- and your consumer secret ==> $cs
-
-IF THIS IS WRONG, PRESS CTRL-C NOW AND RESTART THE WIZARD!
-
-Now we will verify your Imperial battle station is fully operational by
-signing in with OAuth.
-
-1. Visit, in your browser, ALL ON ONE LINE (you should still be logged in),
-
-${oauthauthurl}?oauth_token=$mytoken
-
-2. Verify that your app is the requesting application, and that its permissions
-are as you expect (read your timeline, see who you follow and follow new
-people, update your profile, post tweets on your behalf and access your
-direct messages). IF THIS IS NOT CORRECT, PRESS CTRL-C NOW!
-
-3. Click Authorize app.
-
-4. A PIN will appear. Enter it below.
-
-EOF
- print $stdout "Enter PIN> ";
- chomp($j = <STDIN>);
- print $stdout "\nRequest from $oauthaccurl ...";
- ($at, $ats) = &tryhardfortoken($oauthaccurl, "oauth_verifier=$j");
-
- print $stdout <<"EOF";
-
-Consumer key =========> $ck
-Consumer secret ======> $cs
-Access token =========> $at
-Access token secret ==> $ats
-
-EOF
- open(W, ">$keyf") || (print $stdout ("Unable to write to $keyf: $!\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";
- print $stdout "Now restart TTYtter.\n";
- exit;
-}
-
-# now, get a token (either from Basic Auth, the keyfile or OAuth)
-($mytoken, $mytokensecret) = &authtoken;
-} # unless anonymous
-
-# if we are testing the stream, this is where we split
-if ($streamtest) {
- print $stdout ">>> STREAMING CONNECT TEST <<< (kill process to end)\n";
- &start_streaming; } # this never returns in this mode
-
-# initial login tests and command line controls
-if ($statusurl) {
- $shorstatusturl = &urlshorten($statusurl);
- $status = ((length($status)) ? "$status " : "") . $shorstatusturl;
-}
-$phase = 0;
-$didhold = $hold;
-$hold = -1 if ($hold == 1 && !$script);
-$credentials = '';
-$status = pack("U0C*", unpack("C*", $status))
- unless ($seven || !length($status) || $LANG =~ /8859/); # kludgy also
-if ($status eq '-') {
- chomp(@status = <STDIN>);
- $status = join("\n", @status);
-}
-for(;;) {
- $rv = 0;
- die(
- "sorry, you can't tweet anonymously. use an authenticated username.\n")
- if ($anonymous && length($status));
- die(
-"sorry, status too long: reduce by @{[ &length_tco($status)-$linelength ]} chars, ".
-"or use -autosplit={word,char,cut}.\n")
- if (&length_tco($status) > $linelength && !$autosplit);
- ($status, $next) = &csplit($status, ($autosplit eq 'char' ||
- $autosplit eq 'cut') ? 1 : 0)
- if (!length($next));
- if ($autosplit eq 'cut' && length($next)) {
- print "-- warning: input autotrimmed to $linelength bytes\n";
- $next = "";
- }
- if (!$anonymous && !length($whoami) && !length($status)) {
- # we must be using OAuth tokens. we'll need
- # to get our screen name from Twitter. we DON'T need this
- # if we're just posting with -status.
- print "(checking credentials) "; $data =
- $credentials = &backticks($baseagent, '/dev/null', undef,
- $credurl, undef, $anonymous, @wind);
- $rv = $? || &is_fail_whale($data) || &is_json_error($data);
- }
- if (!$rv && length($status) && $phase) {
- print "post attempt "; $rv = &updatest($status, 0);
- } else {
- # no longer a way to test anonymous logins
- unless ($rv || $anonymous) {
- print "test-login ";
- $data = &backticks($baseagent, '/dev/null', undef,
- $url, undef, $anonymous, @wind);
- $rv = $?;
- }
- }
- if ($rv || &is_fail_whale($data) || &is_json_error($data)) {
- if (&is_fail_whale($data)) {
- print "FAILED -- Fail Whale detected\n";
- } elsif ($x = &is_json_error($data)) {
- print "FAILED!\n*** server reports: \"$x\"\n";
- print "check your password or configuration.\n";
- } else {
- $x = $rv >> 8;
- print
- "FAILED. ($x) bad password, login or URL? server down?\n";
- }
- print "access failure on: ";
- print (($phase) ? $update : $url);
- print "\n";
- print
- "--- data received ($hold) ---\n$data\n--- data received ($hold) ---\n"
- if ($superverbose);
- if ($hold && --$hold) {
- print
- "trying again in 1 minute, or kill process now.\n\n";
- sleep 60;
- next;
- }
- if ($didhold) {
- print "giving up after $didhold tries.\n";
- } else {
- print
- "to automatically wait for a connect, use -hold.\n";
- }
- exit(1);
- }
- if ($status && !$phase) {
- print "SUCCEEDED!\n";
- $phase++;
- next;
- }
- if (length($next)) {
- print "SUCCEEDED!\n(autosplit) ";
- $status = $next;
- $next = "";
- next;
- }
- last;
-}
-print "SUCCEEDED!\n";
-exit(0) if (length($status));
-&sigify(sub { ; }, qw(USR1 PWR XCPU));
-&sigify(sub { $background_is_ready++ }, qw(USR2 SYS UNUSED XFSZ));
-if (length($credentials)) {
- print "-- processing credentials: ";
- $my_json_ref = &parsejson($credentials);
- $whoami = lc($my_json_ref->{'screen_name'});
- if (!length($whoami)) {
- print "FAILED!\nis your account suspended, or wrong token?\n";
- exit;
- }
- print "logged in as $whoami\n";
- $credlog = "-- you are logged in as $whoami\n";
-}
-
-#### BOT/DAEMON MODE STARTUP ####
-
-$last_rate_limit = undef;
-$rate_limit_left = undef;
-$rate_limit_rate = undef;
-$rate_limit_next = 0;
-$effpause = 0; # for both daemon and background
-if ($daemon) {
- if (!$pause) {
- print $stdout "*** kind of stupid to run daemon with pause=0\n";
- exit 1;
- }
- if ($child = fork()) {
- print $stdout "*** detached daemon released. pid = $child\n";
- kill 15, $$;
- exit 0;
- } elsif (!defined($child)) {
- print $stdout "*** fork() failed: $!\n";
- exit 1;
- } else {
- $bufferpid = 0;
- if ($dostream) {
- &sigify(sub {
- kill $SIGHUP, $nursepid if ($nursepid);
- kill $SIGHUP, $bufferpid if ($bufferpid);
- kill 9, $curlpid if ($curlpid);
- sleep 1;
- # send myself a shutdown
- kill 9, $nursepid if ($nursepid);
- kill 9, $bufferpid if ($bufferpid);
- kill 9, $curlpid if ($curlpid);
- kill 9, $$;
- }, qw(TERM HUP PIPE));
- &sigify("IGNORE", qw(INT));
- $bufferpid = &start_streaming;
- $rin = '';
- vec($rin, fileno(STBUF), 1) = 1;
- }
- $parent = 0;
- $dmcount = 1 if ($dmpause); # force fetch
- $is_background = 1;
- DAEMONLOOP: for(;;) {
- my $snooze;
- my $nfound;
- my $wake;
-
- &$heartbeat;
- &update_effpause;
- &refresh(0);
- $dont_refresh_first_time = 0;
- if ($dmpause) {
- if (!--$dmcount) {
- &dmrefresh(0);
- $dmcount = $dmpause;
- }
- }
- # service events on the streaming socket, if
- # we have one.
- $snooze = ($effpause || 0+$pause || 60);
- $wake = time() + $snooze;
- if (!$bufferpid) {
- sleep $snooze;
- } else {
- my $read_failure = 0;
- SLEEP_AGAIN: for(;;) {
- $nfound = select($rout = $rin,
- undef, undef, $snooze);
- if ($nfound &&
- vec($rout, fileno(STBUF), 1)==1) {
- my $buf = '';
- my $rbuf = '';
- my $len;
-
- sysread(STBUF, $buf, 1);
- if (!length($buf)) {
- $read_failure++;
- # a stuck ready FH says
- # our buffer is dead;
- # see MONITOR: below.
- if ($read_failure>100){
-print $stdout "*** unrecoverable failure of buffer process, aborting\n";
- exit;
- }
- next SLEEP_AGAIN;
- }
- $read_failure = 0;
- if ($buf !~ /^[0-9a-fA-F]+$/) {
- print $stdout
- "-- warning: bogus character(s) ".unpack("H*", $buf)."\n"
- if ($superverbose);
- next SLEEP_AGAIN;
- }
- while (length($buf) < 8) {
- # don't read 8 -- read 1. that means we can
- # skip trailing garbage without a window.
- sysread(STBUF,$rbuf,1);
- if ($rbuf =~ /[0-9a-fA-F]/) {
- $buf .= $rbuf;
- } else {
- print $stdout
- "-- warning: bogus character(s) ".unpack("H*", $rbuf)."\n"
- if ($superverbose);
- $buf = ''
- if(length($rbuf));
- }
- }
- print $stdout "-- length packet: $buf\n"
- if ($superverbose);
- $len = hex($buf);
- $buf = '';
- while (length($buf) < $len) {
- sysread(STBUF, $rbuf,
- ($len-length($buf)));
- $buf .= $rbuf;
- }
- &streamevents(
- &parsejson($buf) );
- $snooze = $wake - time();
- next SLEEP_AGAIN if
- ($snooze > 0);
- }
- last SLEEP_AGAIN;
- }
- }
- }
- }
- die("uncaught fork() exception\n");
-}
-
-#### INTERACTIVE MODE and CONSOLE STARTUP ####
-
-unless ($simplestart) {
- print <<"EOF";
-
-###################################################### +oo=========oo+
- ${EM}TTYtter ${TTYtter_VERSION}.${padded_patch_version} (c)2012 cameron kaiser${OFF} @ @
-EOF
- $e = <<'EOF';
- ${EM}all rights reserved.${OFF} +oo= =====oo+
- ${EM}http://www.floodgap.com/software/ttytter/${OFF} ${GREEN}a==:${OFF} ooo
- ${GREEN}.++o++.${OFF} ${GREEN}..o**O${OFF}
- freeware under the floodgap free software license. ${GREEN}+++${OFF} :O${GREEN}:::::${OFF}
- http://www.floodgap.com/software/ffsl/ ${GREEN}+**O++${OFF} # ${GREEN}:ooa${OFF}
- #+$$AB=.
- ${EM}tweet me: http://twitter.com/ttytter${OFF} #;;${YELLOW}ooo${OFF};;
- ${EM}tell me: ckaiser@floodgap.com${OFF} #+a;+++;O
-###################################################### ,$B.${RED}*o***${OFF} O$,
-# a=o${RED}$*O*O*$${OFF}o=a
-# when ready, hit RETURN/ENTER for a prompt. @${RED}$$$$$${OFF}@
-# type /help for commands or /quit to quit. @${RED}o${OFF}@o@${RED}o${OFF}@
-# starting background monitoring process. @=@ @=@
-#
-EOF
- $e =~ s/\$\{([A-Z]+)\}/${$1}/eg; print $stdout $e;
-} else {
- print <<"EOF";
-TTYtter ${TTYtter_VERSION}.${padded_patch_version} (c)2012 cameron kaiser
-all rights reserved. freeware under the floodgap free software license.
-http://www.floodgap.com/software/ffsl/
-
-tweet me: http://twitter.com/ttytter * tell me: ckaiser\@floodgap.com
-type /help for commands or /quit to quit.
-starting background monitoring process.
-
-EOF
-}
-if ($superverbose) {
- print $stdout "-- OMGSUPERVERBOSITYSPAM enabled.\n\n";
-} else {
- print $stdout "-- verbosity enabled.\n\n" if ($verbose);
-}
-sleep 3 unless ($silent);
-
-# these three functions are outside of the usual API assertions for clarity.
-# they represent the main loop, which by default is the interactive console.
-# the main loop can be redefined.
-
-sub defaultprompt {
- my $rv = ($noprompt) ? "" : "TTYtter> ";
- my $rvl = ($noprompt) ? 0 : 9;
- return ($rv, $rvl) if (shift);
- $wrapseq = 0;
- print $stdout "${CCprompt}$rv${OFF}" unless ($termrl);
-}
-sub defaultaddaction { return 0; }
-sub defaultmain {
- if (length($runcommand)) {
- &prinput($runcommand);
- &sync_n_quit;
- }
- @history = ();
- print C "rsga---------------\n";
- $dont_use_counter = $nocounter;
- eval '$termrl->hook_no_counter';
- $tco_sub = sub { return &main::fastturntotco(shift); };
- eval '$termrl->hook_no_tco';
- if ($termrl) {
- while(defined ($_ = $termrl->readline((&$prompt(1))[0]))) {
- kill $SIGUSR1, $child; # suppress output
- $rv = &prinput($_);
- kill $SIGUSR2, $child; # resume output
- last if ($rv < 0);
- &sync_console unless (!$rv || !$synch);
- if ($dont_use_counter ne $nocounter) {
- # only if we have to -- this is expensive
- $dont_use_counter = $nocounter;
- eval '$termrl->hook_no_counter'
- }
- }
- } else {
- &$prompt;
- while(<>) { #not stdin so we can read from script files
- kill $SIGUSR1, $child; # suppress output
- $rv = &prinput(&uforcemulti($_));
- kill $SIGUSR2, $child; # resume output
- last if ($rv < 0);
- &sync_console unless (!$rv || !$synch);
- &$prompt;
- }
- &sync_n_quit if ($script);
- }
-}
-
-# SIGPIPE in particular must be trapped in case someone kills the background
-# or, in streaming mode, buffer processes. we can't recover from that.
-# the streamer MUST have been initialized before we start these signal
-# handlers, or the streamer will try to run them too. eeek!
-#
-# DO NOT trap SIGCHLD: we generate child processes that die normally.
-&sigify(\&end_me, qw(PIPE INT));
-&sigify(\&repaint, qw(USR1 PWR XCPU));
-sub sigify {
- # this routine abstracts setting signals to a subroutine reference.
- # check and see if we have to use POSIX.pm (Perl 5.14+) or we can
- # still use $SIG for proper signalling. We prefer the latter, but
- # must support the former.
- my $subref = shift;
- my $k;
-
- if ($signals_use_posix) {
- my @w;
- my $sigaction = POSIX::SigAction->new($subref);
- while ($k = shift) {
- my $e = &posix_signal_of($k);
- # some signals may not exist on all systems.
- next if (!(0+$e));
- POSIX::sigaction($e, $sigaction)
- || die("sigaction failure: $! $@\n");
- }
- } else {
- while ($k = shift) { $SIG{$k} = $subref; }
- }
-}
-sub posix_signal_of {
- die("never call posix_signal_of if signals_use_posix is false\n")
- if (!$signals_use_posix);
-
- # this assumes that POSIX::SIG* returns a scalar int value.
- # not all signals exist on all systems. this ensures zeroes are
- # returned for locally bogus ones.
- return 0+(eval("return POSIX::SIG".shift));
-}
-
-sub send_repaint {
- unless ($wrapseq){
- return;
- }
- $wrapseq = 0;
- return if ($daemon);
- if ($child) {
- # we are the parent, call our repaint
- &repaint;
- } else {
- # we are not the parent, call the parent to repaint itself
- kill $SIGUSR1, $parent; # send SIGUSR1
- }
-}
-sub repaint {
- # try to speed this up, since we do it a lot.
- $wrapseq = 0;
- return &$repaintcache if ($repaintcache) ;
-
- # cache our repaint function (no-op or redisplay)
- $repaintcache = sub { ; }; # no-op
- return unless ($termrl &&
- ($termrl->Features()->{'canRepaint'} || $readlinerepaint));
- return if ($daemon);
- $termrl->redisplay; $repaintcache = sub { $termrl->redisplay; };
-}
-sub send_removereadline {
- # this just stubs into its own removereadline
- return &$removereadlinecache if ($removereadlinecache);
-
- $removereadlinecache = sub { ; };
- return unless ($termrl && $termrl->Features()->{'canRemoveReadline'});
- return if ($daemon);
- $termrl->removereadline;
- $removereadlinecache = sub { $termrl->removereadline; };
-}
-
-# start the background process
-# this has to be last or the background process can't see the full API
-if ($child = open(C, "|-")) {
- close(P);
- binmode(C, ":utf8") unless ($seven);
-} else {
- close(W);
- goto MONITOR;
-}
-eval'$termrl->hook_background_control' if ($termrl);
-select(C); $|++; select($stdout);
-
-# handshake for synchronicity mode, if we want it.
-if ($synch) {
- # we will get two replies for this.
- print C "synm---------------\n";
- &thump;
- # the second will be cleared by the console
-}
-
-# wait for background to become ready
-sleep 1 while (!$background_is_ready);
-
-# start the
-&$main;
-# loop until we quit and then we'll
-&sync_n_quit if ($script);
-# else
-exit;
-
-#### command processor ####
-
-sub prinput {
- my $i;
- local($_) = shift; # bleh
-
- # validate this string if we are in UTF-8 mode
- unless ($seven) {
- $probe = $_;
- &$utf8_encode($probe);
- die("utf8 doesn't work right in this perl. run with -seven.\n")
- if (&ulength($probe) < length($_));
- # should be at least as big
- if ($probe =~ /($badutf8)/) {
-print $stdout "*** invalid UTF-8: partial delete of a wide character?\n";
- print $stdout "*** ignoring this string\n";
- return 0;
- }
- }
-
- $in_reply_to = 0;
- chomp;
- $_ = &$precommand($_);
- s/^\s+//;
- s/\s+$//;
- my $cfc = 0;
- $cfc++ while (s/\033\[[0-9]?[ABCD]// || s/.[\177]// || s/.[\010]//
- || s/[\000-\037\177]//);
- if ($cfc) {
- $history[0] = $_;
- print $stdout "*** filtered control characters; now \"$_\"\n";
- print $stdout "*** use %% for truncated version, or append to %%.\n";
- return 0;
- }
-
- if (/^$/) {
- return 1;
- }
-
- if (!$slowpost && !$verify && # we assume you know what you're doing!
- ($_ eq 'h' || $_ eq 'help' || $_ eq 'quit' || $_ eq 'q' ||
- /^TTYtter>/ || $_ eq 'ls' || $_ eq '?' ||
- m#^help /# || $_ eq 'exit')) {
-
- &add_history($_);
- unless ($_ eq 'exit' || /^TTYtter>/ || $_ eq 'ls') {
- print $stdout "*** did you mean /$_ ?\n";
- print $stdout
- "*** to send this as a command, type /%%\n";
- } else {
- print $stdout
- "*** did you really mean to tweet \"$_\"?\n";
- }
- print $stdout "*** to tweet it anyway, type %%\n";
- return 0;
- }
-
- if (/^\%(\%|-\d+):p$/) {
- my $x = $1;
- if ($x eq '%') {
- print $stdout "=> \"$history[0]\"\n";
- } else {
- $x += 0;
- if (!$x || $x < -(scalar(@history))) {
- print $stdout "*** illegal index\n";
- } else {
- print $stdout "=> \"$history[-($x + 1)]\"\n";
- }
- }
- return 0;
- }
-
- # handle history substitution (including /%%, %%--, %%*, etc.)
- $i = 0; # flag
-
- if (/^\%(\%|-\d+)(--|-\d+|\*)?/) {
- ($i, $proband, $r, $s) = &sub_helper($1, $2, $_);
- return 0 if (!$i);
-
- $s = quotemeta($s);
- s/^\%${r}${s}/$proband/;
- }
- if (/[^\\]\%(\%|-\d+)(--|-\d+|\*)?$/) {
- ($i, $proband, $r, $s) = &sub_helper($1, $2, $_);
- return 0 if (!$i);
-
- $s = quotemeta($s);
- s/\%${r}${s}$/$proband/;
- }
- # handle variables second, in case they got in history somehow ...
- $i = 1 if (s/^\%URL\%/$urlshort/ || s/\%URL\%$/$urlshort/);
- $i = 1 if (s/^\%RT\%/$retweet/ || s/\%RT\%$/$retweet/);
-
- # and escaped history
- s/^\\\%/%/;
-
- if ($i) {
- print $stdout "(expanded to \"$_\")\n" ;
- $in_reply_to = $expected_tweet_ref->{'id_str'} || 0
- if (defined $expected_tweet_ref &&
- ref($expected_tweet_ref) eq 'HASH');
- } else {
- $expected_tweet_ref = undef;
- }
-
- return 0 unless length; # actually possible to happen
- # with control char filters and history.
-
- &add_history($_);
- $shadow_history = $_;
-
- # handle history display
- if ($_ eq '/history' || $_ eq '/h') {
- for ($i = scalar(@history); $i >= 1; $i--) {
- print $stdout "\t$i\t$history[($i-1)]\n";
- }
- return 0;
- }
-
- my $slash_first = ($_ =~ m#^/#);
-
- return -1 if ($_ eq '/quit' || $_ eq '/q' || $_ eq '/bye' ||
- $_ eq '/exit');
-
- return 0 if (scalar(&$addaction($_)));
-
- # add commands here
-
- # dumper
- if (m#^/du(mp)? ([zZ]?[a-zA-Z]?[0-9]+)$#) {
- my $code = lc($2);
- unless ($code =~ /^d[0-9][0-9]+$/) { # this is a DM.
- my $tweet = &get_tweet($code);
- my $k;
- my $sn;
- my $id;
- my @superfields = (
- [ "user", "screen_name" ], # must always be first
- [ "retweeted_status", "id_str" ],
- [ "user", "geo_enabled" ],
- [ "place", "id" ],
- [ "place", "country_code" ],
- [ "place", "full_name" ],
- [ "place", "place_type" ],
- [ "tag", "type" ],
- [ "tag", "payload" ],
- );
- my $superfield;
-
- if (!defined($tweet)) {
- print $stdout "-- no such tweet (yet?): $code\n";
- return 0;
- }
-
- foreach $superfield (@superfields) {
- my $sfn = join('->', @{ $superfield });
- my $sfk = "{'" . join("'}->{'", @{ $superfield }) .
- "'}";
- my $sfv;
- eval "\$sfv = &descape(\$tweet->$sfk);";
- print $stdout
- substr("$sfn ", 0, 25).
- " $sfv\n";
- $sn = $sfv if (!length($sn) && length($sfv));
- }
- # geo is special
- print $stdout "geo->coordinates (" .
- join(', ', @{ $tweet->{'geo'}->{'coordinates'} })
- . ")\n";
- foreach $k (sort keys %{ $tweet }) {
- next if (ref($tweet->{$k}));
- print $stdout
- substr("$k ", 0, 25) .
- " " . &descape($tweet->{$k}) . "\n";
- }
- # include a URL to the tweet per @augmentedfourth
- $urlshort =
- "${http_proto}://twitter.com/$sn/statuses/$tweet->{'id_str'}";
- print $stdout
- "-- %URL% is now $urlshort (/short to shorten)\n";
- return 0;
- } # if dxxxx, fall through to the below.
- }
-
- if (m#^/du(mp)? ([dD][a-zA-Z]?[0-9]+)$#) {
- my $code = lc($2);
- my $dm = &get_dm($code);
- my $k;
- my $sn;
- my $id;
- my @superfields = (
- [ "sender", "screen_name" ], # must always be first
- );
-
- if (!defined($dm)) {
- print $stdout "-- no such DM (yet?): $code\n";
- return 0;
- }
-
- foreach $superfield (@superfields) {
- my $sfn = join('->', @{ $superfield });
- my $sfk = "{'" . join("'}->{'", @{ $superfield }) .
- "'}";
- my $sfv;
- eval "\$sfv = &descape(\$dm->$sfk);";
- print $stdout
- substr("$sfn ", 0, 25).
- " $sfv\n";
- $sn = $sfv if (!length($sn) && length($sfv));
- }
-
- foreach $k (sort keys %{ $dm }) {
- next if (ref($dm->{$k}));
- print $stdout
- substr("$k ", 0, 25) .
- " " . &descape($dm->{$k}) . "\n";
- }
- return 0;
- }
-
- # evaluator
- if (m#^/ev(al)? (.+)$#) {
- $k = eval $2;
- print $stdout "==> ";
- print $streamout "$k $@\n";
- return 0;
- }
-
- # version check
- if (m#^/v(ersion)?check$# || m#^/u(pdate)?check$#) {
- print $stdout &updatecheck(1);
- return 0;
- }
-
- # url shortener routine
- if (($_ eq '/sh' || $_ eq '/short') && length($urlshort)) {
- $_ = "/short $urlshort";
- print $stdout "*** assuming you meant %URL%: $_\n";
- # and fall through to ...
- }
- if (m#^/sh(ort)? (https?|gopher)(://[^ ]+)#) {
- my $url = $2 . $3;
- my $answer = (&urlshorten($url) || 'FAILED -- %% to retry');
- print $stdout "*** shortened to: ";
- print $streamout ($answer . "\n");
- return 0;
- }
-
- # getter for internal value settings
- if (/^\/r(ate)?l(imit)?$/) {
- $_ = '/print rate_limit_rate';
- # and fall through to ...
- }
-
- if ($_ eq '/p' || $_ eq '/print') {
- foreach $key (sort keys %opts_can_set) {
- print $stdout "*** $key => $$key\n"
- if (!$opts_secret{$key});
- }
- return 0;
- }
- if (/^\/p(rint)?\s+([^ ]+)/) {
- my $key = $2;
- if ($valid{$key} ||
- $key eq 'effpause' ||
- $key eq 'rate_limit_rate' ||
- $key eq 'rate_limit_left') {
- my $value = &getvariable($key);
- print $stdout "*** ";
- print $stdout "(read-only value) "
- if (!$opts_can_set{$key});
- print $stdout "$key => $value\n";
-
- # I don't see a need for these in &getvariable, so they are
- # not currently supported. whine if you disagree.
-
- } elsif ($key eq 'tabcomp') {
- if ($termrl) {
- &generate_otabcomp;
- } else {
- print $stdout "*** readline isn't on\n";
- }
- } elsif ($key eq 'ntabcomp') { # sigh
- if ($termrl) {
- print $stdout "*** new TAB-comp entries: ";
- $did_print = 0;
- foreach(keys %readline_completion) {
- next if ($original_readline{$_});
- $did_print = 1;
- print $stdout "$_ ";
- }
- print $stdout "(none)" if (!$did_print);
- print $stdout "\n";
- } else {
- print $stdout "*** readline isn't on\n";
- }
-
- } else {
- print "*** not a valid option or setting: $key\n";
- }
- return 0;
- }
- if ($_ eq '/verbose' || $_ eq '/ve') {
- $verbose ^= 1;
- $_ = "/set verbose $verbose";
- print $stdout "-- verbosity.\n" if ($verbose);
- # and fall through to set
- }
-
- # search api integration (originally based on @kellyterryjones',
- # @vielmetti's and @br3nda's patches)
- if (/^\/se(arch)?\s+(\+\d+\s+)?(.+)\s*$/) {
- my $countmaybe = $2;
- my $kw = $3;
- $countmaybe =~ s/[^\d]//g if (length($countmaybe));
- $countmaybe += 0;
- $countmaybe ||= $searchhits;
- $kw = &url_oauth_sub($kw);
- $kw = "q=$kw" if ($kw !~ /^q=/);
-
- my $r = &grabjson("$queryurl?$kw", 0, 0, $countmaybe, {
- "type" => "search",
- "payload" => $k
- }, 1);
- if (defined($r) && ref($r) eq 'ARRAY' && scalar(@{ $r })) {
- &dt_tdisplay($r, 'search');
- } else {
- print $stdout "-- sorry, no results were found.\n";
- }
- &$conclude;
- return 0;
- }
- if ($_ eq '/notrack') { # special case
- print $stdout "*** all tracking keywords cancelled\n";
- $track = '';
- &setvariable('track', $track, 1);
- return 0;
- }
- if (s/^\/troff\s+// && s/\s*// && length) {
- # remove it from array, regenerate $track, call tracktags_makearray
- # and then sync
- my $k;
- my $l = '';
- my $q = 0;
- my %w;
- $_ = lc($_);
- my (@ptags) = split(/\s+/, $_);
-
- # filter duplicates and merge quoted strings (again)
- # but this time we're building up a hash for fast searches
- foreach $k (@ptags) {
- if ($q && $k =~ /"$/) { # this has to be first
- $l .= " $k";
- $q = 0;
- } elsif ($k =~ /^"/ || $q) {
- $l .= (length($l)) ? " $k" : $k;
- $q = 1;
- next;
- } else {
- $l = $k;
- }
- next if ($w{$l}); # ignore silently here
- $w{$l} = 1;
- $l = '';
- }
- print $stdout "-- warning: syntax error, missing quote?\n"
- if ($q);
-
- # now filter out of @tracktags
- @ptags = ();
- foreach $k (@tracktags) {
- push (@ptags, $k) unless ($w{$k});
- }
- unless (scalar(@ptags) < scalar(@tracktags)) {
- print $stdout "-- sorry, no track terms matched.\n";
- print $stdout (length($track) ?
- "-- you are tracking: $track\n" :
- "-- (maybe because you're not tracking anything?)\n");
- return 0;
- }
- print $stdout "*** ok, filtered @{[ keys(%w) ]}\n";
- $track = join(' ', @ptags);
- &setvariable('track', $track, 1);
- return 0;
- }
-
- if (s#^/tre(nds)?\s*##) {
- my $t = undef;
- my $wwoeid = (length) ? $_ : $woeid;
- $wwoeid ||= "1";
- my $r = &grabjson("${wtrendurl}?id=${wwoeid}",
- 0, 0, 0, undef, 1);
- my $fr = ($wwoeid && $wwoeid ne '1') ?
- " FOR WOEID $wwoeid" : ' GLOBALLY';
-
- if (defined($r) && ref ($r) eq 'ARRAY') {
- $t = $r->[0]->{'trends'};
- }
- if (defined($t) && ref($t) eq 'ARRAY') {
- my $i;
- my $j;
-
- print $stdout "${EM}<<< TRENDING TOPICS${fr} >>>${OFF}\n";
- foreach $j (@{ $t }) {
- my $k = &descape($j->{'name'});
- my $l = ($k =~ /\sOR\s/) ? $k :
- ($k =~ /^"/) ? $k :
- ('"' . $k . '"');
- print $streamout "/search $l\n";
- $k =~ s/\sOR\s/ /g;
- $k = '"' . $k . '"' if ($k =~ /\s/
- && $k !~ /^"/);
- print $streamout "/tron $k\n";
- }
- print $stdout "${EM}<<< TRENDING TOPICS >>>${OFF}\n";
- } else {
- print $stdout
-"-- sorry, trends not available for WOEID $wwoeid.\n";
- }
- return 0;
- }
-
- # woeid finder based on lat/long
- if ($_ eq '/woeids') {
- my $max = 10;
- if (!$lat && !$long) {
- print $stdout
- "-- set your location with lat/long first.\n";
- return 0;
- }
- my $r = &grabjson("$atrendurl?lat=$lat&long=$long", 0, 0, 0,
- undef, 1);
- if (defined($r) && ref($r) eq 'ARRAY') {
- my $i;
- foreach $i (@{ $r }) {
- my $woeid = &descape($i->{'woeid'});
- my $nm = &descape($i->{'name'}) . ' (' .
- &descape($i->{'countryCode'}) .')';
- print $streamout "$nm\n/set woeid $woeid\n";
- last unless ($max--);
- }
- } else {
- print $stdout
-"-- sorry, couldn't get a supported WOEID for your location.\n";
- }
- return 0;
- }
-
- 1 if (s/^\/#([^\s]+)/\/tron #\1/);
- # /# command falls through to tron
- if (s/^\/tron\s+// && s/\s*$// && length) {
- $_ = lc($_);
- $track .= " " if (length($track));
- $_ = "/set track ${track}$_";
- # fall through to set
- }
- if (/^\/track ([^ ]+)/) {
- s#^/#/set #;
- # and fall through to set
- }
-
- # /listoff
- if (s/^\/list?off\s+// && s/\s*$// && length) {
- if (/,/ || /\s+/) {
- print $stdout "-- one list at a time please\n";
- return 0;
- }
- if (!scalar(@listlist)) {
- print $stdout
- "-- ok! that was easy! (you don't have any lists in your timeline)\n";
- return 0;
- }
- my $w;
- my $newlists = '';
- my $didfilter = 0;
- foreach $w (@listlist) {
- my $x = join('/', @{ $w });
- if ($x eq $_ || "$whoami$_" eq $x ||
- "$whoami/$_" eq $x) {
- print $stdout "*** ok, filtered $x\n";
- $didfilter = 1;
- } else {
- $newlists .= (length($newlists)) ? ",$x"
- : $x;
- }
- }
- if ($didfilter) {
- &setvariable('lists', $newlists, 1);
- } else {
- print $stdout "*** hmm, no such list? current value:\n";
- print $stdout "*** lists => ",
- &getvariable('lists'), "\n";
- }
- return 0;
- }
-
- # /liston
- if (s/^\/list?on\s+// && s/\s*$// && length) {
- if (/,/ || /\s+/) {
- print $stdout "-- one list at a time please\n";
- return 0;
- }
- my $uname;
- my $lname;
- if (m#/#) {
- ($uname, $lname) = split(m#/#, $_, 2);
- } else {
- $lname = $_;
- $uname = '';
- }
- if (!length($uname) && $anonymous) {
- print $stdout
-"-- you must specify a username for a list when anonymous.\n";
- return 0;
- }
- $uname ||= $whoami;
-
- # check the list validity
- my $my_json_ref = &grabjson(
- "${statusliurl}?owner_screen_name=${uname}&slug=${lname}",
- 0, 0, 0, undef, 1);
- if (!$my_json_ref || ref($my_json_ref) ne 'ARRAY') {
- print $stdout
- "*** list $uname/$lname seems bogus; not added\n";
- return 0;
- }
-
- $_ = "/add lists $uname/$lname";
- # fall through to add
- }
- if (s/^\/a(uto)?lists?\s+// && s/\s*$// && length) {
- s/\s+/,/g if (!/,/);
- print $stdout
- "--- warning: lists aren't checked en masse; make sure they exist\n";
- $_ = "/set lists $_";
- # and fall through to set
- }
-
- # setter for internal value settings
- # shortcut for boolean settings
- if (/^\/s(et)? ([^ ]+)\s*$/) {
- my $key = $2;
- $_ = "/set $key 1"
- if($opts_boolean{$key} && $opts_can_set{$key});
- # fall through to three argument version
- }
- if (/^\/uns(et)? ([^ ]+)\s*$/) {
- my $key = $2;
- if ($opts_can_set{$key} && $opts_boolean{$key}) {
- &setvariable($key, 0, 1);
- return 0;
- }
- &setvariable($key, undef, 1);
- return 0;
- }
- # stubs out to set variable
- if (/^\/s(et)? ([^ ]+) (.+)\s*$/) {
- my $key = $2;
- my $value = $3;
- &setvariable($key, $value, 1);
- return 0;
- }
- # append to a variable (if not boolean)
- if (/^\/ad(d)? ([^ ]+) (.+)\s*$/) {
- my $key = $2;
- my $value = $3;
- if ($opts_boolean{$key}) {
- print $stdout
- "*** why are you appending to a boolean?\n";
- return 0;
- }
- if (length(&getvariable($key))) {
- $value = " $value" if ($opts_space_delimit{$key});
- $value = ",$value" if ($opts_comma_delimit{$key});
- }
- &setvariable($key, &getvariable($key).$value, 1);
- return 0;
- }
- # delete from a variable (if not boolean)
- if (/^\/del ([^ ]+) (.+)\s*$/) {
- my $key = $1;
- my $value = $2;
- my $old;
- if ($opts_boolean{$key}) {
- print $stdout
- "*** why are you deleting from a boolean?\n";
- return 0;
- }
- if (!length($old = &getvariable($key))) {
- print $stdout "*** $key is already empty\n";
- return 0;
- }
- my $del =
- ($opts_space_delimit{$key}) ? '\s+' :
- ($opts_comma_delimit{$key}) ? '\s*,\s*' :
- undef;
- if (!defined($del)) {
- # simple substitution
- 1 while ($old =~ s/$value//g);
- } else {
- 1 while ($old =~ s/$del$value($del)/\1/g);
- 1 while ($old =~ s/^$value$del//);
- 1 while ($old =~ s/$del$value//);
- }
- &setvariable($key, $old, 1);
- return 0;
- }
- # I thought about implementing a /pdel but besides being ugly
- # I don't think most people will push a truncated setting. tell me
- # if I'm wrong.
-
- # stackable settings
- if (/^\/pu(sh)? ([^ ]+)\s*$/) {
- my $key = $2;
- if ($opts_can_set{$key}) {
- if ($opts_boolean{$key}) {
- $_ = "/push $key 1";
- # fall through to three argument version
- } else {
- if (!$opts_can_set{$key}) {
- print $stdout
- "*** setting is not stackable: $key\n";
- return 0;
- }
- my $old = &getvariable($key);
- push(@{ $push_stack{$key} }, $old);
- print $stdout
- "--- saved on stack for $key: $old\n";
- return 0;
- }
- }
- }
-
- # common code for set and append
- if (/^\/(pu|push|pad|padd) ([^ ]+) (.+)\s*$/) {
- my $comm = $1;
- my $key = $2;
- my $value = $3;
- $comm = ($comm =~ /^pu/) ? "push" : "padd";
- if ($opts_boolean{$key} && $comm eq 'padd') {
- print $stdout
- "*** why are you appending to a boolean?\n";
- return 0;
- }
- if (!$opts_can_set{$key}) {
- print $stdout
- "*** setting is not stackable: $key\n";
- return 0;
- }
- my $old = &getvariable($key);
- $old += 0 if ($opts_boolean{$key});
- push(@{ $push_stack{$key} }, $old);
- print $stdout "--- saved on stack for $key: $old\n";
- if ($comm eq 'padd' && length($old)) {
- $value = " $value" if ($opts_space_delimit{$key});
- $value = ",$value" if ($opts_comma_delimit{$key});
- $old .= $value;
- } else {
- $old = $value;
- }
- &setvariable($key, $old, 1);
- return 0;
- }
- # we assume that if the setting is in the push stack, it's valid
- if (/^\/pop ([^ ]+)\s*$/) {
- my $key = $1;
- if (!scalar(@{ $push_stack{$key} })) {
- print $stdout
- "*** setting is not stacked: $key\n";
- return 0;
- }
- &setvariable($key, pop(@{ $push_stack{$key} }), 1);
- return 0;
- }
-
- # shell escape
- if (s/^\/\!// && s/\s*$// && length) {
- system("$_");
- $x = $? >> 8;
- print $stdout "*** exited with $x\n" if ($x);
- return 0;
- }
-
- if ($_ eq '/help' || $_ eq '/?') {
- print <<'EOF';
-
- *** BASIC COMMANDS: :a$AAOOOOOOOOOOOOOOOOOAA$a, ==================
- +@A:. .:B@+ ANYTHING WITHOUT
- /refresh =@B HELP!!! HELP!!! B@= A LEADING / IS
- grabs the newest :a$Ao oA$a, SENT AS A TWEET!
- tweets right ;AAA$a; :a$AAAAAAAAAAA; ==================
- away (or tells :AOaaao:, .:oA*:. JUST TYPE TO TALK!
- you if there .;=$$$OBO***+ .+aaaa$:
- is nothing new) :*; :***O@Aaaa*o, ============
- by thumping .+++++: o#o REMEMBER!!
- the background :OOOOOOA*:::, =@o ,:::::. ============
- process. .+++++++++: =@*.....=a$OOOB#; MANY COMMANDS, AND
- =@OoO@BAAA#@$o, ALL TWEETS ARE
- /again =@o .+aaaaa: --ASYNCHRONOUS--
- displays most recent =@Aaaaaaaaaa*o*a;, and might not always
- tweets, both old and =@$++=++++++:,;+aA: respond
- new. ,+$@*.=O+ ...oO; oAo+. immediately!
- ,+o$OO=.+aA#####Oa;.*OO$o+.
- /dm and /dmagain for DMs. +Ba::;oaa*$Aa=aA$*aa=;::$B:
- ,===O@BOOOOOOOOO#@$===,
- /replies o@BOOOOOOOOO#@+ ==================
- shows replies and mentions. o@BOB@B$B@BO#@+ USE + FOR A COUNT:
- o@*.a@o a@o.$@+ /re +30 => last 30 replies
- /quit resumes your boring life. o@B$B@o a@A$#@+ ==========================
-EOF
- &linein("PRESS RETURN/ENTER>");
- print <<"EOF";
-
-+- MORE COMMANDS -+ -=-=- USER STUFF -=-=-
-| | /whois username displays info about username
-| See the TTYtter | /again username views their most recent tweets
-| home page for | /wagain username combines them all
-| complete list | /follow username follow a username
-| | /leave username stop following a username
-+-----------------+ /dm username message send a username a DM
-+--- TWEET AND DM SELECTION -------------------------------------------------+
-| all DMs and tweets have menu codes (letters + number, d for DMs). example: |
-| a5> <ttytter> Send me Dr Pepper http://www.floodgap.com/TTYtter |
-| [DM da0][ttytter/Sun Jan 32 1969] I think you are cute |
-| /reply a5 message replies to tweet a5 |
-| example: /reply a5 I also like Dr Pepper |
-| becomes \@ttytter I also like Dr Pepper (and is threaded) |
-| /thread a5 if a5 is part of a thread (the username |
-| has a \@) then show all posts up to that |
-| /url a5 opens all URLs in tweet a5 |
-| Mac OS X users, do first: /set urlopen open %U |
-| Dummy terminal users, try /set urlopen lynx -dump %U | more |
-| /delete a5 deletes tweet a5, if it's your tweet |
-| /rt a5 retweets tweet a5: RT \@tytter: Send me...|
-+-- Abbreviations: /re, /th, /url, /del --- menu codes wrap around at end ---+
-=====> /reply, /delete and /url work for direct message menu codes too! <=====
-EOF
- &linein("PRESS RETURN/ENTER>");
- print <<"EOF";
-
-
-
-Use /set to turn on options or set them at runtime. There is a BIG LIST!
-
->> EXAMPLE: WANT ANSI? /set ansi 1
- or use the -ansi command line option.
- WANT TO VERIFY YOUR TWEETS BEFORE POSTING? /set verify 1
- or use the -verify command line option.
-For more, like readline support, UTF-8, SSL, proxies, etc., see the docs.
-
-** READ THE COMPLETE DOCUMENTATION: http://www.floodgap.com/software/ttytter/
-
- TTYtter $TTYtter_VERSION is (c)2012 cameron kaiser + contributors.
- all rights reserved. this software is offered AS IS, with no guarantees. it
- is not endorsed by Obvious or the executives and developers of Twitter.
-
- *** subscribe to updates at http://twitter.com/ttytter
- or http://twitter.com/floodgap
- send your suggestions to me at ckaiser\@floodgap.com
- or http://twitter.com/doctorlinguist
-
-
-
-EOF
- return 0;
- }
- if ($_ eq '/ruler' || $_ eq '/ru') {
- my ($prompt, $prolen) = (&$prompt(1));
- $prolen = " " x $prolen;
- print $stdout <<"EOF";
-${prolen} 1 2 3 4 5 6 7 8 9 0 1 2 3 XX
-${prompt}1...5....0....5....0....5....0....5....0....5....0....5....0....5....0....5....0....5....0....5....0....5....0....5....0....5....0....5...XX
-EOF
- return 0;
- }
- if ($_ eq '/cls' || $_ eq '/clear') {
- if ($ansi) {
- print $stdout "${ESC}[H${ESC}[2J\n";
- } else {
- print $stdout ("\n" x ($ENV{'ROWS'} || 50));
- }
- return 0;
- }
- if ($_ eq '/refresh' || $_ eq '/thump' || $_ eq '/r') {
- print $stdout "-- /refresh in streaming mode is pretty impatient\n"
- if ($dostream);
- &thump;
- return 0;
- }
- if (m#^/a(gain)?(\s+\+\d+)?$#) { # the asynchronous form
- my $countmaybe = $2;
- $countmaybe =~ s/[^\d]//g if (length($countmaybe));
- $countmaybe += 0;
- if ($countmaybe > 999) {
- print $stdout "-- greedy bastard, try +fewer.\n";
- return 0;
- }
- $countmaybe = sprintf("%03i", $countmaybe);
- print $stdout "-- background request sent\n" unless ($synch);
-
- print C "reset${countmaybe}-----------\n";
- &sync_semaphore;
- return 0;
- }
-
- # this is for users -- list form is below
- if ($_ =~ m#^/(w)?a(gain)?\s+(\+\d+\s+)?([^\s/]+)$#) { #synchronous form
- my $mode = $1;
- my $uname = lc($4);
-
- my $countmaybe = $3;
- $countmaybe =~ s/[^\d]//g if (length($countmaybe));
- $countmaybe += 0;
-
- $uname =~ s/^\@//;
- $readline_completion{'@'.$uname}++ if ($termrl);
- print $stdout
- "-- synchronous /again command for $uname ($countmaybe)\n"
- if ($verbose);
- my $my_json_ref =
- &grabjson("${uurl}?screen_name=${uname}&include_rts=true",
- 0, 0, $countmaybe, undef, 1);
- &dt_tdisplay($my_json_ref, 'again');
- unless ($mode eq 'w' || $mode eq 'wf') {
- return 0;
- } # else fallthrough
- }
- if ($_ =~ m#^/w(hois|a|again)?\s+(\+\d+\s+)?\@?([^\s]+)#) {
- my $uname = lc($3);
- $uname =~ s/^\@//;
- $readline_completion{'@'.$uname}++ if ($termrl);
- print $stdout "-- synchronous /whois command for $uname\n"
- if ($verbose);
- my $my_json_ref =
- &grabjson("${wurl}?screen_name=${uname}", 0, 0, 0, undef, 1);
-
- if (defined($my_json_ref) && ref($my_json_ref) eq 'HASH' &&
- length($my_json_ref->{'screen_name'})) {
- my $sturl = undef;
- my $purl =
- &descape($my_json_ref->{'profile_image_url'});
- if ($avatar && length($purl) && $purl !~
-m#^http://[^.]+\.(twimg\.com|twitter\.com).+/images/default_profile_\d+_normal.png#) {
- my $exec = $avatar;
- my $fext;
- ($purl =~ /\.([a-z0-9A-Z]+)$/) &&
- ($fext = $1);
- if ($purl !~ /['\\]/) { # careful!
- $exec =~ s/\%U/'$purl'/g;
- $exec =~ s/\%N/$uname/g;
- $exec =~ s/\%E/$fext/g;
- print $stdout "\n";
- print $stdout "($exec)\n"
- if ($verbose);
- system($exec);
- }
- }
- print $streamout "\n";
- &userline($my_json_ref, $streamout);
- print $streamout &wwrap(
-"\"@{[ &strim(&descape($my_json_ref->{'description'})) ]}\"\n")
- if (length(&strim($my_json_ref->{'description'})));
- if (length($my_json_ref->{'url'})) {
- $sturl =
- $urlshort = &descape($my_json_ref->{'url'});
- $urlshort =~ s/^\s+//;
- $urlshort =~ s/\s+$//;
- print $streamout "${EM}URL:${OFF}\t\t$urlshort\n";
- }
- print $streamout &wwrap(
-"${EM}Location:${OFF}\t@{[ &descape($my_json_ref->{'location'}) ]}\n")
- if (length($my_json_ref->{'location'}));
- print $streamout <<"EOF";
-${EM}Picture:${OFF}\t@{[ &descape($my_json_ref->{'profile_image_url'}) ]}
-
-EOF
- unless ($anonymous || $whoami eq $uname) {
- my $g = &grabjson(
- "$frurl?source_screen_name=$whoami&target_screen_name=$uname", 0, 0, 0,
- undef, 1);
- print $streamout &wwrap(
- "${EM}Do you follow${OFF} this user? ... ${EM}$g->{'relationship'}->{'target'}->{'followed_by'}${OFF}\n")
- if (ref($g) eq 'HASH');
- my $g = &grabjson(
- "$frurl?source_screen_name=$uname&target_screen_name=$whoami", 0, 0, 0,
- undef, 1);
- print $streamout &wwrap(
-"${EM}Does this user follow${OFF} you? ... ${EM}$g->{'relationship'}->{'target'}->{'followed_by'}${OFF}\n")
- if (ref($g) eq 'HASH');
- print $streamout "\n";
- }
- print $stdout &wwrap(
- "-- %URL% is now $urlshort (/short shortens, /url opens)\n")
- if (defined($sturl));
- }
- return 0;
- }
-
- if (m#^/(df|doesfollow)\s+\@?([^\s]+)$#) {
- if ($anonymous) {
- print $stdout "-- who follows anonymous anyway?\n";
- return 0;
- }
- $_ = "/doesfollow $2 $whoami";
- print $stdout "*** assuming you meant: $_\n";
- # fall through to ...
- }
- if (m#^/(df|doesfollow)\s+\@?([^\s]+)\s+\@?([^\s]+)$#) {
- my $user_a = $2;
- my $user_b = $3;
- if ($user_a =~ m#/# || $user_b =~ m#/#) {
- print $stdout "--- sorry, this won't work on lists.\n";
- return 0;
- }
- my $g = &grabjson(
-"${frurl}?source_screen_name=${user_a}&target_screen_name=${user_b}", 0, 0, 0,
- undef, 1);
- if ($msg = &is_json_error($g)) {
- print $stdout <<"EOF";
-${MAGENTA}*** warning: server error message received
-*** "$ec"${OFF}
-EOF
- } elsif ($g->{'relationship'}->{'target'}) {
- print $stdout "--- does $user_a follow ${user_b}? => ";
- print $streamout "$g->{'relationship'}->{'target'}->{'followed_by'}\n"
- } else {
- print $stdout
-"-- sorry, bogus server response, try again later.\n";
- }
- return 0;
- }
-
- # this is dual-headed and supports both lists and regular followers.
- if(s#^/(frs|friends|fos|followers)(\s+\+\d+)?\s*##) {
- my $countmaybe = $2;
- my $mode = $1;
- my $arg = lc($_);
- my $lname = '';
- my $user = '';
- my $what = '';
- $arg =~ s/^@//;
- $who = $arg;
- ($who, $lname) = split(m#/#, $arg, 2) if (m#/#);
- if (length($lname) && !length($user) && $anonymous) {
- print $stdout
- "-- you must specify a username for a list when anonymous.\n";
- return 0;
- }
- $who ||= $whoami;
- if (!length($lname)) {
- $what = ($mode eq 'frs' || $mode eq 'friends')
- ? "friends" : "followers";
- $mode = ($mode eq 'frs' || $mode eq 'friends')
- ? $friendsurl : $followersurl;
- } else {
- $what = ($mode eq 'frs' || $mode eq 'friends')
- ? "friends/members" : "followers/subscribers";
- $mode = ($mode eq 'frs' || $mode eq 'friends')
- ? $getliurl : $getfliurl;
- $user = "&owner_screen_name=${who}&slug=${lname}";
- $who = "list $who/$lname";
- }
- $countmaybe =~ s/[^\d]//g if (length($countmaybe));
- $countmaybe += 0;
- $countmaybe ||= 20;
-
- # we use the undocumented count= support to, by default,
- # reduce the JSON parsing overhead. if we always had to take
- # all 100, we really eat it on parsing. the downside is that,
- # per @episod, the stuff we get is "less" fresh.
- my $countper = ($countmaybe < 100) ? $countmaybe : 100;
-
- if (!length($lname)) {
- # we need to get IDs, then call lookup. right now it's
- # limited to 5000 because that is the limit for API 1.1
- # without having to do pagination here too. sorry.
- if ($countmaybe >= 5000) {
- print $stdout
-"-- who do you think you are? Scoble? currently limited to 4999 or less\n";
- return 0;
- }
-
- # grab all the IDs
- my $ids_ref = &grabjson(
- "$mode?count=${countmaybe}&screen_name=${who}&stringify_ids=true",
- 0, 0, 0, undef, 1);
- return 0 if (!$ids_ref || ref($ids_ref) ne 'HASH' ||
- !$ids_ref->{'ids'});
- $ids_ref = $ids_ref->{'ids'};
- return 0 if (ref($ids_ref) ne 'ARRAY');
- my @ids = @{ $ids_ref };
- @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
- 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));
- $printed++;
- }
- print $stdout "-- sorry, no $what found for $who.\n"
- if (!$printed);
- return 0;
- }
-
- # lists
- # loop through using the cursor until desired number.
- my $cursor = -1; # initial value
- my $printed = 0;
- my $nofetch = 0;
- my $json_ref = undef;
- my @usarray = undef; shift(@usarray); # force underflow
-
- # this is a simpler version of the above.
- FABIO: while($countmaybe--) {
- if(!scalar(@usarray)) {
- last FABIO if ($nofetch);
- $json_ref = &grabjson(
- "${mode}?count=${countper}&cursor=${cursor}${user}",
- 0, 0, 0, undef, 1);
- @usarray = @{ $json_ref->{'users'} };
- last FABIO if (!scalar(@usarray));
- $cursor = $json_ref->{'next_cursor_str'} ||
- $json_ref->{'next_cursor'} || -1;
- $nofetch = ($cursor < 1) ? 1 : 0;
- }
- &$userhandle(shift(@usarray));
- $printed++;
- }
- print $stdout "-- sorry, no $what found for $who.\n"
- if (!$printed);
- return 0;
- }
-
- # threading
- if (m#^/th(read)?\s+(\+\d+\s+)?([zZ]?[a-zA-Z]?[0-9]+)$#) {
- my $countmaybe = $2;
- if (length($countmaybe)) {
- print $stdout
- "-- /thread does not (yet) support +count\n";
- return 0;
- }
- my $code = lc($3);
- my $tweet = &get_tweet($code);
- if (!defined($tweet)) {
- print $stdout "-- no such tweet (yet?): $code\n";
- return 0;
- }
- my $limit = 9;
- my $id = $tweet->{'retweeted_status'}->{'id_str'} ||
- $tweet->{'in_reply_to_status_id_str'};
- my $thread_ref = [ $tweet ];
- while ($id && $limit) {
- print $stdout "-- thread: fetching $id\n"
- if ($verbose);
- my $next = &grabjson("${idurl}?id=${id}", 0, 0, 0,
- undef, 1);
- $id = 0;
- $limit--;
- if (defined($next) && ref($next) eq 'HASH') {
- push(@{ $thread_ref },
- &fix_geo_api_data($next));
- $id = $next->{'retweeted_status'}->{'id_str'}
- || $next->{'in_reply_to_status_id_str'}
- || 0;
- }
- }
- &tdisplay($thread_ref, 'thread', 0, 1); # use the mini-menu
- return 0;
- }
-
- # pull out entities. this works for DMs and tweets.
- # btw: T.CO IS WACK.
- if (m#^/ent?(ities)? ([dDzZ]?[a-zA-Z]?[0-9]+)$#) {
- my $v;
- my $w;
- my $thing;
- my $genurl;
- my $code = lc($2);
- my $hash;
- if ($code !~ /[a-z]/) {
- # this is an optimization: we don't need to get
- # the old tweet since we're going to fetch it anyway.
- $hash = { "id_str" => $code };
- $thing = "tweet";
- $genurl = $idurl;
- } elsif ($code =~ /^d.[0-9]+$/) {
- $hash = &get_dm($code);
- $thing = "DM";
- $genurl = $dmidurl;
- } else {
- $hash = &get_tweet($code);
- $thing = "tweet";
- $genurl = $idurl;
- }
-
- if (!defined($hash)) {
- print $stdout "-- no such $thing (yet?): $code\n";
- return 0;
- }
-
- my $id = $hash->{'id_str'};
- $hash = &grabjson("${genurl}?id=${id}", 0, 0, 0, undef, 1);
- if (!defined($hash) || ref($hash) ne 'HASH') {
- print $stdout "-- failed to get entities from server, sorry\n";
- return 0;
- }
-
- # if a retweeted status, get the status.
- $hash = $hash->{'retweeted_status'}
- if (defined($hash->{'retweeted_status'}) &&
- ref($hash->{'retweeted_status'}) eq 'HASH');
-
- my $didprint = 0;
- # Twitter puts entities in multiple fields.
- foreach $w (qw(media urls)) {
- my $p = $hash->{'entities'}->{$w};
- next if (!defined($p) || ref($p) ne 'ARRAY');
- foreach $v (@{ $p }) {
- next if (!defined($v) || ref($v) ne 'HASH');
- next if (!length($v->{'url'}) ||
- (!length($v->{'expanded_url'}) &&
- !length($v->{'media_url'})));
- my $u1 = &descape($v->{'url'});
- my $u2 = &descape($v->{'expanded_url'});
- my $u3 = &descape($v->{'media_url'});
- my $u4 = &descape($v->{'media_url_https'});
- $u2 = $u4 || $u3 || $u2;
- print $stdout "$u1 => $u2\n";
- $urlshort = $u4 || $u3 || $u1;
- $didprint++;
- }
- }
- if ($didprint) {
- print $stdout &wwrap(
- "-- %URL% is now $urlshort (/url opens)\n");
- } else {
- print $stdout "-- no entities or URLs found\n";
- }
- return 0;
- }
-
- if (($_ eq '/url' || $_ eq '/open') && length($urlshort)) {
- $_ = "/url $urlshort";
- print $stdout "*** assuming you meant %URL%: $_\n";
- # and fall through to ...
- }
- if (m#^/(url|open)\s+(http|gopher|https|ftp)://.+# &&
- s#^/(url|open)\s+##) {
- &openurl($_);
- return 0;
- }
- if (m#^/(url|open) ([dDzZ]?[a-zA-Z]?[0-9]+)$#) {
- my $code = lc($2);
- my $tweet;
- my $genurl = undef;
- $urlshort = undef;
-
- if ($code =~ /^d/ && length($code) > 2) {
- $tweet = &get_dm($code); # USO!
- if (!defined($tweet)) {
- print $stdout
- "-- no such DM (yet?): $code\n";
- return 0;
- }
- $genurl = $dmidurl;
- } else {
- $tweet = &get_tweet($code);
- if (!defined($tweet)) {
- print $stdout
- "-- no such tweet (yet?): $code\n";
- return 0;
- }
- $genurl = $idurl;
- }
-
- # to be TOS-compliant, we must try entities first to use
- # t.co wrapped links. this is a tiny version of /entities.
- unless ($notco) {
- my $id = $tweet->{'retweeted_status'}->{'id_str'}
- || $tweet->{'id_str'};
- my $hash;
-
- # only fetch if we have to. if we already fetched
- # because we were given a direct id_str instead of a
- # menu code, then we already have the entities.
- if ($code !~ /^[0-9]+$/) {
- $hash = &grabjson("${genurl}?id=${id}",
- 0, 0, 0, undef, 1);
- } else {
- # MAKE MONEY FAST WITH OUR QUICK CACHE PLAN
- $hash = $tweet;
- }
- if (defined($hash) && ref($hash) eq 'HASH') {
- my $w;
- my $v;
- my $didprint = 0;
-
- # Twitter puts entities in multiple fields.
- foreach $w (qw(media urls)) {
- my $p = $hash->{'entities'}->{$w};
- next if (!defined($p) ||
- ref($p) ne 'ARRAY');
- foreach $v (@{ $p }) {
- next if (!defined($v) ||
- ref($v) ne 'HASH');
- next if (!length($v->{'url'}) ||
- (!length($v->{'expanded_url'}) &&
- !length($v->{'media_url'})));
- my $u1 = &descape($v->{'url'});
- &openurl($u1);
- $didprint++;
- }
- }
- print $stdout
- "-- sorry, couldn't find any URL.\n"
- if (!$didprint);
- return 0;
- }
- print $stdout
- "-- unable to use t.co URLs, using fallback\n";
- }
- # that failed, so fall back on the old method.
- my $text = &descape($tweet->{'text'});
- # findallurls
- while ($text
- =~ s#(h?ttp|h?ttps|ftp|gopher)://([a-zA-Z0-9_~/:%\-\+\.\=\&\?\#,]+)##){
-# sigh. I HATE YOU TINYARRO.WS
-#TODO
-# eventually we will have to put a punycode implementation into openurl
-# to handle things like Mac OS X's open which don't understand UTF-8 URLs.
-# when we do, uncomment this again
-# =~ s#(http|https|ftp|gopher)://([^'\\]+?)('|\\|\s|$)##) {
- my $url = $1 . "://$2";
- $url = "h$url" if ($url =~ /^ttps?:/);
- $url =~ s/[\.\?]$//;
- &openurl($url);
- }
- print $stdout "-- sorry, couldn't find any URL.\n"
- if (!defined($urlshort));
- return 0;
- }
-
-#TODO
- if (s/^\/(favourites|favorites|faves|favs|fl)(\s+\+\d+)?\s*//) {
- my $my_json_ref;
- my $countmaybe = $2;
- $countmaybe =~ s/[^\d]//g if (length($countmaybe));
- $countmaybe += 0;
-
- if (length) {
- $my_json_ref = &grabjson("${favsurl}?screen_name=$_",
- 0, 0, $countmaybe, undef, 1);
- } else {
- if ($anonymous) {
- print $stdout
- "-- sorry, you can't haz favourites if you're anonymous.\n";
- } else {
- print $stdout
- "-- synchronous /favourites user command\n"
- if ($verbose);
- $my_json_ref = &grabjson($favsurl, 0, 0,
- $countmaybe, undef, 1);
- }
- }
- if (defined($my_json_ref)
- && ref($my_json_ref) eq 'ARRAY') {
- if (scalar(@{ $my_json_ref })) {
- my $w = "-==- favourites " x 10;
- $w = $EM . substr($w, 0, $wrap || 79) . $OFF;
- print $stdout "$w\n";
- &tdisplay($my_json_ref, "favourites");
- print $stdout "$w\n";
- } else {
- print $stdout
- "-- no favourites found, boring impartiality concluded.\n";
- }
- }
- &$conclude;
- return 0;
- }
- if (
-m#^/(un)?f(rt|retweet|a|av|ave|avorite|avourite)? ([zZ]?[a-zA-Z]?[0-9]+)$#) {
- my $mode = $1;
- my $secondmode = $2;
- my $code = lc($3);
- $secondmode = ($secondmode eq 'retweet') ? 'rt' : $secondmode;
- if ($mode eq 'un' && $secondmode eq 'rt') {
- print $stdout
- "-- hmm. seems contradictory. no dice.\n";
- return 0;
- }
- my $tweet = &get_tweet($code);
- if (!defined($tweet)) {
- print $stdout "-- no such tweet (yet?): $code\n";
- return 0;
- }
- &cordfav($tweet->{'id_str'}, 1,
- (($mode eq 'un') ? $favdelurl : $favurl),
- &descape($tweet->{'text'}),
- (($mode eq 'un') ? 'removed' : 'created'));
- if ($secondmode eq 'rt') {
- $_ = "/rt $code";
- # and fall through
- } else {
- return 0;
- }
- }
-
- # Retweet API and manual RTs
- if (s#^/([oe]?)r(etweet|t) ([zZ]?[a-zA-Z]?[0-9]+)\s*##) {
- my $mode = $1;
- my $code = lc($3);
- my $tweet = &get_tweet($code);
- if (!defined($tweet)) {
- print $stdout "-- no such tweet (yet?): $code\n";
- return 0;
- }
- # use a native retweet unless we can't (or user used /ort /ert)
- unless ($nonewrts || length || length($mode)) {
- # we don't always get rs->text, so we simulate it.
- my $text = &descape($tweet->{'text'});
- $text =~ s/^RT \@[^\s]+:\s+//
- if ($tweet->{'retweeted_status'}->{'id_str'});
- print $stdout "-- status retweeted\n"
- unless(&updatest($text, 1, 0, undef,
- $tweet->{'retweeted_status'}->{'id_str'}
- || $tweet->{'id_str'}));
- return 0;
- }
- # we can't or user requested /ert /ort
- $retweet = "RT @" .
- &descape($tweet->{'user'}->{'screen_name'}) .
- ": " . &descape($tweet->{'text'});
- if ($mode eq 'e') {
- &add_history($retweet);
- print $stdout &wwrap(
- "-- ok, %RT% and %% are now \"$retweet\"\n");
- return 0;
- }
- $_ = (length) ? "$retweet $_" : $retweet;
- print $stdout &wwrap("(expanded to \"$_\")");
- print $stdout "\n";
- goto TWEETPRINT; # fugly! FUGLY!
- }
-
- if (m#^/(re)?rts?of?me?(\s+\+\d+)?$# && !$nonewrts) {
-#TODO
-# when more fields are added, integrate them over the JSON_ref
- my $mode = $1;
- my $countmaybe = $2;
- $countmaybe =~ s/[^\d]//g if (length($countmaybe));
- $countmaybe += 0;
-
- my $my_json_ref = &grabjson($rtsofmeurl, 0, 0, $countmaybe);
- &dt_tdisplay($my_json_ref, "rtsofme");
- if ($mode eq 're') {
- $_ = '/re'; # and fall through ...
- } else {
- return 0;
- }
- }
- if (m#^/rts?of\s+([zZ]?[a-zA-Z]?[0-9]+)$# && !$nonewrts) {
- my $code = lc($1);
- my $tweet = &get_tweet($code);
- my $id;
-
- if (!defined($tweet)) {
- print $stdout "-- no such tweet (yet?): $code\n";
- return 0;
- }
- $id = $tweet->{'retweeted_status'}->{'id_str'} ||
- $tweet->{'id_str'};
- if (!$id) {
- print $stdout "-- hmmm, that tweet is major bogus.\n";
- return 0;
- }
- my $url = $rtsbyurl;
- $url =~ s/%I/$id/;
- my $users_ref = &grabjson("$url", 0, 0, 100, undef, 1);
- return if (!defined($users_ref) || ref($users_ref) ne 'ARRAY');
- my $k = scalar(@{ $users_ref });
- if (!$k) {
- print $stdout
- "-- no known retweeters, or they're private.\n";
- return 0;
- }
- my $j;
- foreach $j (@{ $users_ref }) {
- &$userhandle($j->{'user'});
- }
- return 0;
- }
-
- # enable and disable NewRTs from users
- # we allow this even if newRTs are off from -nonewrts
- if (s#^/rts(on|off)\s+## && length) {
- &rtsonoffuser($_, 1, ($1 eq 'on'));
- return 0;
- }
-
- if (m#^/del(ete)?\s+([zZ]?[a-zA-Z]?[0-9]+)$#) {
- my $code = lc($2);
- unless ($code =~ /^d[0-9][0-9]+$/) { # this is a DM.
- my $tweet = &get_tweet($code);
- if (!defined($tweet)) {
- print $stdout "-- no such tweet (yet?): $code\n";
- return 0;
- }
- if (lc(&descape($tweet->{'user'}->{'screen_name'}))
- ne lc($whoami)) {
- print $stdout
- "-- not allowed to delete somebody's else's tweets\n";
- return 0;
- }
- print $stdout &wwrap(
-"-- verify you want to delete: \"@{[ &descape($tweet->{'text'}) ]}\"");
- print $stdout "\n";
- $answer = lc(&linein(
- "-- sure you want to delete? (only y or Y is affirmative):"));
- if ($answer ne 'y') {
- print $stdout "-- ok, tweet is NOT deleted.\n";
- return 0;
- }
- $lastpostid = -1 if ($tweet->{'id_str'} == $lastpostid);
- &deletest($tweet->{'id_str'}, 1);
- return 0;
- } # dxxx falls through to ...
- }
- # DM delete version
- if (m#^/del(ete)? ([dD][a-zA-Z]?[0-9]+)$#) {
- my $code = lc($2);
- my $dm = &get_dm($code);
- if (!defined($dm)) {
- print $stdout "-- no such DM (yet?): $code\n";
- return 0;
- }
- print $stdout &wwrap(
- "-- verify you want to delete: " .
- "(from @{[ &descape($dm->{'sender'}->{'screen_name'}) ]}) ".
- "\"@{[ &descape($dm->{'text'}) ]}\"");
- print $stdout "\n";
- $answer = lc(&linein(
- "-- sure you want to delete? (only y or Y is affirmative):"));
- if ($answer ne 'y') {
- print $stdout "-- ok, DM is NOT deleted.\n";
- return 0;
- }
- &deletedm($dm->{'id_str'}, 1);
- return 0;
- }
- # /deletelast
- if (m#^/de?l?e?t?e?last$#) {
- if (!$lastpostid) {
- print $stdout "-- you haven't posted yet this time!\n";
- return 0;
- }
- if ($lastpostid == -1) {
- print $stdout "-- you already deleted it!\n";
- return 0;
- }
- print $stdout &wwrap(
-"-- verify you want to delete: \"$lasttwit\"");
- print $stdout "\n";
- $answer = lc(&linein(
- "-- sure you want to delete? (only y or Y is affirmative):"));
- if ($answer ne 'y') {
- print $stdout "-- ok, tweet is NOT deleted.\n";
- return 0;
- }
- &deletest($lastpostid, 1);
- $lastpostid = -1;
- return 0;
- }
-
- if (s#^/(v)?re(ply)? ([zZ]?[a-zA-Z]?[0-9]+) ## && length) {
- my $mode = $1;
- my $code = lc($3);
- unless ($code =~ /^d[0-9][0-9]+/) { # this is a DM
- my $tweet = &get_tweet($code);
- if (!defined($tweet)) {
- print $stdout "-- no such tweet (yet?): $code\n";
- return 0;
- }
- my $target = &descape($tweet->{'user'}->{'screen_name'});
- $_ = '@' . $target . " $_";
- unless ($mode eq 'v') {
- $in_reply_to = $tweet->{'id_str'};
- $expected_tweet_ref = $tweet;
- } else {
- $_ = ".$_";
- }
- $readline_completion{'@'.lc($target)}++ if ($termrl);
- print $stdout &wwrap("(expanded to \"$_\")");
- print $stdout "\n";
- goto TWEETPRINT; # fugly! FUGLY!
- } else {
- # this is a DM, reconstruct it
- $_ = "/${mode}re $code $_";
- # and fall through to ...
- }
- }
- # DM reply version
- if (s#^/(dm)?re(ply)? ([dD][a-zA-Z]?[0-9]+) ## && length) {
- my $code = lc($3);
- my $dm = &get_dm($code);
- if (!defined($dm)) {
- print $stdout "-- no such DM (yet?): $code\n";
- return 0;
- }
- # in the future, add DM in_reply_to here
- my $target = &descape($dm->{'sender'}->{'screen_name'});
- $readline_completion{'@'.lc($target)}++ if ($termrl);
- $_ = "/dm $target $_";
- print $stdout &wwrap("(expanded to \"$_\")");
- print $stdout "\n";
- # and fall through to /dm
- }
- # replyall (based on @FunnelFiasco's extension)
- if (s#^/(v)?r(eply)?(to)?a(ll)? ([zZ]?[a-zA-Z]?[0-9]+) ## && length) {
- my $mode = $1;
- my $code = $5;
-
- # common code from /vreply
- my $tweet = &get_tweet($code);
- if (!defined($tweet)) {
- print $stdout "-- no such tweet (yet?): $code\n";
- return 0;
- }
- my $target = &descape($tweet->{'user'}->{'screen_name'});
- my $text = $_;
- $_ = '@' . $target;
- unless ($mode eq 'v') {
- $in_reply_to = $tweet->{'id_str'};
- $expected_tweet_ref = $tweet;
- } else {
- $_ = ".$_";
- }
-
- # don't repeat the target or myself; track other mentions
- my %did_mentions = map { $_ => 1 } (lc($target));
- my $reply_tweet = &descape($tweet->{'text'});
-
- while($reply_tweet =~ s/\@(\w+)//) {
- my $name = $1;
- my $mame = lc($name); # preserve camel case
- next if ($mame eq $whoami || $did_mentions{$mame}++);
- $_ .= " \@$name";
- }
- $_ .= " $text";
-
- # add everyone in did_mentions to readline_completion
- grep { $readline_completion{'@'.$_}++ } (keys %did_mentions)
- if ($termrl);
-
- # and fall through to post
- print $stdout &wwrap("(expanded to \"$_\")");
- print $stdout "\n";
- goto TWEETPRINT; # fugly! FUGLY!
- }
-
- if (m#^/re(plies)?(\s+\+\d+)?$#) {
- my $countmaybe = $2;
- $countmaybe =~ s/[^\d]//g if (length($countmaybe));
- $countmaybe += 0;
-
- if ($anonymous) {
- print $stdout
- "-- sorry, how can anyone reply to you if you're anonymous?\n";
- } else {
- # we are intentionally not keeping track of "last_re"
- # in this version because it is not automatically
- # updated and may not act as we expect.
- print $stdout "-- synchronous /replies command\n"
- if ($verbose);
- my $my_json_ref = &grabjson($rurl, 0, 0, $countmaybe,
- undef, 1);
- &dt_tdisplay($my_json_ref, "replies");
- }
- return 0;
- }
-
- # DMs
- if ($_ eq '/dm' || $_ eq '/dmrefresh' || $_ eq '/dmr') {
- &dmthump;
- return 0;
- }
- # /dmsent, /dmagain
- if (m#^/dm(s|sent|a|again)(\s+\+\d+)?$#) {
- my $mode = $1;
- my $countmaybe = $2;
- $countmaybe =~ s/[^\d]//g if (length($countmaybe));
- $countmaybe += 0;
- if ($countmaybe > 999) {
- print $stdout "-- greedy bastard, try +fewer.\n";
- return 0;
- }
- $countmaybe = sprintf("%03i", $countmaybe);
- print $stdout "-- background request sent\n" unless ($synch);
-
- $mode = ($mode =~ /^s/) ? 's' : 'd';
- print C "${mode}mreset${countmaybe}---------\n";
- &sync_semaphore;
- return 0;
- }
- if (s#^/dm \@?([^\s]+)\s+## && length) {
- return &common_split_post($_, undef, $1);
- }
-
- # follow and leave users
- if (m#^/(follow|leave|unfollow) \@?([^\s/]+)$#) {
- my $m = $1;
- my $u = lc($2);
- &foruuser($u, 1,
- (($m eq 'follow') ? $followurl : $leaveurl),
- (($m eq 'follow') ? 'started' : 'stopped'));
- return 0;
- }
-
- # follow and leave lists. this is, frankly, pointless; it does
- # nothing other than to mark you. otherwise, /liston and /listoff
- # actually add lists to your timeline.
- if (m#^/(l?follow|l?leave|l?unfollow) \@?([^\s/]*)/([^\s/]+)$#) {
- my $m = $1;
- my $uname = lc($2);
- my $lname = lc($3);
-
- if (!length($uname) || $uname eq $whoami) {
- print $stdout &wwrap(
-"** you can't mark/unmark yourself as a follower of your own lists!\n");
- print $stdout &wwrap(
-"** to add/remove your own lists from your timeline, use /liston /listoff\n");
- return 0;
- }
- if ($m !~ /^l/) {
- print $stdout &wwrap(
-"-- to mark/unmark you as a follower of a list, use /lfollow /lleave\n");
- print $stdout &wwrap(
-"-- to add/remove your own lists from your timeline, use /liston /listoff\n");
- return 0;
- }
-
- my $r = &postjson(
- ($m ne 'lfollow') ? $delfliurl : $crefliurl,
- "owner_screen_name=$uname&slug=$lname");
- if ($r) {
- my $t = ($m eq 'lfollow') ? "" : "un";
- print $stdout &wwrap(
-"*** ok, you are now ${t}marked as a follower of $uname/${lname}.\n");
- my $c = ($t eq 'un') ? "off" : "on";
- $t = ($t eq 'un') ? "remove from" : "add to";
- print $stdout &wwrap(
-"--- to also $t your timeline, use /list${c}\n");
- }
- return 0;
- }
-
- # block and unblock users
- if (m#^/(block|unblock) \@?([^\s/]+)$#) {
- my $m = $1;
- my $u = lc($2);
- if ($m eq 'block') {
- $answer = lc(&linein(
- "-- sure you want to block $u? (only y or Y is affirmative):"));
- if ($answer ne 'y') {
- print $stdout "-- ok, $u is NOT blocked.\n";
- return 0;
- }
- }
- &boruuser($u, 1,
- (($m eq 'block') ? $blockurl : $blockdelurl),
- (($m eq 'block') ? 'started' : 'stopped'));
- return 0;
- }
-
- # list support
- # /withlist (/withlis, /with, /wl)
- if (s#^/(withlist|withlis|withl|with|wl)\s+([^/\s]+)\s+## &&
- ($lname=lc($2)) && s/\s*$// && length) {
- my $comm = '';
- my $args = '';
- my $dont_return = 0;
- if ($anonymous) {
- print $stdout "-- no list love for anonymous\n";
- return 0;
- }
- if (/\s+/) {
- ($comm, $args) = split(/\s+/, $_, 2);
- } else {
- $comm = $_;
- }
-
- my $return;
- # this is a Twitter bug -- it will not give you the
- # new slug in the returned hash.
- my $state = "modified list $lname (WAIT! then /lists to see new slug)";
- if ($comm eq 'create') {
- my $desc;
- ($args, $desc) = split(/\s+/, $args, 2)
- if ($args =~ /\s+/);
- if ($args ne 'public' && $args ne 'private') {
- print $stdout
- "-- must specify public or private\n";
- return 0;
- }
- $state = "created new list $lname (mode $args)";
- $desc = "description=".&url_oauth_sub($desc)."&"
- if (length($desc));
- $return = &postjson($creliurl,
- "${desc}mode=$args&name=$lname");
- } elsif ($comm eq 'private' || $comm eq 'public') {
- $return = &postjson($modifyliurl,
- "mode=$comm&owner_screen_name=${whoami}&slug=${lname}");
- } elsif ($comm eq 'desc' || $comm eq 'description') {
- if (!length($args)) {
- print $stdout "-- $comm needs an argument\n";
- return 0;
- }
- $return = &postjson($modifyliurl,
- "description=".&url_oauth_sub($args).
- "&owner_screen_name=${whoami}&slug=${lname}");
- } elsif ($comm eq 'name') {
- if (!length($args)) {
- print $stdout "-- $comm needs an argument\n";
- return 0;
- }
- $return = &postjson($modifyliurl,
- "name=".&url_oauth_sub($args).
- "&owner_screen_name=${whoami}&slug=${lname}");
- $state = "RENAMED list $lname (WAIT! then /lists to see new slug)";
- } elsif ($comm eq 'add' || $comm eq 'adduser' ||
- ($comm eq 'delete' && length($args))) {
- my $u = ($comm eq 'delete') ? $deluliurl : $adduliurl;
- $state = ($comm eq 'delete')
- ? "user(s) deleted from list $lname"
- : "user(s) added to list $lname";
- if ($args !~ /,/ || $args =~ /\s+/) {
- 1 while ($args =~ s/\s+/,/);
- }
- if ($args =~ /\s*,\s+/ || $args =~ /\s+,\s*/) {
- 1 while ($args =~ s/\s+//);
- }
- if (!length($args)) {
- print $stdout "-- illegal/missing argument\n";
- return 0;
- }
- print $stdout "--- warning: user list not checked\n";
- $return = &postjson($u,
- "owner_screen_name=${whoami}".
- "&screen_name=".&url_oauth_sub($args).
- "&slug=${lname}");
- } elsif ($comm eq 'delete' && !length($args)) {
- $state = "deleted list $lname";
- print $stdout
- "-- verify you want to delete list $lname\n";
- my $answer = lc(&linein(
- "-- sure you want to delete? (only y or Y is affirmative):"));
- if ($answer ne 'y') {
- print $stdout "-- ok, list is NOT deleted.\n";
- return 0;
- }
- $return = &postjson($delliurl,
- "owner_screen_name=${whoami}&slug=${lname}");
- if ($return) {
- # check and see if this is in our autolists.
- # if it is, delete it there too.
- my $value = &getvariable('lists');
- &setvariable('lists', $value, 1)
- if ($value=~s#(^|,)${whoami}/${lname}($|,)##);
- }
- } elsif ($comm eq 'list') { # synonym for /list
- $_ = "/list /$lname";
- $dont_return = 1; # and fall through
- } else {
- print $stdout "*** illegal list operation $comm\n";
- }
- if ($return) {
- print $stdout "*** ok, $state\n";
- }
- return 0 unless ($dont_return);
- }
-
- # /a to show statuses in a list
- if (m#^/a(gain)?\s+(\+\d+\s+)?\@?([^\s/]*)/([^\s/]+)#) {
- my $uname = lc($3);
- if ($anonymous && !length($uname)) {
- print $stdout "-- you must specify a username when anonymous.\n";
- return 0;
- }
- my $lname = lc($4);
- my $countmaybe = $2;
- $countmaybe =~ s/[^\d]//g if (length($countmaybe));
- $countmaybe += 0;
- $uname ||= $whoami;
-
- my $my_json_ref = &grabjson(
- "${statusliurl}?owner_screen_name=${uname}&slug=${lname}",
- 0, 0, $countmaybe, undef, 1);
- &dt_tdisplay($my_json_ref, "again");
- return 0;
- }
-
- # /lists command: if @, show their lists. if @?../... show that list.
- # trivially duplicates /frs and /fos for lists
- # also handles /listfos and /listfrs
- if (length($whoami) &&
- (m#^/list?s?$# || m#^/list?f[ro](llower|iend)?s$#)) {
- $_ .= " $whoami";
- }
- if (m#^/lis(t|ts|t?fos|tfollowers|t?frs|tfriends)?\s+(\+\d+\s+)?(\@?[^\s]+)$#) {
- my $mode = $1;
- my $countmaybe = $2;
- my $uname = lc($3);
- my $lname = '';
-
- $mode = ($mode =~ /^t?fo/) ? 'fo' :
- ($mode =~ /^t?fr/) ? 'fr' :
- '';
- $uname =~ s/^\@//;
- ($uname, $lname) = split(m#/#, $uname, 2) if ($uname =~ m#/#);
- if ($anonymous && !length($uname) && length($mode)) {
- print $stdout "-- you must specify a username when anonymous.\n";
- return 0;
- }
- $uname ||= $whoami;
- if (length($lname) && length($mode)) {
- print $stdout "-- specify username only\n";
- return 0;
- }
-
- $countmaybe =~ s/[^\d]//g if (length($countmaybe));
- $countmaybe += 0;
- $countmaybe ||= 20;
-
- # this is copied from /friends and /followers (q.v.)
- my $countper = ($countmaybe < 100) ? $countmaybe : 100;
-
- my $cursor = -1; # initial value
- my $nofetch = 0;
- my $printed = 0;
- my $json_ref = undef;
- my @usarray = undef; shift(@usarray); # force underflow
- my $furl = (length($lname)) ? ($getliurl."?owner_")
- : ($mode eq '') ? ($getlisurl."?")
- : ($mode eq 'fo') ? ($getuliurl."?")
- : ($getufliurl."?");
- $furl .= "screen_name=${uname}";
- $furl .= "&slug=${lname}" if (length($lname));
-
- LABIO: while($countmaybe--) {
- if(!scalar(@usarray)) {
- last LABIO if ($nofetch);
- $json_ref = &grabjson(
- "${furl}&count=${countper}&cursor=${cursor}", 0, 0, 0,
- undef, 1);
- @usarray = @{ ((length($lname)) ?
- $json_ref->{'users'} :
- $json_ref
- ) };
- last LABIO if (!scalar(@usarray));
- if (length($lname)) {
- $cursor = $json_ref->{'next_cursor_str'} ||
- $json_ref->{'next_cursor'} || -1;
- $nofetch = ($cursor < 1) ? 1 : 0;
- } else { $nofetch = 1; }
- }
- my $list_ref = shift(@usarray);
- if (length($lname)) {
- &$userhandle($list_ref);
- } else {
- # lists/list returns their lists AND the
- # ones they subscribe to, different from 1.0.
- # right now we just deal with that.
- #next if ($uname ne
- # $list_ref->{'user'}->{'screen_name'});
-
- # listhandle?
- my $list_name =
-"\@$list_ref->{'user'}->{'screen_name'}/@{[ &descape($list_ref->{'slug'}) ]}";
- my $list_full_name =
- (length($list_ref->{'name'})) ?
-&descape($list_ref->{'name'})."${OFF} ($list_name)" : $list_name;
- my $list_mode =
- (lc(&descape($list_ref->{'mode'})) ne 'public') ?
-" ${EM}(@{[ ucfirst(&descape($list_ref->{'mode'})) ]})${OFF}" : "";
- print $streamout <<"EOF";
-${CCprompt}$list_full_name${OFF} (f:$list_ref->{'member_count'}/$list_ref->{'subscriber_count'})$list_mode
-EOF
- my $desc = &strim(&descape($list_ref->{'description'}));
- my $klen = ($wrap || 79) - 9;
- $klen = 10 if ($klen < 0);
- $desc = substr($desc, 0, $klen)."..."
- if (length($desc) > $klen);
- print $streamout (' "' . $desc . '"' . "\n")
- if (length($desc));
- }
- $printed++;
- }
- if (!$printed) {
- print $stdout ((length($lname))
- ? "-- list $uname/$lname does not follow anyone.\n"
- : ($mode eq 'fr')
- ? "-- user $uname doesn't follow any lists.\n"
- : ($mode eq 'fo')
- ? "-- user $uname isn't followed by any lists.\n"
- : "-- no lists found for user $uname.\n");
- }
- return 0;
- }
-
- &sync_n_quit if ($_ eq '/end' || $_ eq '/e');
-
- #####
- #
- # below this point, we are posting
- #
- #####
-
- if (m#^/me\s#) {
- $slash_first = 0; # kludge!
- }
-
- if ($slash_first) {
- if (!m#^//#) {
- print $stdout "*** invalid command\n";
- print $stdout "*** to pass as a tweet, type /%%\n";
- return 0;
- }
- s#^/##; # leave the second slash on
- }
-
-TWEETPRINT: # fugly! FUGLY!
- return &common_split_post($_, $in_reply_to, undef);
-}
-
-# this is the common code used by standard updates and by the /dm command.
-sub common_split_post {
- my $k = shift;
- my $in_reply_to = shift;
- my $dm_user = shift;
-
- my $dm_lead = (length($dm_user)) ? "/dm $dm_user " : '';
- my $ol = "$dm_lead$k";
-
- my (@tweetstack) = &csplit($k, ($autosplit eq 'char' ||
- $autosplit eq 'cut') ? 1 : 0);
- my $m = shift(@tweetstack);
- if (scalar(@tweetstack)) {
- $l = "$dm_lead$m";
- $history[0] = $l;
- if (!$autosplit) {
- print $stdout &wwrap(
-"*** sorry, too long to send; ".
-"truncated to \"$l\" (@{[ length($m) ]} chars)\n");
- print $stdout "*** use %% for truncated version, or append to %%.\n";
- return 0;
- }
- print $stdout &wwrap(
- "*** over $linelength; autosplitting to \"$l\"\n");
- }
- # there was an error; stop autosplit, restore original command
- if (&updatest($m, 1, $in_reply_to, $dm_user)) {
- $history[0] = $ol;
- return 0;
- }
- if (scalar(@tweetstack)) {
- $k = shift(@tweetstack);
- $l = "$dm_lead$k";
- &add_history($l);
- print $stdout &wwrap("*** next part is ready: \"$l\"\n");
- print $stdout "*** (this will also be automatically split)\n"
- if (length($k) > $linelength);
- print $stdout
- "*** to send this next portion, use %%.\n";
- }
- return 1;
-}
-
-# helper functions for the command line processor.
-sub add_history {
- my $h = shift;
-
- @history = (($h, @history)[0..&min(scalar(@history), $maxhist)]);
- if ($termrl) {
- if ($termrl->Features()->{'canSetTopHistory'}) {
- $termrl->settophistory($h);
- } else {
- $termrl->addhistory($h);
- }
- }
-}
-sub sub_helper {
- my $r = shift;
- my $s = shift;
- my $g = shift;
- my $x;
- my $q = 0;
- my $proband;
-
- if ($r eq '%') {
- $x = -1;
- } else {
- $x = $r + 0;
- }
- if (!$x || $x < -(scalar(@history))) {
- print $stdout "*** illegal history index\n";
- return (0, $_, undef, undef, undef);
- }
- $proband = $history[-($x + 1)];
- if ($s eq '--') {
- $q = 1;
- } elsif ($s eq '*') {
- if ($x != -1 || !length($shadow_history)) {
- print $stdout
- "*** can only %%* on most recent command\n";
- return (0, $_, undef, undef, undef);
- }
- # we assume it's at the end; it's only relevant there
- $proband = substr($shadow_history, length($g)-(2+length($r)));
- } else {
- $q = -(0+$s);
- }
- if ($q) {
- my $j;
- my $c;
- for($j=0; $j<$q; $j++) {
- $c++ if ($proband =~ s/\s+[^\s]+$//);
- }
- if ($j != $c) {
- print $stdout "*** illegal word index\n";
- return (0, $_, undef, undef, undef);
- }
- }
- return (1, $proband, $r, $s);
-}
-
-# this is used for synchronicity mode to make sure we receive the
-# GA semaphore from the background before printing another prompt.
-sub sync_console {
- &thump;
- &dmthump unless (!$dmpause);
-}
-sub sync_semaphore {
- if ($synch) {
- my $k = '';
-
- while(!length($k)) {
- sysread(W, $k, 1);
- } # wait for semaphore
- }
-}
-
-# wrapper function to get a line from the terminal.
-sub linein {
- my $prompt = shift;
- my $return;
-
- return 'y' if ($script);
-
- $prompt .= " ";
- if ($termrl) {
- $dont_use_counter = 1;
- eval '$termrl->hook_no_counter';
- $return = $termrl->readline($prompt);
- $dont_use_counter = $nocounter;
- eval '$termrl->hook_no_counter';
- } else {
- print $stdout $prompt;
- chomp($return = lc(<$stdin>));
- }
- return $return;
-}
-
-#### this is the background part of the process ####
-
-MONITOR:
-%store_hash = ();
-$is_background = 1;
-$first_synch = $synchronous_mode = 0;
-$rin = '';
-vec($rin,fileno(STDIN),1) = 1;
-# paranoia
-binmode($stdout, ":crlf") if ($termrl);
-unless ($seven) {
- binmode(STDIN, ":utf8");
- binmode($stdout, ":utf8");
-}
-
-# allow foreground process to squelch us
-# we have to cover all the various versions of 30/31 signals on various
-# systems just in case we are on a system without POSIX.pm. this set should
-# cover Linux 2.x/3.x, AIX, Mac OS X, *BSD and Solaris. we have to assert
-# these signals before starting streaming, or we may "kill" ourselves by
-# accident because it is possible to process a tweet before these are
-# operational.
-&sigify(sub {
- $suspend_output ^= 1 if ($suspend_output != -1);
- $we_got_signal = 1;
-}, qw(USR1 PWR XCPU));
-&sigify( sub {
- $suspend_output = -1; $we_got_signal = 1;
-}, qw(USR2 SYS UNUSED XFSZ));
-&sigify("IGNORE", qw(INT)); # don't let slowpost kill us
-
-# now we can safely initialize streaming
-if ($dostream) {
- @events = ();
- $lasteventtime = time();
- &sigify(sub {
- print $stdout "-- killing processes $nursepid $bufferpid\n"
- if ($verbose);
- kill $SIGHUP, $nursepid if ($nursepid);
- kill $SIGHUP, $bufferpid if ($bufferpid);
- kill 9, $curlpid if ($curlpid);
- sleep 1;
- # send myself a shutdown
- kill 9, $nursepid if ($nursepid);
- kill 9, $bufferpid if ($bufferpid);
- kill $SIGTERM, $$;
- }, qw(HUP)); # use SIGHUP etc. from parent process to signal end
- $bufferpid = &start_streaming;
- vec($rin, fileno(STBUF), 1) = 1;
-} else {
- &sigify("IGNORE", qw(HUP)); # we only respond to SIGKILL/SIGTERM
-}
-
-$interactive = $previous_last_id = $we_got_signal = 0;
-$suspend_output = -1;
-$stream_failure = 0;
-$dm_first_time = ($dmpause) ? 1 : 0;
-$stuck_stdin = 0;
-
-# tell the foreground we are ready
-kill $SIGUSR2, $parent;
-
-# loop until we are killed or told to stop.
-# we receive instructions on stdin, and send data back on our pipe().
-for(;;) {
- &$heartbeat;
- &update_effpause;
- $wrapseq = 0; # remember, we don't know when commands are sent.
- &refresh($interactive, $previous_last_id) unless
- (!$effpause && !$interactive);
- $dont_refresh_first_time = 0;
- $previous_last_id = $last_id;
- if ($dmpause && ($effpause || $synch)) {
- if ($dm_first_time) {
- &dmrefresh(0);
- $dmcount = $dmpause;
- } elsif (!$interactive) {
- if (!--$dmcount) {
- &dmrefresh($interactive); # using dm_first_time
- $dmcount = $dmpause;
- }
- }
- }
-DONT_REFRESH:
- # nrvs is tricky with synchronicity
- if (!$synch || ($synch && $synchronous_mode && !$dm_first_time)) {
- $k = length($notify_rate) + length($vs) + length($credlog);
- if ($k) {
- &send_removereadline if ($termrl);
- print $stdout $notify_rate;
- print $stdout $vs;
- print $stdout $credlog;
- $wrapseq = 1;
- }
- $notify_rate = "";
- $vs = "";
- $credlog = "";
- }
- print P "0" if ($synchronous_mode && $interactive);
- &send_repaint if ($termrl);
-
- # this core loop is tricky. most signals will not restart the call.
- # -- respond to alarms if we are ignoring our timeout.
- # -- do not respond to bogus packets if a signal handler triggered it.
- # -- clear our flag when we detect a signal handler has been called.
-
- # if our master select is interrupted, we must restart with the
- # appropriate time taken from effpause. however, most implementations
- # don't report timeleft, so we must.
- $restarttime = time() + $effpause;
-RESTART_SELECT:
- &send_repaint if ($termrl);
- $interactive = 0;
- $we_got_signal = 0; # acknowledge all signals
- if ($effpause == undef) { # -script and anonymous have no effpause.
- print $stdout "-- select() loops forever\n" if ($verbose);
- $nfound = select($rout = $rin, undef, undef, undef);
- } else {
- $actualtime = $restarttime - time();
- print $stdout "-- select pending ($actualtime sec left)\n"
- if ($superverbose);
- if ($actualtime <= 0) {
- $nfound = 0;
- } else {
- $nfound = select(
- $rout = $rin, undef, undef, $actualtime);
- }
- }
- if ($nfound > 0) {
- my $len;
-
- # service the streaming socket first, if we have one.
- if ($dostream) {
- if (vec($rout, fileno(STBUF), 1) == 1) {
- my $json_ref;
- my $buf = '';
- my $rbuf;
- my $reads = 0;
-
- print $stdout "-- data on streaming socket\n"
- if ($superverbose);
-
- # read until we get eight hex digits. this forces the
- # data stream to synchronize.
- # first, however, make sure we actually have valid
- # data, or we sit here and slow down the user.
- sysread(STBUF, $buf, 1);
- if (!length($buf)) {
- # if we get a "ready" but there's actually
- # no data, that means either 1) a signal
- # occurred on the buffer, which we need to
- # ignore, or 2) something killed the
- # buffer, which is unrecoverable. if we keep
- # getting repeated ready-no data situations,
- # it's probably the latter.
- $stream_failure++;
- &screech(<<"EOF") if ($stream_failure > 100);
-
-*** fatal error ***
-something killed the streaming buffer process. I can't recover from this.
-please restart TTYtter.
-EOF
- goto DONESTREAM;
- }
- $stream_failure = 0;
- if ($buf !~ /^[0-9a-fA-F]+$/) {
- print $stdout
- "-- warning: bogus character(s) ".unpack("H*", $buf)."\n"
- if ($superverbose);
- goto DONESTREAM;
- }
- while (length($buf) < 8) {
- # don't read 8 -- read 1. that means we can
- # skip trailing garbage without a window.
- sysread(STBUF, $rbuf, 1);
- $reads++;
- if ($rbuf =~ /[0-9a-fA-F]/) {
- $buf .= $rbuf;
- $reads = 0;
- } else {
- print $stdout
- "-- warning: bogus character(s) ".unpack("H*", $rbuf)."\n"
- if ($superverbose);
- $buf = ''
- if (length($rbuf)); # bogus data
- }
- print $stdout
- "-- master, I am stuck: $reads reads on stream and no valid data\n"
- if ($reads > 0 && ($reads % 1000) == 0);
- }
- print $stdout "-- length packet: $buf\n"
- if ($superverbose);
- $len = hex($buf);
- $buf = '';
- while (length($buf) < $len) {
- sysread(STBUF, $rbuf, ($len-length($buf)));
- $buf .= $rbuf;
- }
-
- print $stdout
- "-- streaming data ($len) --\n$buf\n-- streaming data --\n\n"
- if ($superverbose);
- $json_ref = &parsejson($buf);
- push(@events, $json_ref);
-
- if (scalar(@events) > $eventbuf || (scalar(@events) &&
- (time()-$lasteventtime) > $effpause)){
- sleep 5 while ($suspend_output > 0);
- &streamevents(@events);
- &send_repaint if ($termrl);
- @events = ();
- $lasteventtime = time();
- }
- }
- DONESTREAM: print $stdout "-- done with streaming events\n"
- if ($superverbose);
- }
-
- # then, check if there is data on our control socket.
- # command packets should always be (initially) 20 characters.
- # if we come up short, it's either a bug, signal or timeout.
- if ($we_got_signal) {
- goto RESTART_SELECT;
- }
- goto RESTART_SELECT if(vec($rout, fileno(STDIN), 1) != 1);
- print $stdout "-- waiting for data ", scalar localtime, "\n"
- if ($superverbose);
- if(sysread(STDIN, $rout, 20) != 20) {
- # if we get repeated "ready" but no data on STDIN,
- # like the streaming buffer, we probably lost our
- # IPC and we should die here.
- if (++$stuck_stdin > 100) {
- print $stdout "parent is dead; we die too\n";
- kill 9,$$;
- }
- goto RESTART_SELECT;
- }
- $stuck_stdin = 0;
- # background communications central command code
- # we received a command from the console, so let's look at it.
- print $stdout "-- command received ", scalar
- localtime, " $rout" if ($verbose);
- if ($rout =~ /^rsga/) {
- $suspend_output = 0; # reset our status
- goto RESTART_SELECT;
- } elsif ($rout =~ /^pipet (..)/) {
- my $key = &get_tweet($1);
- my $ms = $key->{'menu_select'} || 'XX';
- my $ds = $key->{'created_at'} || 'argh, no created_at';
- $ds =~ s/\s/_/g;
- my $src = $key->{'source'} || 'unknown';
- $src =~ s/\|//g; # shouldn't be any anyway.
- $key = substr(( "$ms ".($key->{'id_str'})." ".
- ($key->{'in_reply_to_status_id_str'})." ".
- ($key->{'retweeted_status'}->{'id_str'})." ".
- ($key->{'user'}->{'geo_enabled'} || "false") . " ".
- ($key->{'geo'}->{'coordinates'}->[0]). " ".
- ($key->{'geo'}->{'coordinates'}->[1]). " ".
- $key->{'place'}->{'id'} . " ".
- $key->{'place'}->{'country_code'} ." ".
- $key->{'place'}->{'place_type'} . " ".
- unpack("${pack_magic}H*", $key->{'place'}->{'full_name'})." ".
- $key->{'tag'}->{'type'}. " ". # NO SPACES!
- unpack("${pack_magic}H*", $key->{'tag'}->{'payload'}). " ".
- ($key->{'retweet_count'} || "0") . " " .
- $key->{'user'}->{'screen_name'}." $ds $src|".
- unpack("${pack_magic}H*", $key->{'text'}).
- $space_pad), 0, 1024);
- print P $key;
- goto RESTART_SELECT;
- } elsif ($rout =~ /^piped (..)/) {
- my $key = $dm_store_hash{$1};
- my $ms = $key->{'menu_select'} || 'XX';
- my $ds = $key->{'created_at'} || 'argh, no created_at';
- $ds =~ s/\s/_/g;
- $key = substr(( "$ms ".($key->{'id_str'})." ".
- $key->{'sender'}->{'screen_name'}." $ds ".
- unpack("${pack_magic}H*", $key->{'text'}).
- $space_pad), 0, 1024);
- print P $key;
- goto RESTART_SELECT;
- } elsif ($rout =~ /^ki ([^\s]+) /) {
- my $key = $1;
- my $module;
- sysread(STDIN, $module, 1024);
- $module =~ s/\s+$//;
- $module = pack("H*", $module);
- print $stdout "-- fetch for module $module key $key\n"
- if ($verbose);
- print P substr(unpack("${pack_magic}H*",
- $master_store->{$module}->{$key}).$space_pad,
- 0, 1024);
- goto RESTART_SELECT;
- } elsif ($rout =~ /^kn ([^\s]+) /) {
- my $key = $1;
- my $module;
- sysread(STDIN, $module, 1024);
- $module =~ s/\s+$//;
- $module = pack("H*", $module);
- print $stdout "-- nulled module $module key $key\n"
- if ($verbose);
- $master_store->{$module}->{$key} = undef;
- goto RESTART_SELECT;
- } elsif ($rout =~ /^ko ([^\s]+) /) {
- my $key = $1;
- my $value;
- my $module;
- sysread(STDIN, $module, 1024);
- $module =~ s/\s+$//;
- $module = pack("H*", $module);
- sysread(STDIN, $value, 1024);
- $value =~ s/\s+$//;
- print $stdout
- "-- set module $module key $key = $value\n"
- if ($verbose);
- $master_store->{$module}->{$key} = pack("H*", $value);
- goto RESTART_SELECT;
- } elsif ($rout =~ /^sync/) {
- print $stdout "-- synced; exiting at ",
- scalar localtime, "\n"
- if ($verbose);
- exit $laststatus;
- } elsif ($rout =~ /^synm/) {
- $first_synch = $synchronous_mode = 1;
- print $stdout "-- background is now synchronous\n"
- if ($verbose);
- } elsif ($rout =~ /([\=\?\+])([^ ]+)/) {
- $comm = $1;
- $key =$2;
- if ($comm eq '?') {
- print P substr("${$key}$space_pad", 0, 1024);
- } else {
- sysread(STDIN, $value, 1024);
- $value =~ s/\s+$//;
- $interactive = ($comm eq '+') ? 0 : 1;
- if ($key eq 'tquery') {
- print $stdout
- "*** custom query installed\n"
- if ($interactive || $verbose);
- print $stdout
- "$value" if ($verbose);
- @trackstrings = ();
- # already URL encoded
- push(@trackstrings, $value);
- } else {
- $$key = $value;
- print $stdout
- "*** changed: $key => $$key\n"
- if ($interactive || $verbose);
-
- &generate_ansi if ($key eq 'ansi' ||
- $key =~ /^colour/);
- $rate_limit_next = 0
- if ($key eq 'pause' &&
- $value eq 'auto');
- &tracktags_makearray
- if ($key eq 'track');
- &filter_compile
- if ($key eq 'filter');
- &notify_compile
- if ($key eq 'notifies');
- &list_compile
- if ($key eq 'lists');
- &filterflags_compile
- if ($key eq 'filterflags');
- $filterrts_sub =
- &filteruserlist_compile(
- $filterrts_sub, $value)
- if ($key eq 'filterrts');
- $filterusers_sub =
- &filteruserlist_compile(
- $filterusers_sub,$value)
- if ($key eq 'filterusers');
- $filteratonly_sub =
- &filteruserlist_compile(
- $filteratonly_sub,
- $value)
- if ($key eq 'filteratonly');
- &filterats_compile
- if ($key eq 'filterats');
- }
- }
- goto RESTART_SELECT;
- } else {
- $interactive = 1;
- ($fetchwanted = 0+$1, $fetch_id = 0, $last_id = 0)
- if ($rout =~ /^reset(\d+)/);
- ($dmfetchwanted = 0+$1, $last_dm = 0)
- if ($rout =~ /^dmreset(\d+)/);
- if ($rout =~ /^smreset/) { # /dmsent
- $dmfetchwanted = 0+$1
- if ($rout =~ /(\d+)/);
- &dmrefresh(1, 1);
- &send_repaint if ($termrl);
- # we do not want to force a refresh.
- goto DONT_REFRESH;
- }
- if ($rout =~ /^dm/) {
- &dmrefresh($interactive);
- &send_repaint if ($termrl);
- $dmcount = $dmpause;
- goto DONT_REFRESH;
- }
- }
- } else {
- if ($we_got_signal || $nfound == -1) {
- # we need to restart the call. we might be waiting
- # longer, but this is unavoidable.
- goto RESTART_SELECT;
- }
- print $stdout
-"-- routine refresh (effpause = $effpause, $dmcount to next dm) ",
- scalar localtime, "\n" if ($verbose);
- }
-}
-
-#### internal implementation functions for the twitter API. DON'T ALTER ####
-
-# manage automatic rate limiting by checking our max.
-#TODO
-# autoslowdown as we run out of requests, then speed up when hour
-# has passed.
-sub update_effpause {
- return ($effpause = undef) if ($script); # for select()
- if ($pause ne 'auto' && $noratelimit) {
- $effpause = (0+$pause) || undef;
- return;
- }
- $effpause = (0+$pause) || undef
- if ($anonymous || (!$pause && $pause ne 'auto'));
- if (!$rate_limit_next && !$anonymous && ($pause > 0 ||
- $pause eq 'auto')) {
-
- # Twitter 1.0 used a simple remaining_hits and
- # hourly_limit. 1.1 uses multiple rate endpoints. we
- # are only interested in certain specific ones, though
- # we currently fetch them all and we might use more later.
-
- $rate_limit_next = 5;
- $rate_limit_ref = &grabjson($rlurl, 0, 0, 0, undef, 1);
-
- if (defined $rate_limit_ref &&
- ref($rate_limit_ref) eq 'HASH') {
-
- # of mentions_timeline, home_timeline and search/tweets,
- # choose the MOST restrictive and normalize that.
-
- $rate_limit_left = &min(
-0+$rate_limit_ref->{'resources'}->{'statuses'}->{'/statuses/home_timeline'}->{'remaining'},
- &min(
-0+$rate_limit_ref->{'resources'}->{'statuses'}->{'/statuses/mentions_timeline'}->{'remaining'},
-0+$rate_limit_ref->{'resources'}->{'search'}->{'/search/tweets'}->{'remaining'}));
- $rate_limit_rate = &min(
-0+$rate_limit_ref->{'resources'}->{'statuses'}->{'/statuses/home_timeline'}->{'limit'},
- &min(
-0+$rate_limit_ref->{'resources'}->{'statuses'}->{'/statuses/mentions_timeline'}->{'limit'},
-0+$rate_limit_ref->{'resources'}->{'search'}->{'/search/tweets'}->{'limit'}));
- if ($rate_limit_left < 3 && $rate_limit_rate) {
- $estring =
-"*** warning: API rate limit imminent";
- if ($pause eq 'auto') {
- $estring .=
- "; temporarily halting autofetch";
- $effpause = 0;
- }
- &$exception(5, "$estring\n");
- } else {
- if ($pause eq 'auto') {
-
-# the new rate limits do not require us to reduce our fetching for mentions,
-# direct messages or search, because they pull from different buckets, and
-# their rate limits are roughly the same.
- $effpause = 5*$rate_limit_rate;
- # this will usually be 75s
-# for lists, however, we have to drain the list bucket faster, so for every
-# list AFTER THE FIRST ONE we subscribe to, add rate_limit_rate to slow.
-# for search, it has 180 requests, so we don't care so much. if this
-# changes later, we will probably need something similar to this for
-# cases where the search array is > 1.
- $effpause += ((scalar(@listlist)-1)*
- $rate_limit_rate)
- if (scalar(@listlist) > 1);
-
- if (!$effpause) {
- print $stdout
-"-- rate limit rate failure: using 180 second fallback\n";
- $effpause = 180;
- }
-
- # we don't go under sixty.
- $effpause = 60 if ($effpause < 60);
- } else {
- $effpause = 0+$pause;
- }
- }
- print $stdout
-"-- rate limit check: $rate_limit_left/$rate_limit_rate (rate is $effpause sec)\n"
- if ($verbose);
- $adverb = (!$last_rate_limit) ? ' currently' :
- ($last_rate_limit < $rate_limit_rate) ? ' INCREASED to':
- ($last_rate_limit > $rate_limit_rate) ? ' REDUCED to':
- '';
- $notify_rate =
-"-- notification: API rate limit is${adverb} ${rate_limit_rate} req/15min\n"
- if ($last_rate_limit != $rate_limit_rate);
- $last_rate_limit = $rate_limit_rate;
- } else {
- $rate_limit_next = 0;
- $effpause = ($pause eq 'auto') ? 180 : 0+$pause;
- print $stdout
-"-- failed to fetch rate limit (rate is $effpause sec)\n"
- if ($verbose);
- }
- } else {
- $rate_limit_next-- unless ($anonymous);
- }
-}
-
-# streaming API support routines
-
-### INITIALIZE STREAMING
-### spin off a nurse process to proxy data from curl, and a buffer process
-### to protect the background process from signals curl may generate.
-
-sub start_streaming {
- $bufferpid = 0;
- unless ($streamtest) {
- if($bufferpid = open(STBUF, "-|")) {
- # streaming processes initialized
- return $bufferpid;
- }
- }
-
- # now within buffer process
- # verbosity does not work here, so force both off.
- $verbose = 0;
- $superverbose = 0;
-
- $0 = "TTYtter (streaming buffer thread)";
- $in_buffer = 1;
- # set up signal handlers
- $streampid = 0;
- &sigify(sub {
- # in an earlier version we wrote a disconnect packet to the
- # pipe in this handler. THIS IS NOT SAFE on certain OS/Perl
- # combinations. I moved this down to the HELLOAGAINNURSE loop,
- # or otherwise you get random seg faults.
- $i = $streampid;
- $streampid = 0;
- waitpid $i, 0 if ($i);
- }, qw(CHLD PIPE));
- &sigify(sub {
- $i = $streampid;
- $streampid = 0; # suppress handler above
- kill ($SIGHUP, $i) if ($i);
- waitpid $i, 0 if ($i);
- kill 9, $curlpid if ($curlpid && !$i);
- kill 9, $$;
- }, qw(HUP TERM));
- &sigify("IGNORE", qw(INT));
-
- $packets_read = 0; # part of exponential backoff
- $wait_time = 0;
-
- # open the nurse process
- HELLOAGAINNURSE: $w = "{\"packet\" : \"connect\", \"payload\" : {} }";
- select(STDOUT); $|++;
- printf STDOUT ("%08x%s", length($w), $w);
- close(NURSE);
- if (!$packets_read) { $wait_time += (($wait_time) ? $wait_time : 1) }
- else { $wait_time = 0; }
- $packets_read = 0;
- $wait_time = ($wait_time > 60) ? 60 : $wait_time;
- if ($streampid = open(NURSE, "-|")) {
- # within the buffer process
- select(NURSE); $|++; select(STDOUT);
- my $rin = '';
- vec($rin,fileno(NURSE),1) = 1;
- my $datasize = 0;
- my $buf = '';
- my $cuf = '';
- my $duf = '';
-
- # read the curlpid from the stream
- read(NURSE, $curlpax, 8);
- $curlpid = hex($curlpax);
-
- # if we are testing the socket, just emit data.
- if ($streamtest) {
- my $c;
-
- for(;;) {
- sysread(NURSE, $c, 1);
- print STDOUT $c;
- }
- }
- HELLONURSE: while(1) {
- # restart nurse process if it/curl died
- goto HELLOAGAINNURSE if(!$streampid);
-
- # read a line of text (hopefully numbers)
- chomp($buf = <NURSE>);
- # should be nothing but digits and whitespace.
- # if anything else, we're getting garbage, and we
- # should reconnect.
- if ($buf =~ /[^0-9\r\l\n\s]+/s) {
- close(NURSE);
- kill 9, $streampid if ($streampid);
- # and SIGCHLD will reap
- kill 9, $curlpid if ($curlpid);
- goto HELLOAGAINNURSE;
- }
- $datasize = 0+$buf;
- next HELLONURSE if (!$datasize);
- $datasize--;
- read(NURSE, $duf, $datasize);
- # don't send broken entries
- next HELLONURSE if (length($duf) < $datasize);
- # yank out all \r\n
- 1 while $duf =~ s/[\r\n]//g;
- $duf = "{ \"packet\" : \"data\", \"pid\" : \"$streampid\", \"curlpid\" : \"$curlpid\", \"payload\" : $duf }";
- printf STDOUT ("%08x%s", length($duf), $duf);
- $packets_read++;
- }
- } else {
- # within the nurse process
- $0 = "TTYtter (waiting $wait_time sec to connect to stream)";
- sleep $wait_time;
- $curlpid = 0;
- $replarg = ($streamallreplies) ? '&replies=all' : '';
- &sigify(sub {
- kill 9, $curlpid if ($curlpid);
- waitpid $curlpid, 0 unless (!$curlpid);
- $curlpid = 0;
- kill 9, $$;
- }, qw(CHLD PIPE));
- &sigify(sub {
- kill 9, $curlpid if ($curlpid);
- }, qw(INT HUP TERM)); # which will cascade into SIGCHLD
- ($comm, $args, $data) = &$stringify_args($baseagent,
- [ $streamurl, "delimited=length${replarg}" ],
- undef, undef,
- '-s',
- '-A', "TTYtter_Streaming/$TTYtter_VERSION",
- '-N',
- '-H', 'Expect:');
- ($curlpid = open(K, "|$comm")) || die("failed curl: $!\n");
- printf STDOUT ("%08x", $curlpid);
-
- # "DIE QUICKLY"
- $0 = "TTYtter (streaming socket nurse thread to ${curlpid})";
-
- select(K); $|++; select(STDOUT); $|++;
- print K "$args\n";
- close(K);
- waitpid $curlpid, 0;
- $curlpid = 0;
- kill 9, $$;
- }
-}
-
-# handle a set of events acquired from the streaming socket.
-# ordinarily only the background is calling this.
-sub streamevents {
- my (@events) = (@_);
- my $w;
- my @x;
- my %k; # need temporary dedupe
-
- foreach $w (@events) {
- my $tmp;
-
- # don't send non-data events (yet).
- next if ($w->{'packet'} ne 'data');
-
- # try to get PID information if available for faster shutdown
- $nnursepid = 0+($w->{'pid'});
- if ($nnursepid != $nursepid) {
- $nursepid = $nnursepid;
- print $stdout
-"-- got new pid of streaming nurse socket process: $nursepid\n"
- if ($verbose);
- }
- $ncurlpid = 0+($w->{'curlpid'});
- if ($ncurlpid != $curlpid) {
- $curlpid = $ncurlpid;
- print $stdout
-"-- got new pid of streaming curl process: $ncurlpid\n"
- if ($verbose);
- }
-
- # we don't use this (yet).
- next if ($w->{'payload'}->{'friends'});
-
- sleep 5 while ($suspend_output > 0);
-
- # dispatch tweets
- if ($w->{'payload'}->{'text'} && !$notimeline) {
- # normalize the tweet first.
- my $payload = &normalizejson($w->{'payload'});
- my $sid = $payload->{'id_str'};
-
- $payload->{'tag'}->{'type'} = 'timeline';
- $payload->{'tag'}->{'payload'} = 'stream';
-
- # filter replies from streaming socket if the
- # user requested it. use $tweettype to determine
- # this so the user can interpose custom logic.
- if ($nostreamreplies) {
- my $sn = &descape(
- $payload->{'user'}->{'screen_name'});
- my $text = &descape($payload->{'text'});
- next if (&$tweettype($payload, $sn, $text) eq
- 'reply');
- }
-
- # finally, filter everything else and dedupe.
- unless (length($id_cache{$sid}) ||
- $filter_next{$sid} ||
- $k{$sid}) {
- &tdisplay([ $payload ]);
- $k{$sid}++;
- }
-
- # roll *_id so that we don't do unnecessary work
- # testing the API. don't roll fetch_id, search uses
- # it. don't roll if last_id was zero, because that
- # means we are streaming *before* the API backfetch.
- $last_id = $sid unless (!$last_id);
- }
-
- # dispatch DMs
- elsif (($tmp = $w->{'payload'}->{'direct_message'}) &&
- $dmpause) {
- &dmrefresh(0, 0, [ $tmp ]);
- # don't roll last_dm yet.
- }
-
- # must be an event. see if standardevent can make sense of it.
- elsif (!$notimeline) {
- $w = $w->{'payload'};
- my $sou_sn =
- &descape($w->{'source'}->{'screen_name'});
- if (!length($sou_sn) || !$filterusers_sub ||
- !&$filterusers_sub($sou_sn)) {
- &send_removereadline if ($termrl);
- &$eventhandle($w);
- $wrapseq = 1;
- &send_repaint if ($termrl);
- }
- }
- }
-}
-
-# REST API support
-#
-# thump for timeline
-# THIS MUST ONLY BE RUN BY THE BACKGROUND.
-sub refresh {
- my $interactive = shift;
- my $relative_last_id = shift;
- my $k;
- my $my_json_ref = undef;
- my $i;
- my @streams = ();
- my $dont_roll_back_too_far = 0;
-
- # this mixes all the tweet streams (timeline, hashtags, replies
- # and lists) into a single unified data river.
- # backload can be zero, but this will still work since &grabjson
- # sees a count of zero as "default."
-
- # first, get my own timeline
- # note that anonymous has no timeline (but they can sample the
- # stream)
- unless ($notimeline || $anonymous) {
- # in streaming mode, use $last_id
- # in API mode, use $fetch_id
- my $base_json_ref = &grabjson($url,
- ($dostream) ? $last_id : $fetch_id,
- 0,
- (($last_id) ? 250 : $fetchwanted || $backload), {
- "type" => "timeline",
- "payload" => "api"
- }, 1);
- # if I can't get my own timeline, ABORT! highest priority!
- return if (!defined($base_json_ref) ||
- ref($base_json_ref) ne 'ARRAY');
-
- # we have to filter against the ID cache right now, because
- # we might not have any other streams!
- if ($fetch_id && $last_id) {
- $my_json_ref = [];
- my $l;
- my %k; # need temporary dedupe
- foreach $l (@{ $base_json_ref }) {
- unless (length($id_cache{$l->{'id_str'}}) ||
- $filter_next{$l->{'id_str'}} ||
- $k{$l->{'id_str'}}) {
- push(@{ $my_json_ref }, $l);
- $k{$l->{'id_str'}}++;
- }
- }
- } else {
- $my_json_ref = $base_json_ref;
- }
- }
-
- # add stream for replies, if requested
- if ($mentions) {
- # same thing
- my $r = &grabjson($rurl,
- ($dostream && !$nostreamreplies) ? $last_id : $fetch_id,
- 0,
- (($last_id) ? 250
- : $fetchwanted || $backload), {
- "type" => "reply",
- "payload" => ""
- }, 1);
- push(@streams, $r)
- if (defined($r) &&
- ref($r) eq 'ARRAY' &&
- scalar(@{ $r }));
- }
-
- # next handle hashtags and tracktags
- # failure here does not abort, because search may be down independently
- # of the main timeline.
- if (!$notrack && scalar(@trackstrings)) {
- my $r;
- my $k;
- my $l;
-
- if (!$last_id) {
- $l = &min($backload, $searchhits);
- } else {
- $l = (($fetchwanted) ? $fetchwanted :
- &max(100, $searchhits));
- }
- # temporarily squelch server complaints (see below)
- $muffle_server_messages = 1 unless ($verbose);
- foreach $k (@trackstrings) {
- # use fetch_id here in both modes.
- $r = &grabjson("$queryurl?${k}&result_type=recent",
- $fetch_id, 0, $l, {
- "type" => "search",
- "payload" => $k
- }, 1);
- # depending on the state of the search API, we might be using
- # a bogus search ID that is too far back. so if this fails,
- # try again with last_id, but not if we're streaming (it
- # will always fetch zero).
- if (!defined($r) || ref($r) ne 'ARRAY' || !$dostream) {
- print $stdout "-- search retry $k attempted with last_id\n"
- if ($verbose);
- $r = &grabjson("$queryurl?${k}&result_type=recent",
- $last_id, 0, $l, {
- "type" => "search",
- "payload" => $k
- }, 1);
- $dont_roll_back_too_far = 1;
- }
- # or maybe not even then?
- if (!defined($r) || ref($r) ne 'ARRAY') {
- print $stdout "-- search retry $k attempted with zero!\n"
- if ($verbose);
- $r = &grabjson("$queryurl?${k}&result_type=recent",
- 0, 0, $l, {
- "type" => "search",
- "payload" => $k
- }, 1);
- $dont_roll_back_too_far = 1;
- }
- push(@streams, $r)
- if (defined($r) &&
- ref($r) eq 'ARRAY' &&
- scalar(@{ $r }));
- }
- $muffle_server_messages = 0;
- }
-
- # add stream for lists we have on with /set lists, and tag it with
- # the list.
- if (scalar(@listlist)) {
- foreach $k (@listlist) {
- # always use fetch_id
- my $r = &grabjson(
- "${statusliurl}?owner_screen_name=".$k->[0].'&slug='.$k->[1],
- $fetch_id, 0,
- (($last_id) ? 250 : $fetchwanted), {
- "type" => "list",
- "payload" => ($k->[0] ne $whoami) ?
- "$k->[0]/$k->[1]" :
- "$k->[1]"
- }, 1);
- push(@streams, $r)
- if (defined($r) && ref($r) eq 'ARRAY' &&
- scalar(@{ $r }));
- }
- }
-
- $fetchwanted = 0; # done with that.
- # now, streamix all the streams into my_json_ref, discarding duplicates
- # a simple hash lookup is no good; it has to be iterative. because of
- # that, we might as well just splice it in here and save a sort later.
- # the streammix logic is unnecessarily complex, probably.
- # remember, the most recent tweets are FIRST.
- if (scalar(@streams)) {
- my $j;
- my $k;
- my $l = scalar(@{ $my_json_ref });
- my $m;
- my $n;
-
- foreach $n (@streams) {
- SMIX0: foreach $j (@{ $n }) {
- my $id = $j->{'id_str'}; # for ease of use
- # possible to happen if search tryhard is on
- next SMIX0 if ($id < $fetch_id);
-
- # filter this lot against the id cache
- # and any tweets we just filtered.
- next SMIX0 if (length($id_cache{$id}) &&
- $fetch_id);
- next SMIX0 if ($filter_next{$id} &&
- $fetch_id);
-
- if (!$l) { # degenerate case
- push (@{ $my_json_ref }, $j);
- $l++;
- next SMIX0;
- }
-
- # find the same ID, or one just before,
- # and splice in
- $m = -1;
- SMIX1: for($i=0; $i<$l; $i++) {
- next SMIX0 # it's a duplicate
- if($my_json_ref->[$i]->{'id_str'} == $id);
- if($my_json_ref->[$i]->{'id_str'} < $id) {
- $m = $i;
- last SMIX1; # got it
- }
- }
- if ($m == -1) { # didn't find
- push (@{ $my_json_ref }, $j);
- } elsif ($m == 0) { # degenerate case
- unshift (@{ $my_json_ref }, $j);
- } else { # did find, so splice
- splice(@{ $my_json_ref }, $m, 0,
- $j);
- }
- $l++;
- }
- }
- }
- %filter_next = ();
-
- # fetch_id gyration. initially start with last_id, then roll. we
- # want to keep a window, though, so we try to pick a sensible value
- # that doesn't fetch too much but includes some overlap. we can't
- # do computations on the ID itself, because it's "opaque."
- $fetch_id = 0 if ($last_id == 0);
- &send_removereadline if ($termrl);
- if ($dont_refresh_first_time) {
- $last_id = &max($my_json_ref->[0]->{'id_str'}, $last_id);
- } else {
- ($last_id, $crap) =
- &tdisplay($my_json_ref, undef, $relative_last_id);
- }
- my $new_fi = (scalar(@{ $my_json_ref })) ?
- $my_json_ref->[(scalar(@{ $my_json_ref })-1)]->{'id_str'} :
- '';
- # try to widen the window to a "reasonable amount"
- $fetch_id = ($fetch_id == 0) ? $last_id :
- (length($new_fi) && $new_fi ne $last_id
- && $new_fi > $fetch_id) ? $new_fi :
- ($relative_last_id > 0 && $relative_last_id ne $last_id &&
- $relative_last_id > $fetch_id) ?
- $relative_last_id : $fetch_id;
-
- print $stdout
-"-- last_id $last_id, fetch_id $fetch_id, rollback $relative_last_id\n".
-"-- (@{[ scalar(keys %id_cache) ]} cached)\n"
- if ($verbose);
- &send_removereadline if ($termrl);
- &$conclude;
- $wrapseq = 1;
- &send_repaint if ($termrl);
-}
-
-# convenience function for filters (see below)
-sub killtw { my $j = shift; $filtered++; $filter_next{$j->{'id_str'}}++
- if ($is_background); }
-
-# handle (i.e., display) an array of tweets in standard format
-sub tdisplay { # used by both synchronous /again and asynchronous refreshes
- my $my_json_ref = shift;
- my $class = shift;
- my $relative_last_id = shift;
- my $mini_id = shift;
- my $printed = 0;
- my $disp_max = &min($print_max, scalar(@{ $my_json_ref }));
- my $save_counter = -1;
- my $i;
- my $j;
-
- if ($disp_max) { # null list may be valid if we get code 304
- unless ($is_background) { # reset store hash each console
- if ($mini_id) {
-#TODO
-# generalize this at some point instead of hardcoded menu codes
-# maybe an ma0-mz9?
- $save_counter = $tweet_counter;
- $tweet_counter = $mini_split;
- for(0..9) {
- undef $store_hash{"zz$_"};
- }
- }# else {
- # $tweet_counter = $back_split;
- # %store_hash = ();
- #}
- }
- for($i = $disp_max; $i > 0; $i--) {
- my $g = ($i-1);
- $j = $my_json_ref->[$g];
- my $id = $j->{'id_str'};
- my $sn = $j->{'user'}->{'screen_name'};
- next if (!length($sn));
- $sn = lc(&descape($sn));
-
- #
- # implement filter stages:
- # do so in such a way that we can toss tweets out
- # quickly, because multiple layers eat CPU!
- #
-
- # zeroth: if this is us, do not filter.
- if (($anonymous || $sn ne $whoami) && !($nofilter)) {
-
- # first, filterusers. this is very fast.
- # do for the tweet
- (&killtw($j), next) if
- ($filterusers_sub &&
- &$filterusers_sub($sn));
- # and if the tweet has a retweeted status, do for
- # that.
- (&killtw($j), next) if
- ($j->{'retweeted_status'} &&
- $filterusers_sub &&
- &$filterusers_sub(lc(&descape($j->
- {'retweeted_status'}->
- {'user'}->{'screen_name'}))));
-
- # second, filterrts. this is almost as fast.
- (&killtw($j), next) if
- ($filterrts_sub &&
- length($j->{'retweeted_status'}->{'id_str'})&&
- &$filterrts_sub($sn));
-
- # third, filteratonly. this has a fast case and a
- # slow case.
- my $tex = &descape($j->{'text'});
- (&killtw($j), next) if
- ($filteratonly_sub &&
- &$filteratonly_sub($sn) && # fast test
- $tex !~ /\@$whoami\b/i); # slow test
-
- # fourth, filterats. this is somewhat expensive.
- (&killtw($j), next) if ($filterats_c &&
- &$filterats_c($tex));
-
- # finally, classic -filter. this is the most expensive.
- (&killtw($j), next) if ($filter_c && &$filter_c($tex));
- }
-
- # damn it, user may actually want this tweet.
- # assign menu codes and place into caches
- $key = (($is_background) ? '' : 'z' ).
- substr($alphabet, $tweet_counter/10, 1) .
- $tweet_counter % 10;
- $tweet_counter =
- ($tweet_counter == 259) ? $mini_split :
- ($tweet_counter == ($mini_split - 1))
- ? 0 : ($tweet_counter+1);
- $j->{'menu_select'} = $key;
- $key = lc($key);
-
- # recover ID cache memory: find the old ID with this
- # menu code and remove it, then add the new one
- # except if this is the foreground. we don't use this
- # in the foreground.
- if ($is_background) {
- delete $id_cache{$store_hash{$key}->{'id_str'}};
- $id_cache{$id} = $key;
- }
-
- # finally store in menu code cache
- $store_hash{$key} = $j;
-
- sleep 5 while ($suspend_output > 0);
- &send_removereadline if ($termrl);
- $wrapseq++;
-
- $printed += scalar(&$handle($j,
- ($class || (($id <= $relative_last_id) ? 'again' :
- undef))));
- }
- }
- $tweet_counter = $save_counter if ($save_counter > -1);
- sleep 5 while ($suspend_output > 0);
- &$exception(6,"*** warning: more tweets than menu codes; truncated\n")
- if (scalar(@{ $my_json_ref }) > $print_max);
- if (($interactive || $verbose) && !$printed) {
- &send_removereadline if ($termrl);
- print $stdout "-- sorry, nothing to display.\n";
- $wrapseq = 1;
- }
- return (&max($my_json_ref->[0]->{'id_str'}, $last_id), $j);
-}
-
-sub dt_tdisplay {
- my $my_json_ref = shift;
- my $class = shift;
- if (defined($my_json_ref)
- && ref($my_json_ref) eq 'ARRAY'
- && scalar(@{ $my_json_ref })) {
- my ($crap, $art) = &tdisplay($my_json_ref, $class);
- unless ($timestamp) {
- my ($time, $ts1) = &$wraptime(
-$my_json_ref->[(&min($print_max,scalar(@{ $my_json_ref }))-1)]->{'created_at'});
- my ($time, $ts2) = &$wraptime($art->{'created_at'});
- print $stdout &wwrap(
- "-- update covers $ts1 thru $ts2\n");
- }
- &$conclude;
- }
-}
-
-# thump for DMs
-sub dmrefresh {
- my $interactive = shift;
- my $sent_dm = shift;
- # for streaming API to inject DMs it receives
- my $my_json_ref = shift;
-
- if ($anonymous) {
- print $stdout
- "-- sorry, you can't read DMs 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); # NOT last_dm
-
- $my_json_ref = &grabjson((($sent_dm) ? $dmsenturl : $dmurl),
- (($sent_dm) ? 0 : $last_dm), 0, $dmfetchwanted, undef, 1)
- if (!defined($my_json_ref) ||
- ref($my_json_ref) ne 'ARRAY');
- return if (!defined($my_json_ref)
- || ref($my_json_ref) ne 'ARRAY');
-
- my $orig_last_dm = $last_dm;
- $last_dm = 0 if ($sent_dm);
-
- $dmfetchwanted = 0;
- my $printed = 0;
- my $max = 0;
- my $disp_max = &min($print_max, scalar(@{ $my_json_ref }));
- my $i;
- my $g;
- my $key;
-
- if ($disp_max) { # an empty list can be valid
- if ($dm_first_time) {
- sleep 5 while ($suspend_output > 0);
- &send_removereadline if ($termrl);
- print $stdout
- "-- checking for most recent direct messages:\n";
- $disp_max = 2;
- $interactive = 1;
- }
- for($i = $disp_max; $i > 0; $i--) {
- $g = ($i-1);
- my $j = $my_json_ref->[$g];
- next if (!$sent_dm && $j->{'id_str'} <= $last_dm);
- next if (!length($j->{'sender'}->{'screen_name'}) ||
- !length($j->{'recipient'}->{'screen_name'}));
-
- $key = substr($alphabet, $dm_counter/10, 1) .
- $dm_counter % 10;
- $dm_counter =
- ($dm_counter == 259) ? 0 :
- ($dm_counter+1);
- $j->{'menu_select'} = $key;
- $dm_store_hash{lc($key)} = $j;
-
- sleep 5 while ($suspend_output > 0);
- &send_removereadline if ($termrl);
- $wrapseq++;
-
- $printed += scalar(&$dmhandle($j));
- }
- $max = $my_json_ref->[0]->{'id_str'};
- }
- sleep 5 while ($suspend_output > 0);
- if (($interactive || $verbose) && !$printed && !$dm_first_time) {
- &send_removereadline if ($termrl);
- print $stdout (($sent_dm)
- ? "-- you haven't sent anything yet.\n"
- : "-- sorry, no new direct messages.\n");
- $wrapseq = 1;
- }
- $last_dm = ($sent_dm) ? $orig_last_dm
- : &max($last_dm, $max);
- $dm_first_time = 0 if ($last_dm || !scalar(@{ $my_json_ref }));
- print $stdout "-- dm bookmark is $last_dm.\n" if ($verbose);
- &$dmconclude;
- &send_repaint if ($termrl);
-}
-
-# post an update
-# this is a general API function that handles status updates and sending DMs.
-sub updatest {
- my $string = shift;
- my $interactive = shift;
- my $in_reply_to = shift;
- my $user_name_dm = shift;
- my $rt_id = shift; # even if this is set, string should also be set.
- my $urle = '';
- my $i;
- my $subpid;
- my $istring;
-
- my $verb = (length($user_name_dm)) ? "DM $user_name_dm" :
- ($rt_id) ? 'RE-tweet' :
- 'tweet';
-
- if ($anonymous) {
- print $stdout
- "-- sorry, you can't $verb if you're anonymous.\n"
- if ($interactive);
- return 99;
- }
-
- # "the pastebrake"
- if (!$slowpost && !$verify && !$script) {
- if ((time() - $postbreak_time) < 5) {
- $postbreak_count++;
- if ($postbreak_count == 3) {
- print $stdout
- "-- you're posting pretty fast. did you mean to do that?\n".
- "-- waiting three seconds before taking the next set of tweets\n".
- "-- hit CTRL-C NOW! to kill TTYtter if you accidentally pasted in this window\n";
- sleep 3;
- $postbreak_count = 0;
- }
- } else {
- $postbreak_count = 0;
- }
- $postbreak_time = time();
- }
-
- my $payload = (length($user_name_dm)) ? 'text' : 'status';
- $string = &$prepost($string) unless ($user_name_dm || $rt_id);
-
- # YES, you *can* verify and slowpost. I thought about this and I
- # think I want to allow it.
- if ($verify && !$status) {
- my $answer;
-
- print $stdout
- &wwrap("-- verify you want to $verb: \"$string\"\n");
- $answer = lc(&linein(
- "-- send to server? (only y or Y is affirmative):"));
- if ($answer ne 'y') {
- print $stdout "-- ok, NOT sent to server.\n";
- return 97;
- }
- }
-
- unless ($rt_id) {
- $urle = '';
- foreach $i (unpack("${pack_magic}C*", $string)) {
- my $k = chr($i);
- if ($k =~ /[-._~a-zA-Z0-9]/) {
- $urle .= $k;
- } else {
- $k = sprintf("%02X", $i);
- $urle .= "%$k";
- }
- }
- }
-
- $user_name_dm = (length($user_name_dm)) ?
- "&user=$user_name_dm" : '';
-
- my $i = '';
- $i .= "source=TTYtter&" if ($authtype eq 'basic');
- $i .= "in_reply_to_status_id=${in_reply_to}&" if ($in_reply_to > 0);
- if (!$rt_id && defined $lat && defined $long && $location) {
- print $stdout "-- using lat/long: ($lat, $long)\n";
- $i .= "lat=${lat}&long=${long}&";
- } elsif ((defined $lat || defined $long) && $location && !$rt_id) {
- print $stdout
- "-- warning: incomplete location ($lat, $long) ignored\n";
- }
- $i .= "${payload}=${urle}${user_name_dm}" unless ($rt_id);
- $i .= "id=$rt_id" if ($rt_id);
- $slowpost += 0; if ($slowpost && !$script && !$status && !$silent) {
- if($pid = open(SLOWPOST, '-|')) {
- # pause background so that it doesn't kill itself
- # when this signal occurs.
- kill $SIGUSR1, $child;
- print $stdout &wwrap(
- "-- waiting $slowpost seconds to $verb, ^C cancels: \"$string\"\n");
- close(SLOWPOST); # this should wait for us
- if ($? > 256) {
- print $stdout
- "\n-- not sent, cancelled by user\n";
- return 97;
- }
- print $stdout "-- sending to server\n";
- kill $SIGUSR2, $child;
- &send_removereadline if ($termrl && $dostream);
- } else {
- $in_backticks = 1; # defeat END sub
- &sigify(sub { exit 254; }, qw(BREAK INT TERM PIPE));
- sleep $slowpost;
- exit 0;
- }
- }
- my $return = &backticks($baseagent, '/dev/null', undef,
- (length($user_name_dm)) ? $dmupdate :
- ($rt_id) ? "$rturl/${rt_id}.json" :
- $update, $i, 0, @wend);
- print $stdout "-- return --\n$return\n-- return --\n"
- if ($superverbose);
- if ($? > 0) {
- $x = $? >> 8;
- print $stdout <<"EOF" if ($interactive);
-${MAGENTA}*** warning: connect timeout or no confirmation received ($x)
-*** to attempt a resend, type %%${OFF}
-EOF
- return $?;
- }
- my $ec;
- if ($ec = &is_json_error($return)) {
- print $stdout <<"EOF" if ($interactive);
-${MAGENTA}*** warning: server error message received
-*** "$ec"${OFF}
-EOF
- return 98;
- }
- if ($ec = &is_fail_whale($return) ||
- $return =~ /^\[?\]?<!DOCTYPE\s+html/i ||
- $return =~ /^(Status:\s*)?50[0-9]\s/ ||
- $return =~ /^<html>/i ||
- $return =~ /^<\??xml\s+/) {
- print $stdout <<"EOF" if ($interactive);
-${MAGENTA}*** warning: Twitter Fail Whale${OFF}
-EOF
- return 98;
- }
- $lastpostid = &parsejson($return)->{'id_str'};
- unless ($user_name_dm || $rt_id) {
- $lasttwit = $string;
- &$postpost($string);
- }
- 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.
-sub central_cd_dispatch {
- my ($payload, $interactive, $update) = (@_);
- my $return = &backticks($baseagent, '/dev/null', undef,
- $update, $payload, 0, @wend);
- print $stdout "-- return --\n$return\n-- return --\n"
- if ($superverbose);
- if ($? > 0) {
- $x = $? >> 8;
- print $stdout <<"EOF" if ($interactive);
-${MAGENTA}*** warning: connect timeout or no confirmation received ($x)
-*** to attempt again, type %%${OFF}
-EOF
- return ($?, '');
- }
- my $ec;
- if ($ec = &is_json_error($return)) {
- print $stdout <<"EOF" if ($interactive);
-${MAGENTA}*** warning: server error message received
-*** "$ec"${OFF}
-EOF
- return (98, $return);
- }
- return (0, $return);
-}
-
-# the following functions may be user-exposed in a future version of
-# TTYtter, but are officially still "private interfaces."
-# delete a status
-sub deletest {
- my $id = shift;
- my $interactive = shift;
- my $url = $delurl;
-
- $url =~ s/%I/$id/;
- my ($en, $em) = &central_cd_dispatch("id=$id", $interactive, $url);
- print $stdout "-- tweet id #${id} has been removed\n"
- if ($interactive && !$en);
- print $stdout "*** (was the tweet already deleted?)\n"
- if ($interactive && $en);
- return 0;
-}
-
-# delete a DM
-sub deletedm {
- my $id = shift;
- my $interactive = shift;
-
- my ($en, $em) = &central_cd_dispatch("id=$id", $interactive, $dmdelurl);
- print $stdout "-- DM id #${id} has been removed\n"
- if ($interactive && !$en);
- print $stdout "*** (was the DM already deleted?)\n"
- if ($interactive && $en);
- return 0;
-}
-
-# create or destroy a favourite
-sub cordfav {
- my $id = shift;
- my $interactive = shift;
- my $basefav = shift;
- my $text = shift;
- my $verb = shift;
-
- my ($en, $em) = &central_cd_dispatch("id=$id", $interactive, $basefav);
- print $stdout "-- favourite $verb for tweet id #${id}: \"$text\"\n"
- if ($interactive && !$en);
- print $stdout "*** (was the favourite already ${verb}?)\n"
- if ($interactive && $en);
- return 0;
-}
-
-# follow or unfollow a user
-sub foruuser {
- my $uname = shift;
- my $interactive = shift;
- my $basef = shift;
- my $verb = shift;
-
- my ($en, $em) = &central_cd_dispatch("screen_name=$uname",
- $interactive, $basef);
- print $stdout "-- ok, you have $verb following user $uname.\n"
- if ($interactive && !$en);
- return 0;
-}
-
-# block or unblock a user
-sub boruuser {
- my $uname = shift;
- my $interactive = shift;
- my $basef = shift;
- my $verb = shift;
-
- my ($en, $em) = &central_cd_dispatch("screen_name=$uname",
- $interactive, $basef);
- print $stdout "-- ok, you have $verb blocking user $uname.\n"
- if ($interactive && !$en);
- return 0;
-}
-
-# enable or disable retweets for a user
-sub rtsonoffuser {
- my $uname = shift;
- my $interactive = shift;
- my $selection = shift;
- my $verb = ($selection) ? 'enabled' : 'disabled';
- my $tval = ($selection) ? 'true' : 'false';
-
- my ($en, $em) = &central_cd_dispatch(
- "retweets=${tval}&screen_name=${uname}",
- $interactive, $frupdurl);
- print $stdout "-- ok, you have ${verb} retweets for user $uname.\n"
- if ($interactive && !$en);
- return 0;
-}
-
-#### TTYtter internal API utility functions ####
-# ... which your API *can* call
-
-# gets and returns the contents of a URL (optionally pass a POST body)
-sub graburl {
- my $resource = shift;
- my $data = shift;
-
- return &backticks($baseagent,
- '/dev/null', undef, $resource, $data,
- 1, @wind);
-}
-
-# format a tweet based on user options
-sub standardtweet {
- my $ref = shift;
- my $nocolour = shift;
-
- my $sn = &descape($ref->{'user'}->{'screen_name'});
- my $tweet = &descape($ref->{'text'});
- my $colour;
- my $g;
- my $h;
-
- # wordwrap really ruins our day here, thanks a lot, @augmentedfourth
- # have to insinuate the ansi sequences after the string is wordwrapped
-
- $g = $colour = ${'CC' . scalar(&$tweettype($ref, $sn, $tweet)) }
- unless ($nocolour);
- $colour = $OFF . $colour
- unless ($nocolour);
-
- # prepend screen name "badges"
- $sn = "\@$sn" if ($ref->{'in_reply_to_status_id_str'} > 0);
- $sn = "+$sn" if ($ref->{'user'}->{'geo_enabled'} eq 'true' &&
- (($ref->{'geo'}->{'coordinates'}->[0] ne 'undef' &&
- length($ref->{'geo'}->{'coordinates'}->[0]) &&
- $ref->{'geo'}->{'coordinates'}->[1] ne 'undef' &&
- length($ref->{'geo'}->{'coordinates'}->[0])) ||
- length($ref->{'place'}->{'id'})));
- $sn = "%$sn" if (length($ref->{'retweeted_status'}->{'id_str'}));
- $sn = "*$sn" if ($ref->{'source'} =~ /TTYtter/ && $ttytteristas);
- # prepend list information, if this tweet originated from a list
- $sn = "($ref->{'tag'}->{'payload'})$sn"
- if (length($ref->{'tag'}->{'payload'}) &&
- $ref->{'tag'}->{'type'} eq 'list');
- $tweet = "<$sn> $tweet";
- # twitter doesn't always do this right.
- $h = $ref->{'retweet_count'}; $h += 0; #$h = "${h}+" if ($h >= 100);
- # twitter doesn't always handle single retweets right. good f'n grief.
- $tweet = "(x${h}) $tweet" if ($h > 1 && !$nonewrts);
- # br3nda's modified timestamp patch
- if ($timestamp) {
- my ($time, $ts) = &$wraptime($ref->{'created_at'});
- $tweet = "[$ts] $tweet";
- }
-
- # pull it all together
- $tweet = &wwrap($tweet, ($wrapseq <= 1) ? ((&$prompt(1))[1]) : 0)
- if ($wrap); # remember to account for prompt length on #1
- $tweet =~ s/^([^<]*)<([^>]+)>/${g}\1<${EM}\2${colour}>/
- unless ($nocolour);
- $tweet =~ s/\n*$//;
- $tweet .= ($nocolour) ? "\n" : "$OFF\n";
-
- # highlight anything that we have in track
- if(scalar(@tracktags)) { # I'm paranoid
- foreach $h (@tracktags) {
- $h =~ s/^"//; $h =~ s/"$//; # just in case
-$tweet =~ s/(^|[^a-zA-Z0-9])($h)([^a-zA-Z0-9]|$)/\1${EM}\2${colour}\3/ig
- unless ($nocolour);
- }
- }
-
- # smb's underline/bold patch goes on last (modified for lists)
- unless ($nocolour) {
- # only do this after the < > portion.
- my $k = index($tweet, ">");
- my $botsub = substr($tweet, $k);
- my $topsub = substr($tweet, 0, $k);
- $botsub =~
-s/(^|[^a-zA-Z0-9_])\@([a-zA-Z0-9_\/]+)/\1\@${UNDER}\2${colour}/g;
- $tweet = $topsub . $botsub;
- }
-
- return $tweet;
-}
-
-# format a DM based on standard user options
-sub standarddm {
- my $ref = shift;
- my $nocolour = shift;
-
- my ($time, $ts) = &$wraptime($ref->{'created_at'});
- my $text = &descape($ref->{'text'});
- my $sns = &descape($ref->{'sender'}->{'screen_name'});
- if ($sns eq $whoami) {
- $sns = "->" . &descape($ref->{'recipient'}->{'screen_name'});
- }
- my $g = &wwrap("[DM d$ref->{'menu_select'}]".
- "[$sns/$ts] $text", ($wrapseq <= 1) ? ((&$prompt(1))[1]) : 0);
-
- $g =~ s/^\[DM ([^\/]+)\//${CCdm}[DM ${EM}\1${OFF}${CCdm}\//
- unless ($nocolour);
- $g =~ s/\n*$//;
- $g .= ($nocolour) ? "\n" : "$OFF\n";
- $g =~ s/(^|[^a-zA-Z0-9_])\@(\w+)/\1\@${UNDER}\2${OFF}${CCdm}/g
- unless ($nocolour);
- return $g;
-}
-
-# format an event record based on standard user options (mostly for
-# streaming API, perhaps REST API one day)
-sub standardevent {
- my $ref = shift;
- my $nocolour = shift;
-
- my $g = '>>> ';
- my $verb = &descape($ref->{'event'});
-
- # https://dev.twitter.com/docs/streaming-apis/messages
-
- if (length($verb)) { # see below for server-level events
- my $tar_sn = '@'.&descape($ref->{'target'}->{'screen_name'});
- my $sou_sn = '@'.&descape($ref->{'source'}->{'screen_name'});
-
- my $tar_list_name = '';
- my $tar_list_desc = '';
-
- # For all verbs starting with "list", get name and desc
- if ($verb =~ m/^list/ ) {
- $tar_list_name = &descape($ref->{'target_object'}->{'full_name'});
- $tar_list_desc = &descape($ref->{'target_object'}->{'description'});
- }
-
- if ($verb eq 'favorite' || $verb eq 'unfavorite') {
- my $rto = &destroy_all_tco($ref->{'target_object'});
- my $txt = &descape($rto->{'text'});
- $g .=
- "$sou_sn just ${verb}d ${tar_sn}'s tweet: \"$txt\"";
- } elsif ($verb eq 'follow') {
- $g .= "$sou_sn is now following $tar_sn";
- } elsif ($verb eq 'user_update') {
- $g .= "$sou_sn updated their profile (/whois $sou_sn to see)";
- } elsif ($verb eq 'list_member_added') {
- $g .= "$sou_sn added $tar_sn to the list \"$tar_list_desc\" ($tar_list_name)";
- } elsif ($verb eq 'list_member_removed') {
- $g .= "$sou_sn removed $tar_sn from the list \"$tar_list_desc\" ($tar_list_name)";
- } elsif ($verb eq 'list_user_subscribed') {
- $g .= "$sou_sn is now following the list \"$tar_list_desc\" ($tar_list_name) from $tar_sn";
- } elsif ($verb eq 'list_user_unsubscribed') {
- $g .= "$sou_sn is no longer following the list \"$tar_list_desc\" ($tar_list_name) from $tar_sn";
- } elsif ($verb eq 'list_created') {
- $g .= "$sou_sn created the new list \"$tar_list_desc\" ($tar_list_name)";
- } elsif ($verb eq 'list_destroyed') {
- $g .= "$sou_sn destroyed the list \"$tar_list_desc\" ($tar_list_name)";
- } elsif ($verb eq 'list_updated') {
- $g .= "$sou_sn updated the list \"$tar_list_desc\" ($tar_list_name)";
- } elsif ($verb eq 'block' || $verb eq 'unblock') {
- $g .= "$sou_sn ${verb}ed $tar_sn ($tar_sn is not ".
- "notified)";
- } elsif ($verb eq 'access_revoked') {
- $g .= "$sou_sn revoked oAuth access to $tar_sn";
- } elsif ($verb eq 'access_unrevoked') {
- $g .= "$sou_sn restored oAuth access to $tar_sn";
- } else {
- # try to handle new types of events we don't
- # recognize yet.
- $verb .= ($verb =~ /e$/) ? 'd' : 'ed';
- $g .= "$sou_sn $verb $tar_sn (basic)";
- }
-
- # server events ("public stream messages") are handled differently.
- # we support almost all except for the ones that are irrelevant to
- # this medium.
-
- } elsif ($ref->{'delete'}) {
- # this is the best we can do -- it's already on the screen!
- # we don't want to make it easy which tweet it is, since that
- # would be embarrassing, so just say a delete occurred.
- $g .=
- "tweet ID# ".$ref->{'delete'}->{'status'}->{'id_str'}.
- " deleted by server";
- } elsif ($ref->{'status_withheld'}) {
- # Twitter doesn't document id_str as available here. check.
- if (!length($ref->{'status_withheld'}->{'id_str'})) {
- # do nothing right now
- } else { $g .=
- "tweet ID# ".$ref->{'status_withheld'}->{'id_str'}.
- " censored by server in your country";
- }
- } elsif ($ref->{'user_withheld'}) {
- $g .=
- "user ID# ".$ref->{'user_withheld'}->{'user_id'}.
- " censored by server in your country";
- } elsif ($ref->{'disconnect'}) {
- $g .=
- "DISCONNECTED BY SERVER (".$ref->{'disconnect'}->{'code'}.
- "); will retry: ".$ref->{'disconnect'}->{'reason'};
- } else {
- # we have no idea what this is. just BS our way out.
- $g .= "unknown server event received (non-fatal)";
- }
-
- if ($timestamp) {
- my ($time, $ts) = &$wraptime($ref->{'created_at'});
- $g = "[$ts] $g";
- }
-
- $g = &wwrap("$g\n", ($wrapseq <= 1) ? ((&$prompt(1))[1]) : 0);
- # highlight screen names
- $g =~
-s/(^|[^a-zA-Z0-9_])\@([a-zA-Z0-9_\-\/]+)/\1\@${UNDER}\2${OFF}/g
- unless ($nocolour);
-
- return $g;
-}
-
-# for future expansion: this is the declared API callable method
-# for executing a command as if the console had typed it.
-sub ucommand {
- die("** can't call &ucommand during multi-module loading.\n")
- if ($multi_module_mode == -1);
- &prinput(@_);
-}
-
-# your application can also call &grabjson to get a hashref
-# corresponding to parsed JSON from an arbitrary resource.
-# see that function later on.
-
-
-#### DEFAULT TTYtter INTERNAL API METHODS ####
-# don't change these here. instead, use -exts=yourlibrary.pl and set there.
-# note that these are all anonymous subroutine references.
-# anything you don't define is overwritten by the defaults.
-# it's better'n'superclasses.
-# NOTE: defaultaddaction, defaultmain and defaultprompt
-# are all defined in the "console" section above for
-# clarity.
-
-# this first set are the multi-module aware ones.
-
-# the standard iterator for multi-module methods
-sub multi_module_dispatch {
- my $default = shift;
- my $dispatch_chain = shift;
- my $rv_handler = shift;
- my @args = @_;
-
- local $dispatch_ref; # on purpose; get_key/set_key may need it
- # $*_call_default is a global
- $did_call_default = 0;
- $this_call_default = 0;
- $multi_module_context = 0;
-
- if ($rv_handler == 0) {
- $rv_handler = sub {
- return 0;
- };
- }
-
- # fall through to default if no dispatch chain
- if (!scalar(@{ $dispatch_chain })) {
- return &$default(@args);
- }
- foreach $dispatch_ref (@{ $dispatch_chain }) {
- # each reference has the code, and the file that specified it.
- # set up a multi-module context and run that function. if the
- # default ever gets called, we log it to tell the multi-module
- # handler to call the default at the end.
-
- my $rv;
- my $irv;
- my $caller = (caller(1))[3];
- $caller =~ s/^main::multi//;
-
- $multi_module_context = 1; # defaults then know to defer
- $this_call_default = 0;
- $store = $master_store->{ $dispatch_ref->[0] };
- print "-- calling \$$caller in $dispatch_ref->[0]\n"
- if ($verbose);
- my $code_ref = $dispatch_ref->[1];
- $rv = &$rv_handler(@irv = &$code_ref(@args));
- $multi_module_context = 0;
- if ($rv & 4) {
- # rv_handler indicating to call default and halt
- # if it was called.
- return &$default(@args) if ($did_call_default);
- }
- if ($rv & 2) {
- # rv_handler indicating to make new @args from @irv
- @args = @irv;
- }
- if ($rv & 1) {
- # rv_handler indicating to halt early. do so.
- return (wantarray) ? @irv : $irv[0];
- }
- }
- $multi_module_context = 0;
- return &$default(@args) if ($did_call_default);
- return (wantarray) ? @irv : $irv[0];
-}
-
-# these are the stubs that call the dispatcher.
-sub multiaddaction {
- &multi_module_dispatch(\&defaultaddaction, \@m_addaction, sub{
- # return immediately on the first extension to accept
- return (shift>0);
- }, @_);
-}
-sub multiconclude {
- &multi_module_dispatch(\&defaultconclude, \@m_conclude, 0, @_);
-}
-sub multidmconclude {
- &multi_module_dispatch(\&defaultdmconclude, \@m_dmconclude, 0, @_);
-}
-sub multidmhandle {
- &multi_module_dispatch(\&defaultdmhandle, \@m_dmhandle, sub {
- my $rv = shift;
-
- # skip default calls.
- return 0 if ($this_call_default);
-
- # if not a default call, and the DM was refused for
- # processing by this extension, then the DM is now
- # suppressed. do not call any other extensions after this.
- # even if it ends in suppression, we still call the default
- # if it was ever called before.
- return 5 if ($rv == 0);
-
- # if accepted in any manner, keep calling.
- return 0;
- }, @_);
-}
-sub multieventhandle {
- &multi_module_dispatch(\&defaulteventhandle, \@m_eventhandle, sub {
- my $rv = shift;
-
- # skip default calls.
- return 0 if ($this_call_default);
-
- # if not a default call, and the event was refused for
- # processing by this extension, then the event is now
- # suppressed. do not call any other extensions after this.
- # even if it ends in suppression, we still call the default
- # if it was ever called before.
- return 5 if ($rv == 0);
-
- # if accepted in any manner, keep calling.
- return 0;
- }, @_);
-}
-sub multiexception {
- # this is a secret option for people who want to suppress errors.
- if ($exception_is_maskable) {
- &multi_module_dispatch(\&defaultexception, \@m_exception, sub {
- my $rv = shift;
-
- # same logic as handle/dmhandle, except return -1-
- # to mask from subsequent extensions.
- return 0 if ($this_call_default);
- return 5 if ($rv);
- return 0;
- }, @_);
- } else {
- &multi_module_dispatch(
- \&defaultexception, \@m_exception, 0, @_);
- }
-}
-sub multishutdown {
- return if ($shutdown_already_called++);
- &multi_module_dispatch(\&defaultshutdown, \@m_shutdown, 0, @_);
-}
-
-sub multiuserhandle {
- &multi_module_dispatch(\&defaultuserhandle, \@m_userhandle, sub{
- # skip default calls.
- return 0 if ($this_call_default);
-
- # return immediately on the first extension to accept
- return (shift>0);
- }, @_);
-}
-sub multilisthandle {
- &multi_module_dispatch(\&defaultlisthandle, \@m_listhandle, sub{
- # skip default calls.
- return 0 if ($this_call_default);
-
- # return immediately on the first extension to accept
- return (shift>0);
- }, @_);
-}
-sub multihandle {
- &multi_module_dispatch(\&defaulthandle, \@m_handle, sub {
- my $rv = shift;
-
- # skip default calls.
- return 0 if ($this_call_default);
-
- # if not a default call, and the tweet was refused for
- # processing by this extension, then the tweet is now
- # suppressed. do not call any other extensions after this.
- # even if it ends in suppression, we still call the default
- # if it was ever called before.
- return 5 if ($rv==0);
-
- # if accepted in any manner, keep calling.
- return 0;
- }, @_);
-}
-sub multiheartbeat {
- &multi_module_dispatch(\&defaultheartbeat, \@m_heartbeat, 0, @_);
-}
-sub multiprecommand {
- &multi_module_dispatch(\&defaultprecommand, \@m_precommand, sub {
- return 2; # feed subsequent chains the result.
- }, @_);
-}
-sub multiprepost {
- &multi_module_dispatch(\&defaultprepost, \@m_prepost, sub {
- return 2; # feed subsequent chains the result.
- }, @_);
-}
-sub multipostpost {
- &multi_module_dispatch(\&defaultpostpost, \@m_postpost, 0, @_);
-}
-sub multitweettype {
- &multi_module_dispatch(\&defaulttweettype, \@m_tweettype, sub {
- # if this module DID NOT call default, exit now.
- return (!$this_call_default);
- }, @_);
-}
-
-sub flag_default_call { $this_call_default++; $did_call_default++; }
-
-# now the actual default methods
-
-sub defaultexception {
- (&flag_default_call, return) if ($multi_module_context);
- my $msg_code = shift;
- return if ($msg_code == 2 && $muffle_server_messages);
- my $message = "@_";
- $message =~ s/\n*$//sg;
- if ($timestamp) {
- my ($time, $ts) = &$wraptime(scalar(localtime));
- $message = "[$ts] $message";
- $message =~ s/\n/\n[$ts] /sg;
- }
- &send_removereadline if ($termrl);
- $wrapseq = 1;
- print $stdout "${MAGENTA}${message}${OFF}\n";
- &send_repaint if ($termrl);
- $laststatus = 1;
-}
-sub defaultshutdown {
- (&flag_default_call, return) if ($multi_module_context);
-}
-sub defaultlisthandle {
- (&flag_default_call, return) if ($multi_module_context);
- my $list_ref = shift;
-
- print $streamout "*** for future expansion ***\n";
-
- return 1;
-}
-sub defaulthandle {
- (&flag_default_call, return) if ($multi_module_context);
- my $tweet_ref = shift;
- my $class = shift;
- my $dclass = ($verbose) ? "{$class,$tweet_ref->{'id_str'}} " : '';
- my $sn = &descape($tweet_ref->{'user'}->{'screen_name'});
- my $tweet = &descape($tweet_ref->{'text'});
- my $stweet = &standardtweet($tweet_ref);
- my $menu_select = $tweet_ref->{'menu_select'};
-
- $menu_select = (length($menu_select) && !$script)
- ? (($menu_select =~ /^z/) ?
- "${EM}${menu_select}>${OFF} " :
- "${menu_select}> ")
- : '';
-
- print $streamout $menu_select . $dclass . $stweet;
- &sendnotifies($tweet_ref, $class);
- return 1;
-}
-sub defaultuserhandle {
- (&flag_default_call, return) if ($multi_module_context);
-
- my $user_ref = shift;
- &userline($user_ref, $streamout);
- my $desc = &strim(&descape($user_ref->{'description'}));
- my $klen = ($wrap || 79) - 9;
- $klen = 10 if ($klen < 0);
- $desc = substr($desc, 0, $klen)."..." if (length($desc) > $klen);
- print $streamout (' "' . $desc . '"' . "\n") if (length($desc));
- return 1;
-}
-sub userline { # used by both $userhandle and /whois
- my $my_json_ref = shift;
- my $fh = shift;
-
- my $verified =
- ($my_json_ref->{'verified'} eq 'true') ?
- "${EM}(Verified)${OFF} " : '';
- my $protected =
- ($my_json_ref->{'protected'} eq 'true') ?
- "${EM}(Protected)${OFF} " : '';
- print $fh <<"EOF";
-${CCprompt}@{[ &descape($my_json_ref->{'name'}) ]}${OFF} (@{[ &descape($my_json_ref->{'screen_name'}) ]}) (f:$my_json_ref->{'friends_count'}/$my_json_ref->{'followers_count'}) (u:$my_json_ref->{'statuses_count'}) ${verified}${protected}
-EOF
- return;
-}
-sub sendnotifies { # this is a default subroutine of a sort, right?
- my $tweet_ref = shift;
- my $class = shift;
-
- my $sn = &descape($tweet_ref->{'user'}->{'screen_name'});
- my $tweet = &descape($tweet_ref->{'text'});
-
- # interactive? first time?
- unless (length($class) || !$last_id || !length($tweet)) {
- $class = scalar(&$tweettype($tweet_ref, $sn, $tweet));
- &notifytype_dispatch($class,
- &standardtweet($tweet_ref, 1), $tweet_ref)
- if ($notify_list{$class});
- }
-}
-
-sub defaulttweettype {
- (&flag_default_call, return) if ($multi_module_context);
- my $ref = shift;
- my $sn = shift;
- my $tweet = shift;
-
- # br3nda's and smb's modified colour patch
- unless ($anonymous) {
- if (lc($sn) eq $whoami) {
- # if it's me speaking, colour the line yellow
- return 'me';
- } elsif ($tweet =~ /\@$whoami(\b|$)/i) {
- # if I'm in the tweet, colour red
- return 'reply';
- }
- }
- if ($ref->{'class'} eq 'search') { # anonymous allows this too
- # if this is a search result, colour cyan
- return 'search';
- }
- if ($ref->{'tag'}->{'type'} eq 'list') { # anonymous allows this too
- return 'list';
- }
- return 'default';
-}
-
-sub defaultconclude {
- (&flag_default_call, return) if ($multi_module_context);
- if ($filtered && $filter_attribs{'count'}) {
- print $stdout "-- (filtered $filtered tweets)\n";
- $filtered = 0;
- }
-}
-
-sub defaultdmhandle {
- (&flag_default_call, return) if ($multi_module_context);
- my $dm_ref = shift;
- my $sns = &descape($dm_ref->{'sender'}->{'screen_name'});
-
- print $streamout &standarddm($dm_ref);
- &senddmnotifies($dm_ref) if ($sns ne $whoami);
- return 1;
-}
-
-sub senddmnotifies {
- my $dm_ref = shift;
- &notifytype_dispatch('DM', &standarddm($dm_ref, 1), $dm_ref)
- if ($notify_list{'dm'} && $last_dm);
-}
-
-sub defaulteventhandle {
- (&flag_default_call, return) if ($multi_module_context);
- my $event_ref = shift;
- # in this version, we silently filter delete events, but your
- # extension would still get them delivered.
- return 1 if ($event_ref->{'delete'});
- print $streamout &standardevent($event_ref);
- return 1;
-}
-
-sub defaultdmconclude {
- (&flag_default_call, return) if ($multi_module_context);
-}
-
-sub defaultheartbeat {
- (&flag_default_call, return) if ($multi_module_context);
-}
-
-# not much sense to multi-module protect these.
-sub defaultprecommand { return ("@_"); }
-sub defaultprepost { return ("@_"); }
-
-sub defaultpostpost {
- (&flag_default_call, return) if ($multi_module_context);
- my $line = shift;
- return if (!$termrl);
-
- # populate %readline_completion if readline is on
- while($line =~ s/^\@(\w+)\s+//) {
- $readline_completion{'@'.lc($1)}++;
- }
- if ($line =~ /^[dD]\s+(\w+)\s+/) {
- $readline_completion{'@'.lc($1)}++;
- }
-}
-
-sub defaultautocompletion {
- my ($text, $line, $start) = (@_);
- my $qmtext = quotemeta($text);
- my @proband;
- my @rlkeys;
-
- # handle / completion
- if ($start == 0 && $text =~ m#^/#) {
- return sort grep(/^$qmtext/i, '/history',
- '/print', '/quit', '/bye', '/again',
- '/wagain', '/whois', '/thump', '/dm',
- '/refresh', '/dmagain', '/set', '/help',
- '/reply', '/url', '/thread', '/retweet', '/replyall',
- '/replies', '/ruler', '/exit', '/me', '/vcheck',
- '/oretweet', '/eretweet', '/fretweet', '/liston',
- '/listoff', '/dmsent', '/rtsof', '/rtson', '/rtsoff',
- '/lists', '/withlist', '/add', '/padd', '/push',
- '/pop', '/followers', '/friends', '/lfollow',
- '/lleave', '/listfollowers', '/listfriends',
- '/unset', '/verbose', '/short', '/follow', '/unfollow',
- '/doesfollow', '/search', '/tron', '/troff',
- '/delete', '/deletelast', '/dump',
- '/track', '/trends', '/block', '/unblock',
- '/fave', '/faves', '/unfave', '/eval');
- }
- @rlkeys = keys(%readline_completion);
-
- # handle @ completion. this works slightly weird because
- # readline hands us the string WITHOUT the @, so we have to
- # test somewhat blindly. this works even if a future readline
- # DOES give us the word with @. also handles D, /wa, /wagain,
- # /a, /again, etc.
- if (($line =~ m#^(D|/wa|/wagain|/a|/again) #i) ||
- ($start == 1 && substr($line, 0, 1) eq '@') ||
- # this code is needed to prevent inline @ from flipping out
- ($start >= 1 && substr($line, ($start-2), 2) eq ' @')) {
- @proband = grep(/^\@$qmtext/i, @rlkeys);
- if (scalar(@proband)) {
- @proband = map { s/^\@//;$_ } @proband;
- return @proband;
- }
- }
- # definites that are left over, including @ if it were included
- if(scalar(@proband = grep(/^$qmtext/i, @rlkeys))) {
- return @proband;
- }
-
- # heuristics
- # URL completion (this doesn't always work of course)
- if ($text =~ m#https?://#) {
- return (&urlshorten($text) || $text);
- }
-
- # "I got nothing."
- return ();
-}
-
-#### built-in notification routines ####
-
-# growl for Mac OS X
-sub notifier_growl {
- my $class = shift;
- my $text = shift;
- my $ref = shift; # not used in this version
-
- if (!defined($class) || !length($notify_tool_path)) {
- # we are being asked to initialize
- $notify_tool_path = &wherecheck("trying to find growlnotify",
- "growlnotify",
-"growlnotify must be installed to use growl notifications. check your\n" .
- "documentation for how to do this.\n")
- unless ($notify_tool_path);
- if (!defined($class)) {
- return 1 if ($script || $notifyquiet);
- $class = 'Growl support activated';
- $text =
-'You can configure notifications for TTYtter in the Growl preference pane.';
- }
- }
- # handle this in the background for faster performance.
- # to avoid problems with SIGCHLD, we fork ourselves twice (mmm!),
- # leaving an orphan which init should grab (we need SIGCHLD for
- # proper backticks, so it can't be IGNOREd).
- my $gchild;
- if ($gchild = fork()) {
- # the parent harvests the child, which will die immediately.
- waitpid($gchild, 0);
- return 1;
- } elsif (!defined ($gchild)) {
- print $stdout "warning: failed growl fork: $!\n";
- return 1;
- }
- # this is the child. spawn, then exit and abandon our own child,
- # which init will reap. the problem with teen pregnancy is mounting.
- $in_backticks = 1;
- my $hchild;
- if ($hchild = fork()) {
- exit;
- } elsif (!defined ($hchild)) {
- print $stdout "warning: failed growl fork: $!\n";
- exit;
- }
- # this is the subchild, which is abandoned at a fire sta^W^W^Winit.
- open(GROWL, "|$notify_tool_path -n 'TTYtter' 'TTYtter: $class'");
- binmode(GROWL, ":utf8") unless ($seven);
- print GROWL $text;
- close(GROWL);
- exit;
-}
-
-# libnotify for {Linux,whatevs}
-# this is EXPERIMENTAL, and requires this patch to notify-send:
-# http://www.floodgap.com/software/ttytter/libnotifypatch.txt
-# why it has not already been applied is fricking beyond me, it makes
-# sense. would YOU want arbitrary characters on the command line
-# separated only from overwriting your home directory by a quoting routine?
-sub notifier_libnotify {
- my $class = shift;
- my $text = shift;
- my $ref = shift; # not used in this version
-
- if (!defined($class) || !defined($notify_tool_path)) {
- # we are being asked to initialize
- $notify_tool_path = &wherecheck("trying to find notify-send",
- "notify-send",
-"notify-send must be installed to use libnotify, and it must be modified\n".
-"for standard input. see the documentation for how to do this.\n")
- unless ($notify_tool_path);
- if (!defined($class)) {
- return 1 if ($script || $notifyquiet);
- $class = 'libnotify support activated';
- $text =
-'Congratulations, your notify-send is correctly configured for TTYtter.';
- }
- }
- # figure out the time to display based on length of tweet
- my $t = 1000+50*length($text); # about 150-180wpm read speed
- open(NOTIFYSEND,
- "|$notify_tool_path -t $t -f - 'TTYtter: $class'");
- binmode(NOTIFYSEND, ":utf8") unless ($seven);
- print NOTIFYSEND $text;
- close(NOTIFYSEND);
- return 1;
-}
-
-#### IPC routines for communicating between the foreground + background ####
-
-# this is the central routine that takes a rolling tweet code, figures
-# out where that tweet is, and returns something approximating a tweet
-# structure (or the actual tweet structure itself if it can).
-sub get_tweet {
- my $code = lc(shift);
-
-#TODO
-# implement querying the id_cache here. we need IPC for it, though.
- # if the code is all numbers, treat it like an id_str, and try
- # to get it from the server. we have similar code in get_dm.
- # the first tweet that is of relevance is ID 20. try /dump 20 :)
- return &grabjson("${idurl}?id=${code}", 0, 0, 0, undef, 1)
- if ($code =~ /^[0-9]+$/ && (0+$code > 19));
-
- return undef if ($code !~ /^z?[a-z][0-9]$/);
- my $source = ($code =~ /^z/) ? 1 : 0;
- my $k = '';
- my $l = '';
- my $w = {'user' => {}};
-
- if ($is_background) {
- if ($source == 1) { # foreground only
- return undef;
- }
- return $store_hash{$code};
- }
- return $store_hash{$code} if ($source); # foreground c/foreground twt
-
- print $stdout "-- querying background: $code\n" if ($verbose);
- kill $SIGUSR2, $child if ($child);
- print C "pipet $code ----------\n";
- while(length($k) < 1024) {
- sysread(W, $l, 1024);
- $k .= $l;
- }
- return undef if ($k !~ /[^\s]/);
- $k =~ s/\s+$//; # remove trailing spaces
- print $stdout "-- background store fetch: $k\n" if ($verbose);
- ($w->{'menu_select'}, $w->{'id_str'}, $w->{'in_reply_to_status_id_str'},
- $w->{'retweeted_status'}->{'id_str'},
- $w->{'user'}->{'geo_enabled'},
- $w->{'geo'}->{'coordinates'}->[0],
- $w->{'geo'}->{'coordinates'}->[1],
- $w->{'place'}->{'id'},
- $w->{'place'}->{'country_code'},
- $w->{'place'}->{'place_type'},
- $w->{'place'}->{'full_name'},
- $w->{'tag'}->{'type'},
- $w->{'tag'}->{'payload'},
- $w->{'retweet_count'},
- $w->{'user'}->{'screen_name'}, $w->{'created_at'},
- $l) = split(/\s/, $k, 17);
- ($w->{'source'}, $k) = split(/\|/, $l, 2);
- $w->{'text'} = pack("H*", $k);
- $w->{'place'}->{'full_name'} = pack("H*",$w->{'place'}->{'full_name'});
- $w->{'tag'}->{'payload'} = pack("H*", $w->{'tag'}->{'payload'});
- return undef if (!length($w->{'text'})); # unpossible
- $w->{'created_at'} =~ s/_/ /g;
- return $w;
-}
-
-# this is the analogous function for a rolling DM code. it is somewhat
-# simpler as DM codes are always rolling and have no foreground store
-# currently, so it always executes a background request.
-sub get_dm {
- my $code = lc(shift);
- my $k = '';
- my $l = '';
- my $w = {'sender' => {}};
- return undef if (length($code) < 3 || $code !~ s/^d//);
-
- # this is the aforementioned "similar code" (see get_tweet).
- # optimization: I doubt ANY of us can get DMIDs less than 9.
- return &grabjson("${dmidurl}?id=$code", 0, 0, 0, undef, 1)
- if ($code =~ /^[0-9]+$/ && (0+$code > 9));
-
- return undef if ($code !~ /^[a-z][0-9]$/);
-
- kill $SIGUSR2, $child if ($child); # prime pipe
- print C "piped $code ----------\n"; # internally two alphanum, recall
- while(length($k) < 1024) {
- sysread(W, $l, 1024);
- $k .= $l;
- }
- return undef if ($k !~ /[^\s]/);
- $k =~ s/\s+$//; # remove trailing spaces
- print $stdout "-- background store fetch: $k\n" if ($verbose);
- ($w->{'menu_select'}, $w->{'id_str'},
- $w->{'sender'}->{'screen_name'}, $w->{'created_at'},
- $l) = split(/\s/, $k, 5);
- $w->{'text'} = pack("H*", $l);
- return undef if (!length($w->{'text'})); # not possible
- $w->{'created_at'} =~ s/_/ /g;
- return $w;
-}
-
-# this function requests a $store key from the background. it only works
-# if foreground.
-sub getbackgroundkey {
- if ($is_background) {
- print $stdout "*** can't call getbackgroundkey from background\n";
- return undef;
- }
- my $key = shift;
- my $l;
- my $k;
- print C substr("ki $key ---------------------", 0, 19)."\n";
- my $ref = (length($dispatch_ref->[0])) ? ($dispatch_ref->[0]) :
- "DEFAULT";
- print C substr(unpack("${pack_magic}H*", $ref).$space_pad, 0, 1024);
- while(length($k) < 1024) {
- sysread(W, $l, 1024);
- $k .= $l;
- }
- $k =~ s/[^0-9a-fA-F]//g;
- print $stdout "-- background store fetch: $k\n" if ($verbose);
- return pack("H*", $k);
-}
-
-# this function sends a $store key to the background. it only works if
-# foreground.
-sub sendbackgroundkey {
- if ($is_background) {
- print $stdout "*** can't call sendbackgroundkey from background\n";
- return;
- }
- my $key = shift;
- my $value = shift;
- if (ref($value)) {
- print $stdout "*** send_key only supported for scalars\n";
- return;
- }
- if (!length($value)) {
- print C substr("kn $key ---------------------", 0, 19)."\n";
- } else {
- print C substr("ko $key ---------------------", 0, 19)."\n";
- }
- my $ref = (length($dispatch_ref->[0])) ? ($dispatch_ref->[0]) :
- "DEFAULT";
- print C substr(unpack("${pack_magic}H*", $ref).$space_pad, 0, 1024);
- return if (!length($value));
- print C substr(unpack("${pack_magic}H*", $value).$space_pad, 0, 1024);
-}
-
-sub thump { print C "update-------------\n"; &sync_semaphore; }
-sub dmthump { print C "dmthump------------\n"; &sync_semaphore; }
-
-sub sync_n_quit {
- if ($child) {
- print $stdout "waiting for child ...\n" unless ($silent);
- print C "sync---------------\n";
- waitpid $child, 0;
- $child = 0;
- print $stdout "exiting.\n" unless ($silent);
- exit ($? >> 8);
- }
- exit;
-}
-
-# setter for internal variables, with all the needed side effects for those
-# variables that are programmed to trigger internal actions when changed.
-sub setvariable {
- my $key = shift;
- my $value = shift;
- my $interactive = 0+shift;
-
- $value =~ s/^\s+//;
- $value =~ s/\s+$//; # mostly to avoid problems with /(p)add
-
- if ($key eq 'script') { # this can never be changed by this routine
- print $stdout "*** script may only be changed on init\n";
- return 1;
- }
- if ($key eq 'tquery' && $value eq '0') { # undo tqueries
- $tquery = undef;
- $key = 'track';
- $value = $track; # falls thru to sync
- &tracktags_makearray;
- }
- if ($opts_can_set{$key} ||
- # we CAN set read-only variables during initialization
- ($multi_module_mode == -1 && $valid{$key})) {
- if (length($value) > 1023) {
- # can't transmit this in a packet
- print $stdout "*** value too long\n";
- return 1;
- } elsif ($opts_boolean{$key} && $value ne '0' &&
- $value ne '1') {
- print $stdout "*** 0|1 only (boolean): $key\n";
- return 1;
- } elsif ($opts_urls{$key} &&
- $value !~ m#^(http|https|gopher)://#) {
- print $stdout "*** must be valid URL: $key\n";
- return 1;
- } else {
- KEYAGAIN: $$key = $value;
- print $stdout "*** changed: $key => $$key\n"
- if ($interactive || $verbose);
-
- # handle special values
- &generate_ansi if ($key eq 'ansi' ||
- $key =~ /^colour/);
- &generate_shortdomain if ($key eq 'shorturl');
- &tracktags_makearray if ($key eq 'track');
- &filter_compile if ($key eq 'filter');
- &notify_compile if ($key eq 'notifies');
- &list_compile if ($key eq 'lists');
- &filterflags_compile if ($key eq 'filterflags');
- $filterrts_sub = &filteruserlist_compile(
- $filterrts_sub, $value)
- if ($key eq 'filterrts');
- $filterusers_sub = &filteruserlist_compile(
- $filterusers_sub,$value)
- if ($key eq 'filterusers');
- $filteratonly_sub = &filteruserlist_compile(
- $filteratonly_sub, $value)
- if ($key eq 'filteratonly');
- &filterats_compile if ($key eq 'filterats');
-
- # transmit to background process sync-ed values
- if ($opts_sync{$key}) {
- &synckey($key, $value, $interactive);
- }
- if ($key eq 'superverbose') {
- if ($value eq '0') {
- $key = 'verbose';
- $value = $supreturnto;
- goto KEYAGAIN;
- }
- $supreturnto = $verbose;
- }
- }
- # virtual keys
- } elsif ($key eq 'tquery') {
- my $ivalue = &tracktags_tqueryurlify($value);
- if (length($ivalue) > 139) {
- print $stdout
- "*** custom query is too long (encoded: $ivalue)\n";
- return 1;
- } else {
- $tquery = $value;
- &synckey($key, $ivalue, $interactive);
- }
- } elsif ($valid{$key}) {
- print $stdout
- "*** read-only, must change on command line: $key\n";
- return 1;
- } else {
- print $stdout
- "*** not a valid option or setting: $key\n";
- return 1;
- }
- return 0;
-}
-sub synckey {
- my $key = shift;
- my $value = shift;
- my $interactive = 0+shift;
- my $commchar = ($interactive) ? '=' : '+';
- print $stdout "*** (transmitting to background)\n"
- if ($interactive || $verbose);
- return if (!$child);
- kill $SIGUSR2, $child if ($child);
- print C
- (substr("${commchar}$key ", 0, 19) . "\n");
- print C (substr(($value . $space_pad), 0, 1024));
- sleep 1;
-}
-
-# getter for internal variables. right now this just returns the variable by
-# name and a couple virtuals, but in the future this might be expanded.
-sub getvariable {
- my $key = shift;
- if ($valid{$key}) {
- return $$key;
- }
- if ($key eq 'effpause' ||
- $key eq 'rate_limit_rate' ||
- $key eq 'rate_limit_left') {
- my $value;
- kill $SIGUSR2, $child if ($child);
- print C (substr("?$key ", 0, 19) . "\n");
- sysread(W, $value, 1024);
- $value =~ s/\s+$//;
- return $value;
- }
- return undef;
-}
-
-# compatibility stub for extensions calling the old wraptime
-sub wraptime { return &$wraptime(@_); }
-
-#### url management (/url, /short) ####
-
-sub generate_shortdomain {
- my $x;
- my $y;
-
- undef $shorturldomain;
- ($shorturl =~ m#^http://([^/]+)/#) && ($x = $1);
- # chop off any leading hostname stuff (like api., etc.)
- while(1) {
- $y = $x;
- $x =~ s/^[^\.]*\.//;
- if ($x !~ /\./) { # a cut too far
- $shorturldomain = "http://$y/";
- last;
- }
- }
- print $stdout "-- warning: couldn't parse shortener service\n"
- if (!length($shorturldomain));
-}
-
-sub openurl {
- my $comm = $urlopen;
- my $url = shift;
- $url = "http://gopher.floodgap.com/gopher/gw?".&url_oauth_sub($url)
- if ($url =~ m#^gopher://# && $comm !~ /^[^\s]*lynx/);
- $urlshort = $url;
- $comm =~ s/\%U/'$url'/g;
- print $stdout "($comm)\n";
- system("$comm");
-}
-
-sub urlshorten {
- my $url = shift;
- my $rc;
- my $cl;
-
- $url = "http://gopher.floodgap.com/gopher/gw?".&url_oauth_sub($url)
- if ($url =~ m#^gopher://#);
- return $url if ($url =~ /^$shorturldomain/i); # stop loops
- $url = &url_oauth_sub($url);
- $cl = "$simple_agent \"${shorturl}$url\"";
- print $stdout "$cl\n" if ($superverbose);
- chomp($rc = `$cl`);
- return ($urlshort = (($rc =~ m#^http://#) ? $rc : undef));
-}
-
-##### optimizers -- these compile into an internal format #####
-
-# utility routine for tquery support
-sub tracktags_tqueryurlify {
- my $value = shift;
- $value =~ s/([^ a-z0-9A-Z_])/"%".unpack("H2",$1)/eg;
- $value =~ s/\s/+/g;
- $value = "q=$value" if ($value !~ /^q=/);
- return $value;
-}
-
-# tracking subroutines
-# run when a string is passed
-sub tracktags_makearray {
- @tracktags = ();
- $track =~ s/^'//; $track =~ s/'$//; $track = lc($track);
- if (!length($track)) {
- @trackstrings = ();
- return;
- }
- my $k;
- my $l = '';
- my $q = 0;
- my %w;
- my (@ptags) = split(/\s+/, $track);
-
- # filter duplicates and merge quoted strings
- foreach $k (@ptags) {
- if ($q && $k =~ /"$/) { # this has to be first
- $l .= " $k";
- $q = 0;
- } elsif ($k =~ /^"/ || $q) {
- $l .= (length($l)) ? " $k" : $k;
- $q = 1;
- next;
- } else {
- $l = $k;
- }
-
- if ($w{$l}) {
- print $stdout
- "-- warning: dropping duplicate track term \"$l\"\n";
- } elsif (uc($l) eq 'OR' || uc($l) eq 'AND') {
- print $stdout
- "-- warning: dropping unnecessary logical op \"$l\"\n";
- } else {
- $w{$l} = 1;
- push(@tracktags, $l);
- }
- $l = '';
- }
- print $stdout "-- warning: syntax error, missing quote?\n" if ($q);
- $track = join(' ', @tracktags);
- &tracktags_compile;
-}
-# run when array is altered (based on @kellyterryjones' code)
-sub tracktags_compile {
- @trackstrings = ();
- return if (!scalar(@tracktags));
-
- my $k;
- my $l = '';
- # need to limit track tags to a certain number of pieces
- TAGBAG: foreach $k (@tracktags) {
- if (length($k) > 130) { # I mean, really
- print $stdout
- "-- warning: track tag \"$k\" is TOO LONG\n";
- next TAGBAG;
- }
- if (length($l)+length($k) > 150) { # balance of size/querytime
- push(@trackstrings, "q=".&url_oauth_sub($l));
- $l = '';
- }
- $l = (length($l)) ? "${l} OR ${k}" : "${k}";
- }
- push(@trackstrings, "q=".&url_oauth_sub($l)) if (length($l));
-}
-
-# notification multidispatch
-sub notifytype_dispatch {
- return if (!scalar(@notifytypes));
- my $nt; foreach $nt (@notifytypes) { &$nt(@_); }
-}
-
-# notifications compiler
-sub notify_compile {
- if ($notifies) {
- my $w;
-
- undef %notify_list;
- foreach $w (split(/\s*,\s*/, $notifies)) {
- $notify_list{$w} = 1;
- }
- $notifies = join(',', keys %notify_list);
- }
-}
-
-# lists compiler
-# we don't check the validity of lists here; /liston and /listoff do that.
-sub list_compile {
- my @oldlistlist = @listlist;
- my %already;
-
- undef @listlist;
- if ($lists) {
- my $w;
- my $u;
- my $l;
- foreach $w (split(/\s*,\s*/, $lists)) {
- $w =~ s/^@//;
- if ($w =~ m#/#) {
- ($u, $l) = split(m#\s*/\s*#, $w, 2);
- } else {
- $l = $w;
- }
- if (!length($u) && $anonymous) {
-print $stdout "*** must use fully specified lists when anonymous\n";
- @listlist = @oldlistlist;
- return 0;
- }
- $u ||= $whoami;
- if ($l =~ m#/#) {
-print $stdout "*** syntax error in list $u/$l\n";
- @listlist = @oldlistlist;
- return 0;
- }
- if ($already{"$u/$l"}++) {
- print $stdout "*** duplicate list $u/$l ignored\n";
- } else {
- push(@listlist, [ $u, $l ]);
- }
- }
- $lists = join(',', keys %already);
- }
- return 1;
-}
-
-# -filterflags compiler (replaces old -filter syntax)
-sub filterflags_compile {
- my $s = $filterflags;
- undef %filter_attribs;
- $s =~ s/^\s*['"]?\s*//;
- $s =~ s/\s*['"]?\s*$//;
- return if (!length($s));
- %filter_attribs = map { $_ => 1 } split(/\s*,\s*/, $s);
-}
-
-# -filterrts and -filterusers compiler. these simply use a list of usernames,
-# so they are fast and the same code suffices. emit code to compile that
-# just is one if-expression after another.
-sub filteruserlist_compile {
- my $old = shift;
- my $s = shift;
- undef $k;
- $s =~ s/^\s*['"]?\s*//;
- $s =~ s/\s*['"]?\s*$//;
- return $k if (!length($s));
- my @us = map { $k=lc($_); "\$sn eq '$k'" } split(/\s*,\s*/, $s);
- my $uus = join(' || ', @us);
- my $uuus = <<"EOF";
- \$k = sub {
- my \$sn = shift;
- return 1 if ($uus);
- return 0;
- };
-EOF
-# print $stdout $uuus;
- eval $uuus;
- if (!defined($k)) {
- print $stdout "** bogus name in user list (error = $@)\n";
- return $old;
- }
- return $k;
-}
-
-# -filterats compiler. this takes a list of usernames and then compiles a
-# whole bunch of regexes.
-sub filterats_compile {
- undef $filterats_c;
- my $s = $filterats;
- $s =~ s/^\s*['"]?\s*//;
- $s =~ s/\s*['"]?\s*$//;
- return 1 if (!length($s)); # undef
- my @us = map { $k=lc($_); "\$x=~/\\\@$k\\b/i" } split(/\s*,\s*/, $s);
- my $uus = join(' || ', @us);
- my $uuus = <<"EOF";
- \$filterats_c = sub {
- my \$x = shift;
- return 1 if ($uus);
- return 0;
- };
-EOF
-# print $stdout $uuus;
- eval $uuus;
- if (!defined($filterats_c)) {
- print $stdout "** bogus name in user list (error = $@)\n";
- return 0;
- }
- return 1;
-}
-
-# -filter compiler. this is the generic case.
-sub filter_compile {
- undef %filter_attribs unless (length($filterflags));
- undef $filter_c;
- if (length($filter)) {
- my $tfilter = $filter;
- $tfilter =~ s/^['"]//;
- $tfilter =~ s/['"]$//;
- # note attributes (compatibility)
- while ($tfilter =~ s/^([a-z]+),//) {
- my $atkey = $1;
- $filter_attribs{$atkey}++;
- print $stdout
- "** $atkey filter parameter should be in -filterflags\n";
- }
- my $b = <<"EOF";
- \$filter_c = sub {
- local \$_ = shift;
- return ($tfilter);
- };
-EOF
- #print $b;
- eval $b;
- if (!defined($filter_c)) {
- print $stdout ("** syntax error in your filter: $@\n");
- return 0;
- }
- }
- return 1;
-}
-
-#### common system subroutines follow ####
-
-sub updatecheck {
- my $vcheck_url =
- "http://www.floodgap.com/software/ttytter/02current.txt";
- my $vrlcheck_url =
- "http://www.floodgap.com/software/ttytter/01readlin.txt";
- my $update_url = shift;
-
- my $vs = '';
- my $vvs;
- my $tverify;
- my $inversion;
- my $bversion;
- my $rcnum;
- my $download;
- my $maj;
- my $min;
- my $s1, $s2, $s3;
- my $update_trlt = undef;
-
- if ($termrl && $termrl->ReadLine eq 'Term::ReadLine::TTYtter') {
- my $trlv = $termrl->Version;
- print $stdout
- "-- checking Term::ReadLine::TTYtter version: $vrlcheck_url\n";
- $vvs = `$simple_agent $vrlcheck_url`;
- print $stdout "-- server response: $vvs\n" if ($verbose);
- ($vvs, $s1, $s2, $s3) = split(/--__--\n/s, $vvs);
- $s1 = undef if ($s1 !~ /^\*/) ;
- $s2 = undef if ($s2 !~ /^\*/) ;
- $s3 = undef if ($s3 !~ /^\*/) ;
- chomp($vvs);
- # right now we're only using $inversion (no betas/rcs).
- ($tverify, $inversion, $bversion, $rcnum, $download,
- $bdownload) = split(/;/, $vvs, 6);
- if ($tverify ne 'trlt') {
-$vs .= "-- warning: unable to verify Term::ReadLine::TTYtter version\n";
- } else {
- if ($trlv < 0+$inversion) {
-$vs .= "** NEW Term::ReadLine::TTYtter VERSION AVAILABLE: $inversion **\n" .
- "** GET IT: $download\n";
- $update_trlt = $download;
- } else {
-$vs .= "-- your version of Term::ReadLine::TTYtter is up to date ($trlv)\n";
- }
- }
- }
-
- print $stdout "-- checking TTYtter version: $vcheck_url\n";
- $vvs = `$simple_agent $vcheck_url`;
- print $stdout "-- server response: $vvs\n" if ($verbose);
- ($vvs, $s1, $s2, $s3) = split(/--__--\n/s, $vvs);
- $s1 = undef if ($s1 !~ /^\*/) ;
- $s2 = undef if ($s2 !~ /^\*/) ;
- $s3 = undef if ($s3 !~ /^\*/) ;
- chomp($vvs);
- ($tverify, $inversion, $bversion, $rcnum, $download, $bdownload) =
- split(/;/, $vvs, 6);
- if ($tverify ne 'ttytter') {
- $vs .= "-- warning: unable to verify TTYtter version\n";
- } else {
- if ($my_version_string eq $bversion) {
- $vs .=
-"** REMINDER: you are using a beta version (${my_version_string}b${TTYtter_RC_NUMBER})\n";
- $vs .=
-"** NEW TTYtter RELEASE CANDIDATE AVAILABLE: build $rcnum **\n" .
-"** get it: $bdownload\n$s2"
- if ($TTYtter_RC_NUMBER < $rcnum);
- $vs .= "** (this is the most current beta)\n"
- if ($TTYtter_RC_NUMBER == $rcnum);
- $vs .= "$s1$s3";
- if ($TTYtter_RC_NUMBER < $rcnum) {
- if ($update_url) {
- $vs .=
-"-- %URL% is now $bdownload (/short shortens, /url opens)\n";
- $urlshort = $bdownload;
- }
- } elsif (length($update_trlt) && $update_url) {
- $urlshort = $update_trlt;
- $vs .= "-- %URL% is now $urlshort (/short shortens, /url opens)\n";
- }
- return $vs;
- }
- if ($my_version_string eq $inversion && $TTYtter_RC_NUMBER) {
- $vs .=
-"** FINAL TTYtter RELEASE NOW AVAILABLE for version $inversion **\n" .
-"** get it: $download\n$s2$s1";
- if ($update_url) {
- $vs .=
-"-- %URL% is now $bdownload (/short shortens, /url opens)\n";
- $urlshort = $bdownload;
- }
- return $vs;
- }
- ($inversion =~/^(\d+\.\d+)\.(\d+)$/) && ($maj = 0+$1,
- $min = 0+$2);
- if (0+$TTYtter_VERSION < $maj ||
- (0+$TTYtter_VERSION == $maj &&
- $TTYtter_PATCH_VERSION < $min)) {
- $vs .=
- "** NEWER TTYtter VERSION NOW AVAILABLE: $inversion **\n" .
- "** get it: $download\n$s2$s1";
- if ($update_url) {
- $vs .=
-"-- %URL% is now $download (/short shortens, /url opens)\n";
- $urlshort = $download;
- }
- return $vs;
- } elsif (0+$TTYtter_VERSION > $maj ||
- (0+$TTYtter_VERSION == $maj &&
- $TTYtter_PATCH_VERSION > $min)) {
- $vs .=
- "** unable to identify your version of TTYtter\n$s1";
- } else {
- $vs .=
- "-- your version of TTYtter is up to date ($inversion)\n$s1";
- }
- }
-
- # if we got this far, then there is no TTYtter update, but maybe a
- # T:RL:T update, so we offer that as the URL
- if (length($update_trlt) && $update_url) {
- $urlshort = $update_trlt;
- $vs .= "-- %URL% is now $urlshort (/short shortens, /url opens)\n";
- }
- return $vs;
-}
-
-sub generate_otabcomp {
- if (scalar(@j = keys(%readline_completion))) {
- # print optimized readline. include all that we
- # manually specified, plus/including top @s, total 10.
- @keys = sort { $readline_completion{$b} <=>
- $readline_completion{$a} } @j;
- $factor = $readline_completion{$keys[0]};
- foreach(keys %original_readline) {
- $readline_completion{$_} += $factor;
- }
- print $stdout "*** optimized readline:\n";
- @keys = sort { $readline_completion{$b} <=>
- $readline_completion{$a} } keys
- %readline_completion;
- @keys = @keys[0..14] if (scalar(@keys) > 15);
- print $stdout "-readline=\"@keys\"\n";
- }
-}
-sub end_me { exit; } # which falls through to, via END, ...
-sub killkid {
- # for streaming assistance
- if ($child) {
- print $stdout "\n\ncleaning up.\n";
- kill $SIGHUP, $child; # warn it about shutdown
- if (length($track)) {
- print $stdout "*** you were tracking:\n";
- print $stdout "-track='$track'\n";
- }
- if (length($filter)) {
- print $stdout "*** your current filter expression:\n";
- print $stdout "-filter='$filter'\n";
- }
- &generate_otabcomp;
- sleep 2 if ($dostream);
- kill 9, $curlpid if ($curlpid);
- kill 9, $child;
- }
- &$shutdown unless (!$shutdown);
-}
-
-sub generate_ansi {
- my $k;
-
- $BLUE = ($ansi) ? "${ESC}[34;1m" : '';
- $RED = ($ansi) ? "${ESC}[31;1m" : '';
- $GREEN = ($ansi) ? "${ESC}[32;1m" : '';
- $YELLOW = ($ansi) ? "${ESC}[33m" : '';
- $MAGENTA = ($ansi) ? "${ESC}[35m" : '';
- $CYAN = ($ansi) ? "${ESC}[36m" : '';
-
- $EM = ($ansi) ? "${ESC}[1m" : '';
- $UNDER = ($ansi) ? "${ESC}[4m" : '';
- $OFF = ($ansi) ? "${ESC}[0m" : '';
-
- foreach $k (qw(prompt me dm reply warn search list default)) {
- ${"colour$k"} = uc(${"colour$k"});
- if (!defined($${"colour$k"})) {
- print $stdout
- "-- warning: bogus colour '".${"colour$k"}."'\n";
- } else {
- eval("\$CC$k = \$".${"colour$k"});
- }
- }
-
- eval '$termrl->hook_use_ansi' if ($termrl);
-}
-
-# always POST
-sub postjson {
- my $url = shift;
- my $postdata = shift; # add _method=DELETE for delete
- my $data;
-
- # this is copied mostly verbatim from grabjson
- chomp($data = &backticks($baseagent, '/dev/null', undef, $url,
- $postdata, 0, @wend));
- my $k = $? >> 8;
-
- $data =~ s/[\r\l\n\s]*$//s;
- $data =~ s/^[\r\l\n\s]*//s;
-
- if (!length($data) || $k == 28 || $k == 7 || $k == 35) {
- &$exception(1, "*** warning: timeout or no data\n");
- return undef;
- }
-
- # old non-JSON based error reporting code still supported
- if ($data =~ /^\[?\]?<!DOCTYPE\s+html/i || $data =~ /^(Status:\s*)?50[0-9]\s/ || $data =~ /^<html>/i || $data =~ /^<\??xml\s+/) {
- print $stdout $data if ($superverbose);
- if (&is_fail_whale($data)) {
- &$exception(2, "*** warning: Twitter Fail Whale\n");
- } else {
- &$exception(2, "*** warning: Twitter error message received\n" .
- (($data =~ /<title>Twitter:\s*([^<]+)</) ?
- "*** \"$1\"\n" : ''));
- }
- return undef;
- }
- if ($data =~ /^rate\s*limit/i) {
- print $stdout $data if ($superverbose);
- &$exception(3,
-"*** warning: exceeded API rate limit for this interval.\n" .
-"*** no updates available until interval ends.\n");
- return undef;
- }
-
- if ($k > 0) {
- &$exception(4,
-"*** warning: unexpected error code ($k) from user agent\n");
- return undef;
- }
-
- # handle things like 304, or other things that look like HTTP
- # error codes
- if ($data =~ m#^HTTP/\d\.\d\s+(\d+)\s+#) {
- $code = 0+$1;
- print $stdout $data if ($superverbose);
-
- # 304 is actually a cop-out code and is not usually
- # returned, so we should consider it a non-fatal error
- if ($code == 304 || $code == 200 || $code == 204) {
- &$exception(1, "*** warning: timeout or no data\n");
- return undef;
- }
- &$exception(4,
-"*** warning: unexpected HTTP return code $code from server\n");
- return undef;
- }
-
- # test for error/warning conditions with trivial case
- if ($data =~ /^\s*\{\s*(['"])(warning|error)\1\s*:\s*\1([^\1]*?)\1/s
- || $data =~ /(['"])(warning|error)\1\s*:\s*\1([^\1]*?)\1\}/s) {
- print $stdout $data if ($superverbose);
- &$exception(2, "*** warning: server $2 message received\n" .
- "*** \"$3\"\n");
- return undef;
- }
-
- return &parsejson($data);
-}
-
-# always GET
-sub grabjson {
- my $data;
- my $url = shift;
- my $last_id = shift;
- my $is_anon = shift;
- my $count = shift;
- my $tag = shift;
- my $do_entities = shift;
-
- my $kludge_search_api_adjust = 0;
- my $my_json_ref = undef; # durrr hat go on foot
- my $i;
- my $tdata;
- my $seed;
-
- #undef $/; $data = <STDIN>;
-
- # we may need to sort our args for more flexibility here.
- my @xargs = (); my $i = index($url, "?");
- if ($i > -1) {
- # throw an error if "?" is at the end.
- push(@xargs, split(/\&/, substr($url, ($i+1))));
- $url = substr($url, 0, $i);
- }
-
- # count needs to be removed for the default case due to show, etc.
- push(@xargs, "count=$count") if ($count);
- # timeline control. this speeds up parsing since there's less data.
- # can't use skip_user: no SN
- push (@xargs, "since_id=${last_id}") if ($last_id);
-
- # request entities, which should be supported everywhere now
- push (@xargs, "include_entities=1") if ($do_entities);
-
- my $resource = (scalar(@xargs)) ?
- [ $url, join('&', sort @xargs) ] : $url;
-
- chomp($data = &backticks($baseagent,
- '/dev/null', undef, $resource, undef,
- $is_anon + $anonymous, @wind));
- my $k = $? >> 8;
-
- $data =~ s/[\r\l\n\s]*$//s;
- $data =~ s/^[\r\l\n\s]*//s;
-
- if (!length($data) || $k == 28 || $k == 7 || $k == 35) {
- &$exception(1, "*** warning: timeout or no data\n");
- return undef;
- }
-
- # old non-JSON based error reporting code still supported
- if ($data =~ /^\[?\]?<!DOCTYPE\s+html/i || $data =~ /^(Status:\s*)?50[0-9]\s/ || $data =~ /^<html>/i || $data =~ /^<\??xml\s+/) {
- print $stdout $data if ($superverbose);
- if (&is_fail_whale($data)) {
- &$exception(2, "*** warning: Twitter Fail Whale\n");
- } else {
- &$exception(2, "*** warning: Twitter error message received\n" .
- (($data =~ /<title>Twitter:\s*([^<]+)</) ?
- "*** \"$1\"\n" : ''));
- }
- return undef;
- }
- if ($data =~ /^rate\s*limit/i) {
- print $stdout $data if ($superverbose);
- &$exception(3,
-"*** warning: exceeded API rate limit for this interval.\n" .
-"*** no updates available until interval ends.\n");
- return undef;
- }
-
- if ($k > 0) {
- &$exception(4,
-"*** warning: unexpected error code ($k) from user agent\n");
- return undef;
- }
-
- # handle things like 304, or other things that look like HTTP
- # error codes
- if ($data =~ m#^HTTP/\d\.\d\s+(\d+)\s+#) {
- $code = 0+$1;
- print $stdout $data if ($superverbose);
-
- # 304 is actually a cop-out code and is not usually
- # returned, so we should consider it a non-fatal error
- if ($code == 304 || $code == 200 || $code == 204) {
- &$exception(1, "*** warning: timeout or no data\n");
- return undef;
- }
- &$exception(4,
-"*** warning: unexpected HTTP return code $code from server\n");
- return undef;
- }
-
- # test for error/warning conditions with trivial case
- if ($data =~ /^\s*\{\s*(['"])(warning|error)\1\s*:\s*\1([^\1]*?)\1/s
- || $data =~ /(['"])(warning|error)\1\s*:\s*\1([^\1]*?)\1\}/s) {
- print $stdout $data if ($superverbose);
- &$exception(2, "*** warning: server $2 message received\n" .
- "*** \"$3\"\n");
- return undef;
- }
-
- # if wrapped in statuses object, unwrap it
- # (and tag it to do more later)
- if ($data =~ s/^\s*(\{)\s*['"]statuses['"]\s*:\s*(\[.*\]).*$/$2/isg) {
- $kludge_search_api_adjust = 1;
- }
-
- $my_json_ref = &parsejson($data);
-
- # normalize the data into a standard form.
- # single tweets such as from statuses/show aren't arrays, so
- # we special-case for them.
- if (defined($my_json_ref) && ref($my_json_ref) eq 'HASH' &&
- $my_json_ref->{'favorited'} &&
- $my_json_ref->{'source'} &&
- ((0+$my_json_ref->{'id'}) ||
- length($my_json_ref->{'id_str'}))) {
- $my_json_ref = &normalizejson($my_json_ref);
- }
- if (defined($my_json_ref) && ref($my_json_ref) eq 'ARRAY') {
- foreach $i (@{ $my_json_ref }) {
- $i = &normalizejson($i,$kludge_search_api_adjust,$tag);
- }
- }
-
- $laststatus = 0;
- return $my_json_ref;
-}
-
-# convert t.co into actual URLs. separate from normalizejson because other
-# things need this. modified from /entities.
-sub destroy_all_tco {
- my $hash = shift;
- return $hash if ($notco);
- my $v;
- my $w;
-
- # Twitter puts entities in multiple fields.
- foreach $w (qw(media urls)) {
- my $p = $hash->{'entities'}->{$w};
- next if (!defined($p) || ref($p) ne 'ARRAY');
- foreach $v (@{ $p }) {
- next if (!defined($v) || ref($v) ne 'HASH');
- next if (!length($v->{'url'}) ||
- (!length($v->{'expanded_url'}) &&
- !length($v->{'media_url'})));
- my $u1 = quotemeta($v->{'url'});
- my $u2 = $v->{'expanded_url'};
- my $u3 = $v->{'media_url'};
- my $u4 = $v->{'media_url_https'};
- $u2 = $u4 || $u3 || $u2;
- $hash->{'text'} =~ s/$u1/$u2/;
- }
- }
- return $hash;
-}
-
-# takes a tweet structure and normalizes it according to settings.
-# what this currently does is the following gyrations:
-# - if there is no id_str, see if we can convert id into one. if
-# there is loss of precision, warn the user. same for
-# in_reply_to_status_id_str.
-# - if the source of this JSON data source is the Search API, translate
-# its fields into the standard API.
-# - if the calling function has specified a tag, tag the tweets, since
-# we're iterating through them anyway. the tag should be a hashref payload.
-# - if the tweet is an newRT, unwrap it so that the full tweet text is
-# revealed (unless -nonewrts).
-# - if this appears to be a tweet, put in a stub geo hash if one does
-# not yet exist.
-# - if coordinates are flat string 'null', turn into a real null.
-# one day I would like this code to go the hell away.
-sub normalizejson {
- my $i = shift;
- my $kludge_search_api_adjust = shift;
- my $tag = shift;
- my $rt;
-
- # tag the tweet
- $i->{'tag'} = $tag if (defined($tag));
-
- # id -> id_str if needed
- if (!length($i->{'id_str'})) {
- my $k = "" + (0 + $i->{'id'});
- if ($k !~ /[eE][+-]/) {
- $i->{'id_str'} = $k;
- } else {
- # desperately try to convert
- $k =~ s/[eE][+-]\d+$//;
- $k =~ s/\.//g;
- # this is a hack, so we warn.
- &$exception(13,
-"*** impending doom: ID overflows Perl precision; stubbed to $k\n");
- $i->{'id_str'} = $k;
- }
- }
- # irtsid -> irtsid_str (if there is one)
- if (!length($i->{'in_reply_to_status_id_str'}) &&
- $i->{'in_reply_to_status_id'}) {
- my $k = "" + (0+$i->{'in_reply_to_status_id'});
- if ($k !~ /[eE][+-]/) {
- $i->{'in_reply_to_status_id_str'} = $k;
- } else {
- # desperately try to convert
- $k =~ s/[eE][+-]\d+$//;
- $k =~ s/\.//g;
- # this is a hack, so we warn.
- &$exception(13,
-"*** impending doom: IRT-ID overflows Perl precision; stubbed to $k\n");
- $i->{'in_reply_to_status_id_str'} = $k;
- }
- }
-
- # normalize geo. if this has a source and it has a
- # favorited, then it is probably a tweet and we will
- # add a stub geo hash if one doesn't exist yet.
- if ($kludge_search_api_adjust ||
- ($i->{'favorited'} && $i->{'source'})){
- $i = &fix_geo_api_data($i);
- }
-
- # hooray! this just tags it
- if ($kludge_search_api_adjust) {
- $i->{'class'} = "search";
- }
-
- # normalize newRTs
- # if we get newRTs with -nonewrts, oh well
- if (!$nonewrts && ($rt = $i->{'retweeted_status'})) {
- # reconstruct the RT in a "canonical" format
- # without truncation, but detco it first
- $rt = &destroy_all_tco($rt);
- $i->{'retweeted_status'} = $rt;
- $i->{'text'} =
- "RT \@$rt->{'user'}->{'screen_name'}" . ': ' . $rt->{'text'};
- }
-
- return &destroy_all_tco($i);
-}
-
-# process the JSON data ... simplemindedly, because I just write utter crap,
-# am not a professional programmer, and don't give a flying fig whether
-# kludges suck or no. this used to be part of grabjson, but I split it out.
-sub parsejson {
- my $data = shift;
- my $my_json_ref = undef; # durrr hat go on foot
- my $i;
- my $tdata;
- my $seed;
- my $bbqqmask;
- my $ddqqmask;
- my $ssqqmask;
-
- # test for single logicals
- return {
- 'ok' => 1,
- 'result' => (($1 eq 'true') ? 1 : 0),
- 'literal' => $1,
- } if ($data =~ /^['"]?(true|false)['"]?$/);
-
- # first isolate escaped backslashes with a unique sequence.
- $bbqqmask = "BBQQ";
- $seed = 0;
- $seed++ while ($data =~ /$bbqqmask$seed/);
- $bbqqmask .= $seed;
- $data =~ s/\\\\/$bbqqmask/g;
-
- # next isolate escaped quotes with another unique sequence.
- $ddqqmask = "DDQQ";
- $seed = 0;
- $seed++ while ($data =~ /$ddqqmask$seed/);
- $ddqqmask .= $seed;
- $data =~ s/\\\"/$ddqqmask/g;
-
- # then turn literal ' into another unique sequence. you'll see
- # why momentarily.
- $ssqqmask = "SSQQ";
- $seed = 0;
- $seed++ while ($data =~ /$ssqqmask$seed/);
- $ssqqmask .= $seed;
- $data =~ s/\'/$ssqqmask/g;
-
- # here's why: we're going to turn doublequoted strings into single
- # quoted strings to avoid nastiness like variable interpolation.
- $data =~ s/\"/\'/g;
-
- # and then we're going to turn the inline ones all back except
- # ssqq, which we'll do last so that our syntax checker still works.
- $data =~ s/$bbqqmask/\\\\/g;
- $data =~ s/$ddqqmask/"/g;
-
- print $stdout "$data\n" if ($superverbose);
-
- # trust, but verify. I'm sure twitter wouldn't send us malicious
- # or bogus JSON, but one day this might talk to something that would.
- # in particular, need to make sure nothing in this will eval badly or
- # run arbitrary code. that would really suck!
- # first, generate a syntax tree.
- $tdata = $data;
- 1 while $tdata =~ s/'[^']*'//; # empty strings are valid too ...
- $tdata =~ s/-?[0-9]+\.?[0-9]*([eE][+-][0-9]+)?//g;
- # have to handle floats *and* their exponents
- $tdata =~ s/(true|false|null)//g;
- $tdata =~ s/\s//g;
-
- print $stdout "$tdata\n" if ($superverbose);
-
- # now verify the syntax tree.
- # the remaining stuff should just be enclosed in [ ], and only {}:,
- # for example, imagine if a bare semicolon were in this ...
- if ($tdata !~ s/^\[// || $tdata !~ s/\]$// || $tdata =~ /[^{}:,]/) {
- $tdata =~ s/'[^']*$//; # cut trailing strings
- if (($tdata =~ /^\[/ && $tdata !~ /\]$/)
- || ($tdata =~ /^\{/ && $tdata !~ /\}$/)) {
- # incomplete transmission
- &$exception(10, "*** JSON warning: connection cut\n");
- return undef;
- }
-# it seems that :[], or :[]} should be accepted as valid in the syntax tree
-# since identica uses this as possible for null properties
-# ,[], shouldn't be, etc.
- if ($tdata =~ /(^|[^:])\[\]($|[^},])/) { # oddity
- &$exception(11, "*** JSON warning: null list\n");
- return undef;
- }
- # at this point all we should have are structural elements.
- # if something other than JSON structure is visible, then
- # the syntax tree is mangled. don't try to run it, it
- # might be unsafe. this exception was formerly uniformly
- # fatal. it is now non-fatal as of 2.1.
- if ($tdata =~ /[^\[\]\{\}:,]/) {
- &$exception(99, "*** JSON syntax error\n");
- print $stdout <<"EOF" if ($verbose);
---- data received ---
-$data
---- syntax tree ---
-$tdata
---- JSON PARSING ABORTED DUE TO SYNTAX TREE FAILURE --
-EOF
- return undef;
- }
- }
-
- # syntax tree passed, so let's turn it into a Perl reference.
- # have to turn colons into ,s or Perl will gripe. but INTELLIGENTLY!
- 1 while
- ($data =~ s/([^'])'\s*:\s*(true|false|null|\'|\{|\[|-?[0-9])/\1\',\2/);
-
- # finally, single quotes, just before interpretation.
- $data =~ s/$ssqqmask/\\'/g;
-
- # now somewhat validated, so safe (?) to eval() into a Perl struct
- eval "\$my_json_ref = $data;";
- print $stdout "$data => $my_json_ref $@\n" if ($superverbose);
-
- # do a sanity check
- if (!defined($my_json_ref)) {
- &$exception(99, "*** JSON syntax error\n");
- print $stdout <<"EOF" if ($verbose);
---- data received ---
-$data
---- syntax tree ---
-$tdata
---- JSON PARSING FAILED --
-$@
---- JSON PARSING FAILED --
-EOF
- }
-
- return $my_json_ref;
-}
-
-sub fix_geo_api_data {
- my $ref = shift;
- $ref->{'geo'}->{'coordinates'} = undef
- if ($ref->{'geo'}->{'coordinates'} eq 'null' ||
- $ref->{'geo'}->{'coordinates'}->[0] eq '' ||
- $ref->{'geo'}->{'coordinates'}->[1] eq '');
- $ref->{'geo'}->{'coordinates'} ||= [ "undef", "undef" ];
- return $ref;
-}
-
-sub is_fail_whale {
- # is this actually the dump from a fail whale?
- my $data = shift;
- return ($data =~ m#<title>Twitter.+Over.+capacity.*</title>#i ||
- $data =~ m#[\r\l\n\s]*DB_DataObject Error: Connect failed#s);
-}
-
-# {'errors':[{'message':'Rate limit exceeded','code':88}]}
-sub is_json_error {
- # is this actually a JSON error message? if so, extract it
- my $data = shift;
- if ($data =~ /(['"])(warning|errors?)\1\s*:\s*/s) {
- if ($data =~ /^\s*\{/s) { # JSON object?
- my $dref = &parsejson($data);
- print $stdout "*** is_json_error returning true\n"
- if ($verbose);
- # support 1.0 and 1.1 error objects
- return $dref->{'error'} if (length($dref->{'error'}));
- return $dref->{'errors'}->[0]->{'message'}
- if (length($dref->{'errors'}->[0]->{'message'}));
- return (split(/\\n/, $dref->{'errors'}))[0]
- if(length($dref->{'errors'}));
- }
- return $data;
- }
- return undef;
-}
-
-sub backticks {
- # more efficient/flexible backticks system
- my $comm = shift;
- my $rerr = shift;
- my $rout = shift;
- my $resource = shift;
- my $data = shift;
- my $dont_do_auth = shift;
- my $buf = '';
- my $undersave = $_;
- my $pid;
- my $args;
-
- ($comm, $args, $data) = &$stringify_args($comm, $resource,
- $data, $dont_do_auth, @_);
- print $stdout "$comm\n$args\n$data\n" if ($superverbose);
- if(open(BACTIX, '-|')) {
- while(<BACTIX>) {
- $buf .= $_;
- } close(BACTIX);
- $_ = $undersave;
- return $buf; # and $? is still in $?
- } else {
- $in_backticks = 1;
- &sigify(sub {
- die(
- "** user agent not honouring timeout (caught by sigalarm)\n");
- }, qw(ALRM));
- alarm 120; # this should be sufficient
- if (length($rerr)) {
- close(STDERR);
- open(STDERR, ">$rerr");
- }
- if (length($rout)) {
- close(STDOUT);
- open(STDOUT, ">$rout");
- }
- if(open(FRONTIX, "|$comm")) {
- print FRONTIX "$args\n";
- print FRONTIX "$data" if (length($data));
- close(FRONTIX);
- } else {
- die(
- "backticks() failure for $comm $rerr $rout @_: $!\n");
- }
- $rv = $? >> 8;
- exit $rv;
- }
-}
-
-sub wherecheck {
- my ($prompt, $filename, $fatal) = (@_);
- my (@paths) = split(/\:/, $ENV{'PATH'});
- my $setv = '';
-
- push(@paths, '/usr/bin'); # the usual place
- @paths = ('') if ($filename =~ m#^/#); # for absolute paths
-
- print $stdout "$prompt ... " unless ($silent);
- foreach(@paths) {
- if (-r "$_/$filename") {
- $setv = "$_/$filename";
- 1 while $setv =~ s#//#/#;
- print $stdout "$setv\n" unless ($silent);
- last;
- }
- }
- if (!length($setv)) {
- print $stdout "not found.\n";
- if ($fatal) {
- print $stdout $fatal;
- exit(1);
- }
- }
- return $setv;
-}
-
-sub screech {
- print $stdout "\n\n${BEL}${BEL}@_";
- if ($is_background) {
- kill 9, $parent;
- kill 9, $$;
- } elsif ($child) {
- kill 9, $child;
- kill 9, $$;
- }
- die("death not achieved conventionally");
-}
-
-# &in($x, @y) returns true if $x is a member of @y
-sub in { my $key = shift; my %mat = map { $_ => 1 } @_;
- return $mat{$key}; }
-
-sub descape {
- my $x = shift;
- my $mode = shift;
-
- $x =~ s#\\/#/#g;
-
- # try to do something sensible with unicode
- if ($mode) { # this probably needs to be revised
- $x =~ s/\\u([0-9a-fA-F]{4})/"&#" . hex($1) . ";"/eg;
- } else {
- # intermediate form if HTML entities get in
- $x =~ s/\&\#([0-9]+);/'\u' . sprintf("%04x", $1)/eg;
-
- $x =~ s/\\u202[89]/\\n/g;
-
- # canonicalize Unicode whitespace
- 1 while ($x =~ s/\\u(00[aA]0)/ /g);
- 1 while ($x =~ s/\\u(200[0-9aA])/ /g);
- 1 while ($x =~ s/\\u(20[25][fF])/ /g);
- if ($seven) {
- # known UTF-8 entities (char for char only)
- $x =~ s/\\u201[89]/\'/g;
- $x =~ s/\\u201[cCdD]/\"/g;
-
- # 7-bit entities (32-126) also ok
- $x =~ s/\\u00([2-7][0-9a-fA-F])/chr(((hex($1)==127)?46:hex($1)))/eg;
-
- # dot out the rest
- $x =~ s/\\u([0-9a-fA-F]{4})/./g;
- $x =~ s/[\x80-\xff]/./g;
- } else {
- # try to promote to UTF-8
- &$utf8_decode($x);
-
- # Twitter uses UTF-16 for high code points, which
- # Perl's UTF-8 support does not like as surrogates.
- # try to decode these here; they are always back-to-
- # back surrogates of the form \uDxxx\uDxxx
- $x =~
-s/\\u([dD][890abAB][0-9a-fA-F]{2})\\u([dD][cdefCDEF][0-9a-fA-F]{2})/&deutf16($1,$2)/eg;
-
- # decode the rest
- $x =~ s/\\u([0-9a-fA-F]{4})/chr(hex($1))/eg;
- $x = &uforcemulti($x);
- }
- $x =~ s/\&quot;/"/g;
- $x =~ s/\&apos;/'/g;
- $x =~ s/\&lt;/\</g;
- $x =~ s/\&gt;/\>/g;
- $x =~ s/\&amp;/\&/g;
- }
- if ($newline) {
- $x =~ s/\\n/\n/sg;
- $x =~ s/\\r//sg;
- }
- return $x;
-}
-
-# used by descape: turn UTF-16 surrogates into a Unicode character
-sub deutf16 {
- my $one = hex(shift);
- my $two = hex(shift);
- # subtract 55296 from $one to yield top ten bits
- $one -= 55296; # $d800
- # subtract 56320 from $two to yield bottom ten bits
- $two -= 56320; # $dc00
-
- # experimentally, Twitter uses this endianness below (we have no BOM)
- # see RFC 2781 4.3
- return chr(($one << 10) + $two + 65536);
-}
-sub max { return ($_[0] > $_[1]) ? $_[0] : $_[1]; }
-sub min { return ($_[0] < $_[1]) ? $_[0] : $_[1]; }
-sub prolog { my $k = shift;
- return "" if (!scalar(@_));
- my $l = shift; return (&$k($l) . &$k(@_)); }
-# this is mostly a utility function for /eval. it is a recursive descent
-# pretty printer.
-sub a {
- my $w;
- my $x;
- return '' if(scalar(@_) < 1);
- if(scalar(@_) > 1) { $x = "(";
- foreach $w (@_) {
- $x .= &a($w);
- }
- return $x."), ";
- }
- $w = shift;
- if(ref($w) eq 'SCALAR') { return "\\\"". $$w . "\", "; }
- if(ref($w) eq 'HASH') { my %m = %{ $w };
- return "\n\t{".&prolog(\&a, %m)."}, "; }
- if(ref($w) eq 'ARRAY') { return "\n\t[".&prolog(\&a, @{ $w })."], "; }
- return "\"$w\", ";
-}
-sub ssa { return (scalar(@_) ? ("('" . join("', '", @_) . "')") : "NULL"); }
-
-sub strim { my $x=shift; $x=~ s/^\s+//; $x=~ s/\s+$//; return $x; }
-
-sub wwrap {
- return shift if (!$wrap);
-
- my $k;
- my $klop = ($wrap > 1) ? $wrap : ($ENV{'COLUMNS'} || 79);
- $klop--; # don't ask me why
- my $lop;
- my $buf = '';
- my $string = shift;
- my $indent = shift; # for very first time with the prompt
- my $needspad = 0;
- my $stringpad = " " x 3;
-
- $indent += 4; # for the menu select string
-
- $lop = $klop - $indent;
- $lop -= $indent;
- W: while($k = length($string)) {
- $lop += $indent if ($lop < $klop);
- ($buf .= $string, last W) if ($k <= $lop && $string !~ /\n/);
- ($string =~ s/^\s*\n//) && ($buf .= "\n",
- $needspad = 1,
- next W);
- if ($needspad) {
- $string = " $string";
- $needspad = 0;
- }
- # I don't know if people will want this, so it's commented out.
- #($string =~ s#^(http://[^\s]+)# #) && ($buf .= "$1\n",
- # next W);
- ($string =~ s/^(.{4,$lop})\s/ /) && ($buf .= "$1\n",
- next W); # i.e., at least one char, plus 3 space indent
- ($string =~ s/^(.{$lop})/ /) && ($buf .= "$1\n",
- next W);
- warn
- "-- pathologic string somehow failed wordwrap! \"$string\"\n";
- return $buf;
- }
- 1 while ($buf =~ s/\n\n\n/\n\n/s); # mostly paranoia
- $buf =~ s/[ \t]+$//;
- return $buf;
-}
-
-# these subs look weird, but they're encoding-independent and run anywhere
-sub uforcemulti { # forces multi-byte interpretation by abusing Perl
- my $x = shift;
- return $x if ($seven);
- $x = "\x{263A}".$x;
- return pack("${pack_magic}H*", substr(unpack("${pack_magic}H*",$x),6));
-}
-sub ulength { my @k; return (scalar(@k = unpack("${pack_magic}C*", shift))); }
-sub uhex {
- # URL-encode an arbitrary string, even UTF-8
- # more versatile than the miniature one in &updatest
- my $k = '';
- my $s = shift;
- &$utf8_encode($s);
-
- foreach(split(//, $s)) {
- my $j = unpack("H256", $_);
- while(length($j)) {
- $k .= '%' . substr($j, 0, 2);
- $j = substr($j, 2);
- }
- }
- return $k;
-}
-
-# for t.co
-# adapted from github.com/twitter/twitter-text-js/blob/master/twitter-text.js
-# this is very hard to get right, and I know there are edge cases. this first
-# one is designed to be quick and dirty because it needs to be fast more than
-# it needs to be accurate, since T:RL:T calls it a LOT. however, it can be
-# fooled, see below.
-sub fastturntotco {
- my $s = shift;
- my $w;
-
- # turn domain names into http urls. this should look at .com, .net,
- # .etc., but things like you.suck.too probably *should* hit this
- # filter. this uses the heuristic that a domain name over some limit
- # is probably not actually a domain name.
- ($s =~ s#\b(([a-zA-Z0-9-_]\.)+([a-zA-Z]){2,})\b#((length($w="$1")>45)?$w:"http://$w")#eg);
-
- # now turn all http and https URLs into t.co strings
- ($s =~ s#\b(https?)://[a-zA-Z0-9-_]+[^\s]*?('|\\|\s|[\.;:,!\?]\s+|[\.;:,!\?]$|$)#\1://t.co/1234567\2#gi);
- return $s;
-}
-# slow t.co converter. this is for future expansion.
-sub turntotco {
- return &fastturntotco(shift);
-}
-
-sub ulength_tco {
- my $w = shift;
- return &ulength(($notco) ? $w : &turntotco($w));
-}
-sub length_tco {
- my $w = shift;
- return length(($notco) ? $w : &turntotco($w));
-}
-# take a string and return up to $linelength CHARS plus the rest.
-sub csplit { return &cosplit(@_, sub { return &length_tco(shift); }); }
-# take a string and return up to $linelength BYTES plus the rest.
-sub usplit { return &cosplit(@_, sub { return &ulength_tco(shift); }); }
-sub cosplit {
- # this is the common code for &csplit and &usplit.
- # this is tricky because we don't want to split up UTF-8 sequences, so
- # we let Perl do the work since it internally knows where they end.
- my $orig_k = shift;
- my $mode = shift;
- my $lengthsub = shift;
- my $z;
- my @m;
- my $q;
- my $r;
-
- $mode += 0;
- $k = $orig_k;
-
- # optimize whitespace
- $k =~ s/^\s+//;
- $k =~ s/\s+$//;
- $k =~ s/\s+/ /g;
- $z = &$lengthsub($k);
- return ($k) if ($z <= $linelength); # also handles the trivial case
-
- # this needs to be reply-aware, so we put @'s at the beginning of
- # the second half too (and also Ds for DMs)
- $r .= $1 while ($k =~ s/^(\@[^\s]+\s)\s*// ||
- $k =~ s/^(D\s+[^\s]+\s)\s*//); # we have r/a, so while
- $k = "$r$k";
-
- my $i = $linelength;
- $i-- while(($z = &$lengthsub($q = substr($k, 0, $i))) > $linelength);
- $m = substr($k, $i);
-
- # if we just wanted split-on-byte, return now (mode = 1)
- if ($mode) {
- # optimize again in case we split on whitespace
- $q =~ s/\s+$//;
- $m =~ s/^\s+//;
- return ($q, "$r$m");
- }
-
- # else try to do word boundary and cut even more
- if (!$autosplit) { # use old mechanism first: drop trailing non-alfanum
- ($q =~ s/([^a-zA-Z0-9]+)$//) && ($m = "$1$m");
- # optimize again in case we split on whitespace
- $q =~ s/\s+$//;
- return (&cosplit($orig_k, 1, $lengthsub))
- if (!length($q) && !$mode);
- # it totally failed. fall back on charsplit.
- if (&$lengthsub($q) < $linelength) {
- $m =~ s/^\s+//;
- return($q, "$r$m")
- }
- }
- ($q =~ s/\s+([^\s]+)$//) && ($m = "$1$m");
- return (&cosplit($orig_k, 1, $lengthsub)) if (!length($q) && !$mode);
- # it totally failed. fall back on charsplit.
- return ($q, "$r$m");
-}
-
-### OAuth methods, including our own homegrown SHA-1 and HMAC ###
-### no Digest:* required! ###
-### these routines are not byte-safe and need a use bytes; before you call ###
-
-# this is a modified, deciphered and deobfuscated version of the famous Perl
-# one-liner SHA-1 written by John Allen. hope he doesn't mind.
-sub sha1 {
- my $string = shift;
- print $stdout "string length: @{[ length($string) ]}\n"
- if ($showwork);
-
- my $constant = "D9T4C`>_-JXF8NMS^\$#)4=L/2X?!:\@GF9;MGKH8\\;O-S*8L'6";
- my @A = unpack('N*', unpack('u', $constant));
- my @K = splice(@A, 5, 4);
- my $M = sub { # 64-bit warning
- my $x;
- my $m;
- ($x = pop @_) - ($m=4294967296) * int($x / $m);
- };
- my $L = sub { # 64-bit warning
- my $n = pop @_;
- my $x;
- ((($x = pop @_) << $n) | ((2 ** $n - 1) & ($x >> 32 - $n))) &
- 4294967295;
- };
- my $l = '';
- my $r;
- my $a;
- my $b;
- my $c;
- my $d;
- my $e;
- my $us;
- my @nuA;
- my $p = 0;
- $string = unpack("H*", $string);
-
- do {
- my $i;
- $us = substr($string, 0, 128);
- $string = substr($string, 128);
- $l += $r = (length($us) / 2);
- print $stdout "pad length: $r\n" if ($showwork);
- ($r++, $us .= "80") if ($r < 64 && !$p++);
- my @W = unpack('N16', pack("H*", $us) . "\000" x 7);
- $W[15] = $l * 8 if ($r < 57);
- foreach $i (16 .. 79) {
- push(@W,
- &$L($W[$i - 3] ^ $W[$i - 8] ^ $W[$i - 14] ^ $W[$i - 16], 1));
- }
- ($a, $b, $c, $d, $e) = @A;
- foreach $i (0 .. 79) {
- my $qq = ($i < 20) ? ($b & ($c ^ $d) ^ $d) :
- ($i < 40) ? ($b ^ $c ^ $d) :
- ($i < 60) ? (($b | $c) & $d | $b & $c) :
- ($b ^ $c ^ $d);
- $t = &$M($qq + $e + $W[$i] + $K[$i / 20] + &$L($a, 5));
- $e = $d;
- $d = $c;
- $c = &$L($b, 30);
- $b = $a;
- $a = $t;
- }
- @nuA = ($a, $b, $c, $d, $e);
- print $stdout "$a $b $c $d $e\n" if ($showwork);
- $i = 0;
- @A = map({ &$M($_ + $nuA[$i++]); } @A);
- } while ($r > 56);
- my $x = sprintf('%.8x' x 5, @A);
- @A = unpack("C*", pack("H*", $x));
- return($x, @A);
-}
-
-# heavily modified from MIME::Base64
-sub simple_encode_base64 {
- my $result = '';
- my $input = shift;
-
- pos($input) = 0;
- while($input =~ /(.{1,45})/gs) {
- $result .= substr(pack("u", $1), 1);
- chop($result);
- }
- $result =~ tr|` -_|AA-Za-z0-9+/|;
- my $padding = (3 - length($input) % 3) % 3;
- $result =~ s/.{$padding}$/("=" x $padding)/e if ($padding);
-
- return $result;
-}
-
-# from RFC 2104/RFC 2202
-
-sub hmac_sha1 {
- my $message = shift;
- my @key = (@_);
- my $opad;
- my $ipad;
- my $i;
- my @j;
-
- # sha1 blocksize is 512, so key should be 64 bytes
-
-print $stdout " KEY HASH \n" if ($showwork);
- ($i, @key) = &sha1(pack("C*", @key)) while (scalar(@key) > 64);
- push(@key, 0) while(scalar(@key) < 64);
- $opad = pack("C*", map { ($_ ^ 92) } @key);
- $ipad = pack("C*", map { ($_ ^ 54) } @key);
-
-print $stdout " MESSAGE HASH \n" if ($showwork);
- ($i, @j) = &sha1($ipad . $message);
-print $stdout " FINAL HASH \n" if ($showwork);
- $i = pack("C*", @j); # output hash is 160 bits
- ($i, @j) = &sha1($opad . $i);
- $i = &simple_encode_base64(pack("C20", @j));
-
- return $i;
-}
-
-# simple encoder for OAuth modified URL encoding (used for lots of things,
-# actually)
-# this is NOT UTF-8 safe
-sub url_oauth_sub {
- my $x = shift;
- $x =~ s/([^-0-9a-zA-Z._~])/"%".uc(unpack("H*",$1))/eg; return $x;
-}
-
-# default method of getting password: ask for it. only relevant for Basic Auth,
-# which is no longer the default.
-sub defaultgetpassword {
- # original idea by @jcscoobyrs, heavily modified
- my $k;
- my $l;
- my $pass;
-
- $l = "no termios; password WILL";
- if ($termios) {
- $termios->getattr(fileno($stdin));
- $k = $termios->getlflag;
- $termios->setlflag($k ^ &POSIX::ECHO);
- $termios->setattr(fileno($stdin));
- $l = "password WILL NOT";
- }
- print $stdout "enter password for $whoami ($l be echoed): ";
- chomp($pass = <$stdin>);
- if ($termios) {
- print $stdout "\n";
- $termios->setlflag($k);
- $termios->setattr(fileno($stdin));
- }
- return $pass;
-}
-
-# this returns an immutable token corresponding to the current authenticated
-# session. in the case of Basic Auth, it is simply the user:password pair.
-# it does not handle OAuth -- that is run by a separate wizard.
-# the function then returns (token,secret) which for Basic Auth is token,undef.
-# most of the time we will be using tokens in a keyfile, however, so this
-# function runs in that case as a stub.
-sub authtoken {
- my @foo;
- my $pass;
- my $sig;
- my $return;
- my $tries = ($hold > 3) ? $hold : 3;
- # give up on token if we don't get one
-
- return (undef,undef) if ($anonymous);
- return ($tokenkey,$tokensecret)
- if (length($tokenkey) && length($tokensecret));
- @foo = split(/:/, $user, 2);
- $whoami = $foo[0];
- die("choose -user=username[:password], or -anonymous.\n")
- if (!length($whoami) || $whoami eq '1');
- $pass = length($foo[1]) ? $foo[1] : &$getpassword;
- die("a password must be specified.\n") if (!length($pass));
- return ($whoami, $pass);
-}
-
-# this is a sucky nonce generator. I was looking for an awesome nonce
-# generator, and then I realized it would only be used once, so who cares?
-# *rimshot*
-sub generate_nonce { unpack("H9000", pack("u", rand($$).$$.time())); }
-
-# this signs a request with the token and token secret. the result is undef if
-# Basic Auth. payload should already be URL encoded and *sorted*.
-# this is typically called by stringify_args to get authentication information.
-sub signrequest {
-
- # this horrible kludge is needed to account for both 5.005, or for
- # 5.6+ installs with no stdlibs and just a bare Perl, both of which
- # we support. I hope Larry Wall will forgive me for messing with
- # compiler internals next time I see him at church.
- BEGIN { $^H |= 0x00000008 unless ($] < 5.006); }
-
- my $resource = shift;
- my $payload = shift;
-
- # when we sign the initial request for an token, we obviously
- # don't have one yet, so mytoken/mytokensecret can be null.
-
- my $nonce = &generate_nonce;
- my @keybytes;
- my $sig_base;
- my $timestamp = time();
- return undef if ($authtype eq 'basic');
-
- # stub for oAuth 2.0
- return undef if (!length($oauthkey) || !length($oauthsecret));
-
- (@keybytes) = map { ord($_) }
- split(//, $oauthsecret.'&'.$mytokensecret);
- if (ref($resource) eq 'ARRAY' || length($payload)) {
- # split into _a and _b payloads lexically
- my $payload_a = '';
- my $payload_b = '';
- my $payload_c = ''; # this is for a special case
- my $w;
- my $aorb = 0;
- my $verifier = '';
- my $method = "GET";
- my $url;
-
- if (length($payload)) {
- $method = "POST";
- # this is a bit problematic since it won't be
- # sorted. we'll deal with this as we need to.
- if (ref($resource) eq 'ARRAY') {
- $url = &url_oauth_sub($resource->[0]);
- $payload .= "&" . $resource->[1];
- } else {
- $url = &url_oauth_sub($resource);
- }
- } elsif (ref($resource) eq 'ARRAY') {
- $url = &url_oauth_sub($resource->[0]);
- $payload = $resource->[1];
- } else {
- $url = &url_oauth_sub($resource);
- }
-
- # this is pretty simplistic but it's really all we need.
- # the exception is oauth_verifier: that has to be wormed
- # into the middle, and we assume it's just that.
- if ($payload !~ /^oauth_verifier/) {
- foreach $w (split(/\&/, $payload)) {
- $aorb = 1 if
- ($w =~ /^[p-z]/ || $w =~ /^o[b-z]/);
- $w = &url_oauth_sub("${w}&");
- if ($aorb) {
- $payload_b .= $w;
- } else {
- $payload_a .= $w;
- }
- }
- } else {
- $payload_c = &url_oauth_sub($payload) . "%26";
- $payload_a = $payload_b = '';
- $payload =~ s/^oauth_verifier=//;
- $verifier = ' oauth_verifier=\\"' . $payload . '\\",';
- }
- $payload_b =~ s/%26$//;
- $sig_base = $method . "&" .
- $url . "&" .
- (length($payload_a) ? $payload_a : '').
- "oauth_consumer_key%3D" . $oauthkey . "%26" .
- "oauth_nonce%3D" . $nonce . "%26" .
- "oauth_signature_method%3DHMAC-SHA1%26" .
- "oauth_timestamp%3D" . $timestamp . "%26" .
- (length($mytoken) ?
- ("oauth_token%3D" . $mytoken . "%26") : '') .
- $payload_c .
- "oauth_version%3D1.0" .
- (length($payload_b) ? ("%26" . $payload_b) : '');
- } else {
- $sig_base = "GET&" .
- &url_oauth_sub($resource) . "&" .
- "oauth_consumer_key%3D" . $oauthkey . "%26" .
- "oauth_nonce%3D" . $nonce . "%26" .
- "oauth_signature_method%3DHMAC-SHA1%26" .
- "oauth_timestamp%3D" . $timestamp . "%26" .
- (length($mytoken) ?
- ("oauth_token%3D" . $mytoken . "%26") : '') .
- $payload_c . # could be part of it
- "oauth_version%3D1.0" ;
- }
- print $stdout
-"token-secret: $mytokensecret\nconsumer-secret: $oauthsecret\nsig-base: $sig_base\n"
- if ($superverbose);
- return ($timestamp, $nonce,
- &url_oauth_sub(&hmac_sha1($sig_base, @keybytes)),
- $verifier);
-}
-
-# this takes a token request and "tries hard" to get it.
-sub tryhardfortoken {
- my $url = shift;
- my $body = shift;
- my $tries = shift;
- my $rawtoken;
- $tries ||= 3;
-
- while($tries) {
- my $i;
- $rawtoken = &backticks($baseagent, '/dev/null', undef,
- $url, $body, 0, @wend);
- print $stdout ("token = $rawtoken\n")
- if ($superverbose);
- my (@keyarr) = split(/\&/, $rawtoken);
- my $got_token = '';
- my $got_secret = '';
- foreach $i (@keyarr) {
- my $key;
- my $value;
-
- ($key, $value) = split(/\=/, $i);
- $got_token = $value if ($key eq 'oauth_token');
- $got_secret = $value if ($key eq 'oauth_token_secret');
- }
- if (length($got_token) && length($got_secret)) {
- print $stdout " SUCCEEDED!\n";
- return ($got_token, $got_secret);
- }
- print $stdout ".";
- $tries--;
- }
- print $stdout " FAILED!: \"$rawtoken\"\n";
-die("unable to fetch token. here are some possible reasons:\n".
- " - root certificates are not updated (see documentation)\n".
- " - you entered your authentication information wrong\n".
- " - your computer's clock is not set correctly\n" .
- " - Twitter farted\n" .
- "fix these possible problems, or try again later.\n");
- exit;
-}