diff options
Diffstat (limited to 'ttytter')
| -rwxr-xr-x | ttytter | 7989 | 
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. -¬ify_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'); -					¬ify_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) = ¢ral_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) = ¢ral_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) = ¢ral_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) = ¢ral_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) = ¢ral_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) = ¢ral_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)); -		¬ifytype_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; -	¬ifytype_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'); -			¬ify_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/\"/"/g; -		$x =~ s/\'/'/g; -		$x =~ s/\</\</g; -		$x =~ s/\>/\>/g; -		$x =~ s/\&/\&/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; -} | 
