# googlesearch.pl: !google search trigger for irssi # # Copyright (C) 2007 Tristan Willy # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . use strict; use warnings; use Text::ParseWords; use HTML::Entities; use Compress::Zlib; use URI::Escape; use JSON; use LWP; use vars qw($VERSION %IRSSI); use Irssi; $VERSION = '0.01'; %IRSSI = ( 'authors' => 'Tristan Willy', 'contact' => 'tristan.willy at gmail.com', 'name' => 'Google Search', 'description' => 'Google Search for irssi. Uses Google\'s AJAX API.', 'license' => 'GPL v2' ); Irssi::settings_add_str($IRSSI{'name'}, 'gsearch_channels', ''); Irssi::settings_add_str($IRSSI{'name'}, 'gsearch_site_url', ''); Irssi::settings_add_str($IRSSI{'name'}, 'gsearch_key', ''); Irssi::signal_add('event privmsg', 'irc_privmsg'); sub irc_privmsg { my ($server, $data, $from, $address) = @_; my ($to, $message) = split(/\s+:/, $data, 2); my ($me, $target) = ($server->{'nick'}, find_target($to, $from)); my (%gsearch_active_chans, $site_url, $ajax_key); # Are we triggered? if($message !~ /^\s*!google\s+(.+)/i){ return 1; } my $encoded_query = uri_escape($1, "^A-Za-z0-9\-_.!~*'()+"); # pull in settings map { $gsearch_active_chans{uc($_)} = 1 } quotewords(',', 0, Irssi::settings_get_str('gsearch_channels')); $site_url = Irssi::settings_get_str('gsearch_site_url'); $ajax_key = Irssi::settings_get_str('gsearch_key'); # verify settings and trigger perms if(not $site_url or not $ajax_key){ Irssi::print("Google search: search detected, but settings not set."); return 1; } if(not (uc($to) eq uc($me) or $gsearch_active_chans{uc($to)})){ return 1; } my $agent = LWP::UserAgent->new( 'agent' => 'Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.8.0.1) ' . 'Gecko/20060124 Firefox/1.5.0.1'); my $reply = $agent->get( 'http://www.google.com/uds/GwebSearch?' . 'callback=GwebSearch.RawCompletion&' . 'context=0&lstkp=0&rsz=large&hl=en&gss=.com&' . 'sig=827f423b91df4dc97aaeb3a17d5711a0&' . 'q=' . $encoded_query . '&' . 'key=' . $ajax_key . '&v=1.0', 'Referer' => $site_url); my $results; if($reply->is_success){ if($reply->content_encoding and $reply->content_encoding eq 'gzip'){ $results = Compress::Zlib::memGunzip($reply->content); } else { # assume they're plain-text... $results = $reply->content; } } else { $server->command("msg $target Error: Google died. The end is near."); return 1; } # Cut out the JS call and parse $results =~ s/^GwebSearch.RawCompletion\('\d+',//; my $resh = jsonToObj($results); if(not $resh){ $server->command("msg $target Error: Google is speaking gibberish."); return 1; } if($#{$resh->{results}} < 0){ $server->command("msg $target No results."); return 1; } my $first = $resh->{results}[0]; $server->command("msg $target " . untag(decode_entities($first->{title})) . " <$first->{unescapedUrl}>\n"); return 1; } sub find_target { my ($to, $from) = @_; if($to =~ /^#/){ return $to; # from a channel } return $from; # from a private message } # powerman's HTML striper: http://www.perlmonks.org/?node_id=161281 sub untag { local $_ = $_[0] || $_; # ALGORITHM: # find < , # comment , # or comment , # or one of the start tags which require correspond # end tag plus all to end tag # or if \s or =" # then skip to next " # else [^>] # > s{ < # open tag (?: # open group (A) (!--) | # comment (1) or (\?) | # another comment (2) or (?i: # open group (B) for /i ( TITLE | # one of start tags SCRIPT | # for which APPLET | # must be skipped OBJECT | # all content STYLE # to correspond ) # end tag (3) ) | # close group (B), or ([!/A-Za-z]) # one of these chars, remember in (4) ) # close group (A) (?(4) # if previous case is (4) (?: # open group (C) (?! # and next is not : (D) [\s=] # \s or "=" ["`'] # with open quotes ) # close (D) [^>] | # and not close tag or [\s=] # \s or "=" with `[^`]*` | # something in quotes ` or [\s=] # \s or "=" with '[^']*' | # something in quotes ' or [\s=] # \s or "=" with "[^"]*" # something in quotes " )* # repeat (C) 0 or more times | # else (if previous case is not (4)) .*? # minimum of any chars ) # end if previous char is (4) (?(1) # if comment (1) (?<=--) # wait for "--" ) # end if comment (1) (?(2) # if another comment (2) (?<=\?) # wait for "?" ) # end if another comment (2) (?(3) # if one of tags-containers (3) ]*)? # skip junk to ">" ) # end if (3) > # tag closed }{}gsx; # STRIP THIS TAG return $_ ? $_ : ""; }