Ircart.tcl

TCL Guest 7 Views Size: 8.63 KB Posted on: Aug 24, 26 @ 10:18 PM
  1. # ascii-art.tcl
  2. # display ASCII/ANSI art from text files via Eggdrop's
  3. # flood-protected queue, with a stop that actually clears the queue.
  4. #
  5. # Channel usage:
  6. #   !art <name>            !art fr-terminator/!art ans/lgcy-002/fr-terminator
  7. #                          extension optional: fr-terminator.ans also works
  8. #   !art list              list available art, grouped by folder
  9. #   !art stop              abort whatever is sending to this channel
  10.  
  11. namespace eval asciiart {
  12.     # ---- config ----------------------------------------------------------
  13.     variable trigger    "!art"
  14.     variable flags      "-|-"
  15.     variable artdir     "scripts/art"
  16.     variable exts       {txt ans asc nfo diz}   ;# extensions treated as art
  17.     variable artencoding cp437                   ;# decoding for .ans-family files
  18.  
  19.     # --- flood protectioopn ---------------------------------------------
  20.    # Output goes through putserv => Eggdrop's flood protection stays ON.
  21.    # We feed the queue in small refills so it never gets huge, which means
  22.    # 'stop' only ever has a little to flush. 'queuecap' is how many art
  23.    # lines we let sit in the server queue at once; raise it for faster
  24.    # output at the cost of a slightly longer drain after 'stop'.
  25.     variable queuecap   5
  26.     variable tick       1            ;# seconds between refills (utimer min = 1)
  27.    # ----------------------------------------------------------------------
  28.  
  29.     variable maxlines   0            ;# max lines per file (0 = unlimited)
  30.     variable cooldown   10           ;# seconds between art uses, per channel
  31.  
  32.     variable encmap ; array set encmap {txt utf-8}   ;# .txt = mIRC art (utf-8)
  33.     variable timer  ; array set timer {}   ;# chan -> active utimer id
  34.     variable last   ; array set last  {}   ;# chan -> unix time of last art use
  35.  
  36.     bind pub $flags $trigger asciiart::pub
  37.     putlog "ascii-art.tcl loaded: $trigger (dir: $artdir, queuecap: $queuecap)"
  38. }
  39.  
  40. # Recursively index art files -> list of {relname fullpath}.
  41. proc asciiart::index {dir {prefix ""}} {
  42.     variable exts
  43.     set out {}
  44.     foreach f [lsort [glob -nocomplain -directory $dir -type f *]] {
  45.         set e [string tolower [string trimleft [file extension $f] .]]
  46.         if {[lsearch -exact $exts $e] < 0} continue
  47.         set base [file rootname [file tail $f]]
  48.         set rel  [expr {$prefix eq "" ? $base : "$prefix/$base"}]
  49.         lappend out [list $rel $f]
  50.     }
  51.     foreach d [lsort [glob -nocomplain -directory $dir -type d *]] {
  52.         set sub [file tail $d]
  53.         set np  [expr {$prefix eq "" ? $sub : "$prefix/$sub"}]
  54.         set out [concat $out [asciiart::index $d $np]]
  55.     }
  56.     return $out
  57. }
  58.  
  59. # Read art, decode by extension, trim DOS-EOF (0x1A) + trailing SAUCE.
  60. proc asciiart::readart {path} {
  61.     variable encmap; variable artencoding
  62.     set ext [string tolower [string trimleft [file extension $path] .]]
  63.     set enc [expr {[info exists encmap($ext)] ? $encmap($ext) : $artencoding}]
  64.  
  65.     if {[catch {open $path r} fp]} { return -code error "open failed" }
  66.     if {$enc eq "binary"} {
  67.         fconfigure $fp -translation binary -encoding binary
  68.     } else {
  69.         fconfigure $fp -encoding $enc -translation auto
  70.     }
  71.     set lines {}
  72.     set first 1
  73.     while {[gets $fp line] >= 0} {
  74.         if {$first} { set line [string trimleft $line \uFEFF] ; set first 0 }
  75.         set z [string first \x1A $line]
  76.         if {$z >= 0} {
  77.             set line [string range $line 0 [expr {$z - 1}]]
  78.             if {$line ne ""} { lappend lines $line }
  79.             break
  80.         }
  81.         if {$line eq ""} { set line " " }
  82.         lappend lines $line
  83.     }
  84.     close $fp
  85.     return $lines
  86. }
  87.  
  88. proc asciiart::pub {nick uhost hand chan text} {
  89.     variable trigger; variable artdir; variable exts; variable maxlines
  90.     variable cooldown; variable timer; variable last
  91.  
  92.     set arg [string trim [lindex [split $text] 0]]
  93.  
  94.     # ---- stop ----
  95.     if {$arg eq "stop"} {
  96.         if {[info exists timer($chan)]} {
  97.             catch {killutimer $timer($chan)}
  98.             unset timer($chan)
  99.             catch {clearqueue server}   ;# flush whatever's still queued in Eggdrop
  100.             putserv "PRIVMSG $chan :stopped."
  101.         } else {
  102.             putserv "NOTICE $nick :nothing is sending to $chan"
  103.         }
  104.         return
  105.     }
  106.  
  107.     # one job at a time per channel (art OR a long listing)
  108.     if {[info exists timer($chan)]} {
  109.         putserv "NOTICE $nick :busy on $chan — use '$trigger stop' to abort"
  110.         return
  111.     }
  112.  
  113.     # ---- list ----
  114.     if {$arg eq "list"} {
  115.         set idx [asciiart::index $artdir]
  116.         if {![llength $idx]} {
  117.             putserv "PRIVMSG $chan :no art found in $artdir"
  118.             return
  119.         }
  120.         array set groups {}
  121.         foreach pair $idx {
  122.             set rel [lindex $pair 0]
  123.             lappend groups([file dirname $rel]) [file tail $rel]
  124.         }
  125.         set lines {}
  126.         foreach g [lsort [array names groups]] {
  127.             set label [expr {$g eq "." ? "(top)" : $g}]
  128.             lappend lines "$label: [join [lsort $groups($g)] {, }]"
  129.         }
  130.         asciiart::start $chan $lines
  131.         return
  132.     }
  133.  
  134.     if {$arg eq ""} {
  135.         putserv "PRIVMSG $chan :usage: $trigger <name>  ('$trigger list', '$trigger stop')"
  136.         return
  137.     }
  138.  
  139.     # cooldown (art only)
  140.     set now [clock seconds]
  141.     if {[info exists last($chan)] && ($now - $last($chan)) < $cooldown} {
  142.         set wait [expr {$cooldown - ($now - $last($chan))}]
  143.         putserv "NOTICE $nick :cooldown, try again in ${wait}s"
  144.         return
  145.     }
  146.  
  147.     # ---- resolve name ----
  148.     set segs [split $arg /]
  149.     foreach s $segs {
  150.         if {$s eq ".." || ![regexp {^[A-Za-z0-9._-]+$} $s]} {
  151.             putserv "NOTICE $nick :invalid name"
  152.             return
  153.         }
  154.     }
  155.     set lastseg [lindex $segs end]
  156.     set lastext [string tolower [string trimleft [file extension $lastseg] .]]
  157.     if {[lsearch -exact $exts $lastext] >= 0} {
  158.         set segs [lreplace $segs end end [file rootname $lastseg]]
  159.     }
  160.     set req  [join $segs /]
  161.     set reqL [string tolower $req]
  162.  
  163.     set idx [asciiart::index $artdir]
  164.     set matches {}
  165.     foreach pair $idx {
  166.         if {[string tolower [lindex $pair 0]] eq $reqL} { lappend matches $pair }
  167.     }
  168.     if {![llength $matches] && [llength $segs] == 1} {
  169.         foreach pair $idx {
  170.             if {[string tolower [file tail [lindex $pair 0]]] eq $reqL} {
  171.                 lappend matches $pair
  172.             }
  173.         }
  174.     }
  175.     if {![llength $matches]} {
  176.         putserv "NOTICE $nick :no such art: $req"
  177.         return
  178.     }
  179.     if {[llength $matches] > 1} {
  180.         set opts {}
  181.         foreach pair $matches { lappend opts [lindex $pair 0] }
  182.         putserv "NOTICE $nick :ambiguous '$req' — try: [join [lsort $opts] {, }]"
  183.         return
  184.     }
  185.  
  186.     set path [lindex [lindex $matches 0] 1]
  187.     if {[catch {asciiart::readart $path} lines]} {
  188.         putserv "NOTICE $nick :can't read $req"
  189.         return
  190.     }
  191.     if {![llength $lines]} {
  192.         putserv "NOTICE $nick :$req is empty"
  193.         return
  194.     }
  195.     if {$maxlines > 0 && [llength $lines] > $maxlines} {
  196.         putserv "NOTICE $nick :$req is [llength $lines] lines (max $maxlines)"
  197.         return
  198.     }
  199.  
  200.     set last($chan) $now
  201.     asciiart::start $chan $lines
  202. }
  203.  
  204. # begin a send job for a channel
  205. proc asciiart::start {chan lines} {
  206.     variable timer
  207.    # mark busy immediately so the guard + stop work even before first refill
  208.     set timer($chan) "init"
  209.     asciiart::pump $chan $lines 0
  210. }
  211.  
  212. # Refill the server queue up to queuecap, then reschedule. The timer stays
  213. # alive until BOTH all lines are handed over AND Eggdrop's queue has drained,
  214. # so 'stop' always has a live timer to kill and a queue to clear.
  215. proc asciiart::pump {chan lines idx} {
  216.     variable queuecap; variable tick; variable timer
  217.  
  218.     set total [llength $lines]
  219.  
  220.     # still have lines to hand over? top the queue back up to the cap.
  221.     if {$idx < $total} {
  222.         set room [expr {$queuecap - [queuesize server]}]
  223.         set i $idx
  224.         while {$room > 0 && $i < $total} {
  225.             putserv "PRIVMSG $chan :[lindex $lines $i]"
  226.             incr i
  227.             incr room -1
  228.         }
  229.         set timer($chan) [utimer $tick [list asciiart::pump $chan $lines $i]]
  230.         return
  231.     }
  232.  
  233.     # all lines handed over — keep ticking until the queue actually empties,
  234.    # so we don't free the channel (and drop 'stop's clear target) early.
  235.     if {[queuesize server] > 0} {
  236.         set timer($chan) [utimer $tick [list asciiart::pump $chan $lines $total]]
  237.         return
  238.     }
  239.  
  240.     catch {unset timer($chan)}
  241. }

Raw Paste

Comments 0
Login to post a comment.
  • No comments yet. Be the first.
Login to post a comment. Login or Register
We use cookies. To comply with GDPR in the EU and the UK we have to show you these.

We use cookies and similar technologies to keep this website functional (including spam protection via Google reCAPTCHA or Cloudflare Turnstile), and — with your consent — to measure usage and show ads. See Privacy.