slang.tcl 7.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272
  1. #
  2. # slang.tcl - June 24 2010
  3. # by horgh
  4. #
  5. # Requires Tcl 8.5+ and tcllib
  6. #
  7. # Made with heavy inspiration from perpleXa's urbandict script!
  8. #
  9. # Must .chanset #channel +ud
  10. #
  11. # Uses is.gd to shorten long definition URL if isgd.tcl package present
  12. #
  13. package require htmlparse
  14. package require http
  15. namespace eval ::ud {
  16. # set this to !ud or whatever you want
  17. variable trigger "slang"
  18. # maximum lines to output
  19. variable max_lines 1
  20. # approximate characters per line
  21. variable line_length 400
  22. # show truncated message / url if more than one line
  23. variable show_truncate 1
  24. # toggle whether we store raw response data.
  25. # this will store the response from an http request to urbandictionary.com
  26. # in files for debugging.
  27. # NOTE: enabling this will cause a file to be created for every request
  28. # the script makes, so these can pile up quickly!
  29. variable store_responses 0
  30. # the directory to store responses if store_responses is on.
  31. # this is under your eggdrop directory.
  32. # files under this directory will be named with unix timestamps
  33. # (microseconds).
  34. variable store_responses_dir slang_responses
  35. variable output_cmd "putserv"
  36. variable client "Mozilla/5.0 (compatible; Y!J; for robot study; keyoshid)"
  37. variable url http://www.urbandictionary.com/define.php
  38. variable url_random http://www.urbandictionary.com/random.php
  39. # regex to find the word
  40. variable word_regex {<a class="word" href=.*?>(.*?)</a>}
  41. variable list_regex {<div class='def-panel' data-defid='[0-9]+?'>.*?<div class='def-footer'>}
  42. variable def_regex {<div class='def-panel' data-defid='([0-9]+?)'>.*?<div class='meaning'>(.*?)</div>}
  43. setudef flag ud
  44. bind pub -|- $::ud::trigger ::ud::handler
  45. # 0 if isgd package is present
  46. variable isgd_disabled [catch {package require isgd}]
  47. }
  48. # write a console log message.
  49. proc ::ud::log {msg} {
  50. if {[string length $msg] == 0} {
  51. return
  52. }
  53. putlog "slang.tcl $msg"
  54. }
  55. proc ::ud::handler {nick uhost hand chan argv} {
  56. if {![channel get $chan ud]} { return }
  57. set argv [string trim $argv]
  58. set argv [split $argv]
  59. if {[string is digit [lindex $argv 0]]} {
  60. set number [lindex $argv 0]
  61. set query [join [lrange $argv 1 end]]
  62. } else {
  63. set query [join $argv]
  64. set number 1
  65. }
  66. set query [string trim $query]
  67. if {[llength $argv] == 1 && [string is digit [lindex $argv 0]]} {
  68. $::ud::output_cmd "PRIVMSG $chan :Usage: $::ud::trigger \[#\] <query> (or just $::ud::trigger for random definition)"
  69. return
  70. }
  71. if {$query == ""} {
  72. ::ud::log "Performing random query..."
  73. if {[catch {::ud::get_random} result]} {
  74. $::ud::output_cmd "PRIVMSG $chan :Error: $result"
  75. return
  76. }
  77. ::ud::output $chan $result
  78. } else {
  79. ::ud::log "Fetching definition $number of $query..."
  80. if {[catch {::ud::get_def $query $number} result]} {
  81. $::ud::output_cmd "PRIVMSG $chan :Error: $result"
  82. return
  83. }
  84. ::ud::output $chan $result
  85. }
  86. }
  87. proc ::ud::output {chan def_dict} {
  88. set output 0
  89. foreach line [::ud::split_line $::ud::line_length [dict get $def_dict definition]] {
  90. if {[incr output] > $::ud::max_lines} {
  91. if {$::ud::show_truncate} {
  92. $::ud::output_cmd "PRIVMSG $chan :Output truncated. [::ud::def_url $def_dict]"
  93. }
  94. break
  95. }
  96. $::ud::output_cmd "PRIVMSG $chan :$line"
  97. }
  98. }
  99. proc ::ud::get_random {} {
  100. set result [::ud::http_fetch $::ud::url_random ""]
  101. set word [dict get $result word]
  102. set defs_html [dict get $result definitions]
  103. if {[llength $defs_html] < 1} {
  104. error "Failure finding random definition."
  105. }
  106. return [::ud::parse $word [lindex $defs_html 0]]
  107. }
  108. proc ::ud::get_def {query number} {
  109. set page [expr {int(ceil($number / 7.0))}]
  110. set number [expr {$number - (($page - 1) * 7)}]
  111. set http_query [http::formatQuery term $query page $page]
  112. set result [::ud::http_fetch $::ud::url $http_query]
  113. set word [dict get $result word]
  114. set defs_html [dict get $result definitions]
  115. if {[llength $defs_html] < $number} {
  116. error "[llength $defs_html] definitions found."
  117. }
  118. return [::ud::parse $word [lindex $defs_html [expr {$number - 1}]]]
  119. }
  120. # store an http request response (if enabled).
  121. proc ::ud::store_response {data} {
  122. if {!$::ud::store_responses} {
  123. return
  124. }
  125. # ensure the directory to store the responses exists.
  126. if {![file isdirectory $::ud::store_responses_dir]} {
  127. # mkdir raises an error if it fails.
  128. file mkdir $::ud::store_responses_dir
  129. }
  130. # make the filename that we will store to.
  131. set base [clock microseconds]
  132. set path [file join $::ud::store_responses_dir $base]
  133. # write out the response
  134. set f [open $path w]
  135. puts -nonewline $f $data
  136. close $f
  137. ::ud::log "stored response to $path"
  138. }
  139. proc ::ud::http_fetch {url http_query} {
  140. http::config -useragent $::ud::client
  141. set token [http::geturl $url -timeout 20000 -query $http_query]
  142. set data [http::data $token]
  143. set ncode [http::ncode $token]
  144. set meta [http::meta $token]
  145. http::cleanup $token
  146. # Follow redirects
  147. if {[regexp -- {30[01237]} $ncode]} {
  148. set new_url [dict get $meta Location]
  149. return [::ud::http_fetch $new_url $http_query]
  150. }
  151. if {$ncode != 200} {
  152. error "HTTP fetch error. Code: $ncode"
  153. }
  154. # we may be storing responses for debugging.
  155. if {[catch {::ud::store_response $data} result]} {
  156. putlog "Problem storing response: $result"
  157. }
  158. return [::ud::parse_word_and_definitions $data]
  159. }
  160. # parse a response from a file.
  161. # this is primarily for debugging purposes. we can pass this function
  162. # a stored response file to try to parse it.
  163. proc ::ud::parse_response_file {path} {
  164. set f [open $path]
  165. set data [read -nonewline $f]
  166. close $f
  167. return [::ud::parse_word_and_definitions $data]
  168. }
  169. # first pass parsing - we pull out the word and the definitions from
  170. # the page.
  171. # we return a dictionary with keys 'word' and 'definitions' on success.
  172. # on failure, we raise an error.
  173. proc ::ud::parse_word_and_definitions {data} {
  174. # pull out the word.
  175. if {![regexp -- $::ud::word_regex $data -> word]} {
  176. error "Word not found. No definitions or parsing problem!"
  177. }
  178. set word [string trim $word]
  179. set definitions [regexp -all -inline -- $::ud::list_regex $data]
  180. set definition_count [llength $definitions]
  181. if {$definition_count == 0} {
  182. error "No definitions found"
  183. }
  184. return [list word $word definitions $definitions]
  185. }
  186. proc ::ud::parse {word raw_definition} {
  187. if {![regexp $::ud::def_regex $raw_definition -> number definition]} {
  188. error "Could not parse definition's HTML"
  189. }
  190. set definition [htmlparse::mapEscapes $definition]
  191. set definition [regsub -all -- {<.*?>} $definition ""]
  192. set definition [regsub -all -- {\n+} $definition " "]
  193. set definition [string tolower $definition]
  194. set definition [string trim $definition]
  195. return [list number $number word $word definition "$word is $definition"]
  196. }
  197. proc ::ud::def_url {def_dict} {
  198. set word [dict get $def_dict word]
  199. set number [dict get $def_dict number]
  200. set raw_url ${::ud::url}?[http::formatQuery term $word defid $number]
  201. if {$::ud::isgd_disabled} {
  202. return $raw_url
  203. } else {
  204. if {[catch {isgd::shorten $raw_url} shortened]} {
  205. return "$raw_url (is.gd error)"
  206. } else {
  207. return $shortened
  208. }
  209. }
  210. }
  211. # by fedex
  212. proc ::ud::split_line {max str} {
  213. set last [expr {[string length $str] -1}]
  214. set start 0
  215. set end [expr {$max -1}]
  216. set lines []
  217. while {$start <= $last} {
  218. if {$last >= $end} {
  219. set end [string last { } $str $end]
  220. }
  221. lappend lines [string trim [string range $str $start $end]]
  222. set start $end
  223. set end [expr {$start + $max}]
  224. }
  225. return $lines
  226. }
  227. putlog "slang.tcl loaded"