Advertisement
Guest User

Untitled

a guest
Apr 19th, 2014
52
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 19.15 KB | None | 0 0
  1. package provide htmlparse 0.5
  2.  
  3. proc htmlparse::mapescapes {text {char utf-8}} {
  4. # code below is neccessary to prevent numerous html markups
  5. # from appearing in the output (ie, ", ᘧ, etc)
  6. # stolen (borrowed is a better term) from perplexa's urban
  7. # dictionary script..
  8. if {![string match *&* $text]} {return $text}
  9. if {[string match "*;*" $char]} {set char [string trim $char {;}] }
  10. set escapes {
  11.   \xa0 ¡ \xa1 ¢ \xa2 £ \xa3 ¤ \xa4
  12. ¥ \xa5 ¦ \xa6 § \xa7 ¨ \xa8 © \xa9
  13. ª \xaa « \xab ¬ \xac ­ \xad ® \xae
  14. ¯ \xaf ° \xb0 ± \xb1 ² \xb2 ³ \xb3
  15. ´ \xb4 µ \xb5 ¶ \xb6 · \xb7 ¸ \xb8
  16. ¹ \xb9 º \xba » \xbb ¼ \xbc ½ \xbd
  17. ¾ \xbe ¿ \xbf À \xc0 Á \xc1 Â \xc2
  18. Ã \xc3 Ä \xc4 Å \xc5 Æ \xc6 Ç \xc7
  19. È \xc8 É \xc9 Ê \xca Ë \xcb Ì \xcc
  20. Í \xcd Î \xce Ï \xcf Ð \xd0 Ñ \xd1
  21. Ò \xd2 Ó \xd3 Ô \xd4 Õ \xd5 Ö \xd6
  22. × \xd7 Ø \xd8 Ù \xd9 Ú \xda Û \xdb
  23. Ü \xdc Ý \xdd Þ \xde ß \xdf à \xe0
  24. á \xe1 â \xe2 ã \xe3 ä \xe4 å \xe5
  25. æ \xe6 ç \xe7 è \xe8 é \xe9 ê \xea
  26. ë \xeb ì \xec í \xed î \xee ï \xef
  27. ð \xf0 ñ \xf1 ò \xf2 ó \xf3 ô \xf4
  28. õ \xf5 ö \xf6 ÷ \xf7 ø \xf8 ù \xf9
  29. ú \xfa û \xfb ü \xfc ý \xfd þ \xfe
  30. ÿ \xff ƒ \u192 Α \u391 Β \u392 Γ \u393 Δ \u394
  31. Ε \u395 Ζ \u396 Η \u397 Θ \u398 Ι \u399
  32. Κ \u39A Λ \u39B Μ \u39C Ν \u39D Ξ \u39E
  33. Ο \u39F Π \u3A0 Ρ \u3A1 Σ \u3A3 Τ \u3A4
  34. Υ \u3A5 Φ \u3A6 Χ \u3A7 Ψ \u3A8 Ω \u3A9
  35. α \u3B1 β \u3B2 γ \u3B3 δ \u3B4 ε \u3B5
  36. ζ \u3B6 η \u3B7 θ \u3B8 ι \u3B9 κ \u3BA
  37. λ \u3BB μ \u3BC ν \u3BD ξ \u3BE ο \u3BF
  38. π \u3C0 ρ \u3C1 ς \u3C2 σ \u3C3 τ \u3C4
  39. υ \u3C5 φ \u3C6 χ \u3C7 ψ \u3C8 ω \u3C9
  40. ϑ \u3D1 ϒ \u3D2 ϖ \u3D6 • \u2022
  41. … \u2026 ′ \u2032 ″ \u2033 ‾ \u203E
  42. ⁄ \u2044 ℘ \u2118 ℑ \u2111 ℜ \u211C
  43. ™ \u2122 ℵ \u2135 ← \u2190 ↑ \u2191
  44. → \u2192 ↓ \u2193 ↔ \u2194 ↵ \u21B5
  45. ⇐ \u21D0 ⇑ \u21D1 ⇒ \u21D2 ⇓ \u21D3 ⇔ \u21D4
  46. ∀ \u2200 ∂ \u2202 ∃ \u2203 ∅ \u2205
  47. ∇ \u2207 ∈ \u2208 ∉ \u2209 ∋ \u220B ∏ \u220F
  48. ∑ \u2211 − \u2212 ∗ \u2217 √ \u221A
  49. ∝ \u221D ∞ \u221E ∠ \u2220 ∧ \u2227 ∨ \u2228
  50. ∩ \u2229 ∪ \u222A ∫ \u222B ∴ \u2234 ∼ \u223C
  51. ≅ \u2245 ≈ \u2248 ≠ \u2260 ≡ \u2261 ≤ \u2264
  52. ≥ \u2265 ⊂ \u2282 ⊃ \u2283 ⊄ \u2284 ⊆ \u2286
  53. ⊇ \u2287 ⊕ \u2295 ⊗ \u2297 ⊥ \u22A5
  54. ⋅ \u22C5 ⌈ \u2308 ⌉ \u2309 ⌊ \u230A
  55. ⌋ \u230B ⟨ \u2329 ⟩ \u232A ◊ \u25CA
  56. ♠ \u2660 ♣ \u2663 ♥ \u2665 ♦ \u2666
  57. " \x22 & \x26 < \x3C > \x3E O&Elig; \u152 œ \u153
  58. Š \u160 š \u161 Ÿ \u178 ˆ \u2C6
  59. ˜ \u2DC   \u2002   \u2003   \u2009
  60. ‌ \u200C ‍ \u200D ‎ \u200E ‏ \u200F – \u2013
  61. — \u2014 ‘ \u2018 ’ \u2019 ‚ \u201A
  62. “ \u201C ” \u201D „ \u201E † \u2020
  63. ‡ \u2021 ‰ \u2030 ‹ \u2039 › \u203A
  64. € \u20AC ' \u0027 ‎ "" ‏ "" ‬ "" ‭ ""
  65. ‮ ""
  66. };
  67. set text [string map [list "\]" "\\\]" "\[" "\\\[" "\$" "\\\$" "\\" "\\\\"] [string map $escapes $text]]
  68. regsub -all -- {&#([[:digit:]]{1,5});} $text {[encoding convertto $char [format %c [string trimleft "\1" "0"]]]} text
  69. regsub -all -- {&#x([[:xdigit:]]{1,4});} $text {[encoding converto $char [format %c [scan "\1" %x]]]} text
  70. return [subst "$text"]
  71. }
  72.  
  73. # http-title.tcl --
  74. #
  75. # This script will display the page title of a web link pasted in a channel.
  76. # To enable, use: .chanset #channel +http-title
  77. #
  78. # Copyright (c) 2010, Rickard Utgren <rutgren@gmail.com>
  79. #
  80. # Permission to use, copy, modify, and/or distribute this software for any
  81. # purpose with or without fee is hereby granted, provided that the above
  82. # copyright notice and this permission notice appear in all copies.
  83. #
  84. # THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
  85. # WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
  86. # MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
  87. # ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
  88. # WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
  89. # ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
  90. # OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
  91. #
  92. # RCS: $Id$
  93. #
  94. # v0.1 by Pixelz (rutgren@gmail.com), April 29, 2010
  95.  
  96. # I'm holding release of this script until eggdrop 1.6.20 is released, because
  97. # of the planned utf-8 support and fix for fileevents in 1.6.20. The script has
  98. # only been tested with a beta version of the utf-8 patch, not with the other
  99. # utf-8 patch that's floating around, but I suspect that both will work. If it
  100. # doesn't, too bad. Wait for 1.6.20, or use Tcldrop instead, where everything
  101. # works perfectly!
  102.  
  103. # This script is still UNRELEASED, don't complain about things listed in
  104. # FixMe/ToDo. If you find a problem with something that's supposed to work, and
  105. # isn't listed below, then please contact me.
  106.  
  107. # FixMe:
  108. # 22:20:52 <@Pixelz> http://www.screenjunkies.com/tvphotos/18-awesome-david-caruso-memes
  109. # 22:20:53 <Sunflight> [21:20] Tcl error [::http-title::pubm]: invalid command name "zlib"
  110. #
  111. # ToDo:
  112. # - support gzip (it's not enough to just load the package, duh)
  113. # - cleanup cleanup cleanup
  114. # - fix FixMe's
  115. # - Rewrite to use "::http::geturl -command"
  116. # - Hold release until eggdrop properly supports UTF-8
  117.  
  118. # - Add a manual charset override that's exposed to users (separate from the .tld defaults we set)
  119. # - Add support for reading meta description, perhaps with a list where you can set which sites to do that for
  120. # - Add url-shortening (is.gd?, u.nu?)
  121.  
  122. package require Tcl 8.5
  123. package require eggdrop 1.6.20
  124. package require http 2.5
  125. package require htmlparse 1.1.3
  126.  
  127. namespace eval ::http-title {
  128. setudef flag {http-title}
  129. encoding system {utf-8}
  130. variable ::http::defaultCharset
  131. variable haveTls
  132. variable haveGzip
  133.  
  134. # These packages aren't hard requirements, so we check for the here instead of polluting the global namespace.
  135. # Check for the TLS package
  136. if {![catch {package require tls}]} {
  137. ::http::register https 443 [list ::tls::socket -require 0 -request 1]
  138. set haveTls 1
  139. putlog {http-title: TLS OpenSSL extension found, HTTPS available.}
  140. } else {
  141. set haveTls 0
  142. putlog {http-title: TLS OpenSSL extension not found, HTTPS unavailable.}
  143. }
  144.  
  145. # Check for zlib or Trf
  146. if {([lsearch -exact [info commands] zlib] != -1) || (![catch {package require zlib}])} {
  147. # we have zlib
  148. set haveGzip 1
  149. putlog {http-title: zlib found, gzip compression availible.}
  150. } elseif {([lsearch -exact [info commands] zip] != -1) || (![catch {package require Trf}])} {
  151. # we have Trf
  152. set haveGzip 2
  153. putlog {http-title: Trf found, gzip compression availible.}
  154. } else {
  155. set haveGzip 0
  156. putlog {http-title: zlib or Trf not found, gzip compression unavailable.}
  157. }
  158. }
  159.  
  160. # IDNA implementation by Alexey Shchepin
  161. # http://svn.xmpp.ru/repos/tkabber/trunk/tkabber/idna.tcl
  162. proc ::http-title::domain_toascii {domain} {
  163. set domain [string tolower $domain]
  164. set parts [split $domain "\u002E\u3002\uFF0E\uFF61"]
  165. set res {}
  166. foreach p $parts {
  167. set r [toascii $p]
  168. lappend res $r
  169. }
  170. return [join $res .]
  171. }
  172.  
  173. proc ::http-title::toascii {name} {
  174. # TODO: Steps 2, 3 and 5 from RFC3490
  175.  
  176. if {![string is ascii $name]} {
  177. set name [punycode_encode $name]
  178. set name "xn--$name"
  179. }
  180. return $name
  181. }
  182.  
  183. proc ::http-title::punycode_encode {input} {
  184. set base 36
  185. set tmin 1
  186. set tmax 26
  187. set skew 38
  188. set damp 700
  189. set initial_bias 72
  190. set initial_n 0x80
  191.  
  192. set n $initial_n
  193. set delta 0
  194. set out 0
  195. set bias $initial_bias
  196. set output ""
  197. set input_length [string length $input]
  198. set nonbasic {}
  199.  
  200. for {set j 0} {$j < $input_length} {incr j} {
  201. set c [string index $input $j]
  202. if {[string is ascii $c]} {
  203. append output $c
  204. } else {
  205. lappend nonbasic $c
  206. }
  207. }
  208.  
  209. set nonbasic [lsort -unique $nonbasic]
  210.  
  211. set h [set b [string length $output]];
  212.  
  213. if {$b > 0} {
  214. append output -
  215. }
  216.  
  217. while {$h < $input_length} {
  218. set m [scan [string index $nonbasic 0] %c]
  219. set nonbasic [lrange $nonbasic 1 end]
  220.  
  221. incr delta [expr {($m - $n) * ($h + 1)}]
  222. set n $m
  223.  
  224. for {set j 0} {$j < $input_length} {incr j} {
  225. set c [scan [string index $input $j] %c]
  226.  
  227. if {$c < $n} {
  228. incr delta
  229. } elseif {$c == $n} {
  230. for {set q $delta; set k $base} {1} {incr k $base} {
  231. set t [expr {$k <= $bias ? $tmin :
  232. $k >= $bias + $tmax ? $tmax : $k - $bias}]
  233. if {$q < $t} break;
  234. append output \
  235. [punycode_encode_digit \
  236. [expr {$t + ($q - $t) % ($base - $t)}]]
  237. set q [expr {($q - $t) / ($base - $t)}]
  238. }
  239.  
  240. append output [punycode_encode_digit $q]
  241. set bias [punycode_adapt \
  242. $delta [expr {$h + 1}] [expr {$h == $b}]]
  243. set delta 0
  244. incr h
  245. }
  246. }
  247.  
  248. incr delta
  249. incr n
  250. }
  251.  
  252. return $output;
  253. }
  254.  
  255. proc ::http-title::punycode_adapt {delta numpoints firsttime} {
  256. set base 36
  257. set tmin 1
  258. set tmax 26
  259. set skew 38
  260. set damp 700
  261.  
  262. set delta [expr {$firsttime ? $delta / $damp : $delta >> 1}]
  263. incr delta [expr {$delta / $numpoints}]
  264.  
  265. for {set k 0} {$delta > (($base - $tmin) * $tmax) / 2} {incr k $base} {
  266. set delta [expr {$delta / ($base - $tmin)}];
  267. }
  268.  
  269. return [expr {$k + ($base - $tmin + 1) * $delta / ($delta + $skew)}]
  270. }
  271.  
  272. proc ::http-title::punycode_encode_digit {d} {
  273. return [format %c [expr {$d + 22 + 75 * ($d < 26)}]]
  274. }
  275.  
  276. ## end of IDNA procs
  277.  
  278. proc ::http-title::fixCharset {charset} {
  279. set lcharset [string tolower $charset]
  280. switch -glob -nocase -- $charset {
  281. {utf-8} - {utf8} {
  282. set retval {utf-8}
  283. }
  284. {iso-*} {
  285. set retval [string map -nocase {{iso-} {iso}} $lcharset]
  286. }
  287. {windows-*} {
  288. set retval [string map -nocase {{windows-} {cp}} $lcharset]
  289. }
  290. {shift_jis*} {
  291. set retval [string map -nocase {{shift_jis} {shiftjis}} $lcharset]
  292. }
  293. {euc-*} {
  294. set retval $lcharset
  295. }
  296. default {
  297. set retval $charset
  298. }
  299. }
  300. if {[lsearch -exact [encoding names] $retval] == -1} {
  301. putlog "http-title.tcl error: unhandled charset \"$retval\". PLEASE REPORT THIS BUG!"
  302. return 1
  303. } else {
  304. return $retval
  305. }
  306. }
  307.  
  308. proc ::http-title::pubm {nick uhost hand chan text {url {}} {referer {}} {cookies {}} {redirects {}}} {
  309. if {[channel get $chan {http-title}] && ([string match -nocase "*http://*" [set stext [stripcodes bcruag $text]]] || [string match -nocase "*https://*" $stext] || [string match -nocase "*www.*" $stext]) && [regexp -nocase -- {(?:^|\s)(https?://[^\s\\$]+|www.[^\s\\$]+)} $stext - url]} {
  310. # set nick suffix
  311. if {[string equal -nocase [string index $nick end] s]} { set suffix {'} } else { set suffix {'s} }
  312. if {![string match -nocase "http://*" $url] && ![string match -nocase "https://*" $url]} { set url "http://${url}" }
  313. # fix urls like http://domain.tld?foo
  314. regsub -nocase -- {(^https?://[^/?]+)(\?)} $url {\1/?} url
  315. # split the domain from the url
  316. regexp -nocase -- {(https?://)([^/]+)(.*)} $url - pre domain post
  317. # detect urls like http://-www.google.com, which will be seen as a flag by [socket]
  318. if {[string index $domain 0] eq {-}} { return }
  319. # handle internationalized domain names
  320. set url ${pre}[domain_toascii $domain]${post}
  321. # first, do a HTTP HEAD request, to get an idea of what we're dealing with
  322. if {[set wget [wget $url 1]] eq {}} {
  323. return
  324. } else {
  325. array set state $wget
  326. }
  327. # Grab the content-type
  328. foreach {name value} $state(meta) {
  329. if {[string equal -nocase {content-type} $name]} {
  330. set content-type [regsub -- {^([^\;]+)(\;.*)?} $value {\1}]
  331. }
  332. }
  333. # bail out if we couldn't get the content-type
  334. if {![info exists content-type]} {
  335. return
  336. }
  337. # if content-type isn't html, we stop here
  338. if {${content-type} ne {text/html}} {
  339. array set meta $state(meta)
  340. if {[info exists meta(Content-Length)]} {
  341. putserv "PRIVMSG $chan :${nick}${suffix} URL title: N/A ( ${content-type}\; $meta(Content-Length) bytes )"
  342. return
  343. } else {
  344. putserv "PRIVMSG $chan :${nick}${suffix} URL title: N/A ( ${content-type}\; unknown size )"
  345. }
  346. return
  347. }
  348.  
  349. # now we do the real HTTP request to get the actual data.
  350. if {[set wget [wget $url 0]] eq {}} {
  351. return
  352. } else {
  353. array set state $wget
  354. }
  355. if {$state(status) eq {ok}} {
  356. set data $state(body)
  357. # grab the charset from the HTTP headers
  358. if {[info exists state(charset)]} {
  359. if {[set headerEnc [fixCharset $state(charset)]] eq {1}} { return }
  360. #} elseif {[info exists ::http::defaultCharset] && $::http::defaultCharset ne {}} {
  361. # set defaultHeaderEnc 1
  362. # if {[set headerEnc [fixCharset $::http::defaultCharset]] eq {1}} { return }
  363. #} else {
  364. # set defaultHeaderEnc 1
  365. # set headerEnc {iso8859-1}
  366. }
  367. # grab the charset from the HTML <meta> tags
  368. if {[regexp -nocase -- {<meta [^>]*?charset="?([a-zA-Z0-9\-_]+)[^>]*>} $data - metaEnc]} {
  369. if {[set metaEnc [fixCharset $metaEnc]] eq {1}} { return }
  370. if {![string equal -nocase $headerEnc $metaEnc]} {
  371. # HTTP header charset & meta charset doesn't match, assume that meta is correct
  372. set data [encoding convertfrom [string tolower $metaEnc] $data]
  373. }
  374. }
  375. # No charset detected, or it defaulted to iso8859-1. Set some defaults based on TLD
  376. # FixMe: set ::http::defaultCharset to this instead, before ::http::geturl
  377. # FixMe: doesn't work if a site is redirecting to a tld we're overriding here, ie tinyurl redirecting to a broken .ru site
  378. if {($headerEnc eq {iso8859-1}) && ![info exists metaEnc] && [regexp -nocase -- {^https?://[^/?]+\.([^/?]+)(?:$|[/?]).*$} $url - tld]} {
  379. switch -exact -nocase -- $tld {
  380. {ru} { set data [encoding convertfrom {koi8-r} $data] }
  381. default { }
  382. }
  383. }
  384. foreach {name value} $state(meta) {
  385. if {[string equal -nocase {content-type} $name]} {
  386. set content-type [regsub -- {^([^\;]+)(\;.*)?} $value {\1}]
  387. }
  388. }
  389. if {[regexp -nocase -- {<title>([^<]+)</title>} $data - title]} {
  390. # some sites like to put excessive whitespace in the middle of the title, so we get rid of it here.
  391. set title [join [regexp -inline -all -- {\S+} [::htmlparse::mapEscapes $title]]]
  392. # truncate titles that are too long
  393. if {[string length $title] > 350} {
  394. set title "[string range $title 0 350]..."
  395. }
  396. putserv "PRIVMSG $chan :${nick}${suffix} URL title: $title ( ${content-type}\; [string bytelength $data] bytes )"
  397. } else {
  398. putserv "PRIVMSG $chan :${nick}${suffix} URL title: N/A ( ${content-type}\; [string bytelength $data] bytes )"
  399. }
  400. }
  401. }
  402. }
  403.  
  404. # recursive wget with cookies and referer
  405. # mostly written by speechles
  406. # made to actually work by me
  407. proc ::http-title::wget {url validate {refer ""} {cookies ""} {re 0}} {
  408. http::config -urlencoding {utf-8}
  409. # if we have cookies, let's use em ;)
  410. if {![string length $cookies]} {
  411. catch {set token [http::geturl $url -validate $validate -timeout 3000]} error
  412. } else {
  413. catch {set token [::http::geturl $url -validate $validate -headers [list "Referer" "$refer" "Cookie" "[string trim [join $cookies {;}] {;}]" ] -timeout 3000]} error
  414. }
  415. # error condition 1, invalid socket or other general error
  416. if {![string match -nocase "::http::*" $error]} {
  417. putlog "Error: [string totitle [string map [list "\n" " | "] $error]] \( $url \)"
  418. return
  419. }
  420. # error condition 2, http error
  421. if {![string equal -nocase [::http::status $token] "ok"]} {
  422. putlog "Http error: [string totitle [::http::status $token]] \( $url \)"
  423. http::cleanup $token
  424. return
  425. }
  426. upvar #0 $token state
  427. # iterate through the meta array to grab cookies
  428. foreach {name value} $state(meta) {
  429. # do we have cookies?
  430. if {[regexp -nocase ^Set-Cookie$ $name]} {
  431. # yes, add them to cookie list
  432. lappend ourCookies [lindex [split $value {;}] 0]
  433. }
  434. }
  435. if {![info exists ourCookies]} {
  436. # if no cookies this iteration remember cookies from last
  437. if {[string length $cookies]} {
  438. set ourCookies $cookies
  439. } else {
  440. # we have no cookies at all
  441. set ourCookies {}
  442. }
  443. }
  444. # recursive redirect support, 300's
  445. # the full gambit of browser support, hopefully ... ;)
  446. if {[string match "*[http::ncode $token]*" "303|302|301" ]} {
  447. foreach {name value} $state(meta) {
  448. if {[regexp -nocase ^location$ $name]} {
  449. if {![string match "http*" $value]} {
  450. # fix our locations if needed
  451. if {![string match "/" [string index $value 0]]} {
  452. set value "[join [lrange [split $url {/}] 0 2] "/"]/$value"
  453. } else {
  454. set value "[join [lrange [split $url {/}] 0 2] "/"]$value"
  455. }
  456. }
  457. # catch redirect to self's. There is one rule:
  458. # A url can redirect to itself a few times to attempt to
  459. # gain proper cookies, or referers. This is hard-coded at 2.
  460. # We catch the 3rd time and poison our recursion with it.
  461. # This will stop the madness ;)
  462. # FixMe: I'm not so sure that this even works. The poison var should obviously be passed with the recursion
  463. if {[string match [string map {" " "%20"} $value] $url]} {
  464. if {![info exists poison]} {
  465. set poison 1
  466. } else {
  467. incr poison
  468. if {$poison > 2} {
  469. putlog "HTTP Error: Redirect error self to self \(3rd instance poisoned\) \( $url \)"
  470. return
  471. }
  472. }
  473. }
  474. # poison any nested recursion over 10 traversals deep. no
  475. # legitimate site needs to do this. EVER!
  476. if {[incr re] > 10} {
  477. putlog "HTTP Error: Redirect error (>10 too deep) \( $url \)"
  478. return
  479. }
  480. # recursive redirect by passing cookies and referer
  481. # this is what makes it now work! :)
  482. return [wget [string map {" " "%20"} $value] $validate $url $ourCookies $re]
  483. }
  484. }
  485. }
  486. # waaay down here, we finally check the ncode for 400 or 500 codes
  487. if {[string match 4* [http::ncode $token]] || [string match 5* [http::ncode $token]]} {
  488. putlog "Http resource is not evailable: [http::ncode $token] \( $url \)"
  489. return
  490. }
  491. # return the state array
  492. set retval [array get state]
  493. http::cleanup $token
  494. return $retval
  495. }
  496.  
  497. namespace eval ::http-title {
  498. bind pubm - "*" ::http-title::pubm
  499. putlog "Loaded http-title.tcl v0.1 by Pixelz"
  500. }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement