Herring

Herring - extension for TkChat

Herring extends TkChat by adding wiki search and TWiG capabilities.

Directions to find it at Stu

Stu 2008-10-23 Created this page.


README

Herring - extension for TkChat
Stuart Cassoff
October 2008

Herring is a chunk of Tcl code that extends TkChat,
adding wiki search and TWiG capabilities.


(-O-) Config, Install & Nomenclature (-O-)

* Acquire: herring.
* Modify: change any paths listed in the
  '# Set paths here' chunk near the top of the file.
* Start: TkChat.
* Open: console from debug menu.
* Type: source /path/to/herring.tcl
* Hit: Enter, Return, CR, ^M or whatever.
* No errors should mean that herring is ready to go!

This doc:
* > means what you type.
* ] means response from chat.
* Checksums vetted at time of writing.


{-O-} Herring {-O-}

> /herring
] Herring        Herring 0.2
                October 2008, Stuart Cassoff
                /herring ? for help

> /herring ?
] Herring        Herring 0.2 can do
                /wikref
                /twig
                Consult each one and try to
                figure out what it does.


<-O-> Wikiref <-O->

To search the Tcler's wiki for 'term' and inject
the result into the chat:
> /wikiref term

Send the result to a user:
> /wikiref user term

Send the result to yourself:
> /wikiref me term


[-O-] TWiG [-O-]

Usage:
> /twig
] TWiG        TWiG 0.2
        /twig ? for help

Help:
> /twig ?
] TWiG        TWiG 0.2 Commands
        list ?-raw? ?user?
        run|view ?user? #.
        run|view page ?blocks?
        view ?filename?
        view ? (ask for file to view)
        view {} (view your .twig file)
        load ?filename?
        conf ?name? ?value?

Run 'colliding balls':
> /twig run 8573

View a clock
> /twig view 1011 1

Open a view window:
> /twig view

Ask for a file to load into a view window:
> /twig view ?

Specify a file to load and view:
> /twig view myfile

Load and view myTwigListFile:
> /twig view {}

Config:
> /twig conf
] TWiG        Configuration
        tclsh                /usr/local/bin/tclsh8.5
        wish                /usr/local/bin/wish8.5
        twig                ~/tcl/twig/twig.tcl
        run                /usr/local/bin/wish8.5
        myTwigListFile        ~/twig/twiglist.twig
        runPipe                0

> /twig conf wish
] TWiG        /usr/local/bin/wish8.5

> /twig conf wish ~/test/testwish
] TWiG        ~/test/testwish

Load myTwigListFile twig list:
> /twig load
] TWiG        ~/twig/twiglist.twig loaded ok

Load twig list:
> /twig load anothertwiglist

Ask and load twig list:
> /twig load ?

Lists:
> /twig list
] TWiG        no twigs

> /twig load
] TWiG        ~/twig/twiglist.twig loaded ok

> /twig list
] TWiG        Loaded TWiGs 
        1. Colliding balls
        2. Analog clock in Tk 1
        3. Digital clock in Tk
        4. tkGoldberg
        5. Kandinsky Dance
        6. Starfield

> /twig list -raw
TWiG        Loaded TWiGs  (raw)
        1. 8573 1 0D1C40670AA51DCA086643CCD5BE2AFA {} {Colliding balls}
        2. 1011 1 0620FE9712B3D0F7D222B45CC6BAB383 {} {Analog clock in Tk 1}
        3. 1011 2 DA0E37255F9EDEC2D0A7AD7A5F95DC98 {} {Digital clock in Tk}
        4. 8607 1 6D3E93EA90FF3F77736081A20976BA4E {} tkGoldberg
        5. 15178 1 91F26376C0EA8118032C7CD650B8D262 {} {Kandinsky Dance}
        6. 10100 1 E44CD350F9AE925AAEF2C328DAC550C6 {} Starfield

Refer to twigs from lists by number + dot:
> /twig run 5.
> /twig view 2.


|-O-| TkChat Integration |-O-|

Place twigs in the 'anything else' box of your 'User Info',
sandwiched between: -*- TWiGs -*- like this:

-*- TWiGs -*-
8573 0 0D1C40670AA51DCA086643CCD5BE2AFB {} {Colliding balls}
1011 1 0620FE9712B3D0F7D222B45CC6BAB383 {} {An analog clock in Tk}
8607 0 6D3E93EA90FF3F77736081A20976BA4E {} tkGoldberg
-*- TWiGs -*-

Then, others can access them through TkChat:

> /twig list stu
] TWiG        TWiGs for [email protected]
        1. Colliding balls
        2. An analog clock in Tk
        3. tkGoldberg

> /twig run stu 1.
> /twig view stu 2.

In the above, 'Colliding balls' has the wrong checksum, for testing.


# EOF

SOURCE

# herring
#
# May 2008
# Stuart Cassoff
#
# October 2008
# Slight update
#
# ISC license
#
# Use at your own risk :p
#

namespace eval herring {
        variable HookUserInfoDialog 0
        variable InfoRaw 0
        variable TwigAction [list {} 0]

        variable cfg

        set cfg(version) 0.2
        set cfg(verdate) "October 2008"
        set cfg(author) "Stuart Cassoff"
        set cfg(twigversion) 0.2
        

        # Set paths here
        set cfg(tclsh) /usr/local/bin/tclsh8.5
        set cfg(wish) /usr/local/bin/wish8.5
        set cfg(twig) ~/tcl/twig/twig.tcl
        set cfg(run) $cfg(wish)

        set cfg(myTwigListFile) ~/twig/twiglist.twig
        #


        set cfg(twigs) {}

        # Chat Text Window
        set cfg(ctw) .txt

        set cfg(aliasMap) [list \
                wikiref        ::herring::wikiref \
                twig        ::herring::twig \
                herring ::herring::herring
        ]

        set cfg(floodcount) 0
        set cfg(floodmax) 15

        set cfg(runPipe) 0

        set cfg(cfgElems) [list tclsh wish twig run myTwigListFile runPipe]
}

proc herring::herring {msg} {
        variable cfg
        set m {}
        if {[lindex $msg 0] eq "?"} {
                lappend m "Herring $cfg(version) can do"
                lappend m "/wikref"
                lappend m "/twig"
                lappend m "Consult each one and try to"
                lappend m "figure out what it does."
        } else {
                lappend m "Herring $cfg(twigversion)"
                lappend m "$cfg(verdate), $cfg(author)"
                lappend m "/herring ? for help"
        }
        print [join $m \n] MSG Herring
}

