[Richard Suchenwirth] 2007-12-19 - I love the [Tcl chatroom]. It's like my peer group. So I was quite frustrated when good old Ralfchat (a [Perl] script) on `mini.net` was closed down, because that was my only way to participate in the chat from my cellphone. But isn't Tcl an enabling language? Why not put useful parts from [picoIRC 0.2] and [Playing CGI] together, and build my own bridge between [IRC] and [http]? The following script connects to IRC (you may have to fumble with the ircnick, if it is reported as being in use) and collects what is posted to the #tcl channel. It also provides a web server that listens on port 80, and provides the latest posts to the customer there. Also, it accepts posts and posts them to #tcl... It's all quite crude, but what do you expect from less than 90 lines of code, depending on nothing but [Tcl]? At least, it works quite well from my cell phone, so I can chat from that again (if a server is started). Here goes: ---- #!/usr/bin/env tclsh # webchain.tcl - HTTP <-> IRC bridge set port 80 set encoding iso8859-1 ;# utf-8 set nick someone set ircserver irc.freenode.org set ircport 6667 set chan #tcl set ircnick webchain set log {webchain Welcome.} proc irc_recv {} { gets $::fd line if {[string trim $line] ne ""} {puts $line} # handle PING messages from server if {[lindex [split $line] 0] eq "PING"} { send "PONG [info hostname] [lindex [split $line] 1]"; return } if {[regexp {:([^!]*)![^ ].* +PRIVMSG ([^ :]+) +:(.*)} $line -> \ nick target msg]} { set tag "" if [regexp {\001ACTION(.+)\001} $msg -> msg] {set tag italic} if [in {azbridge ijchain} $nick] { regexp {<([^>]+)>(.+)} $msg -> nick msg } if {$tag eq "italic"} {set msg $msg} lappend ::log $nick $msg } else {lappend ::log - $line} } proc in {list element} {expr {[lsearch -exact $list $element]>=0}} proc post msg { if [regexp {^/me (.+)} $msg -> action] {set msg "\001ACTION $action\001"} foreach line [split $msg \n] {send "PRIVMSG $::chan :<$::nick> $line"} } proc send str {puts $::fd $str; flush $::fd} proc answer {sock host2 port2} { fileevent $sock readable [list serve $sock] } proc encmap enc {string map {utf- UTF iso ISO-} $enc} proc serve sock { fconfigure $sock -blocking 0 -encoding $::encoding gets $sock line if {[fblocked $sock]} return fileevent $sock readable "" set args "" regexp {(/[^ ?]*)\??([^ ]*)?} $line -> tail args puts $sock "HTTP/1.0 200 OK" puts $sock "Content-Type: text/html;charset=$[encmap $::encoding]\n" puts $sock webchain array set a [concat {MSG "" NICK "" CNT 30} [kvsplit $args]] if {$a(MSG) ne ""} { lappend ::log $a(NICK) $a(MSG) set ::log [lrange $::log end-999 end] set ::nick $a(NICK) post $a(MSG) } foreach {nick post} [lrange $::log end-[expr {$a(CNT)*2-1}] end] { puts $sock "$nick: $post
" } puts $sock "

