google.tcl 6.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231
  1. #
  2. # 0.2 - May 10 2010
  3. # - fix for garbled utf chars in api queries
  4. # - added +google channel flag to enable
  5. # - strip html from !convert as some formatting may be present
  6. # - fix decode_html to convert html utf to hex
  7. # - convert <sup></sup> to exponent
  8. #
  9. # 0.1 - Some time in April 2010
  10. # - Initial release
  11. #
  12. # Created Feb 28 2010
  13. #
  14. # Requires Tcl 8.5+
  15. # Requires tcllib for json
  16. #
  17. package require http
  18. package require json
  19. namespace eval google {
  20. #variable output_cmd "cd::putnow"
  21. variable output_cmd "putserv"
  22. # Not enforced for API queries
  23. variable useragent "Lynx/2.8.8dev.2 libwww-FM/2.14 SSL-MM/1.4.1"
  24. variable convert_url "http://www.google.ca/search"
  25. variable convert_regexp {<table class=std>.*?<b>(.*?)</b>.*?</table>}
  26. variable api_url "http://ajax.googleapis.com/ajax/services/search/"
  27. variable api_referer "http://www.egghelp.org"
  28. bind pub -|- "!g" google::search
  29. bind pub -|- "!google" google::search
  30. bind pub -|- "!news" google::news
  31. bind pub -|- "!images" google::images
  32. bind pub -|- "!convert" google::convert
  33. setudef flag google
  34. }
  35. proc google::convert_fetch {terms} {
  36. http::config -useragent $google::useragent
  37. set query [http::formatQuery q $terms]
  38. set token [http::geturl ${google::convert_url}?${query}]
  39. set data [http::data $token]
  40. set ncode [http::ncode $token]
  41. http::cleanup $token
  42. # debug
  43. #set fid [open "g-debug.txt" w]
  44. #puts $fid $data
  45. #close $fid
  46. if {$ncode != 200} {
  47. error "HTTP query failed: $ncode"
  48. }
  49. return $data
  50. }
  51. proc google::convert_parse {html} {
  52. if {![regexp -- $google::convert_regexp $html -> result]} {
  53. error "Parse error or no result"
  54. }
  55. set result [google::decode_html $result]
  56. # change <sup>num</sup> to ^num (exponent)
  57. set result [regsub -all -- {<sup>(.*?)</sup>} $result {^\1}]
  58. # strip rest of html code
  59. return [regsub -all -- {<.*?>} $result ""]
  60. }
  61. # Query normal html for conversions
  62. proc google::convert {nick uhost hand chan argv} {
  63. if {![channel get $chan google]} { return }
  64. if {[string length $argv] == 0} {
  65. $google::output_cmd "PRIVMSG $chan :Please provide a query."
  66. return
  67. }
  68. if {[catch {google::convert_fetch $argv} data]} {
  69. $google::output_cmd "PRIVMSG $chan :Error fetching results: $data."
  70. return
  71. }
  72. if {[catch {google::convert_parse $data} result]} {
  73. $google::output_cmd "PRIVMSG $chan :Error: $result."
  74. return
  75. }
  76. $google::output_cmd "PRIVMSG $chan :\002$result\002"
  77. }
  78. # Output for results from api query
  79. proc google::output {chan url title content} {
  80. regsub -all -- {(?:<b>|</b>)} $title "\002" title
  81. set output "$title @ $url"
  82. $google::output_cmd "PRIVMSG $chan :[google::decode_html $output]"
  83. }
  84. # Return results from API query of $url
  85. proc google::api_fetch {terms url} {
  86. set query [http::formatQuery v "1.0" q $terms safe off]
  87. set headers [list Referer $google::api_referer]
  88. set token [http::geturl ${url}?${query} -headers $headers -method GET -binary 1]
  89. set data [http::data $token]
  90. set ncode [http::ncode $token]
  91. http::cleanup $token
  92. # debug
  93. #set fid [open "g-debug.txt" w]
  94. #fconfigure $fid -translation binary -encoding binary
  95. #puts $fid $data
  96. #close $fid
  97. if {$ncode != 200} {
  98. error "HTTP query failed: $ncode"
  99. }
  100. return [json::json2dict $data]
  101. }
  102. # Validate input and then return list of results
  103. proc google::api_validate {argv url} {
  104. if {[string length $argv] == 0} {
  105. error "Please supply search terms."
  106. }
  107. if {[catch {google::api_fetch $argv $url} data]} {
  108. error "Error fetching results: $data."
  109. }
  110. set response [dict get $data responseData]
  111. set results [dict get $response results]
  112. if {[llength $results] == 0} {
  113. error "No results."
  114. }
  115. return $results
  116. }
  117. # Query api
  118. proc google::api_handler {chan argv url} {
  119. if {[catch {google::api_validate $argv $url} results]} {
  120. $google::output_cmd "PRIVMSG $chan :$results"
  121. return
  122. }
  123. foreach result $results {
  124. dict with result {
  125. # $language holds lang in news results, doesn't exist in web results
  126. if {![info exists language] || $language == "en"} {
  127. google::output $chan $unescapedUrl $title $content
  128. }
  129. }
  130. }
  131. }
  132. # Regular API search
  133. proc google::search {nick uhost hand chan argv} {
  134. if {![channel get $chan google]} { return }
  135. google::api_handler $chan $argv ${google::api_url}web
  136. }
  137. # News from API
  138. proc google::news {nick uhost hand chan argv} {
  139. if {![channel get $chan google]} { return }
  140. google::api_handler $chan $argv ${google::api_url}news
  141. }
  142. # Images from API
  143. proc google::images {nick uhost hand chan argv} {
  144. if {![channel get $chan google]} { return }
  145. google::api_handler $chan $argv ${google::api_url}images
  146. }
  147. # From perpleXa's urbandictionary script
  148. # Replaces html special chars with their hex equivalent
  149. proc google::decode_html {content} {
  150. if {![string match *&* $content]} {
  151. return $content;
  152. }
  153. set escapes {
  154. &nbsp; \x20 &quot; \x22 &amp; \x26 &apos; \x27 &ndash; \x2D
  155. &lt; \x3C &gt; \x3E &tilde; \x7E &euro; \x80 &iexcl; \xA1
  156. &cent; \xA2 &pound; \xA3 &curren; \xA4 &yen; \xA5 &brvbar; \xA6
  157. &sect; \xA7 &uml; \xA8 &copy; \xA9 &ordf; \xAA &laquo; \xAB
  158. &not; \xAC &shy; \xAD &reg; \xAE &hibar; \xAF &deg; \xB0
  159. &plusmn; \xB1 &sup2; \xB2 &sup3; \xB3 &acute; \xB4 &micro; \xB5
  160. &para; \xB6 &middot; \xB7 &cedil; \xB8 &sup1; \xB9 &ordm; \xBA
  161. &raquo; \xBB &frac14; \xBC &frac12; \xBD &frac34; \xBE &iquest; \xBF
  162. &Agrave; \xC0 &Aacute; \xC1 &Acirc; \xC2 &Atilde; \xC3 &Auml; \xC4
  163. &Aring; \xC5 &AElig; \xC6 &Ccedil; \xC7 &Egrave; \xC8 &Eacute; \xC9
  164. &Ecirc; \xCA &Euml; \xCB &Igrave; \xCC &Iacute; \xCD &Icirc; \xCE
  165. &Iuml; \xCF &ETH; \xD0 &Ntilde; \xD1 &Ograve; \xD2 &Oacute; \xD3
  166. &Ocirc; \xD4 &Otilde; \xD5 &Ouml; \xD6 &times; \xD7 &Oslash; \xD8
  167. &Ugrave; \xD9 &Uacute; \xDA &Ucirc; \xDB &Uuml; \xDC &Yacute; \xDD
  168. &THORN; \xDE &szlig; \xDF &agrave; \xE0 &aacute; \xE1 &acirc; \xE2
  169. &atilde; \xE3 &auml; \xE4 &aring; \xE5 &aelig; \xE6 &ccedil; \xE7
  170. &egrave; \xE8 &eacute; \xE9 &ecirc; \xEA &euml; \xEB &igrave; \xEC
  171. &iacute; \xED &icirc; \xEE &iuml; \xEF &eth; \xF0 &ntilde; \xF1
  172. &ograve; \xF2 &oacute; \xF3 &ocirc; \xF4 &otilde; \xF5 &ouml; \xF6
  173. &divide; \xF7 &oslash; \xF8 &ugrave; \xF9 &uacute; \xFA &ucirc; \xFB
  174. &uuml; \xFC &yacute; \xFD &thorn; \xFE &yuml; \xFF
  175. };
  176. set content [string map $escapes $content];
  177. set content [string map [list "\]" "\\\]" "\[" "\\\[" "\$" "\\\$" "\\" "\\\\"] $content];
  178. regsub -all -- {&#([[:digit:]]{1,5});} $content {[format %c [string trimleft "\1" "0"]]} content;
  179. regsub -all -- {&#x([[:xdigit:]]{1,4});} $content {[format %c [scan "\1" %x]]} content;
  180. regsub -all -- {&#?[[:alnum:]]{2,7};} $content "?" content;
  181. return [subst $content];
  182. }