#!/usr/bin/perl # # TTYtter Twitpher gopher<-twitter handler for public timeline # For TTYtter 1.0.0 # # this is all Ed Finkler's fault. # Public domain; you may freely copy or modify as you see fit for any purpose. # by Cameron Kaiser # ###### magic header section ###### # this block of code only executes if we are NOT in the multi_module_loader. # thus, we can call TTYtter directly from this lib, *on* this lib. unless ($multi_module_mode) { # we are not in TTYtter, so we need to load TTYtter on our behalf. # first, sanitize our arguments ... $query = substr("@ARGV", 0, 25); # no buffer overflow FOR YOU! $query =~ s/[^a-zA-Z0-9_]//g; # no shell metacharacters FOR YOU! # then call TTYtter with our desired environment exec("/usr/local/bin/ttytter", # CHANGE IF LOCATED ELSEWHERE "-script", "-seven", "-anonymous", "-exts=$0", # i.e., this script! "-extpref_twitpher_username=$query"); # should never reach this point die("oh noes: I did not exec! $!\n"); } ###### initialization ###### # this tells the multi-module loader that we must be executed with -script # (or -runcommand, which implies -script). the loader defines $EM_* globals. $extension_mode = $EM_SCRIPT_ON; # if the user wanted a username, get it from -extpref_twitpher_username # and sanitize it, then put it in our store hashref. we did this already # but I'm paranoid, and this means other things can call us. $store->{'arg'} = substr($extpref_twitpher_username, 0, 25); # no buffer overflow FOR YOU! $store->{'arg'} =~ s/[^a-zA-Z0-9_]//g; # no shell metacharacters FOR YOU! # some other globals to set up. $store->{'me'} = "/fun/twitpher"; $store->{'source'} = (length($store->{'arg'})) ? "user \@$store->{'arg'}" : (length($runcommand)) ? "the current query" : "the public timeline"; # build our title screen from __DATA__ # we do this now, as there is no guarantee we will still have when # multi-module calls us later. while() { chomp; s/SOURCE/$store->{'source'}/; push(@{ $store->{'title_banner'} }, $_); } # so that ttytter can find lynx/curl, or it will gripe $ENV{'PATH'} = "/bin:/usr/bin:/usr/local/bin:/usr/pkg/bin"; # define our main loop, which is non-interactive (thus we need -script). # essentially we choose a command to force into TTYtter, and execute it, # falling through to gracefully terminate. we only need this if the user # isn't calling us with -runcommand, because then we get this for free. if (!length($runcommand)) { $main = sub { if (length($store->{'arg'})) { &ucommand("/a $store->{'arg'}"); } else { &ucommand("/a"); } # and terminate. don't loop. }; } #### define our internal subroutines so we can call them later #### # gopher line wrapper $store->{'gwrap'} = sub { my $itype = shift; my $string = shift; my $host = ($itype ne 'i') ? 'gopher.floodgap.com' : 'error.host'; my $port = ($itype ne 'i') ? 70 : 1; my $sel = shift; print $streamout "$itype$string\t$sel\t$host\t$port\r\n"; }; # page header $store->{'header'} = sub { my $Lib_gwrap = $store->{'gwrap'}; my $Lib_source = $store->{'source'}; my $Lib_arg = $store->{'arg'}; my $Lib_me = $store->{'me'}; my $i; return if ($store->{'header_printed'}++); &$Lib_gwrap(1, "Floodgap Gopher Fun and Games", "/fun"); foreach $i (@{ $store->{'title_banner'} }) { &$Lib_gwrap('i', $i); } &$Lib_gwrap('i', 'Click a tweet to see more from that user.') unless (length($Lib_arg)); &$Lib_gwrap('i', 'Public timeline only updates once per minute from the API.') unless (length($Lib_arg)); &$Lib_gwrap('i', 'Click replies to see more from the user referenced.') if (length($Lib_arg)); &$Lib_gwrap('i', ' '); }; # page footer $store->{'footer'} = sub { my $Lib_gwrap = $store->{'gwrap'}; my $Lib_arg = $store->{'arg'}; my $Lib_me = $store->{'me'}; &$Lib_gwrap('i', ''); &$Lib_gwrap(1, "Twitter public timeline via Twitpher", $Lib_me); &$Lib_gwrap('h', "\@${Lib_arg}'s Twitter page (WWW)", "URL:http://twitter.com/$Lib_arg") if (length($Lib_arg)); &$Lib_gwrap(1, "Cameron Kaiser's Twitter (\@doctorlinguist)", "$Lib_me?doctorlinguist"); &$Lib_gwrap('h', "Powered by TTYtter (WWW)", "URL:http://www.floodgap.com/software/ttytter/"); print $streamout ".\r\n\r\n"; }; #### define our hooks into the TTYtter API #### # we don't support DMs $dmhandle = sub { return 0; # refuse, and shouldn't even happen }; $dmconclude = sub { return 0; # refuse }; # error handler $exception = sub { my $en = shift; my $et = shift; my $Lib_gwrap = $store->{'gwrap'}; my $Lib_header = $store->{'header'}; &$Lib_header; &$Lib_gwrap('i', "Error $en: $et"); # conclude is called for us by TTYtter. }; # tweet handler $handle = sub { my $Lib_header = $store->{'header'}; my $Lib_gwrap = $store->{'gwrap'}; my $Lib_arg = $store->{'arg'}; my $Lib_me = $store->{'me'}; &$Lib_header; my $ref = shift; my $user = &descape($ref->{'user'}->{'screen_name'}); my $text = &descape($ref->{'text'}); my $string = "<$user> $text -- @{[ &descape($ref->{'created_at'}) ]}"; if (length($Lib_arg)) { if ($text =~ /^\@([a-zA-Z0-9_]+)/) { &$Lib_gwrap('1', $string, "$Lib_me?$1"); } else { &$Lib_gwrap('i', $string); } } else { &$Lib_gwrap('1', $string, "$Lib_me?$user"); } $store->{'processed'}++; return 1; }; # at the end of a tweet display cycle (successful or not), do this $conclude = sub { my $Lib_gwrap = $store->{'gwrap'}; my $Lib_header = $store->{'header'}; my $Lib_footer = $store->{'footer'}; my $Lib_arg = $store->{'arg'}; &$Lib_header; # does nothing if already called &$Lib_gwrap('i', "** No data returned ** Wrong username? Private user? **") unless ($store->{'processed'}); &$Lib_footer; }; # we are finished. let TTYtter finish start up and then run our main function. __DATA__ _ _ _ _ | |___ __ _(_) |_ _ __| |_ ___ _ _ | _\ V V / | _| '_ \ ' \/ -_) '_| \__|\_/\_/|_|\__| .__/_||_\___|_| Gopher-Twitter |_| gateway Reload for most current tweets from SOURCE. .