Nick: Lines:
" puts $sock close $sock } proc kvsplit kv { set res "" foreach i [split $kv &] { foreach {k v} [split $i =] break lappend res $k [unescape $v] } set res } proc unescape str { regsub -all {%(..)} [string map {+ " "} $str] {\u00\1} str subst $str } #-- connect to IRC... set ::fd [socket $::ircserver $::ircport] send "NICK $::ircnick" send "USER $::ircnick 0 * :Tcl user" send "JOIN $::chan" fileevent $::fd readable irc_recv socket -server answer $port puts "Server ready..." vwait forever ---- [jdc] I made some changes to the original scripts from [RS]: * Quoted special html characters * Set encoding of IRC channel to utf-8 * Convert data posted by browser to utf-8 * Added headers to pass W3C xhtml validation and to make the Nokia xhtml browser work * Added error when posting without specifying a nick name * Added logging * Added coloring of your own posts * Added favicon support (thanks [stevel]) * Added redirect after post (thanks [stevel] and validuser) * Added URL detection (copied from [WubWikit]) * Added emoticons (copied from [tkchat]), you'll need to download the emoticons as used by [tkchat] and stored them in the same directory as this script. ====== #!/usr/bin/env tclsh # webchain.tcl - HTTP <-> IRC bridge catch {console show} lappend auto_path /target/staff/decoster/activetcl/8.4.11.2/lib package require html set encoding iso8859-1 ;# utf-8 set nick someone set ircserver irc.freenode.org set ircport 6667 set chan #tcl set ircnick webchain set log {webchain Welcome.} set webchainserver set webchainport #Taken from tclers.tk/~jabber/emoticons/emoticons.tcl and tkchat proc SmileId { name n triggers } { foreach arg $triggers { set ::IMG($arg) $name if { [string is alnum -strict -failindex i $arg] } { lappend ids "\1$arg\2" } elseif { [string is alnum -strict [string index $arg end]] } { if {$i > 0} { lappend ids "\1$arg\2" } else { lappend ids "\3$arg\2" } } else { if {$i > 0} { lappend ids "\1$arg" } else { lappend ids "\3$arg" } } } set ids [join $ids "\0"] set map [list \ | \\| ( \\( ) \\) \[ \\\[ \ - \\- . \\. * \\* ? \\? \ \\ \\\\ ^ \\^ $ \\$ \1 \\m \ \2 \\M \3 \\Y \0 | \ ] # If we ever change this to use () capturing, change tkchat::Insert too. if { [info exists ::IMGre] } { append ::IMGre |[string map $map $ids] } else { set ::IMGre [string map $map $ids] } } SmileId cry 1 {":-(" ":^(" ":("} SmileId grrr 1 {"8-(" "8^(" "8(" "8-|" "8^|" "8|"} SmileId LOL-anim 1 {LOL lol} SmileId mad 1 {">:(" ">:-(" ">:^("} SmileId oh 1 {":-o" ":^o" ":o" ":-O" ":^O" ":O"} SmileId smile 1 {":-)" ":^)" ":)"} SmileId smile-big 1 {":-D" ":^D" ":D"} SmileId smile-dork 1 {"<:-)" "<:^)" "<:)"} SmileId smile-glasses 1 {"8-)" "8^)" "8)"} SmileId smile-tongue-anim 1 {":-p" ":^p" ":p"} SmileId smirk-glasses 1 {";/" ";-/" ";^/" ":/" ":-/" ":^/" "8/" "8-/" "8^/"} SmileId tongue2 1 {":-P" ":^P" ":P"} SmileId updown 1 {"(:" "(^:" "(-:"} SmileId wink-anim 1 {";-)" ";^)" ";)"} SmileId blush 1 {":-\}" ":^\}" ":8\}" ":\}"} SmileId coffee 1 LP SmileId lunch 1 {|O| |o| |0|} SmileId snooze 1 {zz zzz zzZ zZZ ZZZ ZZ} SmileId beer 1 "|_P" SmileId cyclops 1 {"O-\]" "O-)" "0-\]" "0-)"} SmileId donuts 1 "donuts" SmileId bug 1 {"bug #" "bug#"} SmileId wave 2 {~~~ waves} SmileId phone 3 {"on the phone"} SmileId yawn 3 {yawn yawns} SmileId applause 2 {applause applauds} set content_type(.ico) image/vnd.microsoft.icon set content_type(.gif) imagegif proc make_href {url} { return "[::html::quoteFormValue $url]" } proc make_iref {emo} { return "[::html::quoteFormValue $emo]" } proc sfrmt {msg} { set i 0 set n 0 set hmsg "" foreach match [regexp -inline -all -indices -- $::IMGre $msg] { lassign $match start end set emo [string range $msg $start $end] append hmsg [string range $msg $i [expr {$start-1}]] append hmsg [make_iref $emo] set i [expr {$end+1}] } if { $i <= [string length $msg] } { append hmsg [string range $msg $i end] } return $hmsg } proc hfrmt {msg} { set i 0 set n 0 set hmsg "" foreach match [regexp -inline -all -indices -- {(https?|ftp|news|mailto|file):([^\s:]\S*[^\]\)\s\.,!\?;:'>"])} $msg] { ;# keep emacs happy "]) if { $n % 3 == 0 } { lassign $match start end set url [string range $msg $start $end] append hmsg [sfrmt [string range $msg $i [expr {$start-1}]]] append hmsg [make_href $url] set i [expr {$end+1}] } incr n } if { $i <= [string length $msg] } { append hmsg [sfrmt [string range $msg $i end]] } return $hmsg } proc irc_recv {} { gets $::fd line string trim $line # handle PING messages from server if {[lindex [split $line] 0] eq "PING"} { send "PONG [info hostname] [lindex [split $line] 1]"; return } if {[regexp {:([^!]*)![^ ].* +PRIVMSG ([^ :]+) +:(.*)} $line -> \ nick target msg]} { set tag "" if [regexp {\001ACTION(.+)\001} $msg -> msg] {set tag italic} if [in {azbridge ijchain} $nick] { regexp {<([^>]+)>(.+)} $msg -> nick msg } if {$tag eq "italic"} { lappend ::log [::html::quoteFormValue $nick] [::html::quoteFormValue $msg] [hfrmt $msg] } else { lappend ::log [::html::quoteFormValue $nick] [::html::quoteFormValue $msg] [hfrmt $msg] } } else { lappend ::log - [::html::quoteFormValue $line] [hfrmt $line] } } proc in {list element} {expr {[lsearch -exact $list $element]>=0}} proc post msg { if [regexp {^/me (.+)} $msg -> action] { foreach line [split $action \n] {send "PRIVMSG $::chan :* $::nick $line"} } else { foreach line [split $msg \n] {send "PRIVMSG $::chan :<$::nick> $line"} } } proc send str { puts $::fd $str; flush $::fd } proc answer {sock host2 port2} { fileevent $sock readable [list serve $sock $host2 $port2] } proc encmap enc {string map {utf- UTF iso ISO-} $enc} proc serve {sock host2 port2} { fconfigure $sock -blocking 0 -encoding utf-8 gets $sock line puts "[clock seconds] $sock $host2 $port2 $line" if {[fblocked $sock]} return fileevent $sock readable "" set args "" regexp {(/[^ ?]*)\??([^ ]*)?} $line -> tail args if { [string match "/*.gif" $tail] || [string match "/*.ico" $tail] } { set fnm [string range $tail 1 end] if {[catch {set fileChannel [open $fnm RDONLY] } ]} { puts $sock "HTTP/1.0 404 Not found\n" puts $sock "<No such URL.>" puts $sock "
" puts $sock "The URL you requested does not exist on this site." puts $sock "
" close $sock } else { fconfigure $fileChannel -translation binary fconfigure $sock -translation binary -buffering full puts $sock "HTTP/1.0 200 OK" puts $sock "Content-Type: image/$::content_type([file extension $fnm])" puts $sock "Content-Length: [file size $fnm]\n" fcopy $fileChannel $sock -command [list done $fileChannel $sock] } } else { array set a [concat {MSG "" NICK "" CNT 30 GR 0} [kvsplit $args]] if {![string is integer -strict $a(CNT)]} { set a(CNT) 30 } if {![string is integer -strict $a(GR)]} { set a(GR) 0 } set a(NICK) [string map {" " _} $a(NICK)] set enick [::html::quoteFormValue $a(NICK)] if {$tail eq "/_post" && $a(NICK) ne ""} { puts $sock "HTTP/1.0 302 FOUND" puts $sock "Location: http://$::webchainserver:$::webchainport/?NICK=$enick&CNT=$a(CNT)&GR=$a(GR)#end" } else { puts $sock "HTTP/1.0 200 OK" } puts $sock "Content-Type: text/html;charset=UTF-8\n" puts $sock "" puts $sock "webchain" puts $sock "" puts $sock "" if {$tail eq "/_post"} { if {$a(NICK) ne ""} { set enick [::html::quoteFormValue $a(NICK)] if {$a(MSG) ne ""} { if [regexp {^/me (.+)} $a(MSG) -> action] { lappend ::log $enick [::html::quoteFormValue $action] [hfrmt $action] } else { lappend ::log $enick [::html::quoteFormValue $a(MSG)] [hfrmt $a(MSG)] } set ::log [lrange $::log end-998 end] set ::nick $a(NICK) post $a(MSG) set a(MSG) "" } puts $sock "If your browser doesn't automatically redirect, click here" } else { puts $sock "

You can not post without entering a nick name!

" hform $sock a } } else { foreach {nick post hpost} [lrange $::log end-[expr {$a(CNT)*3-1}] end] { if {$nick eq $enick} { set style my_posts } else { set style other_posts } if { $a(GR) } { puts $sock "

$nick: $hpost

" } else { puts $sock "

$nick: $post

" } } set a(MSG) "" hform $sock a } puts $sock close $sock } } proc hform { sock anm } { upvar $anm a puts $sock "
" puts $sock "

" puts $sock "

Nick: " puts $sock "Lines: " puts $sock "" puts $sock "

" } proc done {inChan outChan args} { close $inChan close $outChan } proc kvsplit kv { set res "" foreach i [split $kv &] { foreach {k v} [split $i =] break lappend res $k [unescape $v] } set res } proc unescape str { set str [string map [list + { } "\\" "\\\\"] $str] regsub -all -- {%([A-Fa-f0-9][A-Fa-f0-9])} $str {\\u00\1} str set str [subst -novariable -nocommand $str] set str [encoding convertfrom utf-8 $str] string trim $str } #-- connect to IRC... set ::fd [socket $::ircserver $::ircport] fconfigure $::fd -encoding utf-8 send "NICK $::ircnick" send "USER $::ircnick 0 * :Tcl user" send "JOIN $::chan" fileevent $::fd readable irc_recv socket -server answer $webchainport puts "Server ready..." vwait forever ====== ---- RFox added flying pig logo. [jdc] sorry, lost that mod in my latest version :-( ---- !!!!!! %| [Category Example] | [Category Internet] |% !!!!!!