proc herring::wikisearch {what {max -1}} {
        set z {}
        set c 0
        set h [http::geturl https://wiki.tcl-lang.org/_search?S=$what]
        if {[http::status $h] eq {ok}} {
                foreach {. p n} [regexp -inline -all {<li>.*?\. \. \. <a href="/(.*?)">(.*?)</a>} [http::data $h]] {
                        lappend z https://wiki.tcl-lang.org/$p\ \t$n   
                        if {$max == -1} { continue }
                        if {[incr c] >= $max} {        break }
                }
        }
        http::cleanup $h
        return $z
}

proc herring::wikiref {msg} {
        global Options
        variable cfg

        set local 0
        if {[llength $msg] > 1} {
                set user [lindex $msg 0]
                set msg [lindex $msg 1]
                if {$user eq {me}} {
                        set user $Options(Nickname)
                }
                if {$user eq $Options(Nickname)} {
                        set local 1
                }
        } else  {
                set msg [lindex $msg 0]
                if {$msg eq "?"} {
                        lappend m "Wikiref $cfg(version) Commands"
                        lappend m "term (search term > chat)"
                        lappend m "user term (search term > user)"
                        lappend m "me term (search term > you)"
                        print [join $m \n] MSG Wikiref
                        return
                } elseif {$msg eq ""} {
                        lappend m "Wikiref $cfg(version)"
                        lappend m "/wikiref ? for help"
                        print [join $m \n] MSG Wikiref
                        return
                } else {
                        set user {}
                }
        }

        set msg [string map [list { } %20] $msg]
        set srchres [wikisearch $msg 10]
        if {[llength $srchres] > 0} {
                if {$local} {
                        print [join $srchres \n]
                } else {
                        send [join $srchres \n] $user
                }
        }
}

proc herring::loadTwigs {{fn {}}} {
        variable cfg

        if {$fn eq {}} {
                set fn $cfg(myTwigListFile)
        } elseif {$fn eq {?}} {
                set fn [tk_getOpenFile -defaultextension .twig -filetypes {{{TWiG Files} .twig} {{All Files} *}}]
                if {$fn eq {}} {
                        return {}
                }
        }

        set cfg(twigs) {}

        set f [open $fn r]
        set cfg(twigs) [split [read -nonewline $f] \n]
        close $f

        return "$fn loaded ok"
}

proc herring::prettyTwigs {twigs {raw 0}} {
        if {[llength $twigs] == 0} {
                return {no twigs}
        }
        set t {}
        set n 0
        foreach l $twigs {
                if {$raw} {
                        set l [linsert $l 0 [incr n].]
                } else {
                        set l "[incr n]. [lindex $l 4]"
                }
                lappend t $l
        }
        return [join $t \n]
}

proc herring::twiggit {page {blocks 0} {sum {flipper}}} {
        variable cfg
        return [exec $cfg(tclsh) $cfg(twig) -s $sum $page $blocks]
}

proc herring::runTwig {page {blocks 0} {sum {trout}} {name {}}} {
        variable cfg
        if {$cfg(runPipe)} {
                exec $cfg(tclsh) $cfg(twig) -s $sum $page $blocks > /tmp/cow &
        } else {
                exec $cfg(run) <<[twiggit $page $blocks $sum] &
        }
}

proc herring::viewTwig {page {blocks 0} {sum {zorro}} {name {}}} {
        view [twiggit $page $blocks $sum] $name
}

proc herring::twig {msg} {
        variable cfg

        if {[catch {llength $msg}]} { return }
        set mlen [llength $msg]

        switch -exact -- [lindex $msg 0] {
        conf {
                if {$mlen > 1 && $mlen < 4} {
                        set elem [lindex $msg 1]
                        if {[set i [lsearch -exact $cfg(cfgElems) $elem]] == -1} {
                                print "Unknown config option \"$elem\""
                        }
                        set elemName [lindex $cfg(cfgElems) $i]
                        if {$mlen > 2} {
                                set cfg($elemName) [lindex $msg 2]
                        }
                        print $cfg($elemName)
                } elseif {$mlen == 1} {
                        set m {}
                        lappend m "Configuration"
                        foreach c $cfg(cfgElems) {
                                lappend m $c\t$cfg($c)
                        }
                        print [join $m \n]
                } else {
                        print "conf usage: conf ?name? ?value?"
                }
        }
        load {
                set fn {}
                set tag {}
                if {$mlen == 2} { set fn [lindex $msg 1] }
                if {[catch {loadTwigs $fn} e]} { set tag ERROR }
                if {$e eq {}} { return }
                print $e $tag
        }
        list {
                set start 1
                set raw [expr {[lindex $msg $start] eq {-raw}}]
                if {$raw} { incr start }
                set user [lindex $msg $start]
                if {$user ne {}} {
                        variable HookUserInfoDialog 1
                        variable TwigInfoRaw $raw
                        ::tkjabber::userinfo $user
                        return
                }
                if {[llength $cfg(twigs)] == 0} {
                        print {no twigs}
                        return
                }
                print Loaded\ TWiGs\ [expr {$raw?" (raw)":""}]\n[prettyTwigs $cfg(twigs) $raw] 
        }
        view -
        run {
                set hasNumDot [regexp {^(.+)\.$} [lindex $msg end] -> num]
                set msg0 [lindex $msg 0]
                if {$msg0 eq {view}} {
                        if {$mlen == 1} {
                                view
                                return
                        } elseif {$mlen == 2 && !$hasNumDot} {
                                set tag {}
                                if {[catch {viewFile [lindex $msg 1]} e]} { set tag ERROR }
                                if {$e eq {}} { return }
                                print $e $tag
                                return
                        }
                }
                if {$hasNumDot} {
                        if {$mlen > 2} {
                                variable HookUserInfoDialog 1
                                variable TwigAction [list $msg0 $num]
                                ::tkjabber::userinfo [lindex $msg 1]
                                return
                        }
                        if {[llength $cfg(twigs)] == 0} {
                                print {no twigs}
                                return
                        }
                        if {$num < 1 || $num > [llength $cfg(twigs)]} {
                                print {TWiG not found}
                                return
                        }
                        incr num -1
                        lassign [lindex $cfg(twigs) $num] page blocks sum url name
                } elseif {$mlen > 1} {
                        set page [lindex $msg 1]
                        set blocks [lindex $msg 2]
                        if {[llength $blocks] == 0} {
                                set blocks 0
                        }
                        set name {}
                        set sum DO-NOT-VERIFY-SUM
                } else {
                        print "run|view usage\nrun|view ?user? #.\nrun|view page ?blocks?\nview"
                        return
                }
                if {[catch [list ${msg0}Twig $page $blocks $sum $name] e]} {
                        print $e ERROR
                }
        }
        ? {
                set m {}
                lappend m "TWiG $cfg(twigversion) Commands"
                lappend m "list ?-raw? ?user?"
                lappend m "run|view ?user? #."
                lappend m "run|view page ?blocks?"
                lappend m "view ?filename?"
                lappend m "view ? (ask for file to view)"
                lappend m "view {} (view your .twig file)"
                lappend m "load ?filename?"
                lappend m "conf ?name? ?value?"
                print [join $m \n]
        }
        default {
                print "TWiG $cfg(twigversion)\n/twig ? for help"
        }
        }
}

proc herring::send {msg {user {me}}} {
        global Options
        variable cfg

        incr cfg(floodcount)

        if {$cfg(floodcount) > $cfg(floodmax)} {return}

        if {$cfg(floodcount) == $cfg(floodmax)} {
                print "stubot: outgoing msg count exceeded $cfg(floodmax) msgs - not sending"
                return
        }

        if {[set c [string index $msg 0]] ne { } && $c ne {/}} {
                set msg " $msg"
        }
        if {$user eq {me}} {
                set user $Options(Nickname)
        }

        set max 350
        if {[string length $msg] <= $max} {
                ::tkjabber::msgSend "/nolog$msg" -user $user -attrs [list nolog 1]
        } else {
                print "stubot: outgoing msg exceeded $max chars - not sending"
        }
}

proc herring::print {msg {tag MSG} {from TWiG}} {
        variable cfg
        global Options

        if {$tag eq {}} {
                info default [lindex [info level 0] 0] tag tag
        }

        $cfg(ctw) configure -state normal

        set stuff [split $msg \n]
        $cfg(ctw) insert end $from\t[lindex $stuff 0]\n $tag
        foreach l [lrange $stuff 1 end] {
                $cfg(ctw) insert end \t$l\n $tag
        }

        $cfg(ctw) configure -state disabled
        if {$Options(AutoScroll)} {
                $cfg(ctw) see end
        }
}

proc herring::run {code} {
        variable cfg
        exec $cfg(run) <<$code &        
}

proc herring::save {what {name {}}} {
        if {$name eq {}} {
                set name [tk_getSaveFile]
                if {$name eq {}} {
                        return
                }
        }
        if {[catch {
                set f [open $name w]
                puts -nonewline $f $what
                close $f
        } e]} {
                print $e ERROR
        } else {
                print "$name saved ok"
        }
}

proc herring::viewFile {{fn {}}} {
        variable cfg
        if {$fn eq {}} {
                set fn $cfg(myTwigListFile)
        } elseif {$fn eq {?}} {
                set fn [tk_getOpenFile]
                if {$fn eq {}} {
                        return {}
                }
        }

        set f [open $fn r]
        set d [read -nonewline $f]
        close $f

        view $d $fn

        return {}
}

proc herring::view {{what {}} {title {}}} {
        variable ::tkchat::NS

        set dlg .plop
        set w $dlg.f
        if {![winfo exists $dlg]} {
                set dlg [::tkchat::Dialog $dlg]
                set w [${NS}::frame $w]
                wm transient $dlg {}
                wm withdraw $dlg
                if {[llength [info command ::tkchat::img::Tkchat]] != 0} {
                        catch {wm iconphoto $dlg ::tkchat::img::Tkchat}
                }
                ::tkchat::ScrolledWidget text $w.text 0 1 -height 23 -width 80 -borderwidth 0 -padx 2 -pady 2 -font FNT
                ${NS}::button $w.br -text Run     -width -12 -command "::herring::run \[$w.text get 1.0 end\]"  -default active
                ${NS}::button $w.bc -text Clear   -width -12 -command "$w.text delete 1.0 end"                  -default active
                ${NS}::button $w.bs -text Save    -width -12 -command "::herring::save \[$w.text get 1.0 end\]" -default active
                ${NS}::button $w.bd -text Dismiss -width -12 -command [list wm withdraw $dlg]                   -default active

                grid $w.text -sticky news  -columnspan 4

                grid $w.br                   -sticky se -padx 4
                grid    ^  $w.bc             -sticky se
                grid    ^     ^  $w.bs       -sticky se -padx 4
                grid    ^     ^     ^  $w.bd -sticky se

                grid rowconfigure    $w 0 -weight 1
                grid columnconfigure $w 0 -weight 1

                grid $w -sticky news

                grid rowconfigure    $dlg 0 -weight 1
                grid columnconfigure $dlg 0 -weight 1

                bind $dlg <Control-q> [list $w.bd invoke]
                bind $dlg <Control-Q> [list $w.bd invoke]

                catch {::tk::PlaceWindow $dlg widget .}
        }
        wm title $dlg $title
        $w.text delete 1.0 end
        $w.text insert end $what
        focus $dlg
        wm deiconify $dlg
        wm attributes $dlg -topmost 1
        wm attributes $dlg -topmost 0
        # This was originally TkChat's [About]
}


# Here starts the evil section

proc herring::incomingUserInfo {jid desc} {
        variable TwigInfoRaw
        variable TwigAction

        set delim {-*- TWiGs -*-}
        set delimmatch {-\*-*TWiGs*-\*-}
        set twigs {}
        set inTwigs 0

        foreach l [split $desc \n] {
                set l [string trim $l]
                if {$inTwigs} {
                        if {[string match -nocase $delimmatch $l]} {
                                set inTwigs 0
                        } else {
                                if {![catch {llength $l} len] && $len == 5} {
                                        lappend twigs $l
                                }
                        }
                } elseif {[string match -nocase $delimmatch $l]} {
                        set inTwigs 1
                }
        }

        if {[set action [lindex $TwigAction 0]] ne {} && [set i [lindex $TwigAction 1]] > 0} {
                set TwigAction [list {} 0]
                if {$i < 1 || $i > [llength $twigs]} {
                        print {TWiG not found}
                        return
                }
                incr i -1
                lassign [lindex $twigs $i] page blocks sum url name
                if {[catch {${action}Twig $page $blocks $sum $name} e]} {
                        print $e ERROR
                }
        } else {
                print TWiGs\ for\ $jid[expr {$TwigInfoRaw?" (raw)":""}]\n[prettyTwigs $twigs $TwigInfoRaw]
        }
}

proc herring::pimple {n1 n2 op} {
        uplevel 1 {trace remove variable UI(id) write ::herring::pimple}
        variable TwigInfo [uplevel 1 { set UI(DESC) }]
        rename ::tkchat::Dialog ::herring::_Dialog
        proc ::tkchat::Dialog {id} {
                rename ::tkchat::Dialog {}
                rename ::herring::_Dialog ::tkchat::Dialog
                ::tkchat::addStatus 0 "Getting [uplevel 1 { set jid }]'s TWiGs ..."
                after idle [list ::herring::incomingUserInfo [uplevel 1 { set jid }] [uplevel 1 { set UI(DESC) }]]
                uplevel 1 { unset -nocomplain [namespace current]::$id UI }
                uplevel 1 { return -level 2 }
        }
}

proc ::herring::cow {n1 n2 op} {
        trace remove variable ::tkchat::UserInfoWin write ::herring::cow
        uplevel 1 {trace add variable UI(id) write ::herring::pimple}
}


if {[info command ::tkchat::_UserInfoDialog] eq {}} {
        rename ::tkchat::UserInfoDialog ::tkchat::_UserInfoDialog
}
proc ::tkchat::UserInfoDialog {{jid {}}} {
#        upvar #0 ::herring::HookUserInfoDialog HookUserInfoDialog
        variable ::herring::HookUserInfoDialog

        if {!$HookUserInfoDialog} {
                ::tkchat::_UserInfoDialog $jid
                return
        }

        set HookUserInfoDialog 0

        trace remove variable ::tkchat::UserInfoWin write ::herring::cow
        variable UserInfoWin
        unset -nocomplain UserInfoWin
        trace add variable ::tkchat::UserInfoWin write ::herring::cow

        ::tkchat::_UserInfoDialog $jid

        trace remove variable ::tkchat::UserInfoWin write ::herring::cow
}

# End of evil section


proc ::herring::init {} {
        variable cfg


        foreach {alias proc} $cfg(aliasMap) {
                ::tkchat::processAliasCommand "/alias $alias proc $proc"
        }
}

namespace eval herring {
        init
}


# EOF