-
Notifications
You must be signed in to change notification settings - Fork 1
/
autoscripts.tcl
611 lines (575 loc) · 20.3 KB
/
autoscripts.tcl
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
package require json
package require json::write
package require http
package require tls
set asidx 0
set eggdir "autoscripts"
set cmdtxt "\nEnter your command (done to exit):"
set jsondict [dict create]
set asmajor 1
set asminor 0
bind DCC n autoscript console
proc console {hand idx arg} {
global echostatus
global oldchan
global asidx
set oldchan [getchan $idx]
set echostatus [echo $idx]
set asidx $idx
setchan $idx 469
echo $idx 0
bind FILT n * parse_egg
bind evnt n prerehash {egg_done $asidx}
putdcc $idx " _ _ _ _ "
putdcc $idx " /_\\ _ _| |_ ___ ___ ___ _ __(_)_ __ | |_ "
putdcc $idx " //_\\\\| | | | __/ _ \\/ __|/ __| '__| | '_ \\| __|"
putdcc $idx "/ _ \\ |_| | || (_) \\__ \\ (__| | | | |_) | |_ "
putdcc $idx "\\_/ \\_/\\__,_|\\__\\___/|___/\\___|_| |_| .__/ \\__|"
putdcc $idx " |_| "
putdcc $idx "=======================================================\n"
putdcc $idx "Welcome to the autoscript console. Enter your command: "
}
#Read all JSON script files
proc readjsonfile {} {
global jsondict
global eggdir
set jsonlist {}
### How to get filepath properly
foreach dirname [glob -nocomplain -type d -directory $eggdir *] {
if {![file exists $dirname/manifest.json]} {
file delete -force $dirname
putlog "$dirname missing manifest.json, deleting"
} else {
set fs [open $dirname/manifest.json r]
set contents [read $fs]
set jcontents [json::json2dict $contents]
if {[dict exists $jcontents schema]} {
if {![string equal [dict get $jcontents schema] "1"]} {
putlog "$dirname contains invalid manifest.json format, skipping..."
continue
}
}
lappend jsonlist $contents
close $fs
}
}
set jsondict [json::json2dict "\[[join $jsonlist {,}]\]"]
}
# Write a script's JSON content to file
proc write_json {script jstring} {
global eggdir
set fs [open $eggdir/${script}/manifest.json w]
puts $fs $jstring
close $fs
}
# Send an HTTP request. Type 0 for text, 1 for binary
proc send_http {url type} {
global eggdir
http::register https 443 [list ::tls::socket -autoservername true]
set req [http::config -useragent "eggdrop"]
if {$type} {
set fs [open $eggdir/[lindex [split $url /] end] w]
catch {set req [http::geturl $url -binary 1 -channel $fs]} error
close $fs
} else {
catch {set req [http::geturl $url -headers [dict create "User-Agent" "Mozilla/5.0"] -timeout 10000]} error
}
set status [http::status $req]
if {$status != "ok"} {
putlog "HTTP request error: $error"
return
}
set data [http::data $req]
::http::cleanup $req
return $data
}
proc createvarns {varname} {
for {set i 0} {$i < [llength [split $varname ::]] - 1} {incr i} {
namespace eval [join [lrange [split $varname ::] 0 $i] ::] {}
}
}
# Read all manifest files
proc loadscripts {} {
global jsondict
global eggdir
foreach scriptentry $jsondict {
if [dict get $scriptentry config loaded] {
if {[dict exists $scriptentry config vars]} {
foreach configvar [dict keys [dict get $scriptentry config vars] *] {
uplevel #0 [list createvarns $configvar]
set ::$configvar [dict get $scriptentry config vars $configvar value]
}
}
if {[catch {uplevel #0 [list source $eggdir/[dict get $scriptentry name]/[dict get $scriptentry name].tcl]} err]} {
putlog "Error loading [dict get $scriptentry name]: $err"
return
}
}
}
}
# Initial function called from autoscript console, sends to proper proc based on args
proc parse_egg {idx text} {
global echostatus
global oldchan
global asidx
# Check if this is the user who triggered the console
if {$idx != $asidx} {
return
}
set args [split $text]
set args [lassign $args subcmd arg1 arg2]
if {$subcmd in {done}} {
egg_done $idx "parse"
} elseif {$subcmd in {remote list help}} {
egg_$subcmd $idx
} elseif {$subcmd in {update}} {
egg_$subcmd $idx $arg1
} elseif {$subcmd in {config fetch clean}} {
if {$arg1 eq ""} {
putdcc $idx "Missing parameter, must be $::lastbind $subcmd scriptName"
} else {
egg_$subcmd $idx $arg1
}
} elseif {$subcmd in {load unload}} {
if {$arg1 eq ""} {
putdcc $idx "Missing parameter, must be $::lastbind $subcmd scriptName"
} else {
egg_load $idx $arg1 [expr {$subcmd eq "load"}]
}
} elseif {$subcmd in {set}} {
if {![llength $args]} {
putdcc $idx "Missing parameter, must be $::lastbind $subcmd scriptName settingName newSettingValue"
} else {
egg_$subcmd $idx $arg1 $arg2 [join $args]
}
} elseif {$subcmd in {loaded unloaded all help}} {
egg_$subcmd
} else {
putdcc $idx "Missing or unknown subcommand"
egg_help $idx
}
return
}
# List scripts that are locally present in .egg
proc egg_list {idx} {
global jsondict
global cmdtxt
readjsonfile
putdcc $idx "\nThe following scripts are available for configuration:"
putdcc $idx "-------------------------------------------------------"
foreach script $jsondict {
set loaded [expr {[dict get $script config loaded] == 1 ? "\[X\]" : "\[ \]"}]
putdcc $idx "* $loaded [dict get $script name] (v[dict get $script version_major].[dict get $script version_minor]) - [dict get $script description]"
if {[dict exists $script config requires] && [string length [dict get $script config requires]]} {
foreach pkg [dict get $script config requires] {
if {![string equal $pkg "null"]} {
if {![lsearch -exact [package names] $pkg]} {
putdcc $idx " ( ^ Must install Tcl $pkg package on host before loading)"
}
}
}
}
}
putidx $idx "Additional scripts available for download can be viewed with the 'remote' command"
putidx $idx "$cmdtxt"
}
# Load or unload a script and update JSON field
# loadme is 0 to unload a script, 1 to load a script
proc egg_load {idx script loadme} {
global jsondict
global eggdir
set found 0
readjsonfile
foreach scriptentry $jsondict {
if [string match $script [dict get $scriptentry name]] {
set found 1
if {$loadme} {
if {[dict exists $scriptentry config vars]} {
foreach configvar [dict keys [dict get $scriptentry config vars] *] {
uplevel #0 [list createvarns $configvar]
set ::$configvar [dict get $scriptentry config vars $configvar value]
}
}
if {[catch {uplevel #0 [list source $eggdir/${script}/${script}.tcl]} err]} {
putdcc $idx "Error loading ${script}: $err"
return
}
dict set scriptentry config loaded 1
putdcc $idx "* Loaded $script."
} else {
dict set scriptentry config loaded 0
putdcc $idx "* Removed $script from being loaded. Restart Eggdrop to complete."
set found 1
}
write_json $script [compile_json {dict config {dict vars {dict * dict}}} $scriptentry]
readjsonfile
break
}
}
if {!$found} {
putdcc $idx "* $script not found."
}
return $found
}
# List variables available for a script
proc egg_config {idx script} {
global cmdtxt
global jsondict
set found 0
foreach scriptentry $jsondict {
if {[string match $script [dict get $scriptentry name]]} {
set found 1
if {[dict exists $scriptentry long_description]} {
putdcc $idx "\n* [dict get $scriptentry long_description]\n\n"
}
if {![dict exists $scriptentry config vars]} {
putdcc $idx "* No variables are available to configure for $script"
} else {
putdcc $idx "* The following config options are available for $script:"
putdcc $idx "---------------------------------------------------------"
putdcc $idx "Variables available for configuration via the set command: "
foreach configvar [dict keys [dict get $scriptentry config vars] *] {
set printstr "* $configvar - [dict get $scriptentry config vars $configvar description]"
if {([string length $printstr] > 110) || ([string first "\n" $printstr] ne -1)} {
if {[string first "\n" [string range $printstr 0 110]] ne -1} {
set index [string first "\n" [string range $printstr 0 110]]
} else {
set index [string last { } $printstr 109]
}
putdcc $idx "[string range $printstr 0 $index]"
set printstr [string range $printstr $index+1 end]
while {[string length $printstr] > 0} {
if {([string length $printstr] > 102) || ([string first "\n" $printstr] ne -1)} {
if {[string first "\n" [string range $printstr 0 102]] ne -1} {
set index [string first "\n" [string range $printstr 0 102]]
} else {
set printstr [string trimleft $printstr]
set index [string last { } $printstr 101]
}
putdcc $idx " [string range $printstr 0 $index]"
set printstr [string range $printstr $index+1 end]
} else {
putdcc $idx " [string trimleft $printstr]"
putdcc $idx " (current value: [dict get $scriptentry config vars $configvar value])"
putdcc $idx "\n"
break
}
}
} else {
putdcc $idx "$printstr"
putdcc $idx " (current value: [dict get $scriptentry config vars $configvar value])"
putdcc $idx "\n"
}
}
# treats udef
putdcc $idx "\nChannel settings available for configuration via .chanset: "
if {[dict exists $scriptentry config udef]} {
foreach udef [dict keys [dict get $scriptentry config udef]] {
set utype [dict get $scriptentry config udef $udef type]
set uval null
if {[dict exists $scriptentry config udef $udef value]} {
set uval [dict get $scriptentry config udef $udef value]
}
switch -nocase -- $utype {
"flag" { putdcc $idx "* $udef ($utype) : [dict get $scriptentry config udef $udef description] .chanset <channel> +$udef" }
"str" -
"int" { putdcc $idx "* $udef ($utype) : [dict get $scriptentry config udef $udef description] .chanset <channel> $udef $uval" }
default { putdcc $idx "* $udef seems to exist but is not well defined" }
}
}
putdcc $idx ""
}
}
}
}
if {!$found} {
putdcc $idx "Script $script not found."
}
putidx $idx "$cmdtxt"
}
# Set a variable for a script
proc egg_set {idx script setting value} {
global cmdtxt
global jsondict
set noscript 1
set noset 1
foreach scriptentry $jsondict {
if {[string match $script [dict get $scriptentry name]]} {
set noscript 0
if [dict exists $scriptentry config vars $setting] {
set noset 0
dict set scriptentry config vars $setting value $value
write_json $script [compile_json {dict config {dict vars {dict * dict}}} $scriptentry]
putdcc $idx "* ${script}: Variable \"$setting\" set to \"${value}\""
putdcc $idx "* Use \"load $script\" to enable this change"
readjsonfile
}
}
}
if $noscript {
putdcc $idx "ERROR: Script \"${script}\" not found."
} elseif $noset {
putdcc $idx "* ${script}: Setting \"$setting\" not found."
}
putidx $idx "$cmdtxt"
}
# Pull down remote Tcl file listing
# For future eggheads- 100 is the maximum WP supports without doing pagination
proc egg_remote {idx} {
global cmdtxt
global eggdir
putdcc $idx "* Retrieving script list, please wait..."
send_http "https://raw.githubusercontent.com/eggheads/autoscripts/master/manifest" 1
set fp [open $eggdir/manifest r]
set jsondata [read $fp]
close $fp
set datadict [json::json2dict $jsondata]
putdcc $idx "Scripts available for download:"
putdcc $idx "-------------------------------"
foreach scriptentry $datadict {
# regsub -all {<[^>]+>} [dict get $scriptentry caption rendered] "" scriptcap
putdcc $idx "* [format "%-16s %s" [dict get $scriptentry name] [dict get $scriptentry description]]"
}
putdcc $idx "\n"
putdcc $idx "* Type 'fetch <scriptname>' to download a script"
putidx $idx "$cmdtxt"
}
# Helper command for scripts- return a Tcl list of scripts that are loaded
proc egg_loaded {} {
global jsondict
list scriptlist
foreach scriptentry $jsondict {
if {[dict get $scriptentry config loaded]} {
lappend scriptlist [dict get $scriptentry name]
}
}
return $scriptlist
}
proc egg_update {idx tgtscript} {
global cmdtxt
global jsondict
global version
global asminor
global asmajor
set found 0
readjsonfile
set jsondata [send_http "https://raw.githubusercontent.com/eggheads/autoscripts/master/manifest" 0]
set datadict [json::json2dict $jsondata]
foreach localscript $jsondict {
foreach remotescript $datadict {
if {[string equal -nocase [dict get $remotescript name] [dict get $localscript name]]} {
if { ([dict get $remotescript version_minor] > [dict get $localscript version_minor] &&
[dict get $remotescript version_major] >= [dict get $localscript version_major]) ||
([dict get $remotescript version_major] > [dict get $localscript version_major]) } {
## If we're looking for a specific script, suppress other found messages
if {[string equal -nocase $tgtscript ""]} {
putdcc $idx "* [dict get $localscript name] has an update available."
}
set found 1
if {[string equal -nocase $tgtscript [dict get $localscript name]]} {
putdcc $idx "* Script update feature goes here- coming soon!"
}
}
}
}
}
if {!$found} {
putdcc $idx "* No updates available."
}
}
foreach script $jsondict {
set loaded [expr {[dict get $script config loaded] == 1 ? "\[X\]" : "\[ \]"}]
putdcc $idx "* $loaded [dict get $script name] (v[dict get $script version_major].[dict get $script version_minor]) - [dict get $script description]"
set asversion [send_http "https://www.eggheads.org/wp-content/uploads/simple-file-list/autoscript.txt" 0]
lassign [split $asversion .] major minor
if { ($minor > $asminor && $major >= $asmajor) || ($major > $asmajor)} {
putdcc $idx "* New version of autoscript found!"
} else {
putdcc $idx "* autoscript is up to date."
}
}
# Helper command for scripts- return a Tcl list of scripts that are loaded
proc egg_unloaded {} {
global jsondict
list scriptlist
foreach scriptentry $jsondict {
if {![dict get $scriptentry config loaded]} {
lappend scriptlist [dict get $scriptentry name]
}
}
return $scriptlist
}
# Helper command for scripts- return a Tcl list of scripts that are loaded
proc egg_all {} {
global jsondict
list scriptlist
foreach scriptentry $jsondict {
lappend scriptlist [dict get $scriptentry name]
}
return $scriptlist
}
# Download a script from the eggheads repository
proc egg_fetch {idx script} {
global cmdtxt
global eggdir
try {
set results [exec which tar]
set status 0
} trap CHILDSTATUS {results options} {
if {lindex [dict get $options -errorcode] 2} {
putdcc $idx "* ERROR: This feature is not available (tar not detected on this system, cannot extract a downloaded file)."
putidx $idx "$cmdtxt"
return
}
}
if {[file isdirectory $eggdir/$script]} {
putdcc $idx "* Script directory already exists. Not downloading again!"
putdcc $idx "* Use \"update $script\" if you're trying to update the script"
putidx $idx "$cmdtxt"
return
}
putdcc $idx "* Downloading, please wait..."
set jsondata [send_http "https://api.github.com/repos/eggheads/autoscripts/contents/packages" 0]
set datadict [json::json2dict $jsondata]
foreach scriptentry $datadict {
if {[string match ${script}.tgz [dict get $scriptentry name]]} {
send_http "[dict get $scriptentry download_url]" 1
putdcc $idx "* [dict get $scriptentry name] downloaded."
exec tar -zxf $eggdir/[dict get $scriptentry name] -C $eggdir
if {[file exists $eggdir/$script]} {
file delete $eggdir/[dict get $scriptentry name]
putdcc $idx "* [dict get $scriptentry name] extracted."
putdcc $idx "* Use 'config $script' to configure and then 'load $script' to load."
putidx $idx "$cmdtxt"
readjsonfile
} else {
putdcc $idx "* ERROR: [dict get $scriptentry name] not found. Cannot continue."
}
}
}
}
proc egg_clean {idx script} {
global cmdtxt
global eggdir
if {![egg_load $idx $script 0]} {
putdcc $idx "* Cannot remove $script"
return
}
if {[file isdirectory $eggdir/$script]} {
file delete -force $eggdir/$script
putdcc $idx "* $script deleted"
} else {
putdcc $idx "* $script not found"
}
putidx $idx "$cmdtxt"
}
proc egg_done {idx arg} {
global oldchan
global echostatus
putdcc $idx "Returning to partyline..."
unbind FILT n * parse_egg
unbind EVNT n prerehash {egg_done $asidx}
echo $idx $echostatus
setchan $idx $oldchan
}
proc egg_help {idx} {
global cmdtxt
putidx $idx "* The following commands are available for use with $::lastbind:"
putidx $idx " remote, fetch, list, config, set, load, unload, clean, done"
putidx $idx ""
putidx $idx "* remote : List scripts available for download"
putidx $idx "* fetch <script> : Download the script"
putidx $idx "* list : List all scripts present on the local system available for use"
putidx $idx "* config <script> : View configuration options for a script"
putidx $idx "* set <script> <setting> : Set the value for a script setting"
putidx $idx "* load <script> : Load a script, and enable a script to be loaded when Eggdrop starts"
putidx $idx "* unload <script> : Prevent a script from running when Eggdrop starts"
putidx $idx " (You must restart Eggdrop to stop a currently running script!)"
putidx $idx "* clean <script> : Permanently remove a script and any associated settings or files"
putidx $idx "* update \[script\] : Check for updates for autoscript, or specify a script to update"
putidx $idx "* done : Return to Eggdrop partyline"
putidx $idx "----------------------------------------------------------------------------------------------"
putidx $idx "$cmdtxt"
}
# Yikes.
proc compile_json {spec data} {
while [llength $spec] {
set type [lindex $spec 0]
set spec [lrange $spec 1 end]
switch -- $type {
dict {
lappend spec * unknown
set json {}
foreach {key val} $data {
foreach {keymatch valtype} $spec {
if {[string match $keymatch $key]} {
if {$key in {name displayname}} { set valtype string }
lappend json [subst {"$key":[compile_json $valtype $val]}]
break
}
}
}
return "{[join $json ,]}"
}
list {
if {![llength $spec]} {
set spec unknown
} else {
set spec [lindex $spec 0]
}
set json {}
foreach {val} $data {
lappend json [compile_json $spec $val]
}
return "\[[join $json ,]\]"
}
number {
if {$data eq "" || $data eq "null"} {
return null
} else {
return $data
}
}
string {
if {$data eq "" || $data eq "null"} {
return null
} else {
set new ""
for {set i 0} {$i < [string length $data]} {incr i} {
set c [string index $data $i]
set cc [scan $c %c]
if {$cc < 0x7e && $c ne "\\" && $cc >= 0x20 && $c ne "\""} {
append new $c
} else {
append new "\\u[format %04x $cc]"
}
}
return "\"$new\""
}
}
unknown {
if {$data eq "" || $data eq "null"} {
return null
} elseif {[string is double -strict $data] && ![string equal -nocase $data infinity] && ![string equal -nocase $data nan] && ![string equal -nocase $data -infinity]} {
return $data
} else {
set new ""
for {set i 0} {$i < [string length $data]} {incr i} {
set c [string index $data $i]
set cc [scan $c %c]
if {$cc < 0x7e && $c ne "\\" && $cc >= 0x20 && $c ne "\""} {
append new $c
} else {
append new "\\u[format %04x $cc]"
}
}
return "\"$new\""
}
}
}
}
}
if {![file exists autoscripts]} {file mkdir autoscripts}
readjsonfile
loadscripts
putlog "Loading autoscripts.tcl"