aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGravatar fireonlive2024-08-09 19:04:10 +0000
committerGravatar steering72532024-10-23 00:39:49 -0600
commit985dcb2901d2527b79976df787035425294200b9 (patch)
treed49caf539627b062c3e8406854ffbc214804661c
init
-rw-r--r--.gitignore2
-rw-r--r--.gitmodules3
-rw-r--r--8ball.tcl25
-rw-r--r--CONTENTS71
-rw-r--r--accountscript.tcl141
-rw-r--r--action.fix.tcl24
-rw-r--r--alltools.tcl439
-rw-r--r--archivebot-chat.tcl68
-rw-r--r--atme.tcl37
-rw-r--r--atmisc.tcl136
-rwxr-xr-xautobotchk484
m---------bmotion0
-rw-r--r--cmd_resolve.tcl46
-rw-r--r--compat.tcl136
-rw-r--r--dccwhois.tcl162
-rw-r--r--down-the-tube-chat.tcl67
-rw-r--r--firechannels.tcl56
-rw-r--r--fix-solanum-ban-caching.tcl14
-rw-r--r--forwardedSpamProtection.tcl10
-rw-r--r--getops.tcl369
-rw-r--r--klined.tcl177
-rw-r--r--lilykarma.tcl563
-rw-r--r--nitterhealth.tcl42
-rw-r--r--notes2.tcl218
-rw-r--r--pixseen/pixseen-msggen.tcl85
-rw-r--r--pixseen/pixseen-msgs/en.msg302
-rw-r--r--pixseen/pixseen.tcl1339
-rw-r--r--ques5.tcl368
-rw-r--r--quotepong.tcl289
-rw-r--r--remind.tcl330
-rw-r--r--sentinel.tcl1442
-rw-r--r--tcl.tcl69
-rw-r--r--telegrab-chat.tcl67
-rw-r--r--tell.tcl94
-rw-r--r--tiktok2proxitok.tcl49
-rw-r--r--transferinliner.tcl78
-rw-r--r--twitter2nitter-pre-expand.tcl63
-rw-r--r--twitter2nitter.tcl90
-rw-r--r--userinfo.tcl286
-rwxr-xr-xweed591
-rw-r--r--wiki.tcl111
-rw-r--r--youtube.tcl247
42 files changed, 9190 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..1b07dec
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,2 @@
+*.db
+*.dat
diff --git a/.gitmodules b/.gitmodules
new file mode 100644
index 0000000..9d0bbbe
--- /dev/null
+++ b/.gitmodules
@@ -0,0 +1,3 @@
+[submodule "bmotion"]
+ path = bmotion
+ url = https://github.com/jamesoff/bmotion
diff --git a/8ball.tcl b/8ball.tcl
new file mode 100644
index 0000000..e0d89d2
--- /dev/null
+++ b/8ball.tcl
@@ -0,0 +1,25 @@
+setudef flag 8ball
+
+bind pub * "!8ball" 8ball
+
+set 8ballantifloodsecs 3
+
+# source: https://magic-8ball.com/magic-8-ball-answers/
+set 8ballanswers {"it is certain" "it is decidedly so" "without a doubt" "yes definitely" "you may rely on it" "as i see it, yes" "most likely" "outlook good" "yes" "signs point to yes" "reply hazy, try again" "ask again later" "better not tell you now" "cannot predict now" "concentrate and ask again" "don’t count on it" "my reply is no" "my sources say no" "outlook not so good" "very doubtful"}
+
+set 8ballantiflood [dict create]
+
+proc 8ball {nick uhost hand chan text} {
+ if {[channel get $chan 8ball]} {
+ global 8ballanswers 8ballantiflood 8ballantifloodsecs
+ set host [lindex [split $uhost @] 1]
+ set key "$host,$chan"
+ if {[dict exists $8ballantiflood $key] && ([clock seconds] - [dict get $8ballantiflood $key]) <= $8ballantifloodsecs} {
+ putlog "\[8ball\] ignored '!8ball $text' from $nick!$uhost in $chan - anti-flood"
+ return 1
+ } else {
+ dict set 8ballantiflood $key [clock seconds]
+ putserv "PRIVMSG $chan :🎱: $nick, [lindex $8ballanswers [expr {int(rand()*[llength $8ballanswers])}]]"
+ }
+ }
+}
diff --git a/CONTENTS b/CONTENTS
new file mode 100644
index 0000000..7e395fb
--- /dev/null
+++ b/CONTENTS
@@ -0,0 +1,71 @@
+Directory Contents - scripts/
+Last revised: August 08, 2004
+ _____________________________________________________________________
+
+ Directory Contents - scripts/
+
+ These are the example scripts that come with eggdrop. They're meant to be
+ useful AND to introduce Tcl to those who haven't dealt with it before. Even
+ people who program in Tcl may be confused by Eggdrop's implementation at
+ first, because so many commands were added to interface with the bot.
+
+ action.fix.tcl
+ Gets rid of those ugly /me's people do instead of a .me.
+
+ alltools.tcl
+ Several useful procs for use in scripts.
+
+ autobotchk
+ Tcl script used to crontab your Eggdrop. Type 'scripts/autobotchk' from the
+ Eggdrop's root directory for help.
+
+ botchk
+ A shell script which can be used for auto-starting the bot via 'cron'.
+
+ cmd_resolve.tcl
+ Adds a dcc command called '.resolve' which can be used to resolve hostnames
+ or IP addresses via the partyline.
+
+ compat.tcl
+ Maps old Tcl functions to new ones, for lazy people who can't be bothered
+ updating their scripts.
+
+ dccwhois.tcl
+ Enhances Eggdrop's built-in dcc '.whois' command to allow all
+ users to '.whois' their own handle.
+
+ getops.tcl
+ A way for bots to get ops from other bots on the botnet (if they're on the
+ same channel).
+
+ klined.tcl
+ Removes servers from your server list that your bot has been k-lined on, to
+ prevent admins getting peeved with constant connects from your bot's host.
+
+ notes2.tcl
+ Check your notes on every shared bot of the hub.
+
+ ques5.tcl
+ Makes web pages of who's on each channel, updated periodically (requires
+ alltools.tcl).
+
+ quotepong.tcl
+ Some EFnet servers require the user to type /quote pong :<cookie>
+ when identd is broken or disabled. This will send pong :<cookie> to
+ the server when connecting.
+
+ sentinel.tcl (by slennox)
+ Flood protection script for Eggdrop with integrated BitchX CTCP simulation.
+ This script is designed to provide strong protection for your bot and
+ channels against large floodnets and proxy floods.
+
+ userinfo.tcl
+ Cute user info settings things.
+
+ weed
+ Weed out certain undesirables from an Eggdrop userlist. Type 'scripts/weed'
+ from the Eggdrop's root directory for help.
+
+ _____________________________________________________________________
+
+ Copyright (C) 2001 - 2023 Eggheads Development Team
diff --git a/accountscript.tcl b/accountscript.tcl
new file mode 100644
index 0000000..4a7abdd
--- /dev/null
+++ b/accountscript.tcl
@@ -0,0 +1,141 @@
+bind PUB * ,op acct_op
+
+proc acct_op {nick user hand chan text} {
+ set accthand [finduser -account [getaccount $nick]]
+ if [string match $accthand "*"] {
+ putserv "PRIVMSG $chan :$nick, you are not authenticated to NickServ and/or I don't know you!"
+ return 1
+ }
+ if {[matchattr $accthand +o|+o $chan]} {
+ pushmode $chan +o $text
+ putserv "PRIVMSG $chan :ok"
+ } else {
+ putserv "PRIVMSG $chan :$nick, you do not have access to ,op"
+ }
+}
+
+
+bind PUB * ,masskick acct_masskick
+
+proc acct_masskick {nick user hand chan text} {
+ set accthand [finduser -account [getaccount $nick]]
+ if [string match $accthand "*"] {
+ putserv "PRIVMSG $chan :$nick, you are not authenticated to NickServ and/or I don't know you!"
+ return 1
+ }
+ if {[matchattr $accthand +o $chan]} {
+ if {$text == ""} {
+ putserv "PRIVMSG $chan :\[masskick\] usage: ,masskick <nick>"
+ return 1
+ }
+ putserv "PRIVMSG $chan :\[masskick\] starting..."
+ foreach chann [channels] {
+ if {([botisop $chann]) && ([onchan $text $chann])} {
+ putkick $chann $text "Your behaviour is not conducive to the desired environment."
+ puthelp "PRIVMSG $chan :\[masskick\] kicked $text from $chann"
+ }
+ }
+ puthelp "PRIVMSG $chan :\[masskick\] done"
+ } else {
+ putserv "PRIVMSG $chan :$nick, you do not have access to ,masskick"
+ }
+}
+
+bind PUB * ,whoami acct_whoami
+
+proc acct_whoami {nick user hand chan text} {
+ set acct [getaccount $nick]
+ set accthand [finduser -account $acct]
+ if [string match $acct ""] {
+ putserv "PRIVMSG $chan :idk"
+ return 1
+ }
+ putserv "PRIVMSG $chan :$nick: you're authenticated to NickServ as $acct"
+ if [string match $accthand ""] {
+ putserv "PRIVMSG $chan :$nick: '$acct' isn't a known user to me, though."
+ } else {
+ putserv "PRIVMSG $chan :$nick: hi! '$acct' is also a known user to me! (flags: [chattr $accthand * $chan])"
+ }
+}
+
+bind PUB * ,whois acct_whois
+
+proc acct_whois {nick user hand chan text} {
+ set search [string trim $text]
+ if {$search == ""} {
+ putserv "PRIVMSG $chan :gimmie someone to whois lol"
+ return 1
+ }
+ if {[isbotnick $search]} {
+ putserv "PRIVMSG $chan :hi! i'm eggdrop :3 (fireonlive thinks he owns me, but I actually own him)"
+ return 1
+ }
+ set acct [getaccount $search]
+ set accthand [finduser -account $acct]
+ if [string match $acct "*"] {
+ putserv "PRIVMSG $chan :idk"
+ return 1
+ }
+ putserv "PRIVMSG $chan :$search is authenticated to NickServ as $acct"
+ if [string match $accthand ""] {
+ putserv "PRIVMSG $chan :$acct isn't a known user to me, though."
+ } else {
+ putserv "PRIVMSG $chan :$acct is also a known user to me! (flags: [chattr $accthand * $chan])"
+ }
+}
+
+bind PUB * ,join acct_join
+
+proc acct_join {nick user hand chan text} {
+ set accthand [finduser -account [getaccount $nick]]
+ if [string match $accthand "*"] {
+ putserv "PRIVMSG $chan :$nick, you are not authenticated to NickServ and/or I don't know you!"
+ return 1
+ }
+ if {[matchattr $accthand +o]} {
+ channel add [string trim $text]
+ channel set [string trim $text] +lkarma +transferinliner +seen +8ball
+ putquick "PRIVMSG $chan :\[join\] joined [string trim $text] - set flags +lkarma +transferinliner +seen +8ball"
+ savechannels
+ } else {
+ putserv "PRIVMSG $chan :$nick, you do not have access to ,join"
+ }
+}
+
+bind MSG * join acct_join_pm
+
+proc acct_join_pm {nick user hand text} {
+ set accthand [finduser -account [getaccount $nick]]
+ if [string match $accthand "*"] {
+ putserv "NOTICE $nick :you are not authenticated to NickServ and/or I don't know you!"
+ return 1
+ }
+ if {[matchattr $accthand +o]} {
+ channel add [string trim $text]
+ channel set [string trim $text] +lkarma +transferinliner +seen +8ball +nitter
+ putquick "NOTICE $nick :\[join\] joined [string trim $text] - set flags +lkarma +transferinliner +seen +8ball +nitter"
+ putquick "PRIVMSG #fire-trail :\[join\] $nick ($accthand) requested join [string trim $text]. joined and set flags +lkarma +transferinliner +seen +8ball +nitter"
+ savechannels
+ } else {
+ putserv "NOTICE $nick :you do not have access to join"
+ }
+}
+bind PUB * ,chflags acct_chflags
+bind PUB * ,chanset acct_chflags
+
+proc acct_chflags {nick user hand chan text} {
+ set accthand [finduser -account [getaccount $nick]]
+ if [string match $accthand "*"] {
+ putserv "PRIVMSG $chan :$nick, you are not authenticated to NickServ and/or I don't know you!"
+ return 1
+ }
+ if {[matchattr $accthand +o]} {
+ set target [lindex [split $text] 0]
+ set flags [join [lrange [split $text] 1 end] " "]
+ channel set $target $flags
+ putquick "PRIVMSG $chan :\[chanset\] set flags \"$flags\" for $target"
+ savechannels
+ } else {
+ putserv "PRIVMSG $chan :$nick, you do not have access to ,chflags"
+ }
+}
diff --git a/action.fix.tcl b/action.fix.tcl
new file mode 100644
index 0000000..13e13b4
--- /dev/null
+++ b/action.fix.tcl
@@ -0,0 +1,24 @@
+# action.fix.tcl
+#
+# Copyright (C) 2002 - 2023 Eggheads Development Team
+#
+# Tothwolf 25May1999: cleanup
+# Tothwolf 04Oct1999: changed proc names slightly
+# poptix 07Dec2001: handle irssi (and some others) "correct" messages for DCC CTCP
+
+# Fix for mIRC dcc chat /me's:
+bind filt - "\001ACTION *\001" filt:dcc_action
+bind filt - "CTCP_MESSAGE \001ACTION *\001" filt:dcc_action2
+proc filt:dcc_action {idx text} {
+ return ".me [string trim [join [lrange [split $text] 1 end]] \001]"
+}
+
+proc filt:dcc_action2 {idx text} {
+ return ".me [string trim [join [lrange [split $text] 2 end]] \001]"
+}
+
+# Fix for telnet session /me's:
+bind filt - "/me *" filt:telnet_action
+proc filt:telnet_action {idx text} {
+ return ".me [join [lrange [split $text] 1 end]]"
+}
diff --git a/alltools.tcl b/alltools.tcl
new file mode 100644
index 0000000..78b901f
--- /dev/null
+++ b/alltools.tcl
@@ -0,0 +1,439 @@
+#
+# All-Tools TCL, includes toolbox.tcl, toolkit.tcl and moretools.tcl
+# toolbox was originally authored by cmwagner <cmwagner@sodre.net>
+# toolkit was originally authored by Robey Pointer
+# moretools was originally authored by David Sesno <walker@shell.pcrealm.net>
+# modified for 1.3.0 bots by TG
+#
+# Copyright (C) 1999, 2003 - 2010 Eggheads Development Team
+#
+# Tothwolf 02May1999: rewritten and updated
+# guppy 02May1999: updated even more
+# Tothwolf 02May1999: fixed what guppy broke and updated again
+# Tothwolf 24/25May1999: more changes
+# rtc 20Sep1999: added isnumber, changes
+# dw 20Sep1999: use regexp for isnumber checking
+# Tothwolf 06Oct1999: optimized completely
+# krbb 09Jun2000: added missing return to randstring
+# Tothwolf 18Jun2000: added ispermowner
+# Sup 02Apr2001: added matchbotattr
+# Tothwolf 13Jun2001: updated/modified several commands
+# Hanno 28Sep2001: fixed testip
+# guppy 03Mar2002: optimized
+# Souperman 05Nov2002: added ordnumber
+# Tothwolf 27Dec2003: added matchbotattrany, optimized ordnumber,
+# more minor changes
+#
+########################################
+#
+# Descriptions of available commands:
+#
+##
+## (toolkit):
+##
+#
+# putmsg <nick/chan> <text>
+# send a privmsg to the given nick or channel
+#
+# putchan <nick/chan> <text>
+# send a privmsg to the given nick or channel
+# (for compat only, this is the same as 'putmsg' above)
+#
+# putnotc <nick/chan> <text>
+# send a notice to the given nick or channel
+#
+# putact <nick/chan> <text>
+# send an action to the given nick or channel
+#
+#
+##
+## (toolbox):
+##
+#
+# strlwr <string>
+# string tolower
+#
+# strupr <string>
+# string toupper
+#
+# strcmp <string1> <string2>
+# string compare
+#
+# stricmp <string1> <string2>
+# string compare (case insensitive)
+#
+# strlen <string>
+# string length
+#
+# stridx <string> <index>
+# string index
+#
+# iscommand <command>
+# if the given command exists, return 1
+# else return 0
+#
+# timerexists <command>
+# if the given command is scheduled by a timer, return its timer id
+# else return empty string
+#
+# utimerexists <command>
+# if the given command is scheduled by a utimer, return its utimer id
+# else return empty string
+#
+# inchain <bot>
+# if the given bot is connected to the botnet, return 1
+# else return 0
+# (for compat only, same as 'islinked')
+#
+# randstring <length>
+# returns a random string of the given length
+#
+# putdccall <text>
+# send the given text to all dcc users
+#
+# putdccbut <idx> <text>
+# send the given text to all dcc users except for the given idx
+#
+# killdccall
+# kill all dcc user connections
+#
+# killdccbut <idx>
+# kill all dcc user connections except for the given idx
+#
+#
+##
+## (moretools):
+##
+#
+# iso <nick> <channel>
+# if the given nick has +o access on the given channel, return 1
+# else return 0
+#
+# realtime [format]
+# 'time' returns the current time in 24 hour format '14:15'
+# 'date' returns the current date in the format '21 Dec 1994'
+# not specifying any format will return the current time in
+# 12 hour format '1:15 am'
+#
+# testip <ip>
+# if the given ip is valid, return 1
+# else return 0
+#
+# number_to_number <number>
+# if the given number is between 1 and 15, return its text representation
+# else return the number given
+#
+#
+##
+## (other commands):
+##
+#
+# isnumber <string>
+# if the given string is a valid number, return 1
+# else return 0
+#
+# ispermowner <handle>
+# if the given handle is a permanent owner, return 1
+# else return 0
+#
+# matchbotattr <bot> <flags>
+# if the given bot has all the given flags, return 1
+# else return 0
+#
+# matchbotattrany <bot> <flags>
+# if the given bot has any the given flags, return 1
+# else return 0
+#
+# ordnumber <string>
+# if the given string is a number, returns the
+# "ordinal" version of that number, i.e. 1 -> "1st",
+# 2 -> "2nd", 3 -> "3rd", 4 -> "4th", etc.
+# else return <string>
+#
+########################################
+
+# So scripts can see if allt is loaded.
+set alltools_loaded 1
+set allt_version 206
+
+# For backward compatibility.
+set toolbox_revision 1007
+set toolbox_loaded 1
+set toolkit_loaded 1
+
+#
+# toolbox:
+#
+
+proc putmsg {dest text} {
+ puthelp "PRIVMSG $dest :$text"
+}
+
+proc putchan {dest text} {
+ puthelp "PRIVMSG $dest :$text"
+}
+
+proc putnotc {dest text} {
+ puthelp "NOTICE $dest :$text"
+}
+
+proc putact {dest text} {
+ puthelp "PRIVMSG $dest :\001ACTION $text\001"
+}
+
+#
+# toolkit:
+#
+
+proc strlwr {string} {
+ string tolower $string
+}
+
+proc strupr {string} {
+ string toupper $string
+}
+
+proc strcmp {string1 string2} {
+ string compare $string1 $string2
+}
+
+proc stricmp {string1 string2} {
+ string compare [string tolower $string1] [string tolower $string2]
+}
+
+proc strlen {string} {
+ string length $string
+}
+
+proc stridx {string index} {
+ string index $string $index
+}
+
+proc iscommand {command} {
+ if {[string compare "" [info commands $command]]} then {
+ return 1
+ }
+ return 0
+}
+
+proc timerexists {command} {
+ foreach i [timers] {
+ if {![string compare $command [lindex $i 1]]} then {
+ return [lindex $i 2]
+ }
+ }
+ return
+}
+
+proc utimerexists {command} {
+ foreach i [utimers] {
+ if {![string compare $command [lindex $i 1]]} then {
+ return [lindex $i 2]
+ }
+ }
+ return
+}
+
+proc inchain {bot} {
+ islinked $bot
+}
+
+proc randstring {length {chars abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789}} {
+ if {([string compare "" $length]) && \
+ (![regexp \[^0-9\] $length])} then {
+ set count [string length $chars]
+ if {$count} then {
+ for {set index 0} {$index < $length} {incr index} {
+ append result [string index $chars [rand $count]]
+ }
+ } else {
+ error "empty character string"
+ }
+ } else {
+ error "invalid random string length"
+ }
+ return $result
+}
+
+proc putdccall {text} {
+ foreach i [dcclist CHAT] {
+ putdcc [lindex $i 0] $text
+ }
+}
+
+proc putdccbut {idx text} {
+ foreach i [dcclist CHAT] {
+ set j [lindex $i 0]
+ if {$j != $idx} then {
+ putdcc $j $text
+ }
+ }
+}
+
+proc killdccall {} {
+ foreach i [dcclist CHAT] {
+ killdcc [lindex $i 0]
+ }
+}
+
+proc killdccbut {idx} {
+ foreach i [dcclist CHAT] {
+ set j [lindex $i 0]
+ if {$j != $idx} then {
+ killdcc $j
+ }
+ }
+}
+
+#
+# moretools:
+#
+
+proc iso {nick chan} {
+ matchattr [nick2hand $nick $chan] o|o $chan
+}
+
+proc realtime {args} {
+ switch -exact -- [lindex $args 0] {
+ time {
+ return [strftime %H:%M]
+ }
+ date {
+ return [strftime "%d %b %Y"]
+ }
+ default {
+ return [strftime "%I:%M %P"]
+ }
+ }
+}
+
+proc testip {ip} {
+ set tmp [split $ip .]
+ if {[llength $tmp] != 4} then {
+ return 0
+ }
+ set index 0
+ foreach i $tmp {
+ if {(([regexp \[^0-9\] $i]) || ([string length $i] > 3) || \
+ (($index == 3) && (($i > 254) || ($i < 1))) || \
+ (($index <= 2) && (($i > 255) || ($i < 0))))} then {
+ return 0
+ }
+ incr index
+ }
+ return 1
+}
+
+proc number_to_number {number} {
+ switch -exact -- $number {
+ 0 {
+ return Zero
+ }
+ 1 {
+ return One
+ }
+ 2 {
+ return Two
+ }
+ 3 {
+ return Three
+ }
+ 4 {
+ return Four
+ }
+ 5 {
+ return Five
+ }
+ 6 {
+ return Six
+ }
+ 7 {
+ return Seven
+ }
+ 8 {
+ return Eight
+ }
+ 9 {
+ return Nine
+ }
+ 10 {
+ return Ten
+ }
+ 11 {
+ return Eleven
+ }
+ 12 {
+ return Twelve
+ }
+ 13 {
+ return Thirteen
+ }
+ 14 {
+ return Fourteen
+ }
+ 15 {
+ return Fifteen
+ }
+ default {
+ return $number
+ }
+ }
+}
+
+#
+# other commands:
+#
+
+proc isnumber {string} {
+ if {([string compare "" $string]) && \
+ (![regexp \[^0-9\] $string])} then {
+ return 1
+ }
+ return 0
+}
+
+proc ispermowner {hand} {
+ global owner
+
+ if {([matchattr $hand n]) && \
+ ([lsearch -exact [split [string tolower $owner] ", "] \
+ [string tolower $hand]] != -1)} then {
+ return 1
+ }
+ return 0
+}
+
+proc matchbotattr {bot flags} {
+ foreach flag [split $flags ""] {
+ if {[lsearch -exact [split [botattr $bot] ""] $flag] == -1} then {
+ return 0
+ }
+ }
+ return 1
+}
+
+proc matchbotattrany {bot flags} {
+ foreach flag [split $flags ""] {
+ if {[string first $flag [botattr $bot]] != -1} then {
+ return 1
+ }
+ }
+ return 0
+}
+
+proc ordnumber {string} {
+ if {[isnumber $string]} then {
+ set last [string index $string end]
+ if {[string index $string [expr [string length $string] - 2]] != 1} then {
+ if {$last == 1} then {
+ return ${string}st
+ } elseif {$last == 2} then {
+ return ${string}nd
+ } elseif {$last == 3} then {
+ return ${string}rd
+ }
+ }
+ return ${string}th
+ }
+ return $string
+}
diff --git a/archivebot-chat.tcl b/archivebot-chat.tcl
new file mode 100644
index 0000000..4affa8f
--- /dev/null
+++ b/archivebot-chat.tcl
@@ -0,0 +1,68 @@
+bind pubm * "#archivebot *" abc-privmsg
+
+proc abc-privmsg {nick uhost hand chan text} {
+ if {([string index $text 0] == "!") && ([string index $text 1] != " ")} {
+ return 0
+ }
+ if {([string match "*@archiveteam/Aramaki" $uhost]) || ([string match "*@hackint/user/h2ibot" $uhost])} {
+ #if {([string match "*@archiveteam/Aramaki" $uhost] && ([string first ": Job" $text] != -1) && ([string first ": Sorry, I don't know anything about" $text != -1)) || ([string match "*@hackint/user/h2ibot" $uhost])} {}
+ return 0
+ }
+ if {[string index $text 0] == "\001"} {
+ return 0
+ }
+ if {[isop $nick $chan]} {
+ set nick "@$nick"
+ } elseif {[isvoice $nick $chan]} {
+ set nick "+$nick"
+ }
+
+ putserv "PRIVMSG #archivebot-chat :<$nick> $text"
+}
+
+bind ctcp * "ACTION" abc-action
+
+proc abc-action {nick uhost hand dest keyword text} {
+ if {$dest == "#archivebot"} {
+ if {[isop $nick $dest]} {
+ set nick "@$nick"
+ } elseif {[isvoice $nick $dest]} {
+ set nick "+$nick"
+ }
+ putserv "PRIVMSG #archivebot-chat :\001ACTION <$nick> $text\001"
+ }
+}
+
+bind notc * "*" abc-notice
+
+proc abc-notice {nick uhost hand text {dest ""}} {
+ if {$dest == "#archivebot"} {
+ if {[isop $nick $dest]} {
+ set nick "@$nick"
+ } elseif {[isvoice $nick $dest]} {
+ set nick "+$nick"
+ }
+ putserv "NOTICE #archivebot-chat :<$nick> $text"
+ }
+}
+
+bind out * "% sent" abc-out
+
+proc abc-out {queue text status} {
+ set botnick $::botnick
+ if {[botisop "#archivebot"]} {
+ set botnick "@$botnick"
+ } elseif {[botisvoice "#archivebot"]} {
+ set botnick "+$botnick"
+ }
+ if {[string match "PRIVMSG #archivebot *" $text]} {
+ if {[string match "*\\\[remind\\\]*" $text]} {
+ putserv "PRIVMSG #archivebot-chat :<$botnick> [join [lrange [split $text ":"] 1 end] ":"]"
+ }
+ }
+ if {[string match "NOTICE #archivebot *" $text]} {
+ if {[string match "*\\\[karma\\\]*" $text]} {
+ putserv "NOTICE #archivebot-chat :<$botnick> [join [lrange [split $text ":"] 1 end] ":"]"
+ }
+ }
+}
diff --git a/atme.tcl b/atme.tcl
new file mode 100644
index 0000000..e63ed54
--- /dev/null
+++ b/atme.tcl
@@ -0,0 +1,37 @@
+setudef flag nohelp
+
+bind pubm * "% *" atme
+
+proc atme {nick uhost hand chan text} {
+ global botnick
+ set target [string trim [lindex [split $text] 0] ':,']
+ if {$text == ",help"} {
+ set target $botnick
+ }
+ if {([isbotnick $target]) || ($target == "🥚") || ($target == "🥚💧")} {
+ set atcmd [lindex [split $text] 1]
+ if {$text == ",help"} {
+ set atcmd "help"
+ }
+ set args [join [lrange [split $text] 2 end] " "]
+ switch -- $atcmd {
+ "hi" {
+ putserv "PRIVMSG $chan :hi, $nick!"
+ }
+ "help" {
+ puthelp "NOTICE $nick :\[help\] !tell <nick/hostmask> <message>"
+ puthelp "NOTICE $nick :\[help\] !seen \[-exact/-glob/-regex\] <nick>"
+ puthelp "NOTICE $nick :\[help\] !remind <nick> \"<time>\" <message> - remind someone about something"
+ puthelp "NOTICE $nick :\[help\] !remindme \"<time>\" <message> - remind yourself about something"
+ puthelp "NOTICE $nick :\[help\] - note: if your time declaration for remind(me) is more than one word please encapsulate it in quotation marks"
+ puthelp "NOTICE $nick :\[help\] !clockscan <time> - test out what a reminder's firing time will look like without setting one"
+ puthelp "NOTICE $nick :\[help\] - note: <time> for remind(me)/clockscan is a format parseable by the TCL command 'clock scan' - https://www.tcl.tk/man/tcl8.6/TclCmd/clock.html"
+ puthelp "NOTICE $nick :\[help\] karma: you may \"<item>++\" and \"<item>--\" any word or phrase. \"!kbest\" and \"!kworst\" return the top (and bottom) ten. \"!karma \[item\]\" to look up individual scores. \"!kstats\" for statistics. Use \"!kfind <item>\" and \"!krfind <item>\" to search for things in the database ordered by karma in ascending or descending order (respectively), or just use \"!krand\" for ten random things."
+ puthelp "NOTICE $nick :\[help\] transferinliner: auto-converts https://transfer.archivete.am links to their /inline/ variant for browser viewing"
+ #puthelp "NOTICE $nick :\[help\] twitter2nitter: auto-converts https://(twitter|x).com links to a nitter host for better logged-out viewing"
+ puthelp "NOTICE $nick :\[help\] that's it for now!"
+ }
+ }
+ }
+
+}
diff --git a/atmisc.tcl b/atmisc.tcl
new file mode 100644
index 0000000..1ebcf9c
--- /dev/null
+++ b/atmisc.tcl
@@ -0,0 +1,136 @@
+bind pub * "!c" conoops
+
+proc conoops {nick uhost hand chan text} {
+ if {$chan eq "#archivebot"} {
+ if {[isvoice $nick $chan] || [isop $nick $chan]} {
+ putserv "PRIVMSG $chan :oops, $nick! try !con :c"
+ } else {
+ putserv "PRIVMSG #fire-trail :\[atmisc\] ignored \"!c $text\" from $nick!$uhost in $chan - not a voice or an op"
+ }
+ }
+}
+
+bind pub * "!statussy" statussy
+# the following three for systwi
+bind pub * "!statys" statussy
+bind pub * "!statis" statussy
+bind pub * "!s" statussy
+
+proc statussy {nick uhost hand chan text} {
+ if {$chan eq "#archivebot"} {
+ if {[isvoice $nick $chan] || [isop $nick $chan]} {
+ putserv "PRIVMSG $chan :!status $text"
+ } else {
+ putserv "PRIVMSG #fire-trail :\[atmisc\] ignored \"!statussy/!statys/!statis/!s $text\" from $nick!$uhost in $chan - not a voice or an op"
+ }
+ }
+}
+
+bind pub * "!help" abhelp
+
+proc abhelp {nick uhost hand chan text} {
+ if {$chan eq "#archivebot"} {
+ putserv "PRIVMSG $chan :$nick: see https://archivebot.readthedocs.io/"
+ }
+}
+
+
+bind pub * "!ignores" ignores
+bind pub * "!igs" ignores
+
+proc ignores {nick uhost hand chan text} {
+ if {$chan eq "#archivebot"} {
+ if {[isvoice $nick $chan] || [isop $nick $chan]} {
+ putserv "PRIVMSG $chan :$nick: http://archivebot.com/ignores/$text?compact=true"
+ } else {
+ putserv "PRIVMSG #fire-trail :\[atmisc\] ignored \"!ignores/!igs $text\" from $nick!$uhost in $chan - not a voice or an op"
+ }
+ }
+}
+
+package require uri
+
+proc loadPublicSuffixList {filename} {
+ set publicSuffixList [dict create]
+ set file [open $filename]
+ while {[gets $file line] >= 0} {
+ if {[string trim $line] eq "" || [string index $line 0] eq "/" || [string index $line 0] eq "!"} {
+ continue
+ }
+ # Normalize the line for processing
+ set line [string map {"*." ""} $line]
+ dict set publicSuffixList $line 1
+ }
+ close $file
+ return $publicSuffixList
+}
+
+proc extractRegistrableDomain {domain publicSuffixList} {
+ set parts [split $domain "."]
+ set partCount [llength $parts]
+
+ for {set i 0} {$i < $partCount} {incr i} {
+ set suffix [join [lrange $parts $i end] "."]
+ if {[dict exists $publicSuffixList $suffix]} {
+ if {$i > 0} {
+ return [join [lrange $parts [expr {$i - 1}] end] "."]
+ } else {
+ return $domain
+ }
+ }
+ }
+ return $domain
+}
+
+
+set PSLfilename "public_suffix_list.dat"
+set thePSL [loadPublicSuffixList $PSLfilename]
+
+bind pub * "!igd" igd
+
+proc igd {nick uhost hand chan text} {
+ if {$chan eq "#archivebot"} {
+ if {[isvoice $nick $chan] || [isop $nick $chan]} {
+ global thePSL
+ set components [uri::split $text]
+ set hostname [dict get $components "host"]
+ set registrableDomain [extractRegistrableDomain $hostname $thePSL]
+ set escapedDomain [string map { "." "\\." } $registrableDomain]
+ putserv "PRIVMSG $chan :$nick: ^(http|ftp)s?://(\[^/\]*\[@.\])?$escapedDomain\\.?(:\\d+)?/"
+ } else {
+ putserv "PRIVMSG #fire-trail :\[atmisc\] ignored \"!igd $text\" from $nick!$uhost in $chan - not a voice or an op"
+ }
+ }
+}
+
+bind pub * "!b" bmeme
+
+proc bmeme {nick uhost hand chan text} {
+ putserv "PRIVMSG $chan :🅱️"
+}
+
+bind pub * "!ping" pingcmd
+
+proc pingcmd {nick uhost hand chan text} {
+ if {$text eq ""} {
+ putserv "PRIVMSG $chan :$nick: pong!"
+ } else {
+ putserv "PRIVMSG $chan :$nick: pong ($text)!"
+ }
+}
+
+bind pub * "!ß" punycode
+
+proc punycode {nick uhost hand chan text} {
+ putserv "PRIVMSG $chan :...scheiße!"
+ putserv "PRIVMSG $chan :UnicodeError: ('IDNA does not round-trip', b'xn--scheie-fta', b'scheisse')"
+}
+
+bind pub * !z utctime
+bind pub * !utc utctime
+
+proc utctime {nick uhost hand chan text} {
+ set now [clock seconds]
+ set iso_time [clock format $now -format "%Y-%m-%dT%H:%M:%SZ" -gmt true]
+ putserv "PRIVMSG $chan :$iso_time"
+}
diff --git a/autobotchk b/autobotchk
new file mode 100755
index 0000000..fe2c4e2
--- /dev/null
+++ b/autobotchk
@@ -0,0 +1,484 @@
+#! /bin/sh
+#
+# Copyright (C) 1999-2003 Jeff Fisher (guppy@eggheads.org)
+# Copyright (C) 2004-2023 Eggheads Development Team
+#
+# systemd formatting contributed by PeGaSuS
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+
+# This trick is borrowed from Tothwolf's Wolfpack \
+# Check for working 'grep -E' before using 'egrep' \
+if echo a | (grep -E '(a|b)') >/dev/null 2>&1; \
+then \
+ egrep="grep -E"; \
+else \
+ egrep=egrep; \
+fi; \
+# Search for tclsh[0-9].[0-9] in each valid dir in PATH \
+for dir in $(echo $PATH | sed 's/:/ /g'); \
+do \
+ if test -d $dir; \
+ then \
+ files=$(/bin/ls $dir | $egrep '^tclsh[0-9]\.[0-9]$'); \
+ if test "$files" != ""; \
+ then \
+ versions="${versions:+$versions }$(echo $files | sed 's/tclsh//g')"; \
+ fi; \
+ fi; \
+done; \
+for ver in $versions; \
+do \
+ tmpver=$(echo $ver | sed 's/\.//g'); \
+ if test "$lasttmpver" != ""; \
+ then \
+ if test "$tmpver" -gt "$lasttmpver"; \
+ then \
+ lastver=$ver; \
+ lasttmpver=$tmpver; \
+ fi; \
+ else \
+ lastver=$ver; \
+ lasttmpver=$tmpver; \
+ fi; \
+done; \
+exec tclsh$lastver "$0" ${1+"$@"}
+#
+# AutoBotchk - An eggdrop utility to autogenerate botchk/crontab entries
+#
+# Copyright (C) 1999-2003 Jeff Fisher (guppy@eggheads.org)
+# Copyright (C) 2004-2023 Eggheads Development Team
+#
+# How to use
+# ----------
+#
+# Most people begin to use AutoBotchk by moving it from the script
+# directory to their Eggdrop directory -- this will save you from having to
+# use the -dir option.
+#
+# If you run AutoBotchk without any arguments, it will present you with
+# a list of valid ones. Most people run AutoBotchk by doing:
+#
+# ./autobotchk <config file>
+#
+# This will setup crontab to check every 10 minutes to see whether or not
+# your bot needs to be restarted and it will e-mail if a restart was
+# performed. A lot of people turn off crontab e-mail support; however, I do
+# not recommend this since you will be unable to see any errors that might
+# happen.
+#
+# Updates
+# -------
+# 27Sep2001: added new pidfile setting
+# 14Nov2001: removed old autobotchk update entries and updated the help
+# section a little bit. also made autobotchk move down one
+# directory if being run from the scripts directory.
+# 15Apr2003: cleaned up a few things, fixed a few bugs, and made a little
+# love! j/k
+# 13Dec2016: add possibility to use multiple confs; fixes for (botnet-)nick
+# with len > handlen, for (botnet-)nicks with \[ \] and for
+# $(botnet-)nick used in pidfile, userfile or (botnet-)nick
+# 19Apr2017: Fix running this from a non-eggdrop dir.
+# 28Nov2023: Added -systemd option, to (duh) create a systemd job
+
+if {$argc == 0} {
+ puts "\nusage: $argv0 <eggdrop mainconfig> \[options\] \[additional configs\]"
+ puts "Options:"
+ puts "--------"
+ puts " systemd options:"
+ puts " -systemd (install systemd job instead of cron)"
+ puts ""
+ puts " crontab options:"
+ puts " -dir (directory to run autobotchk in)"
+ puts " -noemail (discard crontab e-mails)"
+ puts " -5 (5 minute checks)"
+ puts " -10 (10 minute checks)"
+ puts " -15 (15 minute checks)"
+ puts " -30 (30 minute checks)"
+ puts ""
+ puts "Additional Configs:"
+ puts "-------------------"
+ puts " If you source additional configs from your main config and they"
+ puts " contain either nick, botnet-nick, userfile or pidfile settings,"
+ puts " list them here so that autobotchk can check in them as well."
+ puts ""
+ exit
+}
+
+fconfigure stdout -buffering none
+
+proc newsplit {text {split " "}} {
+ upvar $text ours
+ append ours $split
+ set index [string first $split $ours]
+ if {$index == -1} {
+ set ours ""
+ return ""
+ }
+ set tmp [string trim [string range $ours 0 $index]]
+ set ours [string trim [string range $ours [expr $index + [string length $split]] end]]
+ return $tmp
+}
+
+puts "\nautobotchk 1.11, (C) 1999-2003 Jeff Fisher (guppy@eggheads.org)"
+puts " (C) 2004-2022 Eggheads Development Team"
+puts "------------------------------------------------------------\n"
+
+set mainconf [newsplit argv]
+set confs [list $mainconf]
+set dir [pwd]
+set delay 10
+set email 1
+set systemd 0
+catch {exec which kill} kill
+
+# If you renamed your eggdrop binary, you should change this variable
+set binary "eggdrop"
+
+while {[set opt [newsplit argv]] != ""} {
+ switch -- $opt {
+ "-systemd" { set systemd 1 }
+ "-time" -
+ "-1" { set delay 1 }
+ "-5" { set delay 5 }
+ "-10" { set delay 10 }
+ "-15" { set delay 15 }
+ "-20" { set delay 20 }
+ "-30" { set delay 30 }
+ "-nomail" -
+ "-noemail" {set email 0}
+ "-dir" {
+ set dir [newsplit argv]
+ if {[string match -* $dir]} {
+ puts "*** ERROR: you did not supply a directory name"
+ puts ""
+ exit
+ }
+ if {![file isdirectory $dir]} {
+ puts "*** ERROR: the directory you supplied is not a directory"
+ puts ""
+ exit
+ }
+ }
+ default {
+ # Treat as extra config file
+ if {[file isfile $opt] && [file readable $opt]} { lappend confs $opt }
+ }
+ }
+}
+
+switch -- $delay {
+ "30" { set minutes "0,30" }
+ "20" { set minutes "0,20,40" }
+ "15" { set minutes "0,15,30,45" }
+ "5" { set minutes "0,5,10,15,20,25,30,35,40,45,50,55" }
+ "1" { set minutes "*" }
+ default { set minutes "0,10,20,30,40,50" }
+}
+
+if {[string match "*/scripts" $dir]} {
+ set dir [string range $dir 0 [expr [string length $dir] - 8]]
+}
+
+set dir [string trimright $dir /]
+
+if {![file exists $dir/help] || ![file isdirectory $dir/help]} {
+ puts "*** ERROR: are you sure you are running from a bot directory?"
+ puts ""
+ exit
+} elseif {![file exists $dir/$binary]} {
+ puts "*** ERROR: are you sure you are running from a bot directory?"
+ puts ""
+ exit
+}
+
+foreach config $confs {
+ puts -nonewline "Opening '$config' for processing ... "
+
+ if {[catch {open $dir/$config r} fd]} {
+ puts "error:"
+ puts " $fd\n"
+ exit
+ } else {
+ puts "done"
+ }
+
+ set count 0
+ puts -nonewline "Scanning the config file "
+
+ while {![eof $fd]} {
+ incr count
+ if {$count == 100} {
+ puts -nonewline "."
+ set count 0
+ }
+ set line [gets $fd]
+ if {[set blarg [newsplit line]] != "set"} {
+ continue
+ }
+ switch -- [set opt [newsplit line]] {
+ "pidfile" -
+ "nick" -
+ "userfile" -
+ "botnet-nick" {
+ set $opt [string trim [newsplit line] " \"{}"]
+ }
+ }
+ }
+ close $fd
+
+ if {$count != 0} {
+ puts -nonewline "."
+ }
+
+ puts " done"
+}
+
+ if {![info exists {botnet-nick}] && [info exists nick]} {
+ puts " Defaulting \$botnet-nick to \"$nick\""
+ set botnet-nick $nick
+ }
+ if {![info exists {botnet-nick}] || ![info exists userfile]} {
+ puts " *** ERROR: could not find either \$userfile or \$botnet-nick"
+ puts ""
+ puts " Are you sure this is a valid eggdrop config file?"
+ puts ""
+ exit
+ }
+ catch {exec "$dir/$binary" -v} execres
+ if {![regexp {handlen=([0-9]+)} "$execres" -> handlen]} {
+ puts " Could not find handlen used, defaulting to 32."
+ # len 32 means up to index 31
+ set handlen 31
+ } else {
+ set handlen [expr $handlen - 1]
+ }
+ set nick [string range [subst -nocommands $nick] 0 $handlen]
+ set botnet-nick [string range [subst -nocommands ${botnet-nick}] 0 $handlen]
+
+### systemd stuff
+if {$systemd} {
+ puts "Enabling user lingering..."
+ if {[catch {exec loginctl enable-linger} res]} {
+ puts "Oops, something went wrong. Is systemd running on this system?"
+ exit
+ }
+ puts "Creating systemd directory..."
+ catch {file mkdir ~/.config/systemd/user } res
+ if {[catch {open ~/.config/systemd/user/${botnet-nick}.service w} fd]} {
+ puts " *** ERROR: unable to open '${botnet-nick}.service' for writing"
+ puts ""
+ exit
+ }
+ puts $fd "### Eggdrop systemd unit, generated by autosystemd"
+ puts $fd ""
+ puts $fd "\[Unit\]"
+ puts $fd "Description=${botnet-nick} (Eggdrop)"
+ puts $fd "After=default.target"
+ puts $fd ""
+ puts $fd "\[Service\]"
+ puts $fd "WorkingDirectory=${dir}"
+ puts $fd "ExecStart=${dir}/eggdrop ${config}"
+ puts $fd "ExecReload=${kill} -s HUP \$MAINPID"
+ puts $fd "Type=forking"
+ puts $fd "Restart=on-abnormal"
+ puts $fd ""
+ puts $fd "\[Install\]"
+ puts $fd "WantedBy=default.target"
+ close $fd
+
+ catch {exec systemctl --user enable ${botnet-nick}.service} res
+ puts $res
+ puts "systemd job successfully installed as '${botnet-nick}.service'."
+ puts "* Use 'systemctl --user <start|stop|restart|reload|enable|disable> ${botnet-nick}.service' to control your Eggdrop"
+ puts "Starting Eggdrop..."
+ if {[catch {exec systemctl --user start ${botnet-nick}.service} res]} {
+ puts "ERROR: Eggdrop did not start."
+ puts $res
+ } else {
+ puts "Success."
+ }
+ exit
+}
+
+
+if {![info exists pidfile]} {
+ puts " Defaulting \$pidfile to \"pid.${botnet-nick}\""
+ set pidfile "pid.${botnet-nick}"
+}
+set pidfile [subst -nocommands $pidfile]
+if {[catch {open $dir/${botnet-nick}.botchk w} fd]} {
+ puts " *** ERROR: unable to open '${botnet-nick}.botchk' for writing"
+ puts ""
+ exit
+}
+puts $fd "#! /bin/sh
+#
+# ${botnet-nick}.botchk (generated on [clock format [clock seconds] -format "%B %d, %Y @ %I:%M%p"])
+#
+# Generated by AutoBotchk 1.11
+# Copyright (C) 1999-2003 Jeff Fisher (guppy@eggheads.org)
+# Copyright (C) 2004-2023 Eggheads Development Team
+#
+
+# change this to the directory you run your bot from:
+botdir=\"$dir\"
+
+# change this to the name of your bot's script in that directory:
+botscript=\"$binary $mainconf\"
+
+# change this to the nickname of your bot (capitalization COUNTS)
+botname=\"${botnet-nick}\"
+
+# change this to the name of your bot's userfile (capitalization COUNTS)
+userfile=\"$userfile\"
+
+# change this to the name of your bot's pidfile (capitalization COUNTS)
+pidfile=\"$pidfile\"
+
+########## you probably don't need to change anything below here ##########
+
+cd \$botdir
+
+# is there a pid file?
+if test -r \$pidfile
+then
+ # there is a pid file -- is it current?
+ botpid=`cat \$pidfile`
+ if `kill -CHLD \$botpid >/dev/null 2>&1`
+ then
+ # it's still going -- back out quietly
+ exit 0
+ fi
+ echo \"\"
+ echo \"Stale \$pidfile file, erasing...\"
+ rm -f \$pidfile
+fi
+
+if test -r CANTSTART.\$botname
+then
+ if test -r \$userfile || test -r \$userfile~new || test -r \$userfile~bak
+ then
+ echo \"\"
+ echo \"Userfile found, removing check file 'CANTSTART.\$botname'...\"
+ rm -f CANTSTART.\$botname
+ fi
+fi
+
+# test if we have run botchk previously and didn't find a userfile
+if test ! -f CANTSTART.\$botname
+then
+ echo \"\"
+ echo \"Couldn't find bot '\$botname' running, reloading...\"
+ echo \"\"
+ # check for userfile and reload bot if found
+ if test -r \$userfile
+ then
+ # It's there, load the bot
+ ./\$botscript
+ exit 0
+ else
+ if test -r \$userfile~new
+ then
+ # Bot f*@!ed up while saving the userfile last time. Move it over.
+ echo \"Userfile missing. Using last saved userfile...\"
+ mv -f \$userfile~new \$userfile
+ ./\$botscript
+ exit 0
+ else
+ if test -r \$userfile~bak
+ then
+ # Userfile is missing, use backup userfile.
+ echo \"Userfile missing. Using backup userfile...\"
+ cp -f \$userfile~bak \$userfile
+ ./\$botscript
+ exit 0
+ else
+ # Well, nothing to work with...
+ echo \"No userfile. Could not reload the bot...\"
+ echo \"no userfile\" > CANTSTART.\$botname
+ exit 1
+ fi
+ fi
+ fi
+fi
+
+exit 0
+ "
+close $fd
+puts "Wrote '${botnet-nick}.botchk' successfully ([file size $dir/${botnet-nick}.botchk] bytes)"
+if {[catch {exec chmod u+x $dir/${botnet-nick}.botchk} 0]} {
+ puts " *** ERROR: unable to 'chmod u+x' the output file"
+ puts ""
+ exit
+}
+puts -nonewline "Scanning crontab entries ... "
+
+set tmp ".autobotchk[clock clicks].[pid]"
+set cronbotchk [string map {\\ \\\\ \[ \\\[ \] \\\]} "${botnet-nick}.botchk"]
+if {$email} {
+ set line "$minutes \* \* \* \* $dir/${cronbotchk}"
+} {
+ set line "$minutes \* \* \* \* $dir/${cronbotchk} >\/dev\/null 2>&1"
+}
+
+if {[catch {exec crontab -l > $tmp} error ]} {
+ if {![string match "*no*cron*" [string tolower $error]] &&
+ ![string match "*can't open*" [string tolower $error]]} {
+ catch {file delete -force $tmp} 0
+ puts "error: unable to get crontab listing"
+ puts ""
+ puts $error
+ puts ""
+ exit
+ }
+}
+
+set cronbotchk [string map {\\ \\\\ \[ \\\[ \] \\\]} "${cronbotchk}"]
+set fd [open $tmp r]
+while {![eof $fd]} {
+ set z [gets $fd]
+ if {[string match "*$dir/${cronbotchk}*" $z] ||
+ [string match "*$dir//${cronbotchk}*" $z]} {
+ puts "found an existing entry, we're done now"
+ puts ""
+ exit
+ }
+}
+
+close $fd
+
+puts "done"
+
+puts -nonewline "Adding the new crontab entry ... "
+set fd [open $tmp a]
+puts $fd $line
+close $fd
+
+if {[catch {exec crontab $tmp} error]} {
+ puts "error: unable to do 'crontab $tmp'"
+ puts ""
+ puts $error
+ puts ""
+ exit
+} else {
+ catch {file delete -force $tmp} 0
+}
+
+puts "done"
+puts ""
+puts "Use 'crontab -l' to view all your current crontab entries"
+puts " 'crontab -r' to remove all your crontab entries"
+puts ""
diff --git a/bmotion b/bmotion
new file mode 160000
+Subproject fa7f67d89b485c5e2d3ef30bf04cd2cfd6fa701
diff --git a/cmd_resolve.tcl b/cmd_resolve.tcl
new file mode 100644
index 0000000..a41f3a7
--- /dev/null
+++ b/cmd_resolve.tcl
@@ -0,0 +1,46 @@
+#
+# cmd_resolve.tcl
+# written by Jeff Fisher (guppy@eggheads.org)
+#
+# This script adds the commands '.resolve' and '.dns' which can be used to
+# lookup hostnames or ip addresses in the partyline without causing the bot
+# to block while doing so thanks to the dns module.
+#
+# updates
+# -------
+# 15Apr2003: fixed a logging bug and stop using regexp incorrectly
+# 05Nov2000: fixed a nasty security hole, .resolve [die] <grin>
+# 04Nov2000: first version
+
+bind dcc -|- resolve resolve_cmd
+bind dcc -|- dns resolve_cmd
+
+proc resolve_cmd {hand idx arg} {
+ global lastbind
+ if {[scan $arg "%s" hostip] != 1} {
+ putidx $idx "Usage: $lastbind <host or ip>"
+ } else {
+ putidx $idx "Looking up $hostip ..."
+ set hostip [split $hostip]
+ dnslookup $hostip resolve_callback $idx $hostip $lastbind
+ }
+ return 0
+}
+
+proc resolve_callback {ip host status idx hostip cmd} {
+ if {![valididx $idx]} {
+ return 0
+ } elseif {!$status} {
+ putidx $idx "Unable to resolve $hostip"
+ } elseif {[string tolower $ip] == [string tolower $hostip]} {
+ putidx $idx "Resolved $ip to $host"
+ } else {
+ putidx $idx "Resolved $host to $ip"
+ }
+ putcmdlog "#[idx2hand $idx]# $cmd $hostip"
+ return 0
+}
+
+loadhelp cmd_resolve.help
+
+putlog "Loaded cmd_resolve.tcl successfully."
diff --git a/compat.tcl b/compat.tcl
new file mode 100644
index 0000000..0ce91fa
--- /dev/null
+++ b/compat.tcl
@@ -0,0 +1,136 @@
+# compat.tcl
+# This script just quickly maps old Tcl commands to the new ones.
+# Use this if you are too lazy to get off your butt and update your scripts :D
+#
+# Copyright (C) 2002 - 2023 Eggheads Development Team
+#
+# Wiktor 31Mar2000: added binds and chnick proc
+# Tothwolf 25May1999: cleanup
+# Tothwolf 06Oct1999: optimized
+# rtc 10Oct1999: added [set|get][dn|up]loads functions
+# pseudo 04Oct2009: added putdccraw
+# Pixelz 08Apr2010: changed [time] to be compatible with Tcl [time]
+
+proc gethosts {hand} {
+ getuser $hand HOSTS
+}
+
+proc addhost {hand host} {
+ setuser $hand HOSTS $host
+}
+
+proc chpass {hand pass} {
+ setuser $hand PASS $pass
+}
+
+
+proc chnick {oldnick newnick} {
+ chhandle $oldnick $newnick
+}
+
+# setxtra is no longer relevant
+
+proc getxtra {hand} {
+ getuser $hand XTRA
+}
+
+proc setinfo {hand info} {
+ setuser $hand INFO $info
+}
+
+proc getinfo {hand} {
+ getuser $hand INFO
+}
+
+proc getaddr {hand} {
+ getuser $hand BOTADDR
+}
+
+proc setaddr {hand addr} {
+ setuser $hand BOTADDR $addr
+}
+
+proc getdccdir {hand} {
+ getuser $hand DCCDIR
+}
+
+proc setdccdir {hand dccdir} {
+ setuser $hand DCCDIR $dccdir
+}
+
+proc getcomment {hand} {
+ getuser $hand COMMENT
+}
+
+proc setcomment {hand comment} {
+ setuser $hand COMMENT $comment
+}
+
+proc getemail {hand} {
+ getuser $hand XTRA email
+}
+
+proc setemail {hand email} {
+ setuser $hand XTRA EMAIL $email
+}
+
+proc getchanlaston {hand} {
+ lindex [getuser $hand LASTON] 1
+}
+
+if {![llength [info commands {TCLTIME}]] && [llength [info commands {time}]]} {
+ rename time TCLTIME
+}
+
+proc time {args} {
+ if {([llength $args] != 0) && [llength [info commands {TCLTIME}]]} {
+ if {[llength [info commands {uplevel}]]} {
+ uplevel 1 TCLTIME $args
+ } else {
+ eval TCLTIME $args
+ }
+ } else {
+ strftime "%H:%M"
+ }
+}
+
+proc date {} {
+ strftime "%d %b %Y"
+}
+
+proc setdnloads {hand {c 0} {k 0}} {
+ setuser $hand FSTAT d $c $k
+}
+
+proc getdnloads {hand} {
+ getuser $hand FSTAT d
+}
+
+proc setuploads {hand {c 0} {k 0}} {
+ setuser $hand FSTAT u $c $k
+}
+
+proc getuploads {hand} {
+ getuser $hand FSTAT u
+}
+
+proc putdccraw {idx size text} {
+ if {!$idx} {
+ putloglev o * "Warning! putdccraw is deprecated. Use putnow instead!"
+ putnow $text
+ return -code ok
+ }
+ putloglev o * "Warning! putdccraw is deprecated. Use putdcc instead!"
+ if {![valididx $idx]} {return -code error "invalid idx"}
+ putdcc $idx $text -raw
+}
+
+# as you can see it takes a lot of effort to simulate all the old commands
+# and adapting your scripts will take such an effort you better include
+# this file forever and a day :D
+
+# Following are some TCL global variables that are obsolete now and have been removed
+# but are still defined here so not to break older scripts
+
+set strict-servernames 0
+
diff --git a/dccwhois.tcl b/dccwhois.tcl
new file mode 100644
index 0000000..77028fb
--- /dev/null
+++ b/dccwhois.tcl
@@ -0,0 +1,162 @@
+###############################################################################
+##
+## dccwhois.tcl - Enhanced '.whois' dcc command for Eggdrop
+## Copyright (C) 2009 Tothwolf <tothwolf@techmonkeys.org>
+##
+## This program is free software; you can redistribute it and/or modify
+## it under the terms of the GNU General Public License as published by
+## the Free Software Foundation; either version 2 of the License, or
+## (at your option) any later version.
+##
+## This program is distributed in the hope that it will be useful,
+## but WITHOUT ANY WARRANTY; without even the implied warranty of
+## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+## GNU General Public License for more details.
+##
+## You should have received a copy of the GNU General Public License
+## along with this program; if not, write to the Free Software
+## Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+##
+###############################################################################
+##
+## Description:
+##
+## This script enhances Eggdrop's built-in dcc '.whois' command to allow all
+## users to '.whois' their own handle.
+##
+## Users without the correct flags who attempt to '.whois' other users will
+## instead see the message: "You do not have access to whois handles other
+## than your own."
+##
+## To load this script, add a source command to your bot's config file:
+##
+## source scripts/dccwhois.tcl
+##
+## This script stores and checks against the flags that are used for
+## Eggdrop's built-in dcc '.whois' command at load time. If you wish to use
+## flags other than the default "to|o", etc, you should unbind and rebind
+## the built-in '.whois' command in your bot's config file before loading
+## this script.
+##
+## Example of how to rebind Eggdrop's built-in '.whois' command:
+##
+## unbind dcc to|o whois *dcc:whois
+## bind dcc to|m whois *dcc:whois
+##
+## Note: if you modify the default flags for '.whois' you may also wish to
+## modify the defaults for '.match'.
+##
+###############################################################################
+##
+## This script has no settings and does not require any configuration.
+## You should not need to edit anything below.
+##
+###############################################################################
+
+
+# This script should not be used with Eggdrop versions 1.6.16 - 1.6.19.
+catch {set numversion}
+if {([info exists numversion]) &&
+ ($numversion >= 1061600) && ($numversion <= 1061900)} then {
+ putlog "Error: dccwhois.tcl is not compatible with Eggdrop version [lindex $version 0]. Please upgrade to 1.6.20 or later."
+ return
+}
+
+
+#
+# dcc:whois --
+#
+# Wrapper proc command for Eggdrop's built-in *dcc:whois
+#
+# Arguments:
+# hand - handle of user who used this command
+# idx - dcc idx of user who used this command
+# arg - arguments passed to this command
+#
+# Results:
+# Calls Eggdrop's built-in *dcc:whois with the given arguments if user has
+# access, otherwise tells the user they don't have access.
+#
+proc dcc:whois {hand idx arg} {
+ global dccwhois_flags
+
+ set who [lindex [split $arg] 0]
+
+ # Did user gave a handle other than their own?
+ if {([string compare "" $who]) &&
+ ([string compare [string toupper $hand] [string toupper $who]])} then {
+
+ # Get user's current .console channel; check the same way Eggdrop does.
+ set chan [lindex [console $idx] 0]
+
+ # User isn't allowed to '.whois' handles other than their own.
+ if {![matchattr $hand $dccwhois_flags $chan]} then {
+ putdcc $idx "You do not have access to whois handles other than your own."
+
+ return 0
+ }
+ }
+
+ # Call built-in whois command.
+ *dcc:whois $hand $idx $arg
+
+ # Return 0 so we don't log command twice.
+ return 0
+}
+
+
+#
+# init_dccwhois --
+#
+# Initialize dccwhois script-specific code when script is loaded
+#
+# Arguments:
+# (none)
+#
+# Results:
+# Set up command bindings and store command access flags.
+#
+proc init_dccwhois {} {
+ global dccwhois_flags
+
+ putlog "Loading dccwhois.tcl..."
+
+ # Sanity check...
+ if {[array exists dccwhois_flags]} then {
+ array unset dccwhois_flags
+ }
+
+ # Search binds for built-in '*dcc:whois' and loop over each bind in list
+ foreach bind [binds "\\*dcc:whois"] {
+
+ # dcc to|o whois 0 *dcc:whois
+ foreach {type flags name count command} $bind {break}
+
+ # We only want to unbind dcc '.whois'
+ if {[string compare $name "whois"]} then {
+ continue
+ }
+
+ # Store $flags so we can reuse them later
+ set dccwhois_flags $flags
+
+ # Unbind built-in *dcc:whois
+ unbind $type $flags $name $command
+ }
+
+ # Make sure $dccwhois_flags exists and isn't empty,
+ # otherwise set to Eggdrop's default "to|o"
+ if {(![info exists dccwhois_flags]) ||
+ (![string compare "" $dccwhois_flags])} then {
+ set dccwhois_flags "to|o"
+ }
+
+ # Add bind for dcc:whois wrapper proc
+ bind dcc -|- whois dcc:whois
+
+ putlog "Loaded dccwhois.tcl"
+
+ return
+}
+
+init_dccwhois
diff --git a/down-the-tube-chat.tcl b/down-the-tube-chat.tcl
new file mode 100644
index 0000000..4efbe7c
--- /dev/null
+++ b/down-the-tube-chat.tcl
@@ -0,0 +1,67 @@
+bind pubm * "#down-the-tube *" dtt-privmsg
+
+proc dtt-privmsg {nick uhost hand chan text} {
+ if {([string index $text 0] == "!") && ([string index $text 1] != " ")} {
+ return 0
+ }
+ if {([string match "*@archiveteam/Aramaki" $uhost]) || ([string match "*@hackint/user/h2ibot" $uhost])} {
+ return 0
+ }
+ if {[string index $text 0] == "\001"} {
+ return 0
+ }
+ if {[isop $nick $chan]} {
+ set nick "@$nick"
+ } elseif {[isvoice $nick $chan]} {
+ set nick "+$nick"
+ }
+
+ putserv "PRIVMSG #down-the-tube-chat :<$nick> $text"
+}
+
+bind ctcp * "ACTION" dtt-action
+
+proc dtt-action {nick uhost hand dest keyword text} {
+ if {$dest == "#down-the-tube"} {
+ if {[isop $nick $dest]} {
+ set nick "@$nick"
+ } elseif {[isvoice $nick $dest]} {
+ set nick "+$nick"
+ }
+ putserv "PRIVMSG #down-the-tube-chat :\001ACTION <$nick> $text\001"
+ }
+}
+
+bind notc * "*" dtt-notice
+
+proc dtt-notice {nick uhost hand text {dest ""}} {
+ if {$dest == "#down-the-tube"} {
+ if {[isop $nick $dest]} {
+ set nick "@$nick"
+ } elseif {[isvoice $nick $dest]} {
+ set nick "+$nick"
+ }
+ putserv "NOTICE #down-the-tube-chat :<$nick> $text"
+ }
+}
+
+bind out * "% sent" dtt-out
+
+proc dtt-out {queue text status} {
+ set botnick $::botnick
+ if {[botisop "#down-the-tube"]} {
+ set botnick "@$botnick"
+ } elseif {[botisvoice "#down-the-tube"]} {
+ set botnick "+$botnick"
+ }
+ if {[string match "PRIVMSG #down-the-tube *" $text]} {
+ if {[string match "*\\\[remind\\\]*" $text]} {
+ putserv "PRIVMSG #down-the-tube-chat :<$botnick> [join [lrange [split $text ":"] 1 end] ":"]"
+ }
+ }
+ if {[string match "NOTICE #down-the-tube *" $text]} {
+ if {[string match "*\\\[karma\\\]*" $text]} {
+ putserv "NOTICE #down-the-tube-chat :<$botnick> [join [lrange [split $text ":"] 1 end] ":"]"
+ }
+ }
+}
diff --git a/firechannels.tcl b/firechannels.tcl
new file mode 100644
index 0000000..8826d10
--- /dev/null
+++ b/firechannels.tcl
@@ -0,0 +1,56 @@
+bind join * "#fire-channels *" firechansonjoin
+
+proc firechansonjoin {nick uhost hand chan} {
+ global botnick
+ if {[getaccount $nick] == "fireonlive"} {
+ return 0
+ }
+ if {[getaccount $nick] == "eggdrop"} {
+ return 0
+ }
+
+ if {$nick == "ChanServ"} {
+ return 0
+ }
+ putquick "NOTICE $nick :one moment please..."
+ putquick "INVITE $nick #0dayinitiative"
+ putquick "INVITE $nick #afterdark"
+ putquick "INVITE $nick #ai"
+ putquick "INVITE $nick #archiveteam-twitter"
+ putquick "INVITE $nick #datahoarders"
+ putquick "INVITE $nick #fire-spam"
+ putquick "INVITE $nick #fulldisclosure"
+ putquick "INVITE $nick #hackernews"
+ putquick "INVITE $nick #hackernews-firehose"
+ putquick "INVITE $nick #infosec"
+ putquick "INVITE $nick #intenttoship"
+ putquick "INVITE $nick #m&a"
+ putquick "INVITE $nick #memes"
+ putquick "INVITE $nick #music"
+ putquick "INVITE $nick #nanog"
+ putquick "INVITE $nick #oss-security"
+ putquick "INVITE $nick #pki"
+ putquick "INVITE $nick #reddark"
+ putquick "INVITE $nick #web3"
+ putquick "NOTICE $nick :done! to subscribe to future channel changes please /msg $botnick chsub"
+ putquick "KICK $chan $nick :check your invites!"
+ newchanban $chan *!$uhost eggdrop "check your invites!" 1 sticky
+}
+
+bind MSG * chsub chsub
+
+proc chsub {nick uhost hand text} {
+ if {[getaccount $nick] == "*"} {
+ putserv "NOTICE $nick :sorry $nick, you are not authenticated to NickServ"
+ putserv "PRIVMSG #fire :\[chsub\] $nick!$uhost attempted to subscribe, but isn't auth'd with services"
+ return 1
+ }
+ if {[getaccount $nick] == ""} {
+ putserv "NOTICE $nick :sorry $nick, you are not authenticated to NickServ"
+ putserv "PRIVMSG #fire :\[chsub\] $nick!$uhost attempted to subscribe, but isn't auth'd with services"
+ return 1
+ }
+ putserv "NOTICE $nick :thanks! you've been added to the list"
+ putserv "PRIVMSG #fire :\[chsub\] $nick!$uhost ([getaccount $nick]) subscribed to the channel changes list"
+
+}
diff --git a/fix-solanum-ban-caching.tcl b/fix-solanum-ban-caching.tcl
new file mode 100644
index 0000000..7ecea9e
--- /dev/null
+++ b/fix-solanum-ban-caching.tcl
@@ -0,0 +1,14 @@
+bind mode - * modeCacheFix
+
+proc modeCacheFix {nick uhost hand chan mchange target} {
+ if {$chan == "#fire-acl"} {
+ if {([string match "*b*" $mchange]) || ([string match "*q*" $mchange])} {
+ putlog "\[modeCacheFix\] $nick!$uhost ($hand) in $chan set $mchange $target, clearing cache on all channels.."
+ foreach chann [channels] {
+ if {($chann != "#fire-acl") && ([botisop $chann])} {
+ putquick "MODE $chann +e-e *!*@invalidating-bants-cache *!*@invalidating-bants-cache"
+ }
+ }
+ }
+ }
+}
diff --git a/forwardedSpamProtection.tcl b/forwardedSpamProtection.tcl
new file mode 100644
index 0000000..45ac554
--- /dev/null
+++ b/forwardedSpamProtection.tcl
@@ -0,0 +1,10 @@
+# born after accidentally triggering hackint's spambot warnin
+
+bind rawt * 470 gotForwarded
+
+proc gotForwarded {from keyword text tags} {
+ set fromChan [lindex [split $text] 1]
+ set toChan [lindex [split $text] 2]
+ channel set $fromChan +inactive
+ putserv "PRIVMSG #fire-trail :\[forward protection\] got forwarded from $fromChan to $toChan, set $fromChan as inactive"
+}
diff --git a/getops.tcl b/getops.tcl
new file mode 100644
index 0000000..da23f92
--- /dev/null
+++ b/getops.tcl
@@ -0,0 +1,369 @@
+
+# Getops 2.3b
+
+# This script is used for bots to request and give ops to each other.
+# For this to work, you'll need:
+
+# - Bots must be linked in a botnet
+# - Every bot that should be ops on your channels must load this script
+# - Add all bots you wanna op with this one using the .+bot nick address
+# command. The "nick" should be exactly the botnet-nick of the other bot
+# - Add the hostmasks that uniquely identify this bot on IRC
+# - Add a global or channel +o flag on all bots to be opped
+# - Do exactly the same on all other bots
+
+# The security of this relies on the fact that the bot which wants to have
+# ops must be 1) linked to the current botnet (which requires a password),
+# 2) have an entry with +o on the bot that he wants ops from and 3) must match
+# the hostmask that is stored on each bots userfile (so it is good to keep the
+# hostmasks up-to-date).
+
+# -----------------------------------------------------------------------------
+
+# 2.3c by PPSlim <ppslim@ntlworld.com>
+# - Small fix on timer handling.
+# Not list formatted, allowing command parsing of channel name
+
+# 2.3b by gregul <unknown>
+# - small fix in getbot
+
+# 2.3a by guppy <guppy@eggheads.org>
+# - fix for bind need
+
+# 2.3 by guppy <guppy@eggheads.org>
+# - minor cleanup to use some 1.6 tcl functions
+# - use bind need over need-op, need-invite, etc ...
+
+# 2.2g by poptix <poptix@poptix.net>
+# - Fabian's 2.2e broke the script, fixed.
+
+# 2.2f by Eule <eule@berlin.snafu.de>
+# - removed key work-around added in 2.2d as eggdrop now handles this
+# correctly.
+
+# 2.2e by Fabian <fknittel@gmx.de>
+# - added support for !channels (so-called ID-channels), using chandname2name
+# functions. This makes it eggdrop 1.5+ specific.
+
+# 2.2d by brainsick <brnsck@mail.earthlink.net>
+# - Undernet now handles keys differently. It no longer gives the key on a
+# join, but instead gives it on an op, but eggdrop doesn't check for this.
+# getops-2.2d should now handle this correctly. (This should be the final
+# fix to the key problems.)
+
+# 2.2c by Progfou (Jean Christophe ANDRE <progfou@rumiko.info.unicaen.fr>)
+# - changed "Requested" to "Requesting" as it was a little confusing
+# - corrected the "I am not on chan..." problem with key request
+# (thanks to Kram |FL| and Gael for reporting it)
+# - removed more unnecessary check
+
+# 2.2b by Progfou (Jean Christophe ANDRE <progfou@rumiko.info.unicaen.fr>)
+# - removed global +o in unknown bot test
+# - removed unnecessary checks due to previous unknown bot test
+
+# 2.2a by Progfou (Jean Christophe ANDRE <progfou@rumiko.info.unicaen.fr>)
+# - removed Polish language!
+
+# 2.2 by Cron (Arkadiusz Miskiewicz <misiek@zsz2.starachowice.pl>)
+# - works good (tested on eggdrop 1.3.11)
+# - asks from unknown (and bots without +bo) are ignored
+# - all messages in Polish language
+# - better response from script to users
+# - fixed several bugs
+
+# 2.1 by Ernst
+# - asks for ops right after joining the channel: no wait anymore
+# - sets "need-op/need-invite/etc" config right after joining dynamic channels
+# - fixed "You aren't +o" being replied when other bot isn't even on channel
+# - better response from bots, in case something went wrong
+# (for example if bot is not recognized (hostmask) when asking for ops)
+# - removed several no-more-used variables
+# - added the information and description above
+
+# 2.0.1 by beldin (1.3.x ONLY version)
+# - actually, iso needed to be modded for 1.3 :P, and validchan is builtin
+# and I'll tidy a couple of functions up to
+
+# 2.0 by DarkDruid
+# - It'll work with dynamic channels(dan is a dork for making it not..)
+# - It will also only ask one bot at a time for ops so there won't be any more
+# annoying mode floods, and it's more secure that way
+# - I also took that annoying wallop and resynch stuff out :P
+# - And I guess this will with with 1.3.x too
+
+# Previously by The_O, dtM.
+
+# Original incarnation by poptix (poptix@poptix.net)
+
+# -----------------------------------------------------------------------------
+
+# [0/1] do you want GetOps to notice when some unknown (unauthorized) bot
+# sends request to your bot
+set go_bot_unknown 1
+
+# [0/1] do you want your bot to request to be unbanned if it becomes banned?
+set go_bot_unban 1
+
+# [0/1] do you want GetOps to notice the channel if there are no ops?
+set go_cycle 0
+
+# set this to the notice txt for the above (go_cycle)
+set go_cycle_msg "Please part the channel so the bots can cycle!"
+
+# -----------------------------------------------------------------------------
+
+set bns ""
+proc gain_entrance {what chan} {
+ global botnick botname go_bot_unban go_cycle go_cycle_msg bns
+ switch -exact $what {
+ "limit" {
+ foreach bs [lbots] {
+ putbot $bs "gop limit $chan $botnick"
+ putlog "GetOps: Requesting limit raise from $bs on $chan."
+ }
+ }
+ "invite" {
+ foreach bs [lbots] {
+ putbot $bs "gop invite $chan $botnick"
+ putlog "GetOps: Requesting invite from $bs for $chan."
+ }
+ }
+ "unban" {
+ if {$go_bot_unban} {
+ foreach bs [lbots] {
+ putbot $bs "gop unban $chan $botname"
+ putlog "GetOps: Requesting unban on $chan from $bs."
+ }
+ }
+ }
+ "key" {
+ foreach bs [lbots] {
+ putbot $bs "gop key $chan $botnick"
+ putlog "GetOps: Requesting key on $chan from $bs."
+ }
+ }
+ "op" {
+ if {[hasops $chan]} {
+ set bot [getbot $chan]
+ if {$bot == ""} {
+ set bns ""
+ set bot [getbot $chan]
+ }
+ lappend bns "$bot"
+ if {$bot != ""} {
+ putbot $bot "gop op $chan $botnick"
+ putlog "GetOps: Requesting ops from $bot on $chan"
+ }
+ } {
+ if {$go_cycle} {
+ putserv "NOTICE [chandname2name $chan] :$go_cycle_msg"
+ }
+ }
+ }
+ }
+}
+
+proc hasops {chan} {
+ foreach user [chanlist $chan] {
+ if {[isop $user $chan]} {
+ return 1
+ }
+ }
+ return 0
+}
+
+proc getbot {chan} {
+ global bns
+ foreach bn [bots] {
+ if {[lsearch $bns $bn] < 0} {
+ if {[matchattr $bn o|o $chan]} {
+ set nick [hand2nick $bn $chan]
+ if {[onchan $nick $chan] && [isop $nick $chan]} {
+ return $bn
+ break
+ }
+ }
+ }
+ }
+}
+
+proc botnet_request {bot com args} {
+ global go_bot_unban go_bot_unknown
+ set args [lindex $args 0]
+ set subcom [lindex $args 0]
+ set chan [string tolower [lindex $args 1]]
+ if {![validchan $chan]} {
+ putbot $bot "gop_resp I don't monitor $chan."
+ return 0
+ }
+ # Please note, 'chandname2name' will cause an error if it is not a valid channel
+ # Thus, we make sure $chan is a valid channel -before- using it. -poptix
+ set idchan [chandname2name $chan]
+ set nick [lindex $args 2]
+
+ if {$subcom != "takekey" && ![botonchan $chan]} {
+ putbot $bot "gop_resp I am not on $chan."
+ return 0
+ }
+ if {![matchattr $bot b]} {
+ if { $go_bot_unknown == 1} {
+ putlog "GetOps: Request ($subcom) from $bot - unknown bot (IGNORED)"
+ }
+ return 0
+ }
+
+ switch -exact $subcom {
+ "op" {
+ if {![onchan $nick $chan]} {
+ putbot $bot "gop_resp You are not on $chan for me."
+ return 1
+ }
+ set bothand [finduser $nick![getchanhost $nick $chan]]
+ if {$bothand == "*"} {
+ putlog "GetOps: $bot requested ops on $chan. Ident not recognized: $nick![getchanhost $nick $chan]."
+ putbot $bot "gop_resp I don't recognize you on IRC (your ident: $nick![getchanhost $nick $chan])"
+ return 1
+ }
+ if {[string tolower $bothand] == [string tolower $nick]} {
+ putlog "GetOps: $bot requested ops on $chan."
+ } {
+ putlog "GetOps: $bot requested ops as $nick on $chan."
+ }
+ if {[iso $nick $chan] && [matchattr $bothand b]} {
+ if {[botisop $chan]} {
+ if {![isop $nick $chan]} {
+ putlog "GetOps: $nick asked for op on $chan."
+ putbot $bot "gop_resp Opped $nick on $chan."
+ pushmode $chan +o $nick
+ }
+ } {
+ putbot $bot "gop_resp I am not +o on $chan."
+ }
+ } {
+ putbot $bot "gop_resp You aren't +o in my userlist for $chan, sorry."
+ }
+ return 1
+ }
+ "unban" {
+ if {$go_bot_unban} {
+ putlog "GetOps: $bot requested that I unban him on $chan."
+ foreach ban [chanbans $chan] {
+ if {[string compare $nick $ban]} {
+ set bug_1 [lindex $ban 0]
+ pushmode $chan -b $bug_1
+ }
+ }
+ return 1
+ } {
+ putlog "GetOps: Refused request to unban $bot ($nick) on $chan."
+ putbot $bot "gop_resp Sorry, not accepting unban requests."
+ }
+ }
+ "invite" {
+ putlog "GetOps: $bot asked for an invite to $chan."
+ putserv "invite $nick $idchan"
+ return 1
+ }
+ "limit" {
+ putlog "GetOps: $bot asked for a limit raise on $chan."
+ pushmode $chan +l [expr [llength [chanlist $chan]] + 1]
+ return 1
+ }
+ "key" {
+ putlog "GetOps: $bot requested the key on $chan."
+ if {[string match *k* [lindex [getchanmode $chan] 0]]} {
+ putbot $bot "gop takekey $chan [lindex [getchanmode $chan] 1]"
+ } {
+ putbot $bot "gop_resp There isn't a key on $chan!"
+ }
+ return 1
+ }
+ "takekey" {
+ putlog "GetOps: $bot gave me the key to $chan! ($nick)"
+ foreach channel [string tolower [channels]] {
+ if {$chan == $channel} {
+ if {$idchan != ""} {
+ putserv "JOIN $idchan $nick"
+ } else {
+ putserv "JOIN $channel $nick"
+ }
+ return 1
+ }
+ }
+ }
+ default {
+ putlog "GetOps: ALERT! $bot sent fake 'gop' message! ($subcom)"
+ }
+ }
+}
+
+proc gop_resp {bot com msg} {
+ putlog "GetOps: MSG from $bot: $msg"
+ return 1
+}
+
+proc lbots {} {
+ set unf ""
+ foreach users [userlist b] {
+ foreach bs [bots] {
+ if {$users == $bs} {
+ lappend unf $users
+ }
+ }
+ }
+ return $unf
+}
+
+# Returns list of bots in the botnet and +o in my userfile on that channel
+proc lobots { channel } {
+ set unf ""
+ foreach users [userlist b] {
+ if {![matchattr $users o|o $channel]} { continue }
+ foreach bs [bots] {
+ if {$users == $bs} { lappend unf $users }
+ }
+ }
+ return $unf
+}
+
+proc iso {nick chan} {
+ return [matchattr [nick2hand $nick $chan] o|o $chan]
+}
+
+proc gop_need {chan type} {
+ # Use bind need over setting need-op, need-invite, etc ...
+ gain_entrance $type $chan
+}
+
+bind need - * gop_need
+bind bot - gop botnet_request
+bind bot - gop_resp gop_resp
+bind join - * gop_join
+
+proc requestop { chan } {
+ global botnick
+ set chan [string tolower $chan]
+ foreach thisbot [lobots $chan] {
+ # Send request to all, because the bot does not have the channel info yet
+ putbot $thisbot "gop op $chan $botnick"
+ lappend askedbot $thisbot
+ }
+ if {[info exist askedbot]} {
+ regsub -all " " $askedbot ", " askedbot
+ putlog "GetOps: Requested Ops from $askedbot on $chan."
+ } {
+ putlog "GetOps: No bots to ask for ops on $chan."
+ }
+ return 0
+}
+
+proc gop_join { nick uhost hand chan } {
+ if {[isbotnick $nick]} {
+ utimer 3 [list requestop $chan]
+ }
+ return 0
+}
+
+set getops_loaded 1
+
+putlog "GetOps v2.3c loaded."
diff --git a/klined.tcl b/klined.tcl
new file mode 100644
index 0000000..16643fe
--- /dev/null
+++ b/klined.tcl
@@ -0,0 +1,177 @@
+#
+# KLined.TCL - Version 1.0
+# By Ian Kaney - ikaney@uk.defiant.org
+#
+# Even at the best of times, your bot will get k-lined by one operator or
+# another on a server you're running your bot on. This script will 'hopefully'
+# handle this by removing it from your bot's server list when it detects
+# you've been k-lined there. Thus, stopping IRC server admins getting
+# rather peeved at the constant connects from your host.
+#
+# USAGE:
+# The actual handling of removing the server from your server list
+# and writing it to the 'klines' file is handled automatically when
+# your bot receives the k-line signal, but there are some DCC commands
+# that have been added, these are:
+#
+# .klines - Lists the 'klines' file showing servers that your bot
+# has registered as being k-lined on.
+# .unkline <server> - Removes the k-line from the server *joke* ;)
+# Actually, this removes the server from the list
+# of servers to remove.
+#
+
+# Bindings
+# ---
+bind load - server remove_kservers
+bind raw - 465 woah_klined
+bind dcc n klines list_kservers
+bind dcc n unkline unkline_server
+
+# Variables
+# ---
+# Change this to suite your tastes - if you can't be bothered, or
+# don't know how, leave it.
+set kfile "klines"
+
+proc list_kservers {handle idx args} {
+global kfile
+
+putcmdlog "#$handle# klines"
+set fd [open $kfile r]
+set kservers { }
+
+while {![eof $fd]} {
+ set tmp [gets $fd]
+ if {[eof $fd]} {break}
+ set kservers [lappend kservers [string trim $tmp]]
+ }
+close $fd
+if {[llength $kservers] == 0} {
+ putdcc $idx "No k-lined servers."
+ return 0
+ }
+putdcc $idx "My k-lined server list:\n"
+foreach tmp $kservers {
+ putdcc $idx $tmp
+ }
+}
+
+proc unkline_server {handle idx args} {
+global kfile
+
+set kservers {}
+
+set fd [open $kfile r]
+set rem [lindex $args 0]
+
+putcmdlog "#$handle# unkline $rem"
+
+while {![eof $fd]} {
+ set tmp [gets $fd]
+ if {[eof $fd]} {break}
+ set kservers [lappend kservers [string trim $tmp]]
+}
+close $fd
+
+set fd [open $kfile w]
+set flag "0"
+
+foreach tmp $kservers {
+ if {$tmp == $rem} {
+ set flag "1"
+ }
+ if {$tmp != $rem} {
+ puts $fd $tmp
+ }
+ }
+close $fd
+if {$flag == "0"} {
+ putdcc $idx "Could not find $rem in the k-lined server list."
+ }
+if {$flag == "1"} {
+ putdcc $idx "Removed server $rem from k-lined server list."
+ }
+}
+
+proc remove_kservers {module} {
+global kfile
+global server servers
+
+if {[catch {set fd [open $kfile r]}] != 0} {
+set fd [open $kfile w]
+close $fd
+set fd [open $kfile r]
+}
+
+while {![eof $fd]} {
+ set from [string trim [gets $fd]]
+ set name "*$from*"
+ if {[eof $fd]} {break}
+
+ for {set j 0} {$j >= 0} {incr j} {
+ set x [lsearch $servers $name]
+ if {$x >= 0} {
+ set servers [lreplace $servers $x $x]
+ }
+ if {$x < 0} {
+ if {$j >= 0} {
+ putlog "Removed server: $from"
+ }
+ break
+ }
+ }
+ }
+close $fd
+return 1
+}
+
+proc woah_klined {from keyword arg} {
+
+global kfile
+global server servers
+
+set kservers {}
+
+set fd [open $kfile r]
+
+while {![eof $fd]} {
+ set tmp [gets $fd]
+ if {[eof $fd]} {break}
+ set kservers [lappend kservers [string trim $tmp]]
+}
+close $fd
+
+set flag "0"
+
+foreach tmp $kservers {
+ if {$tmp == $from} {
+ set flag "1"
+ }
+}
+
+if {$flag != "1"} {
+set fd [open $kfile a]
+puts $fd $from
+close $fd
+}
+
+set name "*$from*"
+
+for {set j 0} {$j >= 0} {incr j} {
+ set x [lsearch $servers $name]
+ if {$x >= 0} {
+ set servers [lreplace $servers $x $x]
+ }
+ if {$x <= 0} {
+ if {$j >= 0} {
+ putlog "Removed server: $from"
+ }
+ break
+ }
+ }
+ return 1
+}
+
+putlog "TCL loaded: k-lined"
+remove_kservers server
diff --git a/lilykarma.tcl b/lilykarma.tcl
new file mode 100644
index 0000000..d804469
--- /dev/null
+++ b/lilykarma.tcl
@@ -0,0 +1,563 @@
+# IRC Karma for Eggdrops by Lily (lily@disorg.net)
+# This is a Karma database script for Eggdrop bots.
+# It has flood control built in, and basic self-karma prevention.
+# Requires TCL8.4 or greater, and the sqlite3 tcl lib. (not mysql!)
+# For deb/ubuntu, that is the libsqlite3-tcl package. Adjust for your flavor.
+# !! 3.x USERS PLEASE NOTE!!! The database change for 4.x means that unless
+# you manually edit your db, you will have to start over. Sorry! You may not want
+# karma entropy and expiry anyway, and thats the big new feature here.
+# 1.0 - self karma prevention
+# 2.0 - flood control, maries mods added
+# 2.5 - command triggers fixed, extra commands removed. comments added. 20101220
+# 3.0 - converted to sqlite. code cleanup. 20111030
+# 3.1 - search and help. 20111104
+# 3.2 - changed dbfile varname to prevent namespace collison 20120220
+# 4.0 - added db timestamping and karma expiry, changed scoring update loop. 20120828
+# 4.1 - karma entropy 20120903
+# 4.2 - !instant changes a random thing -/+1 , !random returns things. 20120905
+# 4.3 - locked text change, faster decay for high vals, trim item whitespace 20130215
+# 4.4 - Immortal karma (user request). Expanded stats. Fixes (locked text, high val decay) 20130526
+# 4.5 - strip ASCII codes from input. 20211221
+# TODO - randomize days a little on entropy
+
+### Usage ###
+# Once you have loaded this from your bots config and rehashed it,
+# do: .chanset #yourchannel +lkarma
+# In your channel you can "!<item>++" and "!<item>--" with any word or phrase.
+# "!good" and "!bad" return the top (and bottom) ten. "!khelp" for help.
+# "!lfind" returns locked items. "!ifind" returns immortalized items.
+# "!karma <item>" for score. "!stats" for total items and average karma (unlocked items).
+# "!find <item>" and "!rfind <item>" to search for things in the database.
+# "!instant" changes a random thing -/+1 , "!random" returns some items.
+# Privileged users can "!lock <item>", "!unlock <item>", and "!delete <item>"
+# They can also "!immortalize <item>" and remove it from entropy. Cannot be unset.
+
+### Settings ###
+
+set karma(logchan) "#fire-trail"
+
+# Set this to f allow friends in the bot to use karma (+f flag)
+# Set this to - if you want everyone to be able to.
+set karma(flags) "-|-"
+
+# Default flags allow only bot owners to lock/unlock karma.
+set karma(lockflags) "n|-"
+
+# Default flags allow only bot owners to delete karma from the database.
+set karma(deleteflags) "n|-"
+
+# x times in y seconds to trigger flood protection
+set kfflood 4:300
+
+###############################################
+set karma(version) "4.5"
+setudef flag lkarma
+package require sqlite3
+set kdbfile "./lkarma.db"
+if {![file exists $kdbfile]} {
+ sqlite3 kdb $kdbfile
+ kdb eval {CREATE TABLE lkarma(item TEXT NOT NULL COLLATE NOCASE, karma INTEGER NOT NULL, locked TEXT NOT NULL default 'N', modtime INTEGER NOT NULL)}
+ kdb close
+}
+
+bind pubm $karma(flags) "% *--" fixkarma
+bind pubm $karma(flags) "% *++" fixkarma
+
+proc fixkarma {nick uhost hand chan text} {
+ global karma kdbfile kfcount kfflood
+ if {[string match "*+lkarma*" [channel info $chan]]} {
+ set nick [string map { "\{" "\\\{" "\}" "\\\}" "\[" "\\\[" "\]" "\\\]" } $nick]
+ set uhost [string tolower $uhost]
+ set acct [getaccount $nick]
+ set hand [finduser -account $acct]
+ set chan [string tolower $chan]
+ set item [string range $text 0 [expr [string length $text] -3]]
+ set item [stripcodes bcruag $item]
+ set item [string trim $item]
+ if {$hand == "rss"} { return 0 }
+ sqlite3 kdb $kdbfile
+ if {$item == ""} {
+ return 0
+ }
+ if {[string match -nocase *$nick* $item]} {
+ puthelp "NOTICE $chan :\[karma\] self karma is a selfish pursuit."
+ return 0
+ }
+ if {($acct != "*")} {
+ if {([string match -nocase *$acct* $item])} {
+ puthelp "NOTICE $chan :\[karma\] self karma is a selfish pursuit."
+ return 0
+ }
+ }
+ if {[kdb eval {SELECT locked FROM lkarma WHERE item=$item}] == "Y"} {
+ set lockedk [kdb eval {SELECT karma FROM lkarma WHERE item=$item}]
+ if {$lockedk == 0} {
+ puthelp "PRIVMSG $chan :You can't change the karma of \002$item\002!"
+ } {
+ puthelp "PRIVMSG $chan :Karma for \002$item\002 is locked at \002$lockedk\002."
+ }
+ return 0
+ }
+
+ set score [string range $text end-1 end]
+ if {[string match "++" $score]} {
+ set scoring +1
+ } elseif {[string match "--" $score]} {
+ set scoring -1
+ }
+
+ if {![info exists kfcount($uhost)]} {
+ set kfcount($uhost) 0
+ }
+
+ if {$kfcount($uhost) == [lindex $kfflood 0]} {
+ puthelp "NOTICE $nick :please dont flood the karmabot, $nick. try again later."
+ if {[string match "++" $score]} {
+ puthelp "PRIVMSG $karma(logchan) :\[karma\] *ignored - flood* $nick!$uhost ($acct) ++'d '$item' in $chan"
+ } elseif {[string match "--" $score]} {
+ puthelp "PRIVMSG $karma(logchan) :\[karma\] *ignored - flood* $nick!$uhost ($acct) --'d '$item' in $chan"
+ }
+ return 0
+ }
+
+ # check if it was likely a command to another bot
+ #if {[string index $text 0] == "!"} {
+ # if {[string match "++" $score]} {
+ # puthelp "PRIVMSG $karma(logchan) :\[karma\] *ignored - starts with !* $nick!$uhost ($acct) ++'d '$item' in $chan"
+ # } elseif {[string match "--" $score]} {
+ # puthelp "PRIVMSG $karma(logchan) :\[karma\] *ignored - starts with !* $nick!$uhost ($acct) --'d '$item' in $chan"
+ # }
+ # return 0
+ #}
+
+ # ignore karma requests from bots
+ set accthand [finduser -account [getaccount $nick]]
+ if {[string match $accthand "*"]} {
+ # not needed once eggdrop adds support for nickserv account to nick2hand
+ set accthand [nick2hand $nick]
+ }
+ if {![string match $accthand "*"]} {
+ if {[matchattr $accthand +B]} {
+ if {[string match "++" $score]} {
+ puthelp "PRIVMSG $karma(logchan) :\[karma\] *ignored - is a known bot* $nick!$uhost ($acct) ++'d '$item' in $chan"
+ } elseif {[string match "--" $score]} {
+ puthelp "PRIVMSG $karma(logchan) :\[karma\] *ignored - is a known bot* $nick!$uhost ($acct) --'d '$item' in $chan"
+ }
+ return 0
+ }
+ }
+
+ incr kfcount($uhost)
+ set ktime [clock seconds]
+
+ if {[llength [kdb eval {SELECT karma FROM lkarma WHERE item=$item}]] == 0} {
+ kdb eval {INSERT INTO lkarma (item,karma,modtime) VALUES ($item,0,$ktime)}
+ }
+
+ if {[kdb eval {SELECT karma FROM lkarma WHERE item=$item}] < 900} {
+ kdb eval {UPDATE lkarma SET karma=karma+$scoring, modtime=$ktime WHERE item=$item}
+ } {
+ kdb eval {UPDATE lkarma SET karma=karma+$scoring WHERE item=$item}
+ }
+ set newkarma [kdb eval {SELECT karma FROM lkarma WHERE item=$item}]
+ puthelp "NOTICE $chan :\[karma\] '$item' now has $newkarma karma!"
+ if {[string match "++" $score]} {
+ puthelp "PRIVMSG $karma(logchan) :\[karma\] $nick!$uhost ($acct) ++'d '$item' in $chan - new score: $newkarma"
+ } elseif {[string match "--" $score]} {
+ puthelp "PRIVMSG $karma(logchan) :\[karma\] $nick!$uhost ($acct) --'d '$item' in $chan - new score: $newkarma"
+ }
+ kdb close
+ }
+}
+
+###Other Commands###
+
+bind pub $karma(flags) !khelp karmahelp
+proc karmahelp {nick uhost hand chan text} {
+ if {[string match "*+lkarma*" [channel info $chan]]} {
+ if {![string match "" $text]} {return 0}
+ puthelp "NOTICE $chan :\[karma\] you may \"<item>++\" and \"<item>--\" any word or phrase. \"!kbest\" and \"!kworst\" return the top (and bottom) ten. \"!karma \[item\]\" to look up individual scores. \"!kstats\" for statistics. Use \"!kfind <item>\" and \"!krfind <item>\" to search for things in the database ordered by karma in ascending or descending order (respectively), or just use \"!krand\" for ten random things."
+ }
+}
+
+bind pub $karma(flags) !karma checkkarma
+proc checkkarma {nick uhost hand chan text} {
+ if {[regexp -nocase {(.*)(\+\+|\-\-)$} $text]} {return 0}
+ if {[string match "*+lkarma*" [channel info $chan]]} {
+ global karma kdbfile
+ if {[string match "" $text]} {
+ set text $nick
+ }
+ set item [string trim $text]
+ sqlite3 kdb $kdbfile
+ set current [kdb eval {SELECT karma FROM lkarma WHERE item=$item}]
+ if {[llength $current] == 0} {
+ set current 0
+ }
+ if {[kdb eval {SELECT locked FROM lkarma WHERE item=$item}] == "Y"} {
+ puthelp "NOTICE $chan :\[karma\] \"$item\"\ is locked at \002$current\002."
+ } else {
+ puthelp "NOTICE $chan :\[karma\] \"$item\" has $current karma"
+ }
+ if {[kdb eval {SELECT locked FROM lkarma WHERE item=$item}] == "U"} {
+ puthelp "NOTICE $chan :\[karma\] \"$item\" has been immortalized"
+ }
+ kdb close
+ }
+}
+
+bind pub $karma(flags) !kstats karmastats
+proc karmastats {nick uhost hand chan text} {
+ if {![string match "" $text]} {return 0}
+ global karma kdbfile
+ if {[string match "*+lkarma*" [channel info $chan]]} {
+ sqlite3 kdb $kdbfile
+ set lcount [kdb eval {SELECT COUNT(*) FROM lkarma WHERE locked='Y'}]
+ set ucount [kdb eval {SELECT COUNT(*) FROM lkarma WHERE locked='U'}]
+ set gcount [kdb eval {SELECT COUNT(*) FROM lkarma WHERE karma > 0 AND locked='N'}]
+ set bcount [kdb eval {SELECT COUNT(*) FROM lkarma WHERE karma < 0 AND locked='N'}]
+ set total [kdb eval {SELECT COUNT(*) FROM lkarma}]
+ set average [kdb eval {SELECT AVG(karma) FROM lkarma WHERE locked='N'}]
+ set average [expr round($average)]
+ puthelp "NOTICE $chan :\[karma\] stats: $total items in the database. good: $gcount, bad: $bcount, average karma: $average"
+ kdb close
+ }
+}
+
+bind pub $karma(flags) !kbest goodkarma
+bind pub $karma(flags) !ktop goodkarma
+proc goodkarma {nick uhost hand chan text} {
+ if {![string match "" $text]} {return 0}
+ global karma kdbfile
+ if {[string match "*+lkarma*" [channel info $chan]]} {
+ sqlite3 kdb $kdbfile
+ foreach {item score} [kdb eval {SELECT item,karma FROM lkarma WHERE locked='N' ORDER BY karma DESC, item ASC LIMIT 0,10} ] {
+ append outvar "$item: $score "
+ }
+ puthelp "NOTICE $chan :\[karma\] $outvar"
+ kdb close
+ }
+}
+
+bind pub $karma(flags) !kworst badkarma
+bind pub $karma(flags) !kbottom badkarma
+proc badkarma {nick uhost hand chan text} {
+ if {![string match "" $text]} {return 0}
+ global karma kdbfile
+ if {[string match "*+lkarma*" [channel info $chan]]} {
+ sqlite3 kdb $kdbfile
+ foreach {item score} [kdb eval {SELECT item,karma FROM lkarma WHERE locked='N' ORDER BY karma ASC, item ASC LIMIT 0,10}] {
+ append outvar "$item: $score "
+ }
+ puthelp "NOTICE $chan :\[karma\] $outvar"
+ kdb close
+ }
+}
+
+#bind pub $karma(flags) !lfind lockedkarma
+#bind pub $karma(flags) !lkarma lockedkarma
+#proc lockedkarma {nick uhost hand chan text} {
+# if {![string match "" $text]} {return 0}
+# global karma kdbfile
+# if {[string match "*+lkarma*" [channel info $chan]]} {
+# sqlite3 kdb $kdbfile
+# set lcount [kdb eval {SELECT COUNT(*) FROM lkarma WHERE locked='Y'}]
+# if {$lcount != 0} {
+# foreach {item score} [kdb eval {SELECT item,karma FROM lkarma WHERE locked='Y' ORDER BY karma DESC} ] {
+# append outvar "$item:\002$score\002 "
+# }
+# puthelp "NOTICE $chan :\[karma\] \002$lcount\002 locked items: $outvar"
+# } {
+# puthelp "NOTICE $chan :\[karma\] there are no locked items."
+# }
+# kdb close
+# }
+#}
+#
+#bind pub $karma(flags) !ifind immkarma
+#bind pub $karma(flags) !ikarma immkarma
+#proc immkarma {nick uhost hand chan text} {
+# if {![string match "" $text]} {return 0}
+# global karma kdbfile
+# if {[string match "*+lkarma*" [channel info $chan]]} {
+# sqlite3 kdb $kdbfile
+# set icount [kdb eval {SELECT COUNT(*) FROM lkarma WHERE locked='U'}]
+# if {$icount != 0} {
+# foreach {item score} [kdb eval {SELECT item,karma FROM lkarma WHERE locked='U' ORDER BY karma DESC} ] {
+# append outvar "$item:\002$score\002 "
+# }
+# puthelp "NOTICE $chan :\[karma\] \002$icount\002 immortal items: $outvar"
+# } {
+# puthelp "NOTICE $chan :\[karma\] there are no immortal items."
+# }
+# kdb close
+# }
+#}
+
+bind pub $karma(flags) !krand randkarma
+proc randkarma {nick uhost hand chan text} {
+ if {![string match "" $text]} {return 0}
+ global karma kdbfile
+ if {[string match "*+lkarma*" [channel info $chan]]} {
+ sqlite3 kdb $kdbfile
+ foreach {item score} [kdb eval {SELECT item,karma FROM lkarma WHERE locked='N' ORDER BY RANDOM() LIMIT 10} ] {
+ append outvar "$item: $score "
+ }
+ puthelp "NOTICE $chan :\[karma\] $outvar"
+ kdb close
+ }
+}
+
+#bind pub $karma(flags) !instant instantkarma
+#proc instantkarma {nick uhost hand chan text} {
+# return 0
+# if {![string match "" $text]} {return 0}
+# global karma kdbfile kfcount kfflood
+# if {[string match "*+lkarma*" [channel info $chan]]} {
+# set uhost [string tolower $uhost]
+# set chan [string tolower $chan]
+# sqlite3 kdb $kdbfile
+# if {![info exists kfcount($uhost:$chan)]} {
+# set kfcount($uhost:$chan) 0
+# }
+# if {$kfcount($uhost:$chan) == [lindex $kfflood 0]} {
+# puthelp "PRIVMSG $chan :Please dont flood karma, $nick, try again later."
+# return 0
+# }
+# incr kfcount($uhost:$chan)
+# lappend choices "+1" "-1"
+# set scoring [lindex $choices [expr {int(rand()*[llength $choices])}]]
+# putlog "$scoring instant karma randomly selected"
+# sqlite3 kdb $kdbfile
+# set ktime [clock seconds]
+# set rando [kdb onecolumn {SELECT item FROM lkarma WHERE locked='N' ORDER BY RANDOM() LIMIT 1}]
+# kdb eval {UPDATE lkarma SET karma=karma+$scoring, modtime=$ktime WHERE item=$rando}
+# set newkarma [kdb eval {SELECT karma FROM lkarma WHERE item=$rando}]
+# puthelp "PRIVMSG $chan :Karma for \002$rando\002 is now \002$newkarma\002."
+# kdb close
+# }
+#}
+
+bind pub $karma(flags) !kfind findkarma
+proc findkarma {nick uhost hand chan text} {
+ if {[regexp -nocase {(.*)(\+\+|\-\-)$} $text]} {return 0}
+ if {[string match "*+lkarma*" [channel info $chan]]} {
+ global karma kdbfile
+ if {[string match "" $text]} {
+ puthelp "NOTICE $chan :What should be found?"
+ } {
+ sqlite3 kdb $kdbfile
+ set word [string trim $text]
+ set spatrn "%$word%"
+ set scount [kdb eval {SELECT COUNT(1) FROM lkarma WHERE item LIKE $spatrn}]
+ if {$scount == 0 } {
+ puthelp "NOTICE $chan :\[karma\] '$word' not found."
+ } {
+ if {$scount < 9 } {
+ foreach {item score} [kdb eval {SELECT item,karma FROM lkarma WHERE item LIKE $spatrn ORDER BY karma DESC LIMIT 0,9}] {
+ append sreturn "$item: '$score' "
+ }
+ puthelp "NOTICE $chan :\[karma\] $scount matches for '$word': $sreturn"
+ } {
+ foreach {item score} [kdb eval {SELECT item,karma FROM lkarma WHERE item LIKE $spatrn ORDER BY karma DESC LIMIT 0,10}] {
+ append spos "$item: $score "
+ }
+ puthelp "NOTICE $chan :\[karma\] $scount matches for '$word'. top 10 matches: $spos"
+ }
+ }
+ kdb close
+ }
+ }
+}
+
+bind pub $karma(flags) !krfind rfindkarma
+proc rfindkarma {nick uhost hand chan text} {
+ if {[regexp -nocase {(.*)(\+\+|\-\-)$} $text]} {return 0}
+ if {[string match "*+lkarma*" [channel info $chan]]} {
+ global karma kdbfile
+ if {[string match "" $text]} {
+ puthelp "NOTICE $chan :\[karma\] what should be found?"
+ } {
+ sqlite3 kdb $kdbfile
+ set word [string trim $text]
+ set spatrn "%$word%"
+ set scount [kdb eval {SELECT COUNT(1) FROM lkarma WHERE item LIKE $spatrn}]
+ if {$scount == 0 } {
+ puthelp "NOTICE $chan :\[karma\] '$word' not found"
+ } {
+ if {$scount < 9 } {
+ foreach {item score} [kdb eval {SELECT item,karma FROM lkarma WHERE item LIKE $spatrn ORDER BY karma ASC LIMIT 0,9}] {
+ append sreturn "$item: $score "
+ }
+ puthelp "NOTICE $chan :\[karma\] $scount matches for '$word': $sreturn"
+ } {
+ foreach {item score} [kdb eval {SELECT item,karma FROM lkarma WHERE item LIKE $spatrn ORDER BY karma ASC LIMIT 0,10}] {
+ append sneg "$item: $score "
+ }
+ puthelp "NOTICE $chan :\[karma\] $scount matches for '$word'. bottom 10 matches: $sneg"
+ }
+ }
+ kdb close
+ }
+ }
+}
+
+#bind pub $karma(lockflags) !lock lockkarma
+#proc lockkarma {nick uhost hand chan text} {
+# if {[regexp -nocase {(.*)(\+\+|\-\-)$} $text]} {return 0}
+# global karma kdbfile
+# if {[string match "*+lkarma*" [channel info $chan]]} {
+# set item [string trim $text]
+# if {$item == ""} {
+# puthelp "NOTICE $chan :\[karma\] what should be locked?"
+# return
+# }
+# sqlite3 kdb $kdbfile
+# if {[llength [kdb eval {SELECT locked FROM lkarma WHERE item=$item}]] == 0} {
+# puthelp "NOTICE $chan :\[karma\] \002$item\002 is not in the database."
+# } {
+# if {[kdb eval {SELECT locked FROM lkarma WHERE item=$item}] != "N"} {
+# puthelp "NOTICE $chan :\[karma\] \002$item\002 is already locked."
+# } {
+# kdb eval {UPDATE lkarma SET locked='Y' WHERE item=$item}
+# puthelp "NOTICE $chan :\[karma\] \002$item\002 locked."
+# }
+# }
+# kdb close
+# }
+#}
+#
+#bind pub $karma(lockflags) !unlock unlockkarma
+#proc unlockkarma {nick uhost hand chan text} {
+# if {[regexp -nocase {(.*)(\+\+|\-\-)$} $text]} {return 0}
+# global karma kdbfile
+# if {[string match "*+lkarma*" [channel info $chan]]} {
+# set item [string trim $text]
+# if {$item == ""} {
+# puthelp "NOTICE $chan :\[karma\] what should be unlocked?"
+# return
+# }
+# sqlite3 kdb $kdbfile
+# if {[llength [kdb eval {SELECT locked FROM lkarma WHERE item=$item}]] == 0} {
+# puthelp "NOTICE $chan :\[karma\] \002$item\002 is not in the database."
+# } {
+# if {[kdb eval {SELECT locked FROM lkarma WHERE item=$item}] != "Y"} {
+# puthelp "NOTICE $chan :\[karma\] \002$item\002 is not locked."
+# } {
+# kdb eval {UPDATE lkarma SET locked='N' WHERE item=$item}
+# puthelp "NOTICE $chan :\[karma\] \002$item\002 unlocked."
+# }
+# }
+# kdb close
+# }
+#}
+#
+#bind pub $karma(lockflags) !immortalize immortkarma
+#proc immortkarma {nick uhost hand chan text} {
+# if {[regexp -nocase {(.*)(\+\+|\-\-)$} $text]} {return 0}
+# global karma kdbfile
+# if {[string match "*+lkarma*" [channel info $chan]]} {
+# set item [string trim $text]
+# if {$item == ""} {
+# puthelp "NOTICE $chan :\[karma\] what should be immortalized?"
+# return
+# }
+# sqlite3 kdb $kdbfile
+# if {[llength [kdb eval {SELECT locked FROM lkarma WHERE item=$item}]] == 0} {
+# puthelp "NOTICE $chan :\[karma\] \002$item\002 is not in the database."
+# } {
+# if {[kdb eval {SELECT locked FROM lkarma WHERE item=$item}] == "U"} {
+# puthelp "NOTICE $chan :\[karma\] \002$item\002 is already immortalized."
+# } {
+# kdb eval {UPDATE lkarma SET locked='U' WHERE item=$item}
+# puthelp "NOTICE $chan :\[karma\] \002$item\002 immortalized!"
+# }
+# }
+# kdb close
+# }
+#}
+#
+#bind pub $karma(deleteflags) !delete deletekarma
+#proc deletekarma {nick uhost hand chan text} {
+# if {[regexp -nocase {(.*)(\+\+|\-\-)$} $text]} {return 0}
+# global karma kdbfile
+# if {[string match "*+lkarma*" [channel info $chan]]} {
+# set item [string trim $text]
+# if {$item == ""} {
+# puthelp "NOTICE $chan :\[karma\] what should be deleted?"
+# return
+# }
+# sqlite3 kdb $kdbfile
+# if {[llength [kdb eval {SELECT item FROM lkarma WHERE item=$item}]] == 0} {
+# puthelp "NOTICE $chan :\[karma\] \002$item\002 is not in the database."
+# } {
+# if {[kdb eval {SELECT locked FROM lkarma WHERE item=$item}] != "N"} {
+# puthelp "NOTICE $chan :\[karma\] \002$item\002 can't be deleted."
+# } {
+# kdb eval {DELETE FROM lkarma WHERE item=$item}
+# puthelp "NOTICE $chan :\[karma\] \002$item\002 deleted."
+# }
+# }
+# kdb close
+# }
+#}
+
+####TIMER CODE###
+
+# removed call to this everything above for now, i don't like the karma fuzzing... the 'delete stuff at 0 karma for X days' could be interesting, though.
+# was just going to wholesale remove that until I saw that lol
+proc karmaupdate {} {
+ return 0
+# global kdbfile
+# sqlite3 kdb $kdbfile
+# set kutime [clock seconds]
+## plus/minus 1 to 5 days to kutime at random here? or in each loop?
+#
+# set xtime [expr $kutime - (90 * 86400)]
+# kdb eval {DELETE FROM lkarma WHERE modtime < $xtime AND karma between -1 and 1 and locked='N'}
+# kdb eval {UPDATE lkarma SET karma=karma-1, modtime=$kutime WHERE modtime < $xtime AND karma between 2 and 22 AND locked='N'}
+# kdb eval {UPDATE lkarma SET karma=karma+1, modtime=$kutime WHERE modtime < $xtime AND karma between -22 and -2 AND locked='N'}
+#
+# set ytime [expr $kutime - (30 * 86400)]
+# kdb eval {UPDATE lkarma SET karma=karma-1, modtime=$kutime WHERE modtime < $ytime AND karma between 23 and 54 AND locked='N'}
+# kdb eval {UPDATE lkarma SET karma=karma+1, modtime=$kutime WHERE modtime < $ytime AND karma between -54 and -23 AND locked='N'}
+#
+# set yytime [expr $kutime - (7 * 86400)]
+# kdb eval {UPDATE lkarma SET karma=karma-1, modtime=$kutime WHERE modtime < $yytime AND karma between 55 and 80 AND locked='N'}
+# kdb eval {UPDATE lkarma SET karma=karma+1, modtime=$kutime WHERE modtime < $yytime AND karma between -80 and -55 AND locked='N'}
+#
+# set yyytime [expr $kutime - (2 * 86400)]
+# kdb eval {UPDATE lkarma SET karma=karma-1, modtime=$kutime WHERE modtime < $yyytime AND karma between 81 and 120 AND locked='N'}
+# kdb eval {UPDATE lkarma SET karma=karma+1, modtime=$kutime WHERE modtime < $yyytime AND karma between -120 and -81 AND locked='N'}
+#
+# set ztime [expr $kutime - 86400]
+# kdb eval {UPDATE lkarma SET karma=karma-1, modtime=$kutime WHERE modtime < $ztime AND karma between 121 and 540 AND locked='N'}
+# kdb eval {UPDATE lkarma SET karma=karma+1, modtime=$kutime WHERE modtime < $ztime AND karma between -540 and -121 AND locked='N'}
+#
+# set zztime [expr $kutime - (86400 / 3)]
+# kdb eval {UPDATE lkarma SET karma=karma-1, modtime=$kutime WHERE modtime < $zztime AND karma between 541 and 959 AND locked='N'}
+# kdb eval {UPDATE lkarma SET karma=karma+1, modtime=$kutime WHERE modtime < $zztime AND karma between -959 and -541 AND locked='N'}
+#
+# set zzztime [expr $kutime - (86400 / 5)]
+# kdb eval {UPDATE lkarma SET karma=karma-1, modtime=$kutime WHERE modtime < $zzztime AND karma > 960 AND locked='N'}
+# kdb eval {UPDATE lkarma SET karma=karma+1, modtime=$kutime WHERE modtime < $zzztime AND karma < -960 AND locked='N'}
+#
+# kdb close
+}
+
+proc kfreset {} {
+ global kfcount kfflood
+ if {[info exists kfcount]} {
+ unset kfcount
+ }
+ utimer [lindex $kfflood 1] kfreset
+}
+
+if {![string match *kfreset* [utimers]]} {
+ global kfflood
+ utimer [lindex $kfflood 1] kfreset
+}
+set kfflood [split $kfflood :]
+
+
+putlog "LilyKarma $karma(version) loaded."
diff --git a/nitterhealth.tcl b/nitterhealth.tcl
new file mode 100644
index 0000000..31ad0c1
--- /dev/null
+++ b/nitterhealth.tcl
@@ -0,0 +1,42 @@
+# Ensure the http package is available
+package require http
+package require tls
+package require json
+package require Tcl 8.6
+
+bind pub - "!nitterstatus" nitterStatus
+
+proc nitterStatus {nick host handle chan text} {
+ # URL from where to fetch the JSON data
+ set url "https://nitter.vloup.ch/.health"
+
+ # Performing the HTTP GET request
+ set data [exec curl -sS $url]
+ #set data [http::data $response]
+ #http::cleanup $response
+
+ # Assuming the response data is stored in `data` variable and is the JSON string
+ set jsonData [json::json2dict $data]
+
+ # Continue as before
+ set total [dict get $jsonData accounts total]
+ set limited [dict get $jsonData accounts limited]
+ set oldest [dict get $jsonData accounts oldest]
+ set average [dict get $jsonData accounts average]
+ set newest [dict get $jsonData accounts newest]
+
+ # Function to convert date-time from local to UTC
+ proc convertToUTC {dateTime} {
+ set utcTime [clock scan $dateTime -format "%Y-%m-%dT%H:%M:%S%z"]
+ set utcTime [clock format $utcTime -format "%Y-%m-%dT%H:%M:%SZ" -gmt 1]
+ return $utcTime
+ }
+
+ # Converting dates to UTC
+ set oldestUTC [convertToUTC $oldest]
+ set averageUTC [convertToUTC $average]
+ set newestUTC [convertToUTC $newest]
+
+ # Creating the desired output string
+ putserv "PRIVMSG $chan :\[AT/nitter/status\] accounts remaining: $total, limited accounts $limited, oldest: $oldestUTC, average: $averageUTC, newest: $newestUTC"
+}
diff --git a/notes2.tcl b/notes2.tcl
new file mode 100644
index 0000000..115985f
--- /dev/null
+++ b/notes2.tcl
@@ -0,0 +1,218 @@
+#
+# notes2.tcl - v2.1.2 - released by MHT <mht@mygale.org>
+# - a bind apart script from #TSF
+# - for eggdrop 1.3.15+
+#
+####
+#
+# history:
+# --------
+# 2.0.0 - first release for 1.3.14+mht series
+# (get notesat2.tcl for 1.1.5 series)
+#
+# 2.0.2 - Message bug corrected: "erased <m> notes; <n> left." is better.
+# - Corrected weird switch tcl syntax, bug found by Islandic.
+# It's so different from C (I hate tcl!).
+# - Deactivated message "I don't know you", boring me !
+# - No more logs for notes-indexing on join :-)
+#
+# 2.0.3 - Corrected invalid idx bug, if user quits before receiving
+# his notes check.
+#
+# 2.1.0 - Improved protocol to avoid idx mistake for multiple connected users.
+# Backward compatibility is kept, but price is that idx mistake occurs
+# if a multiple connected user quits before receiving notes check.
+# Generally never happens, except in case of 'Chriphil's syndrome' ;-p
+# - Added missing 'You don't have that many messages.'
+#
+# 2.1.1 - fixed a couple of small bugs pertaining to $nick being used instead of
+# $botnet-nick (found by takeda, fixed by guppy)
+#
+# 2.1.2 - fix matchattr for shared bots (found by maimizuno, fixed by Cizzle)
+#
+####
+# Check your notes on every shared bot of the hub.
+#
+# .notes [bot|all] index
+# .notes [bot|all] read <#|all>
+# .notes [bot|all] erase <#|all>
+#
+# # may be numbers and/or intervals separated by ;
+# ex: .notes erase 2-4;8;16-
+# .notes noBOTy read all
+#
+
+
+########
+unbind dcc - notes *dcc:notes
+bind dcc - notes *dcc:notes2
+bind chon - * *chon:notes2
+bind bot - notes2: *bot:notes2
+bind bot - notes2reply: *bot:notes2reply
+
+########
+proc n2_notesindex {bot handle idx} {
+ global nick botnet-nick
+ switch "([notes $handle])" {
+ "(-2)" { putbot $bot "notes2reply: $handle Notefile failure. $idx" }
+ #"-1" { putbot $bot "notes2reply: $handle I don't know you. $idx" }
+ "(-1)" { return 0 }
+ "(0)" { putbot $bot "notes2reply: $handle You have no messages. $idx" }
+ default {
+ putbot $bot "notes2reply: $handle ### You have the following notes waiting: $idx"
+ set index 0
+ foreach note [notes $handle "-"] {
+ if {($note != 0)} {
+ incr index
+ set sender [lindex $note 0]
+ set date [strftime "%b %d %H:%M" [lindex $note 1]]
+ putbot $bot "notes2reply: $handle %$index. $sender ($date) $idx"
+ }
+ }
+ putbot $bot "notes2reply: $handle ### Use '.notes ${botnet-nick} read' to read them. $idx"
+ }
+ }
+ return 1
+}
+
+########
+proc n2_notesread {bot handle idx numlist} {
+ if {($numlist == "")} { set numlist "-" }
+ switch "([notes $handle])" {
+ "(-2)" { putbot $bot "notes2reply: $handle Notefile failure. $idx" }
+ #"(-1)" { putbot $bot "notes2reply: $handle I don't know you. $idx" }
+ "(-1)" { return 0 }
+ "(0)" { putbot $bot "notes2reply: $handle You have no messages. $idx" }
+ default {
+ set count 0
+ set list [listnotes $handle $numlist]
+ foreach note [notes $handle $numlist] {
+ if {($note != 0)} {
+ set index [lindex $list $count]
+ set sender [lindex $note 0]
+ set date [strftime "%b %d %H:%M" [lindex $note 1]]
+ set msg [lrange $note 2 end]
+ incr count
+ putbot $bot "notes2reply: $handle $index. $sender ($date): $msg $idx"
+ } else {
+ putbot $bot "notes2reply: $handle You don't have that many messages. $idx"
+ }
+ }
+ }
+ }
+ return 1
+}
+
+########
+proc n2_noteserase {bot handle idx numlist} {
+ switch [notes $handle] {
+ "(-2)" { putbot $bot "notes2reply: $handle Notefile failure. $idx" }
+ #"(-1)" { putbot $bot "notes2reply: $handle I don't know you. $idx" }
+ "(-1)" { return 0 }
+ "(0)" { putbot $bot "notes2reply: $handle You have no messages. $idx" }
+ default {
+ set erased [erasenotes $handle $numlist]
+ set remaining [notes $handle]
+ if {($remaining == 0) && ($erased == 0)} {
+ putbot $bot "notes2reply: $handle You have no messages. $idx"
+ } elseif {($remaining == 0)} {
+ putbot $bot "notes2reply: $handle Erased all notes. $idx"
+ } elseif {($erased == 0)} {
+ putbot $bot "notes2reply: $handle You don't have that many messages. $idx"
+ } elseif {($erased == 1)} {
+ putbot $bot "notes2reply: $handle Erased 1 note, $remaining left. $idx"
+ } else {
+ putbot $bot "notes2reply: $handle Erased $erased notes, $remaining left. $idx"
+ }
+ }
+ }
+ return 1
+}
+
+########
+proc *bot:notes2 {handle idx arg} {
+ if {(![matchattr $handle ||s])} {
+ return
+ }
+ set nick [lindex $arg 0]
+ set cmd [lindex $arg 1]
+ set num [lindex $arg 2]
+ set idx [lindex $arg 3]
+ if {($num == "") || ($num == "all")} { set num "-" }
+ switch $cmd {
+ "silentindex" { set ret 0; n2_notesindex $handle $nick $idx }
+ "index" { set ret [n2_notesindex $handle $nick $idx] }
+ "read" { set ret [n2_notesread $handle $nick $idx $num] }
+ "erase" { set ret [n2_noteserase $handle $nick $idx $num] }
+ default { set ret 0 }
+ }
+ if {($num == "-")} { set num "" }
+ if {($ret == 1)} { putcmdlog "#$nick@$handle# notes $cmd $num" }
+}
+
+########
+proc *bot:notes2reply {handle idx arg} {
+ # verify that idx is valid (older scripts do not provide idx)
+ set idx [lindex $arg end]
+ if {([valididx $idx]) && ([idx2hand $idx] == [lindex $arg 0])} {
+ set reply [lrange $arg 1 [expr [llength $arg]-2]]
+ } else {
+ set idx [hand2idx [lindex $arg 0]]
+ set reply [lrange $arg 1 end]
+ }
+ if {($idx == -1)} { return }
+ if {([string range $reply 0 0] == "%")} {
+ set reply " [string range $reply 1 end]"
+ }
+ putidx $idx "($handle) $reply"
+}
+
+########
+proc *chon:notes2 {handle idx} {
+ putallbots "notes2: $handle silentindex $idx"
+ return 0
+}
+
+########
+proc *dcc:notes2 {handle idx arg} {
+ global nick botnet-nick
+ if {$arg == ""} {
+ putidx $idx "Usage: notes \[bot|all\] index"
+ putidx $idx " notes \[bot|all\] read <#|all>"
+ putidx $idx " notes \[bot|all\] erase <#|all>"
+ putidx $idx " # may be numbers and/or intervals separated by ;"
+ putidx $idx " ex: notes erase 2-4;8;16-"
+ putidx $idx " notes ${botnet-nick} read all"
+ } else {
+ set bot [string tolower [lindex $arg 0]]
+ set cmd [string tolower [lindex $arg 1]]
+ set numlog [string tolower [lindex $arg 2]]
+ set num $numlog
+ if {($num == "")} { set num "-" }
+ if {($bot != "all") && ([lsearch [string tolower [bots]] $bot] < 0)} {
+ if {($cmd != "index") && ($cmd != "read") && ($cmd != "erase")} {
+ if {($bot == [string tolower $nick])} {
+ return [*dcc:notes $handle $idx [lrange $arg 1 end]]
+ } else {
+ return [*dcc:notes $handle $idx $arg]
+ }
+ } else {
+ putidx $idx "I don't know any bot by that name."
+ return 0
+ }
+ } elseif {($cmd != "index") && ($cmd != "read") && ($cmd != "erase")} {
+ putdcc $idx "Function must be one of INDEX, READ, or ERASE."
+ } elseif {$bot == "all"} {
+ #*dcc:notes $handle $idx [lrange $arg 1 end]
+ putallbots "notes2: $handle $cmd $num $idx"
+ } else {
+ putbot $bot "notes2: $handle $cmd $num $idx"
+ }
+ putcmdlog "#$handle# notes@$bot $cmd $numlog"
+ }
+}
+
+########
+putlog "Notes 2.1.0 - Released by MHT <mht@mygale.org>"
+
+####
diff --git a/pixseen/pixseen-msggen.tcl b/pixseen/pixseen-msggen.tcl
new file mode 100644
index 0000000..5766009
--- /dev/null
+++ b/pixseen/pixseen-msggen.tcl
@@ -0,0 +1,85 @@
+#!/bin/sh
+# the next line restarts using tclsh \
+exec tclsh8.5 "$0" "$@"
+
+encoding system {utf-8}
+
+set fd [open pixseen.tcl r]
+set data [read $fd]
+close $fd
+
+set msgList {}
+foreach {- item} [regexp -all -inline -- {\[mc \{([^\}]+)\}} $data] {
+ if {[lsearch -exact $msgList $item] == -1} {
+ lappend msgList $item
+ }
+}
+
+# generate en.msg
+
+set fd [open pixseen-msgs/en.msg w]
+fconfigure $fd -translation lf -encoding {utf-8}
+puts $fd "# en.msg - automatically generated by pixseen-msggen.tcl on [clock format [clock seconds] -timezone UTC]\n"
+puts $fd "namespace eval ::pixseen \{"
+puts $fd "\tvariable lang \"en\""
+foreach item $msgList {
+ puts $fd "\n\tmcset \$lang \\"
+ puts $fd "\t\t\{$item\} \\"
+ puts $fd "\t\t\{$item\}"
+}
+puts $fd "\n\}"
+close $fd
+
+# generate en_US_bork.msg
+
+proc chef {args} {
+ set subs [list {a([nu])} {u\1}\
+ {A([nu])} {U\1}\
+ {a\Y} e\
+ {A\Y} E\
+ {en\y} ee\
+ {\Yew} oo\
+ {\Ye\y} e-a\
+ {\ye} i\
+ {\yE} I\
+ {\Yf} ff\
+ {\Yir} ur\
+ {(\w+?)i(\w+?)$} {\1ee\2}\
+ {\Yow} oo\
+ {\yo} oo\
+ {\yO} Oo\
+ {^the$} zee\
+ {^The$} Zee\
+ {th\y} t\
+ {\Ytion} shun\
+ {\Yu} {oo}\
+ {\YU} {Oo}\
+ v f\
+ V F\
+ w w\
+ W W\
+ {([a-z])[.]} {\1. Bork Bork Bork!}]
+ foreach word $args {
+ foreach {exp subSpec} $subs {
+ set word [regsub -all -- $exp $word $subSpec]
+# puts "$exp || $subSpec -> $word"
+ }
+ lappend retval $word
+ }
+ return [join $retval]
+}
+
+set fd [open pixseen-msgs/en_us_bork.msg w]
+fconfigure $fd -translation lf -encoding {utf-8}
+puts $fd "# en_US_bork.msg - automatically generated by pixseen-msggen.tcl on [clock format [clock seconds] -timezone UTC]\n"
+puts $fd "namespace eval ::pixseen \{"
+puts $fd "\tvariable lang \"en_us_bork\""
+foreach item $msgList {
+ puts $fd "\n\tmcset \$lang \\"
+ puts $fd "\t\t\{$item\} \\"
+ puts $fd "\t\t\{[chef $item]\}"
+}
+puts $fd "\n\}"
+
+close $fd
+
diff --git a/pixseen/pixseen-msgs/en.msg b/pixseen/pixseen-msgs/en.msg
new file mode 100644
index 0000000..cbd1460
--- /dev/null
+++ b/pixseen/pixseen-msgs/en.msg
@@ -0,0 +1,302 @@
+# en.msg - automatically generated by pixseen-msggen.tcl on Wed Mar 31 12:43:08 UTC 2010
+
+namespace eval ::pixseen {
+ variable lang "en"
+
+ mcset $lang \
+ {years} \
+ {years}
+
+ mcset $lang \
+ {year} \
+ {year}
+
+ mcset $lang \
+ {months} \
+ {months}
+
+ mcset $lang \
+ {month} \
+ {month}
+
+ mcset $lang \
+ {weeks} \
+ {weeks}
+
+ mcset $lang \
+ {week} \
+ {week}
+
+ mcset $lang \
+ {days} \
+ {days}
+
+ mcset $lang \
+ {day} \
+ {day}
+
+ mcset $lang \
+ {hours} \
+ {hours}
+
+ mcset $lang \
+ {hour} \
+ {hour}
+
+ mcset $lang \
+ {minutes} \
+ {minutes}
+
+ mcset $lang \
+ {minute} \
+ {minute}
+
+ mcset $lang \
+ {seconds} \
+ {seconds}
+
+ mcset $lang \
+ {second} \
+ {second}
+
+ mcset $lang \
+ {%1$s error; %2$s was unable to extract uhost. PLEASE REPORT THIS BUG!} \
+ {%1$s error; %2$s was unable to extract uhost. PLEASE REPORT THIS BUG!}
+
+ mcset $lang \
+ {%1$s was last seen %2$s ago.} \
+ {%1$s was last seen %2$s ago.}
+
+ mcset $lang \
+ {%1$s (%2$s) was last seen parting a channel %3$s ago.} \
+ {%1$s (%2$s) was last seen parting a channel %3$s ago.}
+
+ mcset $lang \
+ {%1$s (%2$s) was last seen parting %3$s %4$s ago.} \
+ {%1$s (%2$s) was last seen parting %3$s %4$s ago.}
+
+ mcset $lang \
+ {%1$s (%2$s) was last seen parting a channel %3$s ago, stating "%4$s"} \
+ {%1$s (%2$s) was last seen parting a channel %3$s ago, stating "%4$s"}
+
+ mcset $lang \
+ {%1$s (%2$s) was last seen parting %3$s %4$s ago, stating "%5$s"} \
+ {%1$s (%2$s) was last seen parting %3$s %4$s ago, stating "%5$s"}
+
+ mcset $lang \
+ {%1$s (%2$s) was last seen joining a channel %3$s ago.} \
+ {%1$s (%2$s) was last seen joining a channel %3$s ago.}
+
+ mcset $lang \
+ {%1$s (%2$s) was last seen joining %3$s %4$s ago. %1$s is still on %3$s.} \
+ {%1$s (%2$s) was last seen joining %3$s %4$s ago. %1$s is still on %3$s.}
+
+ mcset $lang \
+ {%1$s (%2$s) was last seen joining %3$s %4$s ago. I don't see %1$s on %3$s now, though.} \
+ {%1$s (%2$s) was last seen joining %3$s %4$s ago. I don't see %1$s on %3$s now, though.}
+
+ mcset $lang \
+ {%1$s (%2$s) was last seen changing nicks to %4$s on a channel %3$s ago.} \
+ {%1$s (%2$s) was last seen changing nicks to %4$s on a channel %3$s ago.}
+
+ mcset $lang \
+ {%1$s (%2$s) was last seen changing nicks to %5$s on %3$s %4$s ago.} \
+ {%1$s (%2$s) was last seen changing nicks to %5$s on %3$s %4$s ago.}
+
+ mcset $lang \
+ {%1$s (%2$s) was last seen changing nicks from %4$s on a channel %3$s ago.} \
+ {%1$s (%2$s) was last seen changing nicks from %4$s on a channel %3$s ago.}
+
+ mcset $lang \
+ {%1$s (%2$s) was last seen changing nicks from %5$s on %3$s %4$s ago. %1$s is still on %3$s.} \
+ {%1$s (%2$s) was last seen changing nicks from %5$s on %3$s %4$s ago. %1$s is still on %3$s.}
+
+ mcset $lang \
+ {%1$s (%2$s) was last seen changing nicks from %5$s on %3$s %4$s ago. I don't see %1$s on %3$s now, though.} \
+ {%1$s (%2$s) was last seen changing nicks from %5$s on %3$s %4$s ago. I don't see %1$s on %3$s now, though.}
+
+ mcset $lang \
+ {%1$s (%2$s) was last seen quitting from a channel %3$s ago.} \
+ {%1$s (%2$s) was last seen quitting from a channel %3$s ago.}
+
+ mcset $lang \
+ {%1$s (%2$s) was last seen quitting from %3$s %4$s ago.} \
+ {%1$s (%2$s) was last seen quitting from %3$s %4$s ago.}
+
+ mcset $lang \
+ {%1$s (%2$s) was last seen quitting from %3$s %4$s ago, stating "%5$s"} \
+ {%1$s (%2$s) was last seen quitting from %3$s %4$s ago, stating "%5$s"}
+
+ mcset $lang \
+ {%1$s (%2$s) was last seen parting a channel due to a netsplit %3$s ago.} \
+ {%1$s (%2$s) was last seen parting a channel due to a netsplit %3$s ago.}
+
+ mcset $lang \
+ {%1$s (%2$s) was last seen parting %3$s due to a netsplit %4$s ago.} \
+ {%1$s (%2$s) was last seen parting %3$s due to a netsplit %4$s ago.}
+
+ mcset $lang \
+ {%1$s (%2$s) was last seen rejoining a channel from a netsplit %3$s ago.} \
+ {%1$s (%2$s) was last seen rejoining a channel from a netsplit %3$s ago.}
+
+ mcset $lang \
+ {%1$s (%2$s) was last seen rejoining %3$s from a netsplit %4$s ago. %1$s is still on %3$s.} \
+ {%1$s (%2$s) was last seen rejoining %3$s from a netsplit %4$s ago. %1$s is still on %3$s.}
+
+ mcset $lang \
+ {%1$s (%2$s) was last seen rejoining %3$s from a netsplit %4$s ago. I don't see %1$s on %3$s now, though.} \
+ {%1$s (%2$s) was last seen rejoining %3$s from a netsplit %4$s ago. I don't see %1$s on %3$s now, though.}
+
+ mcset $lang \
+ {%1$s (%2$s) was last seen kicked from a channel %3$s ago.} \
+ {%1$s (%2$s) was last seen kicked from a channel %3$s ago.}
+
+ mcset $lang \
+ {%1$s (%2$s) was last seen kicked from %3$s by %4$s %5$s ago, with the reason "%6$s"} \
+ {%1$s (%2$s) was last seen kicked from %3$s by %4$s %5$s ago, with the reason "%6$s"}
+
+ mcset $lang \
+ {%1$s (%2$s) was last seen entering the partyline %3$s ago. %1$s is on the partyline right now.} \
+ {%1$s (%2$s) was last seen entering the partyline %3$s ago. %1$s is on the partyline right now.}
+
+ mcset $lang \
+ {%1$s (%2$s) was last seen entering the partyline %3$s ago. I don't see %1$s on the partyline now, though.} \
+ {%1$s (%2$s) was last seen entering the partyline %3$s ago. I don't see %1$s on the partyline now, though.}
+
+ mcset $lang \
+ {%1$s (%2$s) was last seen leaving the partyline %3$s ago. %1$s is on the partyline channel %4$s still.} \
+ {%1$s (%2$s) was last seen leaving the partyline %3$s ago. %1$s is on the partyline channel %4$s still.}
+
+ mcset $lang \
+ {%1$s (%2$s) was last seen leaving the partyline %3$s ago.} \
+ {%1$s (%2$s) was last seen leaving the partyline %3$s ago.}
+
+ mcset $lang \
+ {%1$s (%2$s) was last seen entering the partyline on %3$s %4$s ago. %1 is on the partyline right now.} \
+ {%1$s (%2$s) was last seen entering the partyline on %3$s %4$s ago. %1 is on the partyline right now.}
+
+ mcset $lang \
+ {%1$s (%2$s) was last seen entering the partyline on %3$s %4$s ago. I don't see %1$s on the partyline now, though.} \
+ {%1$s (%2$s) was last seen entering the partyline on %3$s %4$s ago. I don't see %1$s on the partyline now, though.}
+
+ mcset $lang \
+ {%1$s (%2$s) was last seen leaving the partyline from %3$s %4$s ago. %1$s is on the partyline channel %5$s still.} \
+ {%1$s (%2$s) was last seen leaving the partyline from %3$s %4$s ago. %1$s is on the partyline channel %5$s still.}
+
+ mcset $lang \
+ {%1$s (%2$s) was last seen leaving the partyline from %3$s %4$s ago.} \
+ {%1$s (%2$s) was last seen leaving the partyline from %3$s %4$s ago.}
+
+ mcset $lang \
+ {%1$s was last seen marked as away (%2$s) on the partyline %3$s ago. %1$s is on the partyline right now.} \
+ {%1$s was last seen marked as away (%2$s) on the partyline %3$s ago. %1$s is on the partyline right now.}
+
+ mcset $lang \
+ {%1$s was last seen marked as away (%2$s) on the partyline %3$s ago. I don't see %1$s on the partyline now, though.} \
+ {%1$s was last seen marked as away (%2$s) on the partyline %3$s ago. I don't see %1$s on the partyline now, though.}
+
+ mcset $lang \
+ {%1$s was last seen returning to the partyline %2$s ago. %1$s is on the partyline right now.} \
+ {%1$s was last seen returning to the partyline %2$s ago. %1$s is on the partyline right now.}
+
+ mcset $lang \
+ {%1$s was last seen returning to the partyline %2$s ago. I don't see %1$s on the partyline now, though.} \
+ {%1$s was last seen returning to the partyline %2$s ago. I don't see %1$s on the partyline now, though.}
+
+ mcset $lang \
+ {%1$s error; Unhandled event in %2$s: %3$s} \
+ {%1$s error; Unhandled event in %2$s: %3$s}
+
+ mcset $lang \
+ {I don't remember seeing %s.} \
+ {I don't remember seeing %s.}
+
+ mcset $lang \
+ {%1$s SQL error %2$s; %3$s} \
+ {%1$s SQL error %2$s; %3$s}
+
+ mcset $lang \
+ {SQL error %1$s; %2$s} \
+ {SQL error %1$s; %2$s}
+
+ mcset $lang \
+ {%s: performing database maintenance...} \
+ {%s: performing database maintenance...}
+
+ mcset $lang \
+ {Usage: %s} \
+ {Usage: %s}
+
+ mcset $lang \
+ {%1$s, Usage: %2$s} \
+ {%1$s, Usage: %2$s}
+
+ mcset $lang \
+ {Go look in a mirror.} \
+ {Go look in a mirror.}
+
+ mcset $lang \
+ {%s, go look in a mirror.} \
+ {%s, go look in a mirror.}
+
+ mcset $lang \
+ {You found me!} \
+ {You found me!}
+
+ mcset $lang \
+ {You found me, %s!} \
+ {You found me, %s!}
+
+ mcset $lang \
+ {%s is on the channel right now!} \
+ {%s is on the channel right now!}
+
+ mcset $lang \
+ {%1$s is on the channel right now! %1$s last spoke %2$s ago.} \
+ {%1$s is on the channel right now! %1$s last spoke %2$s ago.}
+
+ mcset $lang \
+ {That is not a valid nickname.} \
+ {That is not a valid nickname.}
+
+ mcset $lang \
+ {%s, that is not a valid nickname.} \
+ {%s, that is not a valid nickname.}
+
+ mcset $lang \
+ {There were no matches to your query.} \
+ {There were no matches to your query.}
+
+ mcset $lang \
+ {Displaying %1$s of %2$s results:} \
+ {Displaying %1$s of %2$s results:}
+
+ mcset $lang \
+ {%s: No existing database found, defining SQL schema.} \
+ {%s: No existing database found, defining SQL schema.}
+
+ mcset $lang \
+ {Fatal Error!} \
+ {Fatal Error!}
+
+ mcset $lang \
+ {%1$s: FATAL ERROR; SQLite database corrupt, exiting.} \
+ {%1$s: FATAL ERROR; SQLite database corrupt, exiting.}
+
+ mcset $lang \
+ {%s: Loaded the seen database.} \
+ {%s: Loaded the seen database.}
+
+ mcset $lang \
+ {%s: Unloaded the seen database.} \
+ {%s: Unloaded the seen database.}
+
+ mcset $lang \
+ {Error: Invalid seen language "%s".} \
+ {Error: Invalid seen language "%s".}
+
+ mcset $lang \
+ {Loaded %1$s v%2$s by %3$s} \
+ {Loaded %1$s v%2$s by %3$s}
+
+}
diff --git a/pixseen/pixseen.tcl b/pixseen/pixseen.tcl
new file mode 100644
index 0000000..11fbed1
--- /dev/null
+++ b/pixseen/pixseen.tcl
@@ -0,0 +1,1339 @@
+# pixseen.tcl --
+#
+# SQLite powered seen script. Keeps track of everyone, based on nickname.
+#
+# Copyright (c) 2010, Rickard Utgren <rutgren@gmail.com>
+#
+# Permission to use, copy, modify, and/or distribute this software for any
+# purpose with or without fee is hereby granted, provided that the above
+# copyright notice and this permission notice appear in all copies.
+#
+# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+#
+# RCS: $Id$
+#
+# v1.1 by Pixelz - unreleased
+# - Fixed a problem with ValidTable always failing on some older SQLite versions
+# - Fixed a problem with the public trigger never showing the syntax help
+# - Minor fixes
+# - Fixed a problem with glob & regex matching where only the oldest matches would ever be returned
+# - Added a setting to change the maximum number of results returned from a query
+# - Fixed a problem with Hand2Uhost never returning anything useful
+#
+# v1.0 by Pixelz - April 5, 2010
+# - Initial release
+
+# ToDo:
+# - add some kind of botnet synching
+# - auto-discover pixseen bots on link
+# - auto-assign a primary bot for each channel that will answer public requests
+# * Possible different routes for inter-bot information sharing:
+# * "Synch everything" route:
+# - synch databases on link
+# - keep synching across the net whenever there's a change.
+# - probably do this blindly, to account for IRCDs that doesn't show all joins/parts (ircu)
+# - perhaps add some logic to this, so bots dont push info to other bots that are in the same channel (and would see it anyway), at least on non-ircu, non-chanmode-D channels
+# * "Ask botnet" route:
+# - Don't synch databases
+# - Ask other bots for information on each seen request to the bot
+# * Considerations:
+# * For both of these methods, the clock probably has to fairly synched up, particularly for the "synch everything" route
+# - A simple solution: check TS delta on link, and complain loudly if it's too high, or compensate for it somehow
+# - Add IRCU support ("hidden" users in +D channels) -> names -d #chan (who #chan cd)
+# - OR just tell quakenet users to use thommey's +D patch
+# - add option to track every channel separately
+# - track channel idle time, this would probably synergize well with the "track each channel separetly" option
+# - find out if it's a good idea to [catch] each sql query (catch is slow, is there a better alternative?)
+# - test the idx lookup stuff more, I suspect there's a bug in it somewhere
+# - perhaps get rid of the daily unused-channels cleanup, and do it the same way as pixinfo.tcl.
+# - Make sure this isn't hugely resource intensive first.
+# - Add a setting to set the default matching type? OR Apply better logic to it? perhaps assume glob if it contains asterisk, Most Users fail at regex anyway...
+
+
+package require Tcl 8.5
+package require msgcat 1.4.2
+package require eggdrop 1.6
+package require sqlite3 3.3.0;# order by desc was added in this version
+
+namespace eval ::pixseen {
+ # path to the database file
+ variable dbfile {scripts/pixseen.db}
+
+ # Output with NOTICE nick (0) or PRIVMSG #chan (1)
+ variable outnotc 1
+
+ # Language
+ variable defaultLang "en"
+
+ # Maximum number of results to display in public
+ variable pubResults 3
+
+ # Maximum number of results to display in private message
+ variable msgResults 5
+
+ # Maximum number of results to display in the partyline
+ variable dccResults 10
+
+ ## end of settings ##
+
+ # list of locales, if you translate the script, add your translation to this list
+ variable locales [list "en"]
+
+ namespace import ::msgcat::*
+ # mcload fails to load _all_ .msg files, so we have to do it manually
+ foreach f [glob -nocomplain -directory [file join [file dirname [info script]] pixseen-msgs] -type {b c f l} *.msg] {
+ source -encoding {utf-8} $f
+ }
+ unset -nocomplain f
+
+ mclocale $defaultLang
+ setudef flag {seen}
+ setudef str {seenlang}
+ variable ::botnick
+ variable ::botnet-nick
+ variable ::nicklen
+ variable seenFlood
+ variable seenver {1.1}
+ variable dbVersion 1
+}
+
+## utility procs
+
+proc ::pixseen::validlang {lang} {
+ variable locales
+ if {[lsearch -exact -nocase $locales $lang] == -1} {
+ return 0
+ } else {
+ return 1
+ }
+}
+
+# msgcat compatible duration proc
+proc ::pixseen::pixduration {seconds} {
+ set map [list \
+ {years} [mc {years}] \
+ {year} [mc {year}] \
+ {months} [mc {months}] \
+ {month} [mc {month}] \
+ {weeks} [mc {weeks}] \
+ {week} [mc {week}] \
+ {days} [mc {days}] \
+ {day} [mc {day}] \
+ {hours} [mc {hours}] \
+ {hour} [mc {hour}] \
+ {minutes} [mc {minutes}] \
+ {minute} [mc {minute}] \
+ {seconds} [mc {seconds}] \
+ {second} [mc {second}] \
+ ]
+ string map $map [duration $seconds]
+}
+
+# takes an idx and returns the user@host associated with it.
+# ONLY to be called from finduhost. DON'T CALL THIS DIRECTLY!
+proc ::pixseen::Idx2Uhost {idx} {
+ # for some mind-boggling reason, eggdrop doesn't provide the uhost for a
+ # lot of the partyline binds, so we extract it from dcclist
+ foreach item [dcclist chat] {
+ lassign $item i - u;
+ if {$idx eq $i} {
+ return $u
+ }
+ }
+ # this should never happen?
+ putlog [mc {%1$s error; %2$s was unable to extract uhost. PLEASE REPORT THIS BUG!} {pixseen.tcl} {::pixseen::Idx2Uhost}]
+ return
+}
+
+# takes a handle and returns the user@host associated with it.
+# ONLY to be called from finduhost. DON'T CALL THIS DIRECTLY!
+proc ::pixseen::Hand2Uhost {botname hand {chan {*}}} {
+ foreach item [whom $chan] {
+ lassign $item Hand Botname Uhost Flags Idle Away
+ if {[string equal -nocase $botname $Botname] && [string equal -nocase $hand $Hand]} {
+ return $Uhost
+ }
+ }
+ # this should never happen?
+ putlog [mc {%1$s error; %2$s was unable to extract uhost. PLEASE REPORT THIS BUG!} {pixseen.tcl} {::pixseen::Hand2Uhost}]
+ return
+}
+
+# figures out the uhost using different methods for different binds
+# returns the uhost found, or "" if it can't find one.
+proc ::pixseen::finduhost {bind args} {
+ # chjn isn't listed because that bind actually calls the proc with the uhost
+ # chon/chof is NOT triggered for remote users
+ # away/chpt (and chjn) IS triggered for remote users
+ switch -exact -- $bind {
+ {chon} - {chof} {
+ lassign $args idx
+ return [Idx2Uhost $idx]
+ }
+ {chpt} {
+ lassign $args idx botname hand chan
+ if {[string equal -nocase $botname ${::botnet-nick}]} {
+ # this is a local user, grab the uhost based on the idx
+ return [Idx2Uhost $idx]
+ } else {
+ # this is a remote user, make an educated guess
+ return [Hand2Uhost $botname $hand $chan]
+ }
+ }
+ {away} {
+ lassign $args idx botname
+ if {[string equal -nocase $botname ${::botnet-nick}]} {
+ # this is a local user, grab the uhost based on the idx
+ return [Idx2Uhost $idx]
+ } else {
+ # this is a remote user. it is NOT POSSIBLE to get their uhost
+ return
+ }
+ }
+ default {
+ return
+ }
+ }
+}
+
+# returns 1 if a module is loaded, 0 if not
+proc ::pixseen::modloaded {module} {
+ if {[lsearch -exact -index 0 [modules] $module] == -1} {
+ return 0
+ } else {
+ return 1
+ }
+}
+
+# returns the name of a partyline channel, or whatever was passed to it
+# if there's no name set
+proc ::pixseen::partychanname {chan} {
+ if {(![modloaded assoc]) || ($chan == 0)} {
+ return $chan
+ } elseif {![catch {set name [assoc $chan]}]} {
+ if {$name ne {}} {
+ return $name
+ } else {
+ return $chan
+ }
+ } else {
+ return $chan
+ }
+}
+
+# returns 1 if the nick is valid on IRC, 0 if not
+proc ::pixseen::validnick {nick} {
+ if {([info exists ::nicklen]) && ($::nicklen > 32)} {
+ set len $::nicklen
+ } else {
+ set len 32
+ }
+ if {[string length $nick] > $len} {
+ return 0
+ # FixMe: make sure these are all of the valid chars
+ } elseif {![regexp -- {^[a-zA-Z\|\[\]`^\{\}][a-zA-Z0-9\-_\|\[\]`^\{\}\\]*$} $nick]} {
+ return 0
+ } else {
+ return 1
+ }
+}
+
+# returns 1 if a channel is set +secret, 0 if not
+proc ::pixseen::issecret {chan} {
+ if {[validchan $chan] && [channel get $chan secret]} {
+ return 1
+ } else {
+ return 0
+ }
+}
+
+proc ::pixseen::handseen {handle} {
+ if {![validuser $handle]} {
+ return
+ } else {
+ lassign [getuser $handle LASTON] timestamp location
+ # the location can be fun things like "linked" or "@botnick" so let's not even go there...
+ return [mc {%1$s was last seen %2$s ago.} $handle [pixduration [expr {[clock seconds] - $timestamp}]]]
+ }
+}
+
+proc ::pixseen::lastspoke {nick chan} {
+ if {[set idle [getchanidle $nick $chan]] == 0} {
+ return
+ } else {
+ # whose great idea was it to return idle time in minutes? o_O
+ return [pixduration [expr {$idle * 60}]]
+ }
+}
+
+# returns the channel if the user is logged in to the partyline. if not, returns ""
+proc ::pixseen::onpartyline {handle} {
+ foreach item [whom *] {
+ lassign $item nick bot uhost flags idle away chan
+ if {[string equal -nocase $handle $nick]} { return [partychanname $chan] }
+ }
+ return
+}
+
+# checks the flood array and removes old timestamps.
+# will eventually remove itself if we're not being flooded
+proc ::pixseen::RemoveFlood {args} {
+ variable seenFlood
+ if {![array exists seenFlood]} { return }
+ set time [clock seconds]
+ foreach uhost [array names seenFlood] {
+ foreach timestamp $seenFlood($uhost) {
+ # don't append the timestamp if it's older than 60 seconds
+ if {[expr {$time - 60}] <= $timestamp} {
+ lappend stampList $timestamp
+ }
+ }
+ if {[info exists stampList]} {
+ set seenFlood($uhost) $stampList
+ } else {
+ array unset seenFlood $uhost
+ }
+ }
+}
+
+# returns 1 if we're flooded, 0 if not
+proc ::pixseen::checkflood {uhost} {
+ variable seenFlood
+ RemoveFlood
+ # case 1: uhost doesn't exist in the array, we're not being flooded
+ if {![info exists seenFlood($uhost)]} {
+ set seenFlood($uhost) [clock seconds]
+ return 0
+ # case 2: the list for this uhost is full, so we're being flooded with 6 lines over 60 seconds
+ } elseif {[llength $seenFlood($uhost)] >= 6} {
+ set seenFlood($uhost) [concat "[lrange [lsort -integer $seenFlood($uhost)] 1 end] [clock seconds]"]
+ return 1
+ # case 3: the list for this uhost isn't full, we're not being flooded
+ } else {
+ lappend seenFlood($uhost) [clock seconds]
+ return 0
+ }
+}
+
+# Formats seen events for output
+proc ::pixseen::formatevent {event nick uhost time chan reason othernick} {
+ set duration [pixduration [expr {[clock seconds] - $time}]]
+ set timestamp [clock format $time -format "%Y-%m-%dT%H:%M:%SZ" -gmt 1]
+ switch -exact -- $event {
+ {0} {;# part
+ if {$reason eq {}} {
+ if {[issecret $chan]} {
+ return [mc {%1$s (%2$s) was last seen parting a channel %3$s ago (%4$s)} $nick $uhost $duration $timestamp]
+ } else {
+ return [mc {%1$s (%2$s) was last seen parting %3$s %4$s ago (%4$s)} $nick $uhost $chan $duration $timestamp]
+ }
+ } else {
+ if {[issecret $chan]} {
+ return [mc {%1$s (%2$s) was last seen parting a channel %3$s ago (%4$s), stating "%5$s"} $nick $uhost $duration $timestamp $reason]
+ } else {
+ return [mc {%1$s (%2$s) was last seen parting %3$s %4$s ago (%4$s), stating "%5$s"} $nick $uhost $chan $duration $timestamp $reason]
+ }
+ }
+ }
+ {1} {;# join
+ if {[issecret $chan]} {
+ return [mc {%1$s (%2$s) was last seen joining a channel %3$s ago (%4$s)} $nick $uhost $duration $timestamp]
+ } else {
+ if {[onchan $nick $chan]} {
+ return [mc {%1$s (%2$s) was last seen joining %3$s %4$s ago (%5$s). %1$s is still on %3$s.} $nick $uhost $chan $duration $timestamp]
+ } else {
+ return [mc {%1$s (%2$s) was last seen joining %3$s %4$s ago (%5$s). I don't see %1$s on %3$s now, though.} $nick $uhost $chan $duration $timestamp]
+ }
+ }
+ }
+ {2} {;# nick (old)
+ if {[issecret $chan]} {
+ return [mc {%1$s (%2$s) was last seen changing nicks to %5$s on a channel %3$s ago (%4$s).} $nick $uhost $duration $timestamp $othernick]
+ } else {
+ return [mc {%1$s (%2$s) was last seen changing nicks to %6$s on %3$s %4$s ago (%5%s).} $nick $uhost $chan $duration $timestamp $othernick]
+ }
+ }
+ {3} {;# nick (new)
+ if {[issecret $chan]} {
+ return [mc {%1$s (%2$s) was last seen changing nicks from %5$s on a channel %3$s ago (%4$s)} $nick $uhost $duration $timestamp $othernick]
+ } else {
+ if {[onchan $nick $chan]} {
+ return [mc {%1$s (%2$s) was last seen changing nicks from %6$s on %3$s %4$s ago (%5$s). %1$s is still on %3$s.} $nick $uhost $chan $duration $timestamp $othernick]
+ } else {
+ return [mc {%1$s (%2$s) was last seen changing nicks from %6$s on %3$s %4$s ago (%5$s). I don't see %1$s on %3$s now, though.} $nick $uhost $chan $duration $timestamp $othernick]
+ }
+ }
+ }
+ {4} {;# sign (quit)
+ if {[issecret $chan]} {
+ return [mc {%1$s (%2$s) was last seen quitting from a channel %3$s ago (%4$s)} $nick $uhost $duration $timestamp]
+ } elseif {$reason eq {}} {
+ return [mc {%1$s (%2$s) was last seen quitting from %3$s %4$s ago (%5$s)} $nick $uhost $chan $duration $timestamp]
+ } else {
+ return [mc {%1$s (%2$s) was last seen quitting from %3$s %4$s ago (%5$s), stating "%6$s"} $nick $uhost $chan $duration $timestamp $reason]
+ }
+ }
+ {5} {;# splt (netsplit)
+ if {[issecret $chan]} {
+ return [mc {%1$s (%2$s) was last seen parting a channel due to a netsplit %3$s ago.} $nick $uhost $duration]
+ } else {
+ return [mc {%1$s (%2$s) was last seen parting %3$s due to a netsplit %4$s ago.} $nick $uhost $chan $duration]
+ }
+ }
+ {6} {;# rejn (netsplit rejoin)
+ if {[issecret $chan]} {
+ return [mc {%1$s (%2$s) was last seen rejoining a channel from a netsplit %3$s ago (%4$s)} $nick $uhost $duration $timestamp]
+ } else {
+ if {[onchan $nick $chan]} {
+ return [mc {%1$s (%2$s) was last seen rejoining %3$s from a netsplit %4$s ago (%5$s). %1$s is still on %3$s.} $nick $uhost $chan $duration $timestamp]
+ } else {
+ return [mc {%1$s (%2$s) was last seen rejoining %3$s from a netsplit %4$s ago (%5$s). I don't see %1$s on %3$s now, though.} $nick $uhost $chan $duration $timestamp]
+ }
+ }
+ }
+ {7} {;# kick
+ if {[issecret $chan]} {
+ return [mc {%1$s (%2$s) was last seen kicked from a channel %3$s ago (%4$s)} $nick $uhost $duration $timestamp]
+ } else {
+ return [mc {%1$s (%2$s) was last seen kicked from %3$s by %4$s %5$s ago (%6$s), with the reason "%7$s"} $nick $uhost $chan $othernick $duration $timestamp $reason]
+ }
+ }
+ {8} {;# chon (enter partyline)
+ if {[onpartyline $nick] ne {}} {
+ return [mc {%1$s (%2$s) was last seen entering the partyline %3$s ago (%4$s). %1$s is on the partyline right now.} $nick $uhost $duration $timestamp]
+ } else {
+ return [mc {%1$s (%2$s) was last seen entering the partyline %3$s ago (%4$s). I don't see %1$s on the partyline now, though.} $nick $uhost $duration $timestamp]
+ }
+ }
+ {9} {;# chof (leaves partyline)
+ if {[set pchan [onpartyline $nick]] ne {}} {
+ return [mc {%1$s (%2$s) was last seen leaving the partyline %3$s ago (%4$s). %1$s is on the partyline channel %4$s still.} $nick $uhost $duration $pchan $timestamp]
+ } else {
+ return [mc {%1$s (%2$s) was last seen leaving the partyline %3$s ago (%4$s)} $nick $uhost $duration $timestamp]
+ }
+ }
+ {10} {;# chjn (joins partyline channel)
+ if {[onpartyline $nick] ne {}} {
+ return [mc {%1$s (%2$s) was last seen entering the partyline on %3$s %4$s ago (%5$s). %1 is on the partyline right now.} $nick $uhost [partychanname $chan] $duration $timestamp]
+ } else {
+ return [mc {%1$s (%2$s) was last seen entering the partyline on %3$s %4$s ago (%5$s). I don't see %1$s on the partyline now, though.} $nick $uhost [partychanname $chan] $duration $timestamp]
+ }
+ }
+ {11} {;# chpt (parts partyline channel)
+ if {[set pchan [onpartyline $nick]] ne {}} {
+ return [mc {%1$s (%2$s) was last seen leaving the partyline from %3$s %4$s ago (%5$s). %1$s is on the partyline channel %6$s still.} $nick $uhost [partychanname $chan] $duration $timestamp $pchan]
+ } else {
+ return [mc {%1$s (%2$s) was last seen leaving the partyline from %3$s %4$s ago (%5$s)} $nick $uhost [partychanname $chan] $duration $timestamp]
+ }
+ }
+ {12} {;# away (partyline away)
+ if {[onpartyline $nick] ne {}} {
+ return [mc {%1$s was last seen marked as away (%2$s) on the partyline %3$s ago (%4$s). %1$s is on the partyline right now.} $nick $reason $duration $timestamp]
+ } else {
+ return [mc {%1$s was last seen marked as away (%2$s) on the partyline %3$s ago (%4$s). I don't see %1$s on the partyline now, though.} $nick $reason $duration $timestamp]
+ }
+ }
+ {13} {;# back (partyline back from away)
+ if {[onpartyline $nick] ne {}} {
+ return [mc {%1$s was last seen returning to the partyline %2$s ago (%3$s). %1$s is on the partyline right now.} $nick $duration $timestamp]
+ } else {
+ return [mc {%1$s was last seen returning to the partyline %2$s ago (%3$s). I don't see %1$s on the partyline now, though.} $nick $duration $timestamp]
+ }
+ }
+ {14} {;# chmsg (messaged channel)
+ if {[issecret $chan]} {
+ return [mc {%1$s (%2$s) was last seen talking on a channel %3$s ago (%4$s)} $nick $uhost $duration $timestamp]
+ } else {
+ if {[onchan $nick $chan]} {
+ return [mc {%1$s (%2$s) was last seen messaging on %3$s %4$s ago (%5$s). %1$s is still on %3$s.} $nick $uhost $chan $duration $timestamp]
+ } else {
+ return [mc {%1$s (%2$s) was last seen messaging on %3$s %4$s ago (%5$s). I don't see %1$s on %3$s now, though.} $nick $uhost $chan $duration $timestamp]
+ }
+ }
+ }
+ default {
+ putlog [mc {%1$s error; Unhandled event in %2$s: %3$s} {pixseen.tcl} {formatevent} $event]
+ return [mc {I don't remember seeing %s.} $nick]
+ }
+ }
+}
+
+## SQLite functions
+
+# This is the SQLite collation function, if it's changed, the index has to be rebuilt with REINDEX or it'll cause Weird Behaviour
+proc ::pixseen::rfccomp {a b} {
+ string compare [string map [list \{ \[ \} \] ~ ^ | \\] [string toupper $a]] [string map [list \{ \[ \} \] ~ ^ | \\] [string toupper $b]]
+}
+
+proc ::pixseen::chan2id {chan} {
+ if {[catch {seendb eval { INSERT OR IGNORE INTO chanTb VALUES(NULL, $chan); }} error]} {
+ putlog [mc {%1$s SQL error %2$s; %3$s} {pixseen.tcl} [seendb errorcode] $error]
+ }
+ if {[catch {set retval [seendb eval { SELECT chanid FROM chanTb WHERE chan=$chan LIMIT 1 }]} error]} {
+ putlog [mc {%1$s SQL error %2$s; %3$s} {pixseen.tcl} [seendb errorcode] $error]
+ return -code error [mc {SQL error %1$s; %2$s} [seendb errorcode] $error]
+ } else {
+ return $retval
+ }
+}
+
+# SQLite regexp function, squelches regex errors and turn on nocase
+proc ::pixseen::pixregexp {args} {
+ if {[catch {set result [regexp -nocase -- {*}$args]}]} {
+ return 0
+ } else {
+ return $result
+ }
+}
+
+##
+
+proc ::pixseen::dbAdd {nick event timestamp uhost args} {
+ switch -exact -- $event {
+ {part} {;# 0
+ lassign $args chan reason
+ if {[catch {seendb eval { INSERT OR REPLACE INTO seenTb VALUES(0, $nick, $uhost, $timestamp, chan2id($chan), $reason, NULL) }} error]} {
+ putlog [mc {%1$s SQL error %2$s; %3$s} {pixseen.tcl} [seendb errorcode] $error]
+ }
+ }
+ {join} {;# 1
+ lassign $args chan
+ if {[catch {seendb eval { INSERT OR REPLACE INTO seenTb VALUES(1, $nick, $uhost, $timestamp, chan2id($chan), NULL, NULL) }} error]} {
+ putlog [mc {%1$s SQL error %2$s; %3$s} {pixseen.tcl} [seendb errorcode] $error]
+ }
+ }
+ {nick} {;# 2 & 3
+ lassign $args chan newnick
+ if {[catch {seendb eval {
+ -- old nick;
+ INSERT OR REPLACE INTO seenTb VALUES(2, $nick, $uhost, $timestamp, chan2id($chan), NULL, $newnick);
+ -- new nick;
+ INSERT OR REPLACE INTO seenTb VALUES(3, $newnick, $uhost, $timestamp, chan2id($chan), NULL, $nick);
+ }} error]} {
+ putlog [mc {%1$s SQL error %2$s; %3$s} {pixseen.tcl} [seendb errorcode] $error]
+ }
+ }
+ {sign} {;# 4 (quit)
+ lassign $args chan reason
+ if {[catch {seendb eval { INSERT OR REPLACE INTO seenTb VALUES(4, $nick, $uhost, $timestamp, chan2id($chan), $reason, NULL) }} error]} {
+ putlog [mc {%1$s SQL error %2$s; %3$s} {pixseen.tcl} [seendb errorcode] $error]
+ }
+ }
+ {splt} {;# 5
+ lassign $args chan
+ if {[catch {seendb eval { INSERT OR REPLACE INTO seenTb VALUES(5, $nick, $uhost, $timestamp, chan2id($chan), NULL, NULL) }} error]} {
+ putlog [mc {%1$s SQL error %2$s; %3$s} {pixseen.tcl} [seendb errorcode] $error]
+ }
+ }
+ {rejn} {;# 6
+ lassign $args chan
+ if {[catch {seendb eval { INSERT OR REPLACE INTO seenTb VALUES(6, $nick, $uhost, $timestamp, chan2id($chan), NULL, NULL) }} error]} {
+ putlog [mc {%1$s SQL error %2$s; %3$s} {pixseen.tcl} [seendb errorcode] $error]
+ }
+ }
+ {kick} {;# 7
+ lassign $args chan reason aggressor
+ if {[catch {seendb eval { INSERT OR REPLACE INTO seenTb VALUES(7, $nick, $uhost, $timestamp, chan2id($chan), $reason, $aggressor) }} error]} {
+ putlog [mc {%1$s SQL error %2$s; %3$s} {pixseen.tcl} [seendb errorcode] $error]
+ }
+ }
+ {chon} {;# 8 (enters partyline)
+ if {[catch {seendb eval { INSERT OR REPLACE INTO seenTb VALUES(8, $nick, $uhost, $timestamp, NULL, NULL, NULL) }} error]} {
+ putlog [mc {%1$s SQL error %2$s; %3$s} {pixseen.tcl} [seendb errorcode] $error]
+ }
+ }
+ {chof} {;# 9 (leaves partyline)
+ if {[catch {seendb eval { INSERT OR REPLACE INTO seenTb VALUES(9, $nick, $uhost, $timestamp, NULL, NULL, NULL) }} error]} {
+ putlog [mc {%1$s SQL error %2$s; %3$s} {pixseen.tcl} [seendb errorcode] $error]
+ }
+ }
+ {chjn} {;# 10 (joins partyline channel)
+ lassign $args chan botname
+ if {[catch {seendb eval { INSERT OR REPLACE INTO seenTb VALUES(10, $nick, $uhost, $timestamp, chan2id($chan), NULL, $botname) }} error]} {
+ putlog [mc {%1$s SQL error %2$s; %3$s} {pixseen.tcl} [seendb errorcode] $error]
+ }
+ }
+ {chpt} {;# 11 (parts partyline channel)
+ lassign $args chan botname
+ if {[catch {seendb eval { INSERT OR REPLACE INTO seenTb VALUES(11, $nick, $uhost, $timestamp, chan2id($chan), NULL, $botname) }} error]} {
+ putlog [mc {%1$s SQL error %2$s; %3$s} {pixseen.tcl} [seendb errorcode] $error]
+ }
+ }
+ {away} {;# 12 (partyline away)
+ lassign $args botname reason
+ if {[catch {seendb eval { INSERT OR REPLACE INTO seenTb VALUES(12, $nick, $uhost, $timestamp, NULL, $reason, $botname) }} error]} {
+ putlog [mc {%1$s SQL error %2$s; %3$s} {pixseen.tcl} [seendb errorcode] $error]
+ }
+ }
+ {back} {;# 13 (partyline back from away)
+ lassign $args botname
+ if {[catch {seendb eval { INSERT OR REPLACE INTO seenTb VALUES(13, $nick, $uhost, $timestamp, NULL, NULL, $botname) }} error]} {
+ putlog [mc {%1$s SQL error %2$s; %3$s} {pixseen.tcl} [seendb errorcode] $error]
+ }
+ }
+ {chmsg} {;# 14
+ lassign $args chan
+ if {[catch {seendb eval { INSERT OR REPLACE INTO seenTb VALUES(14, $nick, $uhost, $timestamp, chan2id($chan), NULL, NULL) }} error]} {
+ putlog [mc {%1$s SQL error %2$s; %3$s} {pixseen.tcl} [seendb errorcode] $error]
+ }
+ }
+ default {
+ putlog [mc {%1$s error; Unhandled event in %2$s: %3$s} {pixseen.tcl} {dbAdd} $event]
+ return
+ }
+ }
+ return
+}
+
+## event binds
+
+proc ::pixseen::PART {nick uhost hand chan msg} {
+ dbAdd $nick {part} [clock seconds] $uhost $chan $msg
+ return
+}
+
+proc ::pixseen::JOIN {nick uhost hand chan} {
+ dbAdd $nick {join} [clock seconds] $uhost $chan
+ return
+}
+
+proc ::pixseen::NICK {nick uhost hand chan newnick} {
+ dbAdd $nick {nick} [clock seconds] $uhost $chan $newnick
+ return
+}
+
+proc ::pixseen::SIGN {nick uhost hand chan reason} {
+ dbAdd $nick {sign} [clock seconds] $uhost $chan $reason
+ return
+}
+
+proc ::pixseen::SPLT {nick uhost hand chan} {
+ dbAdd $nick {splt} [clock seconds] $uhost $chan
+ return
+}
+
+proc ::pixseen::REJN {nick uhost hand chan} {
+ dbAdd $nick {rejn} [clock seconds] $uhost $chan
+ return
+}
+
+proc ::pixseen::KICK {nick uhost hand chan target reason} {
+ dbAdd $target {kick} [clock seconds] [getchanhost $target $chan] $chan $reason $nick
+ return
+}
+
+proc ::pixseen::CHON {hand idx} {
+ if {[set uhost [finduhost {chon} $idx]] ne {}} {
+ dbAdd $hand {chon} [clock seconds] $uhost
+ }
+ return
+}
+
+proc ::pixseen::CHOF {hand idx} {
+ if {[set uhost [finduhost {chof} $idx]] ne {}} {
+ dbAdd $hand {chon} [clock seconds] $uhost
+ }
+ return
+}
+
+proc ::pixseen::CHJN {botname hand chan flag idx uhost} {
+ dbAdd $hand {chon} [clock seconds] $uhost $chan $botname
+ return
+}
+
+proc ::pixseen::CHPT {botname hand idx chan} {
+ if {[set uhost [finduhost {chpt} $idx $botname $hand $chan]] ne {}} {
+ dbAdd $hand {chon} [clock seconds] $uhost $chan $botname
+ }
+ return
+}
+
+proc ::pixseen::AWAY {botname idx text} {
+ if {[string equal -nocase $botname ${::botnet-nick}]} {
+ # this is a local away
+ if {$text ne {}} {
+ # user is away
+ if {[set uhost [finduhost {away} $idx $botname]] ne {}} {
+ dbAdd [idx2hand $idx] {away} [clock seconds] $uhost $botname $text
+ }
+ } else {
+ # user has returned
+ if {[set uhost [idx2uhost {away} $idx $botname]] ne {}} {
+ dbAdd [idx2hand $idx] {back} [clock seconds] $uhost $botname
+ }
+ }
+ } else {
+ # this is a remote away. It's not possible to figure out the handle,
+ # let alone the uhost, so we bail out.
+ return
+ }
+ return
+}
+
+
+proc ::pixseen::CHMSG {nick uhost hand chan text} {
+ dbAdd $nick {chmsg} [clock seconds] $uhost $chan
+ return
+}
+
+##
+
+# returns a list of: id event nick uhost time chan reason othernick
+proc ::pixseen::dbGetNick {target} {
+ if {[catch {set result [seendb eval { SELECT event, nick, uhost, time, chanTb.chan, reason, othernick FROM seenTb, chanTb ON seenTb.chanid = chanTb.chanid WHERE nick=$target LIMIT 1 }]} error]} {
+ putlog [mc {%1$s SQL error %2$s; %3$s} {pixseen.tcl} [seendb errorcode] $error]
+ return
+ }
+ return $result
+}
+
+# returns: a list of nicks matching the pattern
+proc ::pixseen::dbSearchGlob {nick uhost chan} {
+ # transform GLOB syntax into LIKE syntax:
+ set nick [string map [list "\\" "\\\\" "%" "\%" "_" "\_" "*" "%" "?" "_"] $nick]
+ set uhost [string map [list "\\" "\\\\" "%" "\%" "_" "\_" "*" "%" "?" "_"] $uhost]
+ set chan [string map [list "\\" "\\\\" "%" "\%" "_" "\_" "*" "%" "?" "_"] $chan]
+ if {$nick eq {}} { set nick "*" }
+ if {$uhost eq {}} { set uhost "*" }
+ if {[catch { set result [seendb eval { SELECT nick FROM seenTb, chanTb ON seenTb.chanid = chanTb.chanid WHERE nick LIKE $nick ESCAPE '\' AND uhost LIKE $uhost ESCAPE '\' AND chanTb.chan LIKE $chan ESCAPE '\' ORDER BY seenTb.time DESC }] } error]} {
+ putlog [mc {%1$s SQL error %2$s; %3$s} {pixseen.tcl} [seendb errorcode] $error]
+ return
+ } else {
+ return $result
+ }
+}
+
+# returns: a list of nicks matching the pattern
+proc ::pixseen::dbSearchRegex {nick uhost chan} {
+if {[catch { set result [seendb eval { SELECT nick FROM seenTb, chanTb ON seenTb.chanid = chanTb.chanid WHERE nick REGEXP $nick AND uhost REGEXP $uhost AND chanTb.chan REGEXP $chan ORDER BY seenTb.time DESC }] } error]} {
+ putlog [mc {%1$s SQL error %2$s; %3$s} {pixseen.tcl} [seendb errorcode] $error]
+ return
+ } else {
+ return $result
+ }
+}
+
+# cleans out unused channels from the database
+proc ::pixseen::dbCleanup {args} {
+ putlog [mc {%s: performing database maintenance...} {pixseen.tcl}]
+ if {[catch {set idList [seendb eval { SELECT chanid FROM chanTb WHERE chanid NOT IN (SELECT chanid FROM seenTb WHERE chanid = chanTb.chanid) }]} error]} {
+ putlog [mc {%1$s SQL error %2$s; %3$s} {pixseen.tcl} [seendb errorcode] $error]
+ } elseif {$idList ne {}} {
+ foreach id $idList {
+ if {[catch {seendb eval { DELETE FROM chanTb WHERE chanid=$id }} error]} {
+ putlog [mc {%1$s SQL error %2$s; %3$s} {pixseen.tcl} [seendb errorcode] $error]
+ }
+ }
+ }
+ return
+}
+
+# Parses command arguments.
+# Returns: a list of "nick uhost chan mode".
+# Mode 0 = exact matching (the default)
+# Mode 1 = glob matching
+# Mode 2 = regex matching
+proc ::pixseen::ParseArgs {text} {
+ # !seen foobar
+ # !seen foobar.com
+ # !seen #foobar
+ # !seen foobar foobar.com
+ # !seen foobar #foobar
+ # !seen foobar.com #foobar
+ # !seen foobar.com foobar
+ # !seen #foobar foobar
+ # !seen #foobar foobar.com
+ # !seen foobar foobar.com #foobar
+
+ # default to exact mode
+ set mode 0
+
+ # grab the switches
+ set i 0
+ foreach item [set arg [split $text]] {
+ if {$item eq {--}} {
+ incr i
+ break
+ } elseif {[string index $item 0] eq {-}} {
+ switch -glob -nocase -- $item {
+ {-e*} { set mode 0 }
+ {-g*} { set mode 1 }
+ {-r*} { set mode 2 }
+ }
+ incr i
+ continue
+ } else {
+ break
+ }
+ }
+
+ if {([set arglen [llength [set arg [lrange $arg $i end]]]] < 1) || ($arglen > 3)} {
+ return
+ } elseif {$arglen == 3} {
+ lassign $arg nick uhost chan
+ } elseif {$mode == 2} {
+ lassign $arg nick uhost chan
+ if {$uhost eq {}} { set uhost {.*} }
+ if {$chan eq {}} { set chan {.*} }
+ } else {
+ set nick {*}
+ set uhost {*}
+ set chan {*}
+
+ set NickDone 0
+ set ChanDone 0
+ set UhostDone 0
+ foreach item $arg {
+ # nick
+ if {!$NickDone && [regexp -- {^[^#&!+.][^.]*$} $item]} {
+ set nick $item
+ set NickDone 1
+ continue
+ # channel
+ } elseif {!$ChanDone && [string match {[#&!+]*} $item]} {
+ set chan $item
+ set ChanDone 1
+ continue
+ # uhost
+ } elseif {!$UhostDone && [string match {*.*} $item]} {
+ set uhost $item
+ set UhostDone 1
+ continue
+ }
+ }
+ }
+ return [list $nick $uhost $chan $mode]
+}
+
+
+# output proc
+proc ::pixseen::putseen {nick chan notcText {msgText {}}} {
+ variable outnotc
+ if {$outnotc == 0} {
+ puthelp "NOTICE $nick :\[seen\] $notcText"
+ } elseif {$msgText != {}} {
+ puthelp "NOTICE $chan :\[seen\] $msgText"
+ } else {
+ puthelp "NOTICE $chan :\[seen\] $notcText"
+ }
+}
+
+# Handle public !seen
+# !seen [-exact/-glob/-regex] [--] <nick> [user@host] [channel]}
+proc ::pixseen::pubm_seen {nick uhost hand chan text} {
+ variable defaultLang; variable pubResults
+ if {![info exists pubResults] || ![string is integer $pubResults]} { set pubResults {3} }
+ if {![channel get $chan {seen}]} {
+ return
+ } elseif {![matchattr $hand f|f $chan]} {
+ if {[checkflood $uhost] != 0} {
+ return
+ }
+ }
+ # Set the locale for this channel
+ if {[validlang [channel get $chan seenlang]]} { mclocale [channel get $chan seenlang] }
+ if {[set arg [ParseArgs [join [lrange [split $text] 1 end]]]] eq {}} {
+ putseen $nick $chan [mc {Usage: %s} {!seen [-exact/-glob/-regex] [--] <nick> [user@host] [channel]}] [mc {usage: %1$s} {!seen [-exact/-glob/-regex] <nick>}]
+ mclocale $defaultLang
+ return
+ } else {
+ lassign $arg Nick Uhost Chan Mode
+ }
+
+ if {[string equal -nocase $nick $Nick]} {
+ putseen $nick $chan [mc {Go look in a mirror.}] [mc {%s, go look in a mirror.} $nick]
+ mclocale $defaultLang
+ return
+ } elseif {[string equal -nocase ${::botnick} $Nick]} {
+ putseen $nick $chan [mc {You found me!}] [mc {You found me, %s!} $nick]
+ mclocale $defaultLang
+ return
+ # Tcldrop supports glob matching for onchan, so check if Nick is valid first
+# } elseif {[validnick $Nick] && [onchan $Nick $chan]} {
+# if {[set lastspoke [lastspoke $Nick $chan]] eq {}} {
+# putseen $nick $chan [mc {%s is on the channel right now!} $Nick]
+# } else {
+# putseen $nick $chan [mc {%1$s is on the channel right now! %1$s last spoke %2$s ago.} $Nick $lastspoke]
+# }
+# mclocale $defaultLang
+# return
+# }
+
+ switch -exact -- $Mode {
+ {0} {;# exact
+ if {![validnick $Nick]} {
+ putseen $nick $chan [mc {That is not a valid nickname.}] [mc {%s, that is not a valid nickname.} $nick]
+ mclocale $defaultLang
+ return
+ } elseif {[set result [dbGetNick $Nick]] eq {}} {
+ if {[set handseen [handseen $Nick]] ne {}} {
+ putseen $nick $chan $handseen
+ mclocale $defaultLang
+ return 1
+ } else {
+ putseen $nick $chan [mc {I don't remember seeing %s.} $Nick]
+ mclocale $defaultLang
+ return
+ }
+ } else {
+ putseen $nick $chan [formatevent {*}$result]
+ mclocale $defaultLang
+ return 1
+ }
+ }
+ {1} {;# glob
+ set result [dbSearchGlob $Nick $Uhost $Chan]
+ }
+ {2} {;# regex
+ set result [dbSearchRegex $Nick $Uhost $Chan]
+ }
+ }
+ if {$result eq {}} {
+ if {[set handseen [handseen $Nick]] ne {}} {
+ putseen $nick $chan $handseen
+ mclocale $defaultLang
+ return 1
+ } else {
+ putseen $nick $chan [mc {There were no matches to your query.}]
+ mclocale $defaultLang
+ return
+ }
+ } else {
+ if {[set numMatches [llength $result]] > $pubResults} {
+ putseen $nick $chan [mc {Displaying %1$s of %2$s results:} $pubResults $numMatches]
+ } else {
+ putseen $nick $chan [mc {Displaying %1$s of %2$s results:} $numMatches $numMatches]
+ }
+ foreach match [lrange $result 0 [expr {$pubResults - 1}]] {
+ if {$match ne {}} {
+ putseen $nick $chan [formatevent {*}[dbGetNick $match]]
+ }
+ }
+ mclocale $defaultLang
+ return 1
+ }
+ mclocale $defaultLang
+ return
+}
+
+# Handle /msg botnick seen
+proc ::pixseen::msgm_seen {nick uhost hand text} {
+ putlog "\[seen\] ${nick}!${uhost} (hand: ${hand}) requested seen in PM: $text"
+ variable msgResults
+ if {![info exists msgResults] || ![string is integer $msgResults]} { set msgResults {5} }
+ if {![matchattr $hand f]} {
+ if {[checkflood $uhost] != 0} {
+ return
+ }
+ }
+ if {[set arg [ParseArgs [join [lrange [split $text] 1 end]]]] eq {}} {
+ #puthelp "NOTICE $nick :[mc {Usage: %s} {seen [-exact/-glob/-regex] [--] <nick> [user@host] [channel]}]"
+ puthelp "NOTICE $nick :[mc {Usage: %s} {seen [-exact/-glob/-regex] [--] <nick>}]"
+ return
+ } else {
+ lassign $arg Nick Uhost Chan Mode
+ }
+ if {[string equal -nocase $nick $Nick]} {
+ puthelp "NOTICE $nick :[mc {Go look in a mirror.}]"
+ return
+ } elseif {[string equal -nocase ${::botnick} $Nick]} {
+ puthelp "NOTICE $nick :[mc {You found me!}]"
+ return
+ }
+ switch -exact -- $Mode {
+ {0} {;# exact
+ if {![validnick $Nick]} {
+ puthelp "NOTICE $nick :[mc {That is not a valid nickname.}]"
+ return
+ } elseif {[set result [dbGetNick $Nick]] eq {}} {
+ if {[set handseen [handseen $Nick]] ne {}} {
+ puthelp "NOTICE $nick :$handseen"
+ return 1
+ } else {
+ puthelp "NOTICE $nick :[mc {I don't remember seeing %s.} $Nick]"
+ return
+ }
+ } else {
+ puthelp "NOTICE $nick :[formatevent {*}$result]"
+ return 1
+ }
+ }
+ {1} {;# glob
+ set result [dbSearchGlob $Nick $Uhost $Chan]
+ }
+ {2} {;# regex
+ set result [dbSearchRegex $Nick $Uhost $Chan]
+ }
+ }
+ if {$result eq {}} {
+ if {[set handseen [handseen $Nick]] ne {}} {
+ puthelp "NOTICE $nick :$handseen"
+ return 1
+ } else {
+ puthelp "NOTICE $nick :[mc {There were no matches to your query.}]"
+ return
+ }
+ } else {
+ if {[set numMatches [llength $result]] > $msgResults} {
+ puthelp "NOTICE $nick :[mc {Displaying %1$s of %2$s results:} $msgResults $numMatches]"
+ } else {
+ puthelp "NOTICE $nick :[mc {Displaying %1$s of %2$s results:} $numMatches $numMatches]"
+ }
+ foreach match [lrange $result 0 [expr {$msgResults - 1}]] {
+ if {$match ne {}} {
+ puthelp "NOTICE $nick :[formatevent {*}[dbGetNick $match]]"
+ }
+ }
+ return 1
+ }
+ return
+}
+
+# Handle partyline .seen
+proc ::pixseen::dcc_seen {hand idx text} {
+ variable dccResults
+ if {![info exists dccResults] || ![string is integer $dccResults]} { set dccResults {10} }
+ if {[set arg [ParseArgs $text]] eq {}} {
+ putdcc $idx [mc {Usage: %s} {.seen [-exact/-glob/-regex] [--] <nick> [user@host] [channel]}]
+ return
+ } else {
+ lassign $arg Nick Uhost Chan Mode
+ }
+ if {[string equal -nocase $hand $Nick]} {
+ putdcc $idx [mc {Go look in a mirror.}]
+ return
+ } elseif {[string equal -nocase ${::botnick} $Nick]} {
+ putdcc $idx [mc {You found me!}]
+ return
+ }
+ switch -exact -- $Mode {
+ {0} {;# exact
+ if {![validnick $Nick]} {
+ putdcc $idx [mc {That is not a valid nickname.}]
+ return
+ } elseif {[set result [dbGetNick $Nick]] eq {}} {
+ if {[set handseen [handseen $Nick]] ne {}} {
+ putdcc $idx $handseen
+ return 1
+ } else {
+ putdcc $idx [mc {I don't remember seeing %s.} $Nick]
+ return
+ }
+ } else {
+ putdcc $idx [formatevent {*}$result]
+ return 1
+ }
+ }
+ {1} {;# glob
+ set result [dbSearchGlob $Nick $Uhost $Chan]
+ }
+ {2} {;# regex
+ set result [dbSearchRegex $Nick $Uhost $Chan]
+ }
+ }
+ if {$result eq {}} {
+ if {[set handseen [handseen $Nick]] ne {}} {
+ putdcc $idx $handseen
+ return 1
+ } else {
+ putdcc $idx [mc {There were no matches to your query.}]
+ return
+ }
+ } else {
+ if {[set numMatches [llength $result]] > $dccResults} {
+ putdcc $idx [mc {Displaying %1$s of %2$s results:} $dccResults $numMatches]
+ } else {
+ putdcc $idx [mc {Displaying %1$s of %2$s results:} $numMatches $numMatches]
+ }
+ foreach match [lrange $result 0 [expr {$dccResults - 1}]] {
+ if {$match ne {}} {
+ putdcc $idx [formatevent {*}[dbGetNick $match]]
+ }
+ }
+ return 1
+ }
+ return
+}
+
+# verifies table information, return 1 if it's valid, 0 if not
+proc ::pixseen::ValidTable {table data} {
+ switch -exact -- $table {
+ {pixseen} {
+ # 0 dbVersion INTEGER 1 {} 0
+ lassign $data id name type null default primaryKey
+ if {$id != 0 || $name ne {dbVersion} || $type ne {INTEGER} || $null < 1 || $default ne {} || $primaryKey > 0} {
+ return 0
+ }
+ }
+ {seenTb} {
+ foreach item $data {
+ lassign $data id name type null default primaryKey
+ switch -exact -- $id {
+ {0} {
+ # 0 event INTEGER 1 {} 0
+ if {$name ne {event} || $type ne {INTEGER} || $null < 1 || $default ne {} || $primaryKey > 0} {
+ return 0
+ }
+ }
+ {1} {
+ # 1 nick STRING 1 {} 1
+ if {$name ne {nick} || $type ne {STRING} || $null < 1 || $default ne {} || $primaryKey < 1} {
+ return 0
+ }
+ }
+ {2} {
+ # 2 uhost STRING 1 {} 0
+ if {$name ne {uhost} || $type ne {STRING} || $null < 1 || $default ne {} || $primaryKey > 0} {
+ return 0
+ }
+ }
+ {3} {
+ # 3 time INTEGER 1 {} 0
+ if {$name ne {time} || $type ne {INTEGER} || $null < 1 || $default ne {} || $primaryKey > 0} {
+ return 0
+ }
+ }
+ {4} {
+ # 4 chanid INTEGER 0 {} 0
+ if {$name ne {chanid} || $type ne {INTEGER} || $null > 0 || $default ne {} || $primaryKey > 0} {
+ return 0
+ }
+ }
+ {5} {
+ # 5 reason STRING 0 {} 0
+ if {$name ne {reason} || $type ne {STRING} || $null > 0 || $default ne {} || $primaryKey > 0} {
+ return 0
+ }
+ }
+ {6} {
+ # 6 othernick STRING 0 {} 0
+ if {$name ne {othernick} || $type ne {STRING} || $null > 0 || $default ne {} || $primaryKey > 0} {
+ return 0
+ }
+ }
+ default {
+ return 0
+ }
+ }
+
+ }
+ }
+ {chanTb} {
+ foreach item $data {
+ lassign $data id name type null default primaryKey
+ switch -exact -- $id {
+ {0} {
+ #0 chanid INTEGER 1 {} 1
+ if {$name ne {chanid} || $type ne {INTEGER} || $null < 1 || $default ne {} || $primaryKey < 1} {
+ return 0
+ }
+ }
+ {1} {
+ #1 chan STRING 1 {} 0
+ if {$name ne {chan} || $type ne {STRING} || $null < 1 || $default ne {} || $primaryKey > 0} {
+ return 0
+ }
+ }
+ default {
+ return 0
+ }
+ }
+ }
+ }
+ default {
+ return 0
+ }
+ }
+ return 1
+}
+
+# Prepare the database on load
+proc ::pixseen::LOAD {args} {
+ variable dbfile; variable dbVersion
+ sqlite3 ::pixseen::seendb $dbfile
+ seendb collate IRCRFC ::pixseen::rfccomp
+ seendb function chan2id ::pixseen::chan2id
+ seendb function regexp ::pixseen::pixregexp
+ if {[catch {set result [seendb eval {SELECT tbl_name FROM sqlite_master}]} error]} {
+ putlog [mc {%1$s SQL error %2$s; %3$s} {pixseen.tcl} [seendb errorcode] $error]
+ } elseif {$result eq {}} {
+ # there's no tables defined, so we define some
+ putlog [mc {%s: No existing database found, defining SQL schema.} {pixseen.tcl}]
+ if {[catch {seendb eval {
+ -- Create a table and populate it with a version integer in case we need to change the schema in the future.
+ CREATE TABLE pixseen (
+ dbVersion INTEGER UNIQUE NOT NULL
+ );
+ INSERT INTO pixseen VALUES(1);
+
+ -- Create the table where all our seen data goes
+ CREATE TABLE seenTb (
+ event INTEGER NOT NULL,
+ nick STRING PRIMARY KEY COLLATE IRCRFC UNIQUE NOT NULL,
+ uhost STRING COLLATE NOCASE NOT NULL,
+ time INTEGER NOT NULL,
+ chanid INTEGER,
+ reason STRING COLLATE NOCASE,
+ othernick STRING COLLATE IRCRFC
+ );
+
+ -- Create the table that holds channel IDs and their real names
+ CREATE TABLE chanTb (
+ chanid INTEGER PRIMARY KEY UNIQUE NOT NULL,
+ chan STRING UNIQUE NOT NULL COLLATE IRCRFC
+ );
+ }} error]} {
+ putlog [mc {%1$s SQL error %2$s; %3$s} {pixseen.tcl} [seendb errorcode] $error]
+ }
+ } else {
+ # There's already data in this database, so we verify the schema
+ # Verify the table names
+ if {[catch { set result [seendb eval { SELECT tbl_name FROM sqlite_master WHERE type='table' ORDER BY tbl_name }] } error]} {
+ putlog [mc {%1$s SQL error %2$s; %3$s} {pixseen.tcl} [seendb errorcode] $error]
+ die [mc {Fatal Error!}]
+ } elseif {[join $result] ne {chanTb pixseen seenTb}} {
+ putlog [mc {%1$s: FATAL ERROR; SQLite database corrupt, exiting.} {pixseen.tcl}]
+ die [mc {Fatal Error!}]
+
+ # Verify the pixseen table
+ } elseif {[catch { set result [seendb eval { PRAGMA table_info(pixseen) }] } error]} {
+ putlog [mc {%1$s SQL error %2$s; %3$s} {pixseen.tcl} [seendb errorcode] $error]
+ die [mc {Fatal Error!}]
+ } elseif {![ValidTable {pixseen} $result]} {
+ putlog [mc {%1$s: FATAL ERROR; SQLite database corrupt, exiting.} {pixseen.tcl}]
+ die [mc {Fatal Error!}]
+
+ # Verify the database version
+ } elseif {[catch { set result [seendb eval { SELECT dbVersion FROM pixseen LIMIT 1 }] } error]} {
+ putlog [mc {%1$s SQL error %2$s; %3$s} {pixseen.tcl} [seendb errorcode] $error]
+ die [mc {Fatal Error!}]
+ } elseif {$result != $dbVersion} {
+ putlog [mc {%1$s: FATAL ERROR; SQLite database corrupt, exiting.} {pixseen.tcl}]
+ die [mc {Fatal Error!}]
+
+ # Verify the seenTb table
+ } elseif {[catch { set result [seendb eval { PRAGMA table_info(seenTb) }] } error]} {
+ putlog [mc {%1$s SQL error %2$s; %3$s} {pixseen.tcl} [seendb errorcode] $error]
+ die [mc {Fatal Error!}]
+ } elseif {![ValidTable {seenTb} $result]} {
+ putlog [mc {%1$s: FATAL ERROR; SQLite database corrupt, exiting.} {pixseen.tcl}]
+ die [mc {Fatal Error!}]
+
+ # Verify the chanTb table
+ } elseif {[catch { set result [seendb eval { PRAGMA table_info(chanTb) }] } error]} {
+ putlog [mc {%1$s SQL error %2$s; %3$s} {pixseen.tcl} [seendb errorcode] $error]
+ die [mc {Fatal Error!}]
+ } elseif {![ValidTable {chanTb} $result]} {
+ putlog [mc {%1$s: FATAL ERROR; SQLite database corrupt, exiting.} {pixseen.tcl}]
+ die [mc {Fatal Error!}]
+
+ # Everything is OK!
+ } else {
+ # Do some database maintenance
+ dbCleanup
+ putlog [mc {%s: Loaded the seen database.} {pixseen.tcl}]
+ }
+ }
+ return
+}
+
+proc ::pixseen::UNLOAD {args} {
+ seendb close
+ putlog [mc {%s: Unloaded the seen database.} {pixseen.tcl}]
+ return
+}
+
+# We have to verify the password here to make sure that the die is successful
+proc ::pixseen::msg_die {cmdString op} {
+ set hand [lindex $cmdString 3]
+ set pass [lindex $cmdString 4 0]
+ if {[passwdok $hand $pass]} {
+ UNLOAD
+ }
+ return
+}
+
+# chanset wrapper
+# checks the language people set and complains if it's not supported.
+proc ::pixseen::dcc_chanset {hand idx param} {
+ set chan [lindex [set arg [split $param]] 0]
+ if {![validchan $chan]} {
+ *DCC:CHANSET $hand $idx $param
+ return
+ }
+ set settings [lrange $arg 1 end]
+ set found 0
+ foreach setting $settings {
+ if {$found} {
+ set lang $setting
+ } elseif {[string equal -nocase $setting {seenlang}]} {
+ set found 1
+ }
+ }
+ if {[info exists lang] && ![validlang $lang]} {
+ putdcc $idx [mc {Error: Invalid seen language "%s".} $lang]
+ return
+ } else {
+ *DCC:CHANSET $hand $idx $param
+ return
+ }
+}
+
+# This proc will be renamed to ::*dcc:chanset on load. We call out real
+# wrapper from here so that it can stay in the correct namespace
+proc ::pixseen::*dcc:chanset {hand idx param} {
+ ::pixseen::dcc_chanset $hand $idx $param
+}
+
+namespace eval ::pixseen {
+ # trace die so that we can unload the database properly before the bot exist
+ if {![info exists SetTraces]} {
+ trace add execution die enter ::pixseen::UNLOAD
+ # don't try to trace these on Tcldrop
+ if {![info exists ::tcldrop]} {
+ trace add execution *dcc:die enter ::pixseen::UNLOAD
+ trace add execution *msg:die enter ::pixseen::msg_die
+ # wrap chanset so we can validate the language people set
+ # FixMe: add Tcldrop equivalent
+ rename ::*dcc:chanset ::*DCC:CHANSET
+ rename ::pixseen::*dcc:chanset ::*dcc:chanset
+ }
+ variable SetTraces 1
+ }
+ # load the database if it's not already loaded
+ if {[info procs seendb] ne {seendb}} { ::pixseen::LOAD }
+ # unload the database on rehash & restart
+ bind evnt - {prerehash} ::pixseen::UNLOAD
+ bind evnt - {prerestart} ::pixseen::UNLOAD
+ # seen tracking events
+ bind part - "*" ::pixseen::PART
+ bind join - "*" ::pixseen::JOIN
+ bind nick - "*" ::pixseen::NICK
+ bind sign - "*" ::pixseen::SIGN
+ bind splt - "*" ::pixseen::SPLT
+ bind rejn - "*" ::pixseen::REJN
+ bind kick - "*" ::pixseen::KICK
+ bind chon - "*" ::pixseen::CHON
+ bind chof - "*" ::pixseen::CHOF
+ bind chjn - "*" ::pixseen::CHJN
+ bind chpt - "*" ::pixseen::CHPT
+ bind away - "*" ::pixseen::AWAY
+ bind pubm - "% *" ::pixseen::CHMSG
+ # triggers
+ bind pubm - {% !seen *} ::pixseen::pubm_seen
+ bind pubm - {% !seen} ::pixseen::pubm_seen
+ bind msgm - {seen *} ::pixseen::msgm_seen
+ bind msgm - {seen} ::pixseen::msgm_seen
+ bind dcc - {seen} ::pixseen::dcc_seen
+ # flood-array cleanup every 10 minutes
+ bind time - "?0 * * * *" ::pixseen::RemoveFlood
+ # do some database maintenance once daily
+ bind evnt - {logfile} ::pixseen::dbCleanup
+ putlog [mc {Loaded %1$s v%2$s by %3$s} {pixseen.tcl} $seenver {Pixelz}]
+}
diff --git a/ques5.tcl b/ques5.tcl
new file mode 100644
index 0000000..e554fa0
--- /dev/null
+++ b/ques5.tcl
@@ -0,0 +1,368 @@
+#
+# ques5.tcl
+#
+# Copyright (C) 1995 - 1997 Robey Pointer
+# Copyright (C) 1999 - 2023 Eggheads Development Team
+#
+# v1 -- 20aug95
+# v2 -- 2oct95 [improved it]
+# v3 -- 17mar96 [fixed it up for 1.0 multi-channel]
+# v4 -- 3nov97 [Fixed it up for 1.3.0 version bots] by TG
+# v4.00001 nov97 [blurgh]
+# v5-BETA1 -- 26sep99 by rtc
+#
+# o clean webfile var removal
+# o using timezone variable from config file
+# o unified options and removed unnecessary ones.
+# o convert urls, nicks etc. to HTML before we put them into the page.
+# o nice html source indenting
+# o replace the old file after the new one has completely written to
+# disk
+# o the description still contained robey's address, replaced
+# by the eggheads email.
+# o don't link any spaces in the HTML2.0 file
+# v5-RC1 -- 29sep99 by rtc
+# o info line wasn't converted to HTML.
+# o now supports bold, italic and underline text style and colors.
+# v5-FINAL -- 04oct99 by rtc
+# o style converter now strictly follows HTML standard.
+# o Fake color attributes with number > 2^32 don't cause Tcl
+# error anymore.
+# o now uses strftime as time and date functions have both been removed
+# in 1.3.29
+
+# this will create an html file every so often (the default is once every
+# 5 minutes). the html file will have a table showing the people currently
+# on the channel, their user@hosts, who's an op, and who's idle. it
+# uses a table which some browsers (and pseudo-browsers like lynx) can't
+# see, but it can optionally make a second page which will support these
+# archaic browsers. browsers supporting push-pull will receive the updated
+# page automatically periodically.
+#
+# if you have a "url" field defined for a user, their nickname in the
+# table will be a link pointing there. otherwise it checks the info
+# line and comment field to see if they start with "http://" -- if so,
+# that link will be used. as a last resort, it will make a "mailto:"
+# link if an email address is recorded for the user.
+#
+# feel free to modify and play with this. the original was written in
+# 15 minutes, then at various times i fixed bugs and added features.
+# softlord helped me make the design look a little nicer. :) if you make
+# any nifty improvements, please let us know.
+# eggheads@eggheads.org
+
+# this line makes sure other scripts won't interfere
+if {[info exists web_file] || [array exists web_file]} {unset web_file}
+
+# You must define each channel you want a webfile for .
+# If you want a HTML2.0 file, too, put it's filename separated by
+# a colon to the same option, it goes to the same directory.
+#set web_file(#turtle) "/home/lamest/public_html/turtle.html:turtle-lynx.html"
+
+# This example demonstrates how to put lynx files into another dir.
+#set web_file(#gloom) "/home/lamest/public_html/gloom.html:lynx/gloom.html"
+
+# You can also prevent the HTML2.0 file from being written.
+#set web_file(#channel) "/home/lamest/public_html/channel.html"
+
+# You can even let the bot write only a HTML2.0.
+#set web_file(#blah) "/home/lamest/public_html/:blah.html"
+
+# how often should these html files get updated?
+# (1 means once every minute, 5 means once every 5 minutes, etc)
+set web_update 5
+
+# Which characters should be allowed in URLs?
+# DO NOT MODIFY unless you really know what you are doing.
+# Especially never add '<', '"' and '>'
+set web_urlchars "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890 :+-/!\$%&()=[]{}#^~*.:,;\\|?_@"
+
+# IRC -> HTML color translation table
+set web_color(0) "#FFFFFF"
+set web_color(1) "#000000"
+set web_color(2) "#00007F"
+set web_color(3) "#008F00"
+set web_color(4) "#FF0000"
+set web_color(5) "#7F0000"
+set web_color(6) "#9F009F"
+set web_color(7) "#FF7F00"
+set web_color(8) "#F0FF00"
+set web_color(9) "#00F700"
+set web_color(10) "#008F8F"
+set web_color(11) "#00F7FF"
+set web_color(12) "#0000FF"
+set web_color(13) "#FF00FF"
+set web_color(14) "#7F7F7F"
+set web_color(15) "#CFCFCF"
+
+# IRC -> HTML style translation table
+set web_style(\002) "<B> </B>"
+set web_style(\003) "<FONT> </FONT>"
+set web_style(\026) "<I> </I>"
+set web_style(\037) "<U> </U>"
+
+proc getnumber {string} {
+ set result ""
+ foreach char [split $string ""] {
+ if {[string first $char "0123456789"] == -1} {
+ return $result
+ } else {
+ append result $char
+ }
+ }
+ return $result
+}
+
+proc webify {string} {
+ # Tcl8.1 only:
+ #return [string map {\" &quot; & &amp; < &lt; > &gt;} $string]
+
+ # Otherwise use this:
+ regsub -all "\\&" $string "\\&amp;" string
+ regsub -all "\"" $string "\\&quot;" string
+ regsub -all "<" $string "&lt;" string
+ regsub -all ">" $string "&gt;" string
+
+ return $string
+}
+
+proc convstyle {string} {
+ global web_color web_style
+ set result ""
+ set stack ""
+ for {set i 0} "\$i < [string length $string]" {incr i} {
+ set char [string index $string $i]
+ switch -- $char {
+ "\002" - "\026" - "\037" {
+ if {[string first $char $stack] != -1} {
+ # NOT &&
+ if {[string index $stack 0] == $char} {
+ append result [lindex $web_style($char) 1]
+ set stack [string range $stack 1 end]
+ }
+ } else {
+ append result [lindex $web_style($char) 0]
+ set stack $char$stack
+ }
+ }
+ "\003" {
+ if {[string first $char $stack] != -1} {
+ if {[string index $stack 0] == $char} {
+ append result [lindex $web_style($char) 1]
+ set stack [string range $stack 1 end]
+ }
+ }
+ set c [getnumber [string range $string [expr $i + 1] [expr $i + 2]]]
+ if {$c != "" && $c >= 0 && $c <= 15} {
+ incr i [string length $c]
+ append result "<FONT COLOR=\"$web_color($c)\">"
+ set stack $char$stack
+ }
+ }
+ default {append result $char}
+ }
+ }
+ foreach char [split $stack ""] {
+ if {$char == "\002" || $char == "\003" ||
+ $char == "\026" || $char == "\037"} {
+ append result [lindex $web_style($char) 1]
+ }
+ }
+ return $result
+}
+
+proc urlstrip {string} {
+ global web_urlchars
+ set result ""
+ foreach char [split $string ""] {
+ if {[string first $char $web_urlchars] != -1} {
+ append result $char
+ }
+ }
+ return $result
+}
+
+proc do_ques {} {
+ global web_file web_update web_timerid
+ global botnick timezone
+
+ if {[info exists web_timerid]} {unset web_timerid}
+
+ foreach chan [array names web_file] {
+ if {[lsearch -exact [string tolower [channels]] [string tolower $chan]] == -1} {continue}
+ set i [split $web_file($chan) ":"]
+ set dir ""
+ set file1 [lindex $i 0]
+ set file2 [lindex $i 1]
+ set j [string last "/" $file1]
+ if {$j != -1} {
+ set dir [string range $file1 0 $j]
+ set file1 [string range $file1 [expr $j + 1] end]
+ }
+ unset i j
+ if {$file1 != ""} {
+ set fd1 [open $dir$file1~new w]
+ } else {
+ set fd1 [open "/dev/null" w]
+ }
+ if {$file2 != ""} {
+ set fd2 [open $dir$file2~new w]
+ } else {
+ set fd2 [open "/dev/null" w]
+ }
+
+ puts $fd1 "<HTML>"
+ puts $fd1 " <HEAD>"
+ puts $fd1 " <TITLE>People on [webify $chan] right now</TITLE>"
+ puts $fd1 " <META HTTP-EQUIV=\"Refresh\" CONTENT=\"[webify [expr $web_update * 60]]\">"
+ puts $fd1 " <META NAME=\"GENERATOR\" VALUE=\"ques5.tcl\">"
+ puts $fd1 " </HEAD>"
+ puts $fd1 " <BODY>"
+
+ puts $fd2 "<HTML>"
+ puts $fd2 " <HEAD>"
+ puts $fd2 " <TITLE>People on [webify $chan] right now</TITLE>"
+ puts $fd2 " <META HTTP-EQUIV=\"Refresh\" CONTENT=\"[webify [expr $web_update * 60]]\">"
+ puts $fd2 " <META NAME=\"GENERATOR\" VALUE=\"ques5.tcl\">"
+ puts $fd2 " </HEAD>"
+ puts $fd2 " <BODY>"
+ if {![onchan $botnick $chan]} {
+ puts $fd1 " <H1>Oops!</H1>"
+ puts $fd1 " I'm not on [webify $chan] right now for some reason<BR>"
+ puts $fd1 " IRC isn't a very stable place these days..."
+ puts $fd1 " Please try again later!<BR>"
+
+ puts $fd2 " <H1>Oops!</H1>"
+ puts $fd2 " I'm not on [webify $chan] right now for some reason<BR>"
+ puts $fd2 " IRC isn't a very stable place these days..."
+ puts $fd2 " Please try again later!<BR>"
+ } else {
+ puts $fd1 " <H1>[webify $chan]</H1>"
+ puts $fd2 " <H1>[webify $chan]</H1>"
+ if {$file2 != ""} {
+ puts $fd1 " If this page looks screwy on your browser, "
+ puts $fd1 " try the <A HREF=\"$file2\">HTML 2.0 "
+ puts $fd1 " version</A>.<BR>"
+ }
+ puts $fd1 " <TABLE BORDER=\"1\" CELLPADDING=\"4\">"
+ puts $fd1 " <CAPTION>People on [webify $chan] as of [webify [strftime %a,\ %d\ %b\ %Y\ %H:%M\ %Z]]</CAPTION>"
+ puts $fd1 " <TR>"
+ puts $fd1 " <TH ALIGN=\"LEFT\">Nickname</TH>"
+ puts $fd1 " <TH ALIGN=\"LEFT\">Status</TH>"
+ puts $fd1 " <TH ALIGN=\"LEFT\">User@Host</TH>"
+ puts $fd1 " </TR>"
+ puts $fd2 " <EM>People on [webify $chan] as of [webify [strftime %a,\ %d\ %b\ %Y\ %H:%M\ %Z]]</EM>"
+ puts $fd2 " <PRE>"
+ puts $fd2 " Nickname Status User@Host"
+ foreach nick [chanlist $chan] {
+ set len1 9
+ set len2 16
+ puts $fd1 " <TR ALIGN=\"LEFT\" VALIGN=\"TOP\">"
+ if {[isop $nick $chan]} {lappend status "op"}
+ if {[getchanidle $nick $chan] > 10} {lappend status "idle"}
+ set host [getchanhost $nick $chan]
+ set handle [finduser $nick!$host]
+ set host [webify $host]
+ if {[onchansplit $nick $chan]} {
+ lappend status "<STRONG>split</STRONG>"
+ #incr len2 [string length "<STRONG></STRONG>"]
+ incr len2 17
+ }
+ if {![info exists status]} {
+ set status "-"
+ } else {
+ set status [join $status ", "]
+ }
+ set url [urlstrip [getuser $handle XTRA url]]
+ set info [getuser $handle INFO]
+ set comment [getuser $handle COMMENT]
+ set email [getuser $handle XTRA email]
+ if {$url == "" && [string range $comment 0 6] == "http://"} {
+ set url [urlstrip $comment]
+ }
+ if {$url == "" && [string range $info 0 6] == "http://"} {
+ set url [urlstrip $info]
+ }
+ if {$url == "" && $email != "" && [string match *@*.* $email]} {
+ set url [urlstrip mailto:$email]
+ }
+ incr len1 [string length [webify $nick]]
+ incr len1 -[string length $nick]
+ if {[string tolower $nick] == [string tolower $botnick]} {
+ set host "<EM>&lt;- it's me, the channel bot!</EM>"
+ set info ""
+ } elseif {[matchattr $handle b]} {
+ set host "<EM>&lt;- it's another channel bot</EM>"
+ set info ""
+ }
+ if {$url != ""} {
+ incr len1 [string length "<A HREF=\"$url\"></A>"]
+ puts $fd1 " <TD><A HREF=\"$url\">[webify $nick]</A></TD>"
+ puts $fd2 " [format %-${len1}s <A\ HREF=\"$url\">[webify $nick]</A>] [format %-${len2}s $status] $host"
+ } else {
+ puts $fd1 " <TD>[webify $nick]</TD>"
+ puts $fd2 " [format %-${len1}s [webify $nick]] [format %-${len2}s $status] $host"
+ }
+ puts $fd1 " <TD>$status</TD>"
+ puts $fd1 " <TD>$host</TD>"
+ puts $fd1 " </TR>"
+ if {$info != ""} {
+ puts $fd1 " <TR ALIGN=\"LEFT\" VALIGN=\"TOP\">"
+ puts $fd1 " <TD></TD><TD COLSPAN=\"2\"><STRONG>Info</STRONG>: [convstyle [webify $info]]</TD>"
+ puts $fd1 " </TR>"
+ puts $fd2 " <STRONG>Info:</STRONG> [convstyle [webify $info]]"
+ }
+ unset len1 len2 status info url host comment email
+ }
+ puts $fd1 " </TABLE>"
+ puts $fd2 " </PRE>"
+ }
+ puts $fd1 " <HR>"
+ puts $fd1 " This page is automatically refreshed every [webify $web_update] minute(s).<BR>"
+ puts $fd1 " <ADDRESS>Created by quesedilla v5 via <A HREF=\"http://www.eggheads.org/\">eggdrop</A>.</ADDRESS>"
+ puts $fd1 " </BODY>"
+ puts $fd1 "</HTML>"
+ puts $fd1 ""
+ puts $fd2 " <HR>"
+ puts $fd2 " This page is automatically refreshed every [webify $web_update] minute(s).<BR>"
+ puts $fd2 " <ADDRESS>Created by quesedilla v5 via <A HREF=\"http://www.eggheads.org/\">eggdrop</A>.</ADDRESS>"
+ puts $fd2 " </BODY>"
+ puts $fd2 "</HTML>"
+ puts $fd2 ""
+ close $fd1
+ close $fd2
+ if {$file1 != ""} {exec /bin/mv $dir$file1~new $dir$file1}
+ if {$file2 != ""} {exec /bin/mv $dir$file2~new $dir$file2}
+ unset nick file1 file2 dir fd1 fd2
+ }
+
+ set web_timerid [timer $web_update do_ques]
+}
+
+#if {[info exists web_timerid]} {
+# killtimer $web_timerid
+# unset web_timerid
+#}
+if {![info exists web_timerid] && $web_update > 0} {
+ set web_timerid [timer $web_update do_ques]
+}
+#do_ques
+
+foreach chan [array names web_file] {
+ if {[string first ":" $web_file($chan)] != -1} {
+ lappend channels "$chan"
+ } else {
+ lappend channels "$chan (no lynx)"
+ }
+}
+
+if {![info exists channels]} {
+ putlog "Quesedilla v5 final loaded (no channels)"
+} else {
+ putlog "Quesedilla v5 final loaded: [join $channels ,\ ]"
+ unset channels
+}
+
+if {![info exists timezone]} {
+ set timezone [clock format 0 -format %Z]
+}
diff --git a/quotepong.tcl b/quotepong.tcl
new file mode 100644
index 0000000..4a2c493
--- /dev/null
+++ b/quotepong.tcl
@@ -0,0 +1,289 @@
+# quotepong.tcl by [sL] (Feb 14, 08)
+# Based on quotepass.tcl by simple, guppy, [sL]
+#
+# Ascii Letter definitions provided by Freeder
+#
+# Description:
+#
+# Some EFnet servers require the user to type /quote pong :<cookie>
+# when ident is broken or disabled. This will send pong :<cookie> to
+# the server when connecting.
+#
+
+set a2t_alphabet(a) "\n\ /\\ \n\
+\ / \\ \n\
+\ / /\\ \\ \n\
+\ / ____ \\ \n\
+/_/ \\_\\\n"
+
+set a2t_alphabet(b) "\ ____ \n\
+| _ \\ \n\
+| |_) |\n\
+| _ < \n\
+| |_) |\n\
+|____/"
+
+set a2t_alphabet(c) "\ _____ \n\
+\ / ____|\n\
+| | \n\
+| | \n\
+| |____ \n\
+\ \\_____|"
+
+set a2t_alphabet(d) "\ _____ \n\
+| __ \\ \n\
+| | | |\n\
+| | | |\n\
+| |__| |\n\
+|_____/ "
+
+set a2t_alphabet(e) "\ ______ \n\
+| ____|\n\
+| |__ \n\
+| __| \n\
+| |____ \n\
+|______|\n"
+
+set a2t_alphabet(f) "\ ______ \n\
+| ____|\n\
+| |__ \n\
+| __| \n\
+| | \n\
+|_| "
+
+
+set a2t_alphabet(g) "\ _____\n\
+\ / ____|\n\
+| | __ \n\
+| | |_ |\n\
+| |__| |\n\
+\ \\_____|"
+
+set a2t_alphabet(h) "\ _ _ \n\
+| | | |\n\
+| |__| |\n\
+| __ |\n\
+| | | |\n\
+|_| |_|"
+
+set a2t_alphabet(i) "\ _____ \n\
+|_ _|\n\
+\ | | \n\
+\ | | \n\
+\ _| |_ \n\
+|_____|"
+
+set a2t_alphabet(j) "\ _ \n\
+\ | |\n\
+\ | |\n\
+\ _ | |\n\
+| |__| |\n\
+\ \\____/ "
+
+set a2t_alphabet(k) "\ _ __\n\
+| |/ /\n\
+| ' / \n\
+| < \n\
+| . \\ \n\
+|_|\\_\\"
+
+set a2t_alphabet(l) "\ _ \n\
+| | \n\
+| | \n\
+| | \n\
+| |____ \n\
+|______|"
+
+set a2t_alphabet(m) "\ __ __ \n\
+| \\/ |\n\
+| \\ / |\n\
+| |\\/| |\n\
+| | | |\n\
+|_| |_|"
+
+set a2t_alphabet(n) "\ _ _ \n\
+| \\ | |\n\
+| \\| |\n\
+| . ` |\n\
+| |\\ |\n\
+|_| \\_|"
+
+
+set a2t_alphabet(o) "\ ____ \n\
+\ / __ \\ \n\
+| | | |\n\
+| | | |\n\
+| |__| |\n\
+\ \\____/ "
+
+set a2t_alphabet(p) "\ _____ \n\
+| __ \\ \n\
+| |__) |\n\
+| ___/ \n\
+| | \n\
+|_| "
+
+set a2t_alphabet(q) "\ ____ \n\
+\ / __ \\ \n\
+| | | |\n\
+| | | |\n\
+| |__| |\n\
+\ \\___\\_\\"
+
+set a2t_alphabet(r) "\ _____ \n\
+| __ \\ \n\
+| |__) |\n\
+| _ / \n\
+| | \\ \\ \n\
+|_| \\_\\"
+
+set a2t_alphabet(s) "\ _____ \n\
+\ / ____|\n\
+| (___ \n\
+\ \\___ \\ \n\
+\ ____) |\n\
+|_____/ "
+
+set a2t_alphabet(t) "\ _______ \n\
+|__ __|\n\
+\ | | \n\
+\ | | \n\
+\ | | \n\
+\ |_| "
+
+
+set a2t_alphabet(u) "\ _ _ \n\
+| | | |\n\
+| | | |\n\
+| | | |\n\
+| |__| |\n\
+\ \\____/ "
+
+
+set a2t_alphabet(v) " __ __\n\
+\\ \\ / /\n\
+\ \\ \\ / / \n\
+\ \\ \\/ / \n\
+\ \\ / \n\
+\ \\/ "
+
+set a2t_alphabet(w) " __ __\n\
+\\ \\ / /\n\
+\ \\ \\ /\\ / / \n\
+\ \\ \\/ \\/ / \n\
+\ \\ /\\ / \n\
+\ \\/ \\/ "
+
+
+set a2t_alphabet(x) " __ __\n\
+\\ \\ / /\n\
+\ \\ V / \n\
+\ > < \n\
+\ / . \\ \n\
+/_/ \\_\\"
+
+set a2t_alphabet(y) " __ __\n\
+\\ \\ / /\n\
+\ \\ \\_/ / \n\
+\ \\ / \n\
+\ | | \n\
+\ |_| "
+
+
+set a2t_alphabet(z) "\ ______\n\
+|___ /\n\
+\ / / \n\
+\ / / \n\
+\ / /__ \n\
+/_____|"
+
+proc a2t_ascii2text {ascii {count 6}} {
+ global a2t_alphabet
+# foreach line [split $ascii \n] { putlog $line }
+ set a2t_result ""
+ for {set i 0} {$i < $count} {incr i} {
+ foreach let [split abcdefghijklmnopqrstuvwxyz ""] {
+ set match 1
+ set tascii $ascii
+ foreach alph_line [split $a2t_alphabet($let) \n] {
+ set alph_line [string range $alph_line 1 end]
+ set asc_line [lindex [split $tascii \n] 0]
+ set tascii [join [lrange [split $tascii \n] 1 end] \n]
+ # need to fix our match pattern
+ regsub -all {\\} $alph_line {\\\\} alph_line
+ if {![string match "[string trim $alph_line]*" [string trim $asc_line]]} {
+ set match 0
+ break
+ }
+ }
+ if {$match} {
+ append a2t_result $let
+ # remove the ascii letter
+ set new_ascii [list]
+ foreach alph_line [split $a2t_alphabet($let) \n] {
+ set alph_line [string range $alph_line 1 end]
+ set asc_line [lindex [split $ascii \n] 0]
+ set ascii [join [lrange [split $ascii \n] 1 end] \n]
+ # need to fix our regspec
+ regsub -all {\\} $alph_line {\\\\} alph_line
+ regsub -all {\|} $alph_line "\\|" alph_line
+ regsub -all {\)} $alph_line "\\)" alph_line
+ regsub -all {\(} $alph_line "\\(" alph_line
+
+ regsub -- $alph_line "$asc_line" "" asc_line
+ lappend new_ascii $asc_line
+ }
+ set ascii [join $new_ascii \n]
+ }
+ if {$match} { break }
+ }
+ }
+ return [string toupper $a2t_result]
+}
+
+set quotepong_match "/QUOTE PONG :cookie"
+
+bind evnt - init-server quotepong_unbind
+bind evnt - disconnect-server quotepong_unbind
+bind evnt - connect-server quotepong_bind
+
+proc quotepong_servermsg {from cmd text} {
+ global quotepong_match quotepong_count quotepong_ascii
+ if {![info exists quotepong_count] && [string match "*$quotepong_match*" $text]} {
+ set quotepong_count 0
+ set quotepong_ascii [list]
+ return 0
+ }
+ if {[info exists quotepong_count] && ($cmd == "998")} {
+ if {$quotepong_count == 0} {
+ putlog "Received ASCII Cookie from server:"
+ }
+ incr quotepong_count
+ lappend quotepong_ascii [lindex [split $text :] 1]
+ putlog "[lindex [split $text :] 1]"
+ if {$quotepong_count == 6} {
+ # time to send back to server
+ set cookie [a2t_ascii2text [join $quotepong_ascii \n]]
+ putlog "Sending Cookie to server: $cookie"
+ putserv "PONG :$cookie"
+ catch {unset quotepong_count}
+ }
+ }
+ return 0
+}
+
+proc quotepong_unbind {type} {
+ # Try to unbind our raw NOTICE bind once we are connected since it will
+ # never be needed again
+ catch {
+ unbind raw - NOTICE quotepong_servermsg
+ unbind raw - 998 quotepong_servermsg
+ }
+}
+
+proc quotepong_bind {type} {
+ bind raw - NOTICE quotepong_servermsg
+ bind raw - 998 quotepong_servermsg
+}
+
+putlog "Loaded quotepong.tcl"
diff --git a/remind.tcl b/remind.tcl
new file mode 100644
index 0000000..edd6fd7
--- /dev/null
+++ b/remind.tcl
@@ -0,0 +1,330 @@
+################################################
+#################### ABOUT #####################
+################################################
+#
+# Reminder-0.2 by Fredrik Bostrom
+# for Eggdrop IRC bot
+#
+# Usage:
+# !remind nick time message
+# - creates a new reminder for nick in the
+# current channel at time with message
+# - time is a format parseable by the tcl
+# command 'clock scan'. If the time consists
+# of several words, it has to be enclosed
+# in "".
+# - examples:
+# !remind morbaq "tomorrow 10:00" Call Peter
+# !remind morbaq "2009-12-31 23:59" Happy new year!
+#
+# !reminders
+# - lists all active reminders
+#
+# !cancelReminder id
+# - cancels the reminder with id
+# - the id is the number preceeding the
+# reminder in the list produced by
+# !reminders
+# - note: the id may change as new reminders
+# are added or old reminders removed. Always
+# check the id just before cancelling
+#
+#
+################################################
+################ CONFIGURATION #################
+################################################
+
+set datafile "scripts/reminders.dat"
+
+################################################
+######## DON'T EDIT BEOYND THIS LINE! ##########
+################################################
+
+bind pub - "!clockscan" pub:clockscan
+bind pub - "!remind" pub:newReminder
+bind pub - "!remindme" pub:newReminderfornick
+bind pub - "!reminders" pub:getReminders
+bind pub n "!cancelReminder" pub:cancelReminder
+bind pub n "\$inspectReminders" pub:inspectReminders
+
+array set reminders {}
+
+# save to file
+proc saveReminders {} {
+ global reminders
+ global datafile
+
+ set file [open $datafile w+]
+ puts $file [array get reminders]
+ close $file
+}
+
+# the run-at-time procedure
+proc at {time args} {
+ if {[llength $args]==1} {
+ set args [lindex $args 0]
+ }
+ set dt [expr {($time - [clock seconds])*1000}]
+ return [after $dt $args]
+}
+
+proc printReminder {reminderId {tonick ""} {fire "false"}} {
+ global reminders
+
+ # get the reminder
+ set reminder $reminders($reminderId)
+
+ set when [clock format [lindex $reminder 0] -format "%Y-%m-%dT%H:%M:%SZ"]
+ set chan [lindex $reminder 1]
+ set who [lindex $reminder 2]
+ set timer [lindex $reminder 3]
+ set what [lindex $reminder 4]
+
+ if {$fire} {
+ putserv "PRIVMSG $chan :\[remind\] $who: $what"
+ } else {
+ putserv "NOTICE $tonick :\[remind\] $reminderId: for $who at $when: $what"
+ }
+}
+
+proc fireReminder {reminderId} {
+ global reminders
+
+ printReminder $reminderId "" "true"
+ unset reminders($reminderId)
+ saveReminders
+}
+
+
+proc pub:clockscan {nick host handle chan text} {
+ set timeError ""
+ # parse parameters
+ set curTime [clock format [clock seconds] -format "%H:%M:%SZ" -gmt 1]
+ regsub -all {([0-9]+)y$} $text "\\1 years $curTime" text
+ regsub -all {([0-9]+)mo$} $text "\\1 months $curTime" text
+ regsub -all {([0-9]+)w$} $text "\\1 weeks $curTime" text
+ regsub -all {([0-9]+)d$} $text "\\1 days $curTime" text
+ regsub -all {([0-9]+)hr?$} $text {\1 hours} text
+ regsub -all {([0-9]+)m$} $text {\1 minutes} text
+ regsub -all {([0-9]+)s$} $text {\1 seconds} text
+ set time [catch {clock scan $text} timeResult]
+
+ if {$time != 0} {
+ putserv "NOTICE $chan :\[remind\] unable to parse time: $timeResult"
+ return 1
+ }
+
+ set ISOTimeResult [clock format $timeResult -format "%Y-%m-%dT%H:%M:%SZ" -gmt 1]
+
+ if {[clock seconds] > $timeResult} {
+ putserv "NOTICE $chan :\[clockscan\] error: \"$text\" (parsed as $timeResult → $ISOTimeResult) is in the past"
+ return 1
+ }
+
+ putserv "NOTICE $chan :\[clockscan\] parsed \"$text\" as $timeResult → $ISOTimeResult"
+}
+
+
+proc pub:newReminderfornick {nick host handle chan text} {
+ global reminders
+
+
+ set timeError ""
+ # parse parameters
+ set id [clock seconds]
+ set who $nick
+ set when [lindex $text 0]
+ set curTime [clock format [clock seconds] -format "%H:%M:%SZ" -gmt 1]
+ regsub -all {([0-9]+)y$} $when "\\1 years $curTime" when
+ regsub -all {([0-9]+)mo$} $when "\\1 months $curTime" when
+ regsub -all {([0-9]+)w$} $when "\\1 weeks $curTime" when
+ regsub -all {([0-9]+)d$} $when "\\1 days $curTime" when
+ regsub -all {([0-9]+)hr?$} $when {\1 hours} when
+ regsub -all {([0-9]+)m$} $when {\1 minutes} when
+ regsub -all {([0-9]+)s$} $when {\1 seconds} when
+ set time [catch {clock scan $when} timeResult]
+ set what [lrange $text 1 end]
+
+ if {$when == ""} {
+ putserv "NOTICE $chan :\[remind\] !remindme \"<time>\" <message> (time is a format parseable by the TCL command 'clock scan' - https://www.tcl.tk/man/tcl8.6/TclCmd/clock.html)"
+ return 0
+ }
+
+ if {$time != 0} {
+ putserv "NOTICE $chan :\[remind\] unable to parse time: $timeResult"
+ return 1
+ }
+
+ set ISOTimeResult [clock format $timeResult -format "%Y-%m-%dT%H:%M:%SZ" -gmt 1]
+
+ if {[clock seconds] > $timeResult} {
+ putserv "NOTICE $chan :\[remind\] error: \"$when\" (parsed as $timeResult → $ISOTimeResult) is in the past"
+ return 1
+ }
+
+ # create new entry
+ set new [list $timeResult $chan $who null $what]
+
+ # activate the event
+ set timer [at $timeResult fireReminder $id]
+
+ # putlog "new timer: $timer"
+
+ # set the timer associated with this reminder
+ set new [lreplace $new 3 3 $timer]
+ # putlog "new reminder: $new"
+
+ set reminders($id) $new
+ saveReminders
+
+ putserv "NOTICE $chan :\[remind\] ok, i'll remind you at $ISOTimeResult"
+}
+
+proc pub:newReminder {nick host handle chan text} {
+ global reminders
+
+ # parse parameters
+ set id [clock seconds]
+ set who [lindex $text 0]
+ set when [lindex $text 1]
+ set curTime [clock format [clock seconds] -format "%H:%M:%SZ" -gmt 1]
+ regsub -all {([0-9]+)y$} $when "\\1 years $curTime" when
+ regsub -all {([0-9]+)mo$} $when "\\1 months $curTime" when
+ regsub -all {([0-9]+)w$} $when "\\1 weeks $curTime" when
+ regsub -all {([0-9]+)d$} $when "\\1 days $curTime" when
+ regsub -all {([0-9]+)hr?$} $when {\1 hours} when
+ regsub -all {([0-9]+)m$} $when {\1 minutes} when
+ regsub -all {([0-9]+)s$} $when {\1 seconds} when
+ set time [catch {clock scan $when} timeResult]
+ set what [lrange $text 2 end]
+
+ if {$who == ""} {
+ putserv "NOTICE $chan :\[remind\] !remind <nick> \"<time>\" <message> (time is a format parseable by the TCL command 'clock scan' - https://www.tcl.tk/man/tcl8.6/TclCmd/clock.html)"
+ return 0
+ }
+
+ if {$time != 0} {
+ putserv "NOTICE $chan :\[remind\] unable to parse time: $timeResult"
+ return 1
+ }
+
+ set ISOTimeResult [clock format $timeResult -format "%Y-%m-%dT%H:%M:%SZ" -gmt 1]
+
+ if {[clock seconds] > $timeResult} {
+ putserv "NOTICE $chan :\[remind\] error: \"$when\" (parsed as $ISOTimeResult) is in the past"
+ return 1
+ }
+
+ # create new entry
+ set new [list $timeResult $chan $who null $what]
+
+ # activate the event
+ set timer [at $timeResult fireReminder $id]
+
+ # putlog "new timer: $timer"
+
+ # set the timer associated with this reminder
+ set new [lreplace $new 3 3 $timer]
+ # putlog "new reminder: $new"
+
+ set reminders($id) $new
+ saveReminders
+
+ putserv "NOTICE $chan :\[remind\] ok, i'll remind $who at $ISOTimeResult"
+}
+
+proc pub:getReminders {nick host handle chan text} {
+ global reminders
+ set chanReminders {}
+
+ # count all reminders for this channel
+ foreach {key value} [array get reminders] {
+ if {[lindex $value 1] == $chan} {
+ lappend chanReminders $key
+ }
+ }
+
+ # count the reminders
+ set howMany [llength $chanReminders]
+
+ # do we have reminders?
+ if {$howMany < 1} {
+ putserv "NOTICE $nick :\[remind\] no active reminders"
+ return
+ }
+
+ # print reminders for this channel
+ putserv "NOTICE $nick :\[remind\] $howMany active reminder(s):"
+ foreach key $chanReminders {
+ printReminder $key $nick
+ }
+}
+
+proc pub:cancelReminder {nick host handle chan text} {
+ global reminders
+
+ set reminder $reminders($text)
+ set timer [lindex $reminder 3]
+
+ # putlog "Cancelling timer: $timer"
+ after cancel $timer
+ unset reminders($text)
+ putserv "PRIVMSG $chan :Removed reminder with id $text"
+
+ saveReminders
+}
+
+proc pub:inspectReminders {nick host handle chan text} {
+ global reminders
+
+ set timerString [after info]
+ set reminderString [array get reminders]
+
+ putserv "NOTICE $nick :Reminders: $reminderString"
+ putserv "NOTICE $nick :Timers: $timerString"
+}
+
+proc initReminders {} {
+ global reminders
+
+ set reminderString [array get reminders]
+ # putlog "Initiating reminders: $reminderString"
+
+ # get current time
+ set time [clock seconds]
+
+ # get active timers
+ set activeTimers [after info]
+
+ # check for expired reminders and fire them
+ foreach {key value} [array get reminders] {
+ if {[lindex $value 0] < $time} {
+ fireReminder $key
+ } elseif {[lsearch $activeTimers [lindex $value 3]] == -1} {
+ # if the reminder hasn't expired, check if the timer is already set
+ set timerId [at [lindex $value 0] fireReminder $key]
+ set reminders($key) [lreplace $value 3 3 $timerId]
+ }
+ }
+ saveReminders
+}
+
+
+# read the old if they exist
+set file [open $datafile]
+set content [read $file]
+close $file
+
+if {$content == ""} {
+ set content {}
+}
+
+array set reminders $content
+
+initReminders
+
+
+###################################
+putlog "Reminder script loaded!"
+###################################
+
diff --git a/sentinel.tcl b/sentinel.tcl
new file mode 100644
index 0000000..86b764e
--- /dev/null
+++ b/sentinel.tcl
@@ -0,0 +1,1442 @@
+# sentinel.tcl v2.70 (15 April 2002)
+# Copyright 1998-2002 by slennox
+# slennox's eggdrop page - http://www.egghelp.org/
+
+# Flood protection system for eggdrop, with integrated BitchX CTCP
+# simulation. This script is designed to provide strong protection for your
+# bot and channels against large floodnets and proxy floods.
+#
+# Note that this script was developed on the eggdrop 1.3, 1.4, and 1.6
+# series and may not work properly on other versions.
+#
+# v2.00 - New standalone release. Contains refinements and features from
+# the netbots.tcl version of sentinel.tcl.
+# v2.50 - Locktimes of less than 30 were erroneously allowed.
+# putquick -next is now supported (eggdrop 1.5+) for faster channel
+# lock.
+# - Ban mechanism now checks if flooders are coming from the same
+# domain or ident and performs wildcard bans instead of banning
+# each IP/host individually.
+# - Added tsunami detection to the avalanche flood detection system.
+# - Variables are now cleared after removing a channel.
+# - Removed unnecessary botonchan checks throughout components where
+# botisop already checks for that.
+# - Removed all unnecessary use of parentheses.
+# v2.60 - Modified putquick compatibility proc.
+# - Added sl_wideban option to make domain/ident bans optional.
+# - Fixed typos in various ban-related functions.
+# - Unused procs are now unloaded.
+# - Wildcard bans covering domains/idents were not doing proper
+# checks on join-part flooders.
+# v2.70 - Fixed "allbans" error which could occur when sl_wideban is
+# disabled.
+# - Added sl_masktype option, currently offering three different
+# ban/ignore mask types.
+# - Merged unsets in sl_unsetarray.
+# - Changed use of "split" in timers to the less ugly "list".
+#
+# sentinel.tcl is centered around its channel lock mechanism. It sets the
+# channel +mi (moderated and invite-only) whenever a substantial flood on
+# the channel is detected. This ensures channel stability when flooded,
+# allowing the bots to deal with the flood as smoothly as possible.
+# sentinel.tcl detects the following types of floods:
+#
+# * Channel CTCP floods. This is the most common type of flood, and will
+# often make users quit from the channel with 'Excess Flood'. A quick
+# channel lock can prevent this.
+# * Channel join-part floods. A common type of channel flood in which many
+# floodbots cycle the channel.
+# * Channel nick floods. Nick floods are unique in that they can occur even
+# after a channel is locked. sentinel has special mechanisms to deal with
+# this as effectively as possible.
+# * Channel avalanche/tsunami floods. While the avalanche flood is quite
+# uncommon these days, tsunami floods are often used to seriously lag
+# mIRC and other clients using control codes (colour, bold, underline,
+# etc.)
+# * Channel text floods. Not small text floods - but when hundreds of
+# messages are sent to the channel within a short period. Detected to
+# stop really aggressive text floods and reduce the possibility of the
+# bot crashing or consuming excessive CPU.
+#
+# sentinel also has additional protection features for the bot and channel:
+#
+# * Bogus username detection. Users with annoying bogus characters in their
+# ident are banned. A channel lock is applied if multiple join in a short
+# period.
+# * Full ban list detection. During a serious flood, the ban list may
+# become full. If this happens, the bots may start kick flooding since
+# they cannot ban. If the ban list is full, the channel will be set +i.
+# * Bot CTCP flood protection. Protects the bot if it is CTCP flooded.
+# * Bot MSG flood protection. Protects the bot if it's flooded with MSGs or
+# MSG commands.
+# * Automatic bans and ignores. During a flood, sentinel will compile a
+# list of the flooders, then kick-ban them after the channel has been
+# locked.
+# * BitchX simulation. This has been built-in mainly because you cannot use
+# a third-party BitchX simulator with sentinel (only one script at a time
+# can have control of CTCPs). sentinel provides accurate simulation of
+# BitchX 75p1+ and 75p3+ CTCP replies and AWAY mode.
+# * Public commands (lc and uc) and DCC commands (.lock and .unlock) for
+# locking/unlocking channels.
+# * DCC command .sentinel displays current settings.
+#
+# Important Notes:
+# - Make sure no bots are enforcing channel mode -i and/or -m.
+# - Bans are added to the bot's internal ban list, and expire after 24
+# hours by default. If you have +dynamicbans set, the bans may be removed
+# from the channel much sooner than this, but the ban will remain in the
+# bot's internal ban list until it expires.
+# - For greater protection against large channel floods, I recommend you
+# also use a channel limiter script, such as chanlimit.tcl.
+# - There is a trade-off between convenience and security. The more
+# automation you enable, the more stress the bot will be under during a
+# flood and the more stuff it will be sending to the server.
+# - Where security is paramount, have one or two bots that aren't running
+# sentinel.tcl. Since sentinel.tcl is a complex script with many
+# automated and convenience features, there is a potential for
+# vulnerabilities.
+
+# The following flood settings are in number:seconds format, 0:0 to
+# disable.
+
+# Bot CTCP flood.
+set sl_bcflood 5:30
+
+# Bot MSG flood.
+set sl_bmflood 6:20
+
+# Channel CTCP flood.
+set sl_ccflood 5:20
+
+# Channel avalanche/tsunami flood.
+set sl_avflood 6:20
+
+# Channel text flood.
+set sl_txflood 80:30
+
+# Channel bogus username join flood.
+set sl_boflood 4:20
+
+# Channel join-part flood.
+set sl_jflood 6:20
+
+# Channel nick flood.
+set sl_nkflood 6:20
+
+# Flood setting notes:
+# - Don't fiddle too much with the seconds field in the flood settings, as
+# it can reduce the effectiveness of the script. The seconds field should
+# set in the 20-60 seconds range.
+# - Avalanche/tsunmami flood detection may be CPU intensive on a busy
+# channel, although I don't think it's a big deal on most systems. If
+# you're concerned about CPU usage you may wish to disable it, perhaps
+# leaving it enabled on one bot. Disabling text flood protection can
+# further reduce CPU usage.
+# - On bots with avalanche/tsunami flood detection enabled, it's
+# recommended that you also enable text flood detection to cap CPU usage
+# during a flood.
+# - If you enable nick flood detection, it's strongly recommended that it
+# be enabled on all bots that have sentinel.tcl loaded. This is required
+# for effective nick flood handling.
+
+# Specify the number of control characters that must be in a line before
+# it's counted by the tsunami flood detector. For efficiency reasons,
+# tsunami detection is implemented with avalanche detection, so sl_avflood
+# must be enabled for tsunami detection to be active. Setting this to 0
+# will disable tsunami detection.
+set sl_tsunami 10
+# Valid settings: 0 to disable, otherwise 1 or higher.
+
+# Length of time in minutes to ban channel flooders. This makes the bot
+# perform kicks and bans on flooders after the channel lock. Because of the
+# reactive nature of automatic bans, you should disable this on at least
+# one bot for the most effective protection.
+set sl_ban 1440
+# Valid settings: 0 to disable, otherwise 1 or higher.
+
+# Length of time in minutes of on-join bans for bogus usernames. For the
+# most effective protection, you should disable this on at least one bot.
+set sl_boban 1440
+# Valid settings: 0 to disable, otherwise 1 or higher.
+
+# Set global bans on channel flooders and bogus usernames?
+set sl_globalban 0
+# Valid settings: 1 for global bans, 0 for channel-specific bans.
+
+# When processing a list of flooders, sentinel.tcl compares the hosts to
+# see if multiple flooders are coming from a particular domain/IP or using
+# the same ident (e.g. different vhosts on a single user account). If
+# multiple flooders come from the same domain/IP, or if multiple flooders
+# have the same ident, then the whole domain/IP (i.e. *!*@*.domain.com or
+# *!*@555.555.555.*) or ident (i.e. *!*username@*) is banned. If you
+# disable this option, all bans will be in *!*@machine.domain.com and
+# *!*@555.555.555.555 format.
+set sl_wideban 1
+# Valid settings: 1 to enable, 0 to disable.
+
+# Maximum number of bans allowed in the bot's ban list before sentinel will
+# stop adding new bans. This prevents the bot from adding hundreds of bans
+# on really large floods. Note that this has nothing to do with the channel
+# ban list.
+set sl_banmax 100
+# Valid settings: 1 or higher.
+
+# Length of time in minutes to ignore bot flooders. On bots with sl_ban
+# active, channel flooders are also added to the ignore list.
+set sl_igtime 240
+# Valid settings: 1 or higher.
+
+# Select the type of hostmask to use when banning and/or ignoring flooders.
+# There are three to choose from:
+# 0 - *!*@machine.domain.com / *!*@555.555.555.555
+# 1 - *!*ident@machine.domain.com / *!*ident@555.555.555.555
+# 2 - *!*ident@*.domain.com / *!*ident@555.555.555.*
+# The default option, 0, is strongly recommended for most situations, and
+# provides the best level of protection. The other two options are provided
+# mainly for special cases.
+set sl_masktype 0
+# Valid settings: 0, 1, or 2, depending on the hostmask type you wish to use.
+
+# Length of time in seconds to set channel +i if flooded. If set to 0, +i
+# will not be removed automatically.
+set sl_ilocktime 120
+# Valid settings: 0 to prevent +i being removed automatically, otherwise 30
+# or higher.
+
+# Length of time in seconds to set channel +m if flooded. If set to 0, +m
+# will not be removed automatically.
+set sl_mlocktime 60
+# Valid settings: 0 to prevent +m being removed automatically, otherwise 30
+# or higher.
+
+# On small floods (two flooders or less), remove the +mi shortly after bans
+# have been set, instead of waiting for the locktimes to expire? This
+# prevents unnecessary extended locks on small floods. This setting is only
+# used by bots with sl_ban enabled.
+set sl_shortlock 0
+# Valid settings: 0 to disable, 1 to enable.
+
+# Number of bans to allow in the channel ban list before setting the
+# channel +i. If enabled, this should preferably be set to just below the
+# maximum number of bans allowed.
+set sl_bfmaxbans 19
+# Valid settings: 0 to disable +i on full ban list, otherwise 1 or higher.
+
+# List of users to send a note to when channel is flooded, bot is flooded,
+# or ban list becomes full.
+set sl_note "YourNick"
+# Valid settings: one user like "Tom", a list of users like
+# "Tom Dick Harry", or "" to specify that no notes are sent.
+
+# Notice to send to channel when locked due to flood.
+set sl_cfnotice "Channel locked temporarily due to flood, sorry for any inconvenience this may cause :-)"
+# Valid settings: a text string, or set it to "" to disable.
+
+# Notice to send to channel when locked due to full ban list.
+set sl_bfnotice "Channel locked temporarily due to full ban list, sorry for any inconvenience this may cause :-)"
+# Valid settings: a text string, or set it to "" to disable.
+
+# Enable 'lc' and 'uc' public commands for locking/unlocking channel?
+set sl_lockcmds 2
+# Valid settings: 0 to disable, 1 to enable, 2 to require user to be opped
+# on the channel to use the command.
+
+# Users with these flags are allowed to use lc/uc public commands, and
+# .lock/.unlock DCC commands.
+set sl_lockflags "o"
+# Valid settings: one flag like "n", or a list of flags like "fo" (means
+# 'f OR o').
+
+# Enable BitchX CTCP and AWAY simulation?
+set sl_bxsimul 0
+# Valid settings: 1 to enable, 0 to disable.
+
+
+# Don't edit below unless you know what you're doing.
+
+if {$numversion < 1032400} {
+ proc botonchan {chan} {
+ global botnick
+ if {![validchan $chan]} {
+ error "illegal channel: $chan"
+ } elseif {![onchan $botnick $chan]} {
+ return 0
+ }
+ return 1
+ }
+ proc putquick {text args} {
+ putserv $text
+ }
+}
+
+proc sl_ctcp {nick uhost hand dest key arg} {
+ global botnet-nick botnick realname sl_ban sl_bflooded sl_bcflood sl_bcqueue sl_bxjointime sl_bxmachine sl_bxonestack sl_bxsimul sl_bxsystem sl_bxversion sl_bxwhoami sl_ccbanhost sl_ccbannick sl_ccflood sl_ccqueue sl_flooded sl_locked sl_note
+ set chan [string tolower $dest]
+ if {[lsearch -exact $sl_ccflood 0] == -1 && [validchan $chan] && ![isop $nick $chan]} {
+ if {$nick == $botnick} {return 0}
+ if {$sl_ban && !$sl_locked($chan) && ![matchattr $hand f|f $chan]} {
+ lappend sl_ccbannick($chan) $nick ; lappend sl_ccbanhost($chan) [string tolower $uhost]
+ utimer [lindex $sl_ccflood 1] [list sl_ccbanqueue $chan]
+ }
+ if {$sl_flooded($chan)} {return 1}
+ incr sl_ccqueue($chan)
+ utimer [lindex $sl_ccflood 1] [list sl_ccqueuereset $chan]
+ if {$sl_ccqueue($chan) >= [lindex $sl_ccflood 0]} {
+ sl_lock $chan "CTCP flood" ${botnet-nick} ; return 1
+ }
+ if {$sl_bflooded} {return 1}
+ } elseif {[lindex $sl_bcflood 0] && $dest == $botnick} {
+ if {$sl_bflooded} {
+ sl_ignore [string tolower $uhost] $hand "CTCP flooder" ; return 1
+ }
+ incr sl_bcqueue
+ utimer [lindex $sl_bcflood 1] {incr sl_bcqueue -1}
+ if {$sl_bcqueue >= [lindex $sl_bcflood 0]} {
+ putlog "sentinel: CTCP flood detected on me! Stopped answering CTCPs temporarily."
+ set sl_bflooded 1
+ utimer [lindex $sl_bcflood 1] {set sl_bflooded 0}
+ if {[info commands sendnote] != ""} {
+ foreach recipient $sl_note {
+ if {[validuser $recipient]} {
+ sendnote SENTINEL $recipient "Bot was CTCP flooded."
+ }
+ }
+ }
+ return 1
+ }
+ }
+
+ if {!$sl_bxsimul} {return 0}
+ if {$sl_bxonestack} {return 1}
+ set sl_bxonestack 1 ; utimer 2 {set sl_bxonestack 0}
+ switch -exact -- $key {
+ "CLIENTINFO" {
+ set bxcmd [string toupper $arg]
+ switch -exact -- $bxcmd {
+ "" {putserv "NOTICE $nick :\001CLIENTINFO SED UTC ACTION DCC CDCC BDCC XDCC VERSION CLIENTINFO USERINFO ERRMSG FINGER TIME PING ECHO INVITE WHOAMI OP OPS UNBAN IDENT XLINK UPTIME :Use CLIENTINFO <COMMAND> to get more specific information\001"}
+ "SED" {putserv "NOTICE $nick :\001CLIENTINFO SED contains simple_encrypted_data\001"}
+ "UTC" {putserv "NOTICE $nick :\001CLIENTINFO UTC substitutes the local timezone\001"}
+ "ACTION" {putserv "NOTICE $nick :\001CLIENTINFO ACTION contains action descriptions for atmosphere\001"}
+ "DCC" {putserv "NOTICE $nick :\001CLIENTINFO DCC requests a direct_client_connection\001"}
+ "CDCC" {putserv "NOTICE $nick :\001CLIENTINFO CDCC checks cdcc info for you\001"}
+ "BDCC" {putserv "NOTICE $nick :\001CLIENTINFO BDCC checks cdcc info for you\001"}
+ "XDCC" {putserv "NOTICE $nick :\001CLIENTINFO XDCC checks cdcc info for you\001"}
+ "VERSION" {putserv "NOTICE $nick :\001CLIENTINFO VERSION shows client type, version and environment\001"}
+ "CLIENTINFO" {putserv "NOTICE $nick :\001CLIENTINFO CLIENTINFO gives information about available CTCP commands\001"}
+ "USERINFO" {putserv "NOTICE $nick :\001CLIENTINFO USERINFO returns user settable information\001"}
+ "ERRMSG" {putserv "NOTICE $nick :\001CLIENTINFO ERRMSG returns error messages\001"}
+ "FINGER" {putserv "NOTICE $nick :\001CLIENTINFO FINGER shows real name, login name and idle time of user\001"}
+ "TIME" {putserv "NOTICE $nick :\001CLIENTINFO TIME tells you the time on the user's host\001"}
+ "PING" {putserv "NOTICE $nick :\001CLIENTINFO PING returns the arguments it receives\001"}
+ "ECHO" {putserv "NOTICE $nick :\001CLIENTINFO ECHO returns the arguments it receives\001"}
+ "INVITE" {putserv "NOTICE $nick :\001CLIENTINFO INVITE invite to channel specified\001"}
+ "WHOAMI" {putserv "NOTICE $nick :\001CLIENTINFO WHOAMI user list information\001"}
+ "OP" {putserv "NOTICE $nick :\001CLIENTINFO OP ops the person if on userlist\001"}
+ "OPS" {putserv "NOTICE $nick :\001CLIENTINFO OPS ops the person if on userlist\001"}
+ "UNBAN" {putserv "NOTICE $nick :\001CLIENTINFO UNBAN unbans the person from channel\001"}
+ "IDENT" {putserv "NOTICE $nick :\001CLIENTINFO IDENT change userhost of userlist\001"}
+ "XLINK" {putserv "NOTICE $nick :\001CLIENTINFO XLINK x-filez rule\001"}
+ "UPTIME" {putserv "NOTICE $nick :\001CLIENTINFO UPTIME my uptime\001"}
+ "default" {putserv "NOTICE $nick :\001ERRMSG CLIENTINFO: $arg is not a valid function\001"}
+ }
+ return 1
+ }
+ "VERSION" {
+ putserv "NOTICE $nick :\001VERSION \002BitchX-$sl_bxversion\002 by panasync \002-\002 $sl_bxsystem :\002 Keep it to yourself!\002\001"
+ return 1
+ }
+ "USERINFO" {
+ putserv "NOTICE $nick :\001USERINFO \001"
+ return 1
+ }
+ "FINGER" {
+ putserv "NOTICE $nick :\001FINGER $realname ($sl_bxwhoami@$sl_bxmachine) Idle [expr [unixtime] - $sl_bxjointime] seconds\001"
+ return 1
+ }
+ "PING" {
+ putserv "NOTICE $nick :\001PING $arg\001"
+ return 1
+ }
+ "ECHO" {
+ if {[validchan $chan]} {return 1}
+ putserv "NOTICE $nick :\001ECHO [string range $arg 0 59]\001"
+ return 1
+ }
+ "ERRMSG" {
+ if {[validchan $chan]} {return 1}
+ putserv "NOTICE $nick :\001ERRMSG [string range $arg 0 59]\001"
+ return 1
+ }
+ "INVITE" {
+ if {$arg == "" || [validchan $chan]} {return 1}
+ set chanarg [lindex [split $arg] 0]
+ if {((($sl_bxversion == "75p1+") && ([string trim [string index $chanarg 0] "#+&"] == "")) || (($sl_bxversion == "75p3+") && ([string trim [string index $chanarg 0] "#+&!"] == "")))} {
+ if {[validchan $chanarg]} {
+ putserv "NOTICE $nick :\002BitchX\002: Access Denied"
+ } else {
+ putserv "NOTICE $nick :\002BitchX\002: I'm not on that channel"
+ }
+ }
+ return 1
+ }
+ "WHOAMI" {
+ if {[validchan $chan]} {return 1}
+ putserv "NOTICE $nick :\002BitchX\002: Access Denied"
+ return 1
+ }
+ "OP" -
+ "OPS" {
+ if {$arg == "" || [validchan $chan]} {return 1}
+ putserv "NOTICE $nick :\002BitchX\002: I'm not on [lindex [split $arg] 0], or I'm not opped"
+ return 1
+ }
+ "UNBAN" {
+ if {$arg == "" || [validchan $chan]} {return 1}
+ if {[validchan [lindex [split $arg] 0]]} {
+ putserv "NOTICE $nick :\002BitchX\002: Access Denied"
+ } else {
+ putserv "NOTICE $nick :\002BitchX\002: I'm not on that channel"
+ }
+ return 1
+ }
+ }
+ return 0
+}
+
+proc sl_bmflood {nick uhost hand text} {
+ global sl_bmflood sl_bflooded sl_bmqueue sl_note
+ if {[matchattr $hand b] && [string tolower [lindex [split $text] 0]] == "go"} {return 0}
+ if {$sl_bflooded} {
+ sl_ignore [string tolower $uhost] $hand "MSG flooder" ; return 0
+ }
+ incr sl_bmqueue
+ utimer [lindex $sl_bmflood 1] {incr sl_bmqueue -1}
+ if {$sl_bmqueue >= [lindex $sl_bmflood 0]} {
+ putlog "sentinel: MSG flood detected on me! Stopped answering MSGs temporarily."
+ set sl_bflooded 1
+ utimer [lindex $sl_bmflood 1] {set sl_bflooded 0}
+ if {[info commands sendnote] != ""} {
+ foreach recipient $sl_note {
+ if {[validuser $recipient]} {
+ sendnote SENTINEL $recipient "Bot was MSG flooded."
+ }
+ }
+ }
+ }
+ return 0
+}
+
+proc sl_avflood {from keyword arg} {
+ global botnet-nick botnick sl_ban sl_avbanhost sl_avbannick sl_avflood sl_avqueue sl_flooded sl_locked sl_txflood sl_txqueue
+ set arg [split $arg]
+ set chan [string tolower [lindex $arg 0]]
+ if {![validchan $chan]} {return 0}
+ set nick [lindex [split $from !] 0]
+ if {$nick == $botnick || $nick == "" || [string match *.* $nick]} {return 0}
+ if {![onchan $nick $chan] || [isop $nick $chan]} {return 0}
+ if {!$sl_flooded($chan) && [lsearch -exact $sl_txflood 0] == -1} {
+ incr sl_txqueue($chan)
+ if {$sl_txqueue($chan) >= [lindex $sl_txflood 0]} {
+ sl_lock $chan "TEXT flood" ${botnet-nick}
+ }
+ }
+ set text [join [lrange $arg 1 end]]
+ if {[sl_checkaval $text] && [lsearch -exact $sl_avflood 0] == -1} {
+ set uhost [string trimleft [getchanhost $nick $chan] "~+-^="]
+ set hand [nick2hand $nick $chan]
+ if {$sl_ban && !$sl_locked($chan) && $nick != $botnick && ![matchattr $hand f|f $chan]} {
+ lappend sl_avbannick($chan) $nick ; lappend sl_avbanhost($chan) [string tolower $uhost]
+ utimer [lindex $sl_avflood 1] [list sl_avbanqueue $chan]
+ }
+ if {$sl_flooded($chan)} {return 0}
+ incr sl_avqueue($chan)
+ utimer [lindex $sl_avflood 1] [list sl_avqueuereset $chan]
+ if {$sl_avqueue($chan) >= [lindex $sl_avflood 0]} {
+ sl_lock $chan "AVALANCHE/TSUNAMI flood" ${botnet-nick}
+ }
+ }
+ return 0
+}
+
+proc sl_checkaval {text} {
+ global sl_tsunami
+ if {[regsub -all -- "\001|\007" $text "" temp] >= 3} {return 1}
+ if {$sl_tsunami && [regsub -all -- "\002|\003|\017|\026|\037" $text "" temp] >= $sl_tsunami} {return 1}
+ return 0
+}
+
+proc sl_nkflood {nick uhost hand chan newnick} {
+ global botnet-nick botnick sl_ban sl_banmax sl_flooded sl_globalban sl_locked sl_nickkick sl_nkbanhost sl_nkflood sl_nkflooding sl_nkqueue
+ set chan [string tolower $chan]
+ if {[isop $newnick $chan]} {return 0}
+ if {$sl_ban && !$sl_locked($chan) && $nick != $botnick && ![matchattr $hand f|f $chan]} {
+ lappend sl_nkbanhost($chan) [string tolower $uhost]
+ utimer [lindex $sl_nkflood 1] [list sl_nkbanqueue $chan]
+ }
+ if {!$sl_nickkick && $sl_flooded($chan) && $sl_locked($chan)} {
+ putserv "KICK $chan $newnick :NICK flooder"
+ set sl_nickkick 1 ; set sl_nkflooding($chan) [unixtime]
+ if {$sl_ban} {
+ set bhost [string tolower [sl_masktype $uhost]]
+ if {$sl_globalban} {
+ if {[llength [banlist]] < $sl_banmax && ![isban $bhost] && ![matchban $bhost]} {
+ newban $bhost sentinel "NICK flooder" $sl_ban
+ }
+ } else {
+ if {[llength [banlist $chan]] < $sl_banmax && ![isban $bhost $chan] && ![matchban $bhost $chan]} {
+ newchanban $chan $bhost sentinel "NICK flooder" $sl_ban
+ }
+ }
+ }
+ utimer [expr [rand 2] + 3] {set sl_nickkick 0}
+ return 0
+ }
+ if {$sl_flooded($chan)} {return 0}
+ incr sl_nkqueue($chan)
+ utimer [lindex $sl_nkflood 1] [list sl_nkqueuereset $chan]
+ if {$sl_nkqueue($chan) >= [lindex $sl_nkflood 0]} {
+ sl_lock $chan "NICK flood" ${botnet-nick}
+ }
+ return 0
+}
+
+proc sl_jflood {nick uhost hand chan} {
+ global botnet-nick botnick sl_ban sl_banmax sl_boban sl_bobanhost sl_bobannick sl_boflood sl_boqueue sl_flooded sl_globalban sl_jbanhost sl_jbannick sl_jflood sl_jqueue sl_locked sl_pqueue
+ if {$nick == $botnick} {
+ sl_setarray $chan
+ } else {
+ set ihost [string tolower [sl_masktype $uhost]]
+ if {[isignore $ihost]} {
+ killignore $ihost
+ }
+ set chan [string tolower $chan]
+ if {[lsearch -exact $sl_boflood 0] == -1 && [sl_checkbogus [lindex [split $uhost @] 0]]} {
+ if {!$sl_locked($chan) && ![matchattr $hand f|f $chan]} {
+ set bhost [string tolower [sl_masktype $uhost]]
+ if {$sl_boban && [botisop $chan] && !$sl_flooded($chan)} {
+ putserv "KICK $chan $nick :BOGUS username"
+ if {$sl_globalban} {
+ if {[llength [banlist]] < $sl_banmax && ![isban $bhost] && ![matchban $bhost]} {
+ newban $bhost sentinel "BOGUS username" $sl_boban
+ }
+ } else {
+ if {[llength [banlist $chan]] < $sl_banmax && ![isban $bhost $chan] && ![matchban $bhost $chan]} {
+ newchanban $chan $bhost sentinel "BOGUS username" $sl_boban
+ }
+ }
+ }
+ if {$sl_ban} {
+ lappend sl_bobannick($chan) $nick ; lappend sl_bobanhost($chan) [string tolower $uhost]
+ utimer [lindex $sl_boflood 1] [list sl_bobanqueue $chan]
+ }
+ }
+ if {!$sl_flooded($chan)} {
+ incr sl_boqueue($chan)
+ utimer [lindex $sl_boflood 1] [list sl_boqueuereset $chan]
+ if {$sl_boqueue($chan) >= [lindex $sl_boflood 0]} {
+ sl_lock $chan "BOGUS joins" ${botnet-nick}
+ }
+ }
+ }
+ if {[lsearch -exact $sl_jflood 0] == -1} {
+ if {$sl_ban && !$sl_locked($chan) && ![matchattr $hand f|f $chan]} {
+ lappend sl_jbannick($chan) $nick ; lappend sl_jbanhost($chan) [string tolower $uhost]
+ utimer [lindex $sl_jflood 1] [list sl_jbanqueue $chan]
+ }
+ if {$sl_flooded($chan)} {return 0}
+ incr sl_jqueue($chan)
+ utimer [lindex $sl_jflood 1] [list sl_jqueuereset $chan]
+ if {$sl_jqueue($chan) >= [lindex $sl_jflood 0] && $sl_pqueue($chan) >= [lindex $sl_jflood 0]} {
+ sl_lock $chan "JOIN-PART flood" ${botnet-nick}
+ }
+ }
+ }
+ return 0
+}
+
+proc sl_checkbogus {ident} {
+ if {[regsub -all -- "\[^\041-\176\]" $ident "" temp] >= 1} {return 1}
+ return 0
+}
+
+proc sl_pflood {nick uhost hand chan {msg ""}} {
+ global botnick sl_ban sl_flooded sl_jflood sl_locked sl_pbanhost sl_pbannick sl_pqueue
+ if {[lsearch -exact $sl_jflood 0] != -1} {return 0}
+ if {$nick == $botnick} {
+ if {![validchan $chan]} {
+ timer 5 [list sl_unsetarray $chan]
+ }
+ return 0
+ }
+ set chan [string tolower $chan]
+ if {$sl_ban && !$sl_locked($chan) && ![matchattr $hand f|f $chan]} {
+ lappend sl_pbannick($chan) $nick ; lappend sl_pbanhost($chan) [string tolower $uhost]
+ utimer [lindex $sl_jflood 1] [list sl_pbanqueue $chan]
+ }
+ if {$sl_flooded($chan)} {return 0}
+ incr sl_pqueue($chan)
+ utimer [lindex $sl_jflood 1] [list sl_pqueuereset $chan]
+ return 0
+}
+
+proc sl_pfloodk {nick uhost hand chan kicked reason} {
+ global botnick sl_flooded sl_jflood sl_pqueue
+ if {[lsearch -exact $sl_jflood 0] != -1} {return 0}
+ if {$kicked == $botnick} {return 0}
+ set chan [string tolower $chan]
+ if {$sl_flooded($chan)} {return 0}
+ incr sl_pqueue($chan)
+ utimer [lindex $sl_jflood 1] [list sl_pqueuereset $chan]
+ return 0
+}
+
+proc sl_lock {chan flood detected} {
+ global botnet-nick sl_bflooded sl_cfnotice sl_flooded sl_ilocktime sl_mlocktime sl_note
+ if {[string tolower $detected] == [string tolower ${botnet-nick}]} {
+ set sl_flooded($chan) 1 ; set sl_bflooded 1
+ if {[botisop $chan]} {
+ sl_quicklock $chan
+ sl_killutimer "sl_unlock $chan *"
+ sl_killutimer "set sl_bflooded 0"
+ if {$sl_mlocktime} {
+ utimer $sl_mlocktime [list sl_unlock $chan m]
+ }
+ if {$sl_ilocktime} {
+ utimer $sl_ilocktime [list sl_unlock $chan i]
+ }
+ utimer 120 {set sl_bflooded 0}
+ putlog "sentinel: $flood detected on $chan! Channel locked temporarily."
+ if {$sl_cfnotice != ""} {
+ puthelp "NOTICE $chan :$sl_cfnotice"
+ }
+ } else {
+ putlog "sentinel: $flood detected on $chan! Cannot lock channel because I'm not opped."
+ utimer 120 {set sl_bflooded 0}
+ }
+ } else {
+ putlog "sentinel: $flood detected by $detected on $chan!"
+ }
+ if {[info commands sendnote] != ""} {
+ foreach recipient $sl_note {
+ if {[validuser $recipient]} {
+ if {[string tolower $detected] == [string tolower ${botnet-nick}]} {
+ sendnote SENTINEL $recipient "$flood detected on $chan."
+ } else {
+ sendnote SENTINEL $recipient "$flood detected by $detected on $chan."
+ }
+ }
+ }
+ }
+ return 0
+}
+
+proc sl_unlock {chan umode} {
+ global sl_bflooded sl_bfmaxbans sl_flooded sl_ilocktime sl_mlocktime sl_nkflooding
+ if {[expr [unixtime] - $sl_nkflooding($chan)] < 12} {
+ putlog "sentinel: nick flooding still in progress on $chan - not removing +mi yet.."
+ set sl_flooded($chan) 1 ; set sl_bflooded 1
+ sl_killutimer "sl_unlock $chan *"
+ sl_killutimer "set sl_bflooded 0"
+ utimer $sl_mlocktime [list sl_unlock $chan m] ; utimer $sl_ilocktime [list sl_unlock $chan i]
+ utimer 120 {set sl_bflooded 0}
+ } else {
+ set sl_flooded($chan) 0
+ if {![botisop $chan]} {return 0}
+ if {$umode == "mi"} {
+ putlog "sentinel: flood was small, performing early unlock.."
+ }
+ if {[string match *i* $umode] && [string match *i* [lindex [split [getchanmode $chan]] 0]]} {
+ if {$sl_bfmaxbans && [llength [chanbans $chan]] >= $sl_bfmaxbans} {
+ putlog "sentinel: not removing +i on $chan due to full ban list."
+ } else {
+ pushmode $chan -i
+ putlog "sentinel: removed +i on $chan"
+ }
+ }
+ if {[string match *m* $umode] && [string match *m* [lindex [split [getchanmode $chan]] 0]]} {
+ pushmode $chan -m
+ putlog "sentinel: removed +m on $chan"
+ }
+ }
+ return 0
+}
+
+proc sl_mode {nick uhost hand chan mode victim} {
+ global botnick sl_ban sl_bfmaxbans sl_bfnotice sl_bfull sl_flooded sl_locked sl_note sl_unlocked
+ set chan [string tolower $chan]
+ if {$mode == "+b" && $sl_bfmaxbans && !$sl_bfull($chan) && ![string match *i* [lindex [split [getchanmode $chan]] 0]] && [botisop $chan] && [llength [chanbans $chan]] >= $sl_bfmaxbans} {
+ putserv "MODE $chan +i"
+ set sl_bfull($chan) 1
+ utimer 5 [list set sl_bfull($chan) 0]
+ putlog "sentinel: locked $chan due to full ban list!"
+ if {$sl_bfnotice != ""} {
+ puthelp "NOTICE $chan :$sl_bfnotice"
+ }
+ if {[info commands sendnote] != ""} {
+ foreach recipient $sl_note {
+ if {[validuser $recipient]} {
+ sendnote SENTINEL $recipient "Locked $chan due to full ban list."
+ }
+ }
+ }
+ } elseif {$mode == "+i" && $sl_flooded($chan)} {
+ set sl_locked($chan) 1
+ if {$sl_ban} {
+ sl_killutimer "sl_*banqueue $chan"
+ utimer 7 [list sl_dokicks $chan] ; utimer 16 [list sl_setbans $chan]
+ }
+ } elseif {$mode == "-i" || $mode == "-m"} {
+ set sl_locked($chan) 0
+ set sl_unlocked($chan) [unixtime]
+ if {$sl_flooded($chan)} {
+ set sl_flooded($chan) 0
+ if {$mode == "-i"} {
+ sl_killutimer "sl_unlock $chan i"
+ } else {
+ sl_killutimer "sl_unlock $chan m"
+ }
+ sl_killutimer "sl_unlock $chan mi"
+ if {$nick != $botnick} {
+ putlog "sentinel: $chan unlocked by $nick"
+ }
+ }
+ }
+ return 0
+}
+
+proc sl_dokicks {chan} {
+ global sl_avbannick sl_bobannick sl_ccbannick sl_kflooders sl_jbannick sl_pbannick
+ if {![botisop $chan]} {return 0}
+ set sl_kflooders 0
+ sl_kick $chan $sl_ccbannick($chan) "CTCP flooder" ; set sl_ccbannick($chan) ""
+ sl_kick $chan $sl_avbannick($chan) "AVALANCHE/TSUNAMI flooder" ; set sl_avbannick($chan) ""
+ sl_kick $chan $sl_bobannick($chan) "BOGUS username" ; set sl_bobannick($chan) ""
+ set jklist $sl_jbannick($chan) ; set pklist $sl_pbannick($chan)
+ if {$jklist != "" && $pklist != ""} {
+ set klist ""
+ foreach nick $jklist {
+ if {[lsearch -exact $pklist $nick] != -1} {
+ lappend klist $nick
+ }
+ }
+ sl_kick $chan $klist "JOIN-PART flooder"
+ }
+ set sl_jbannick($chan) "" ; set sl_pbannick($chan) ""
+ return 0
+}
+
+proc sl_kick {chan klist reason} {
+ global sl_kflooders sl_kicks
+ if {$klist != ""} {
+ set kicklist ""
+ foreach nick $klist {
+ if {[lsearch -exact $kicklist $nick] == -1} {
+ lappend kicklist $nick
+ }
+ }
+ unset nick
+ incr sl_kflooders [llength $kicklist]
+ foreach nick $kicklist {
+ if {[onchan $nick $chan] && ![onchansplit $nick $chan]} {
+ lappend ksend $nick
+ if {[llength $ksend] >= $sl_kicks} {
+ putserv "KICK $chan [join $ksend ,] :$reason"
+ unset ksend
+ }
+ }
+ }
+ if {[info exists ksend]} {
+ putserv "KICK $chan [join $ksend ,] :$reason"
+ }
+ }
+ return 0
+}
+
+proc sl_setbans {chan} {
+ global sl_avbanhost sl_bobanhost sl_ccbanhost sl_kflooders sl_jbanhost sl_nkbanhost sl_pbanhost sl_shortlock sl_unlocked sl_wideban
+ if {![botonchan $chan]} {return 0}
+ set sl_ccbanhost($chan) [sl_dfilter $sl_ccbanhost($chan)]
+ set sl_avbanhost($chan) [sl_dfilter $sl_avbanhost($chan)]
+ set sl_nkbanhost($chan) [sl_dfilter $sl_nkbanhost($chan)]
+ set sl_bobanhost($chan) [sl_dfilter $sl_bobanhost($chan)]
+ set sl_jbanhost($chan) [sl_dfilter $sl_jbanhost($chan)]
+ set sl_pbanhost($chan) [sl_dfilter $sl_pbanhost($chan)]
+ set blist ""
+ if {$sl_jbanhost($chan) != "" && $sl_pbanhost($chan) != ""} {
+ foreach bhost $sl_jbanhost($chan) {
+ if {[lsearch -exact $sl_pbanhost($chan) $bhost] != -1} {
+ lappend blist $bhost
+ }
+ }
+ }
+ set allbans [sl_dfilter [concat $sl_ccbanhost($chan) $sl_avbanhost($chan) $sl_nkbanhost($chan) $sl_bobanhost($chan) $blist]]
+ if {$sl_wideban} {
+ sl_ban $chan [sl_dcheck $allbans] "MULTIPLE IDENT/HOST flooders"
+ }
+ sl_ban $chan $sl_ccbanhost($chan) "CTCP flooder" ; set sl_ccbanhost($chan) ""
+ sl_ban $chan $sl_avbanhost($chan) "AVALANCHE/TSUNAMI flooder" ; set sl_avbanhost($chan) ""
+ sl_ban $chan $sl_nkbanhost($chan) "NICK flooder" ; set sl_nkbanhost($chan) ""
+ sl_ban $chan $sl_bobanhost($chan) "BOGUS username" ; set sl_bobanhost($chan) ""
+ sl_ban $chan $blist "JOIN-PART flooder"
+ set sl_jbanhost($chan) "" ; set sl_pbanhost($chan) ""
+ if {$sl_shortlock && $sl_kflooders <= 2 && [llength $allbans] <= 2 && [expr [unixtime] - $sl_unlocked($chan)] > 120} {
+ sl_killutimer "sl_unlock $chan *"
+ utimer 10 [list sl_unlock $chan mi]
+ }
+ return 0
+}
+
+proc sl_dfilter {list} {
+ set newlist ""
+ foreach item $list {
+ if {[lsearch -exact $newlist $item] == -1} {
+ lappend newlist $item
+ }
+ }
+ return $newlist
+}
+
+proc sl_dcheck {bhosts} {
+ set blist ""
+ foreach bhost $bhosts {
+ set baddr [lindex [split [maskhost $bhost] "@"] 1]
+ set bident [string trimleft [lindex [split $bhost "@"] 0] "~"]
+ if {![info exists baddrs($baddr)]} {
+ set baddrs($baddr) 1
+ } else {
+ incr baddrs($baddr)
+ }
+ if {![info exists bidents($bident)]} {
+ set bidents($bident) 1
+ } else {
+ incr bidents($bident)
+ }
+ }
+ foreach baddr [array names baddrs] {
+ if {$baddrs($baddr) >= 2} {
+ lappend blist *!*@$baddr
+ }
+ }
+ foreach bident [array names bidents] {
+ if {$bidents($bident) >= 2} {
+ lappend blist *!*$bident@*
+ }
+ }
+ return $blist
+}
+
+proc sl_ban {chan blist reason} {
+ global sl_ban sl_banmax sl_globalban
+ if {$blist != ""} {
+ if {$sl_globalban} {
+ foreach bhost $blist {
+ if {![string match *!* $bhost]} {
+ if {[matchban *!$bhost]} {continue}
+ set bhost [sl_masktype $bhost]
+ if {[isban $bhost]} {continue}
+ } else {
+ if {[isban $bhost]} {continue}
+ foreach ban [banlist] {
+ if {[lindex $ban 5] == "sentinel" && [string match $bhost [string tolower [lindex $ban 0]]]} {
+ killban $ban
+ }
+ }
+ }
+ if {[llength [banlist]] >= $sl_banmax} {continue}
+ newban $bhost sentinel $reason $sl_ban
+ putlog "sentinel: banned $bhost ($reason)"
+ sl_ignore $bhost * $reason
+ }
+ } else {
+ foreach bhost $blist {
+ if {![string match *!* $bhost]} {
+ if {[matchban *!$bhost $chan]} {continue}
+ set bhost [sl_masktype $bhost]
+ if {[isban $bhost $chan]} {continue}
+ } else {
+ if {[isban $bhost $chan]} {continue}
+ foreach ban [banlist $chan] {
+ if {[lindex $ban 5] == "sentinel" && [string match $bhost [string tolower [lindex $ban 0]]]} {
+ killchanban $chan $ban
+ }
+ }
+ }
+ if {[llength [banlist $chan]] >= $sl_banmax} {continue}
+ newchanban $chan $bhost sentinel $reason $sl_ban
+ putlog "sentinel: banned $bhost on $chan ($reason)"
+ sl_ignore $bhost * $reason
+ }
+ }
+ }
+ return 0
+}
+
+proc sl_ignore {ihost hand flood} {
+ global sl_igtime
+ if {$hand != "*"} {
+ foreach chan [channels] {
+ if {[matchattr $hand f|f $chan]} {return 0}
+ }
+ }
+ if {![string match *!* $ihost]} {
+ foreach ignore [ignorelist] {
+ if {[string match [string tolower [lindex $ignore 0]] $ihost]} {
+ return 0
+ }
+ }
+ set ihost [sl_masktype $ihost]
+ if {[isignore $ihost]} {return 0}
+ } else {
+ if {[isignore $ihost]} {return 0}
+ foreach ignore [ignorelist] {
+ if {[lindex $ignore 4] == "sentinel" && [string match $ihost [string tolower [lindex $ignore 0]]]} {
+ killignore $ignore
+ }
+ }
+ }
+ newignore $ihost sentinel $flood $sl_igtime
+ putlog "sentinel: added $ihost to ignore list ($flood)"
+ return 1
+}
+
+# queuereset procs allow all queue timers to be killed easily
+proc sl_ccqueuereset {chan} {
+ global sl_ccqueue
+ incr sl_ccqueue($chan) -1
+ return 0
+}
+
+proc sl_bcqueuereset {} {
+ global sl_bcqueue
+ incr sl_bcqueue -1
+ return 0
+}
+
+proc sl_bmqueuereset {} {
+ global sl_bmqueue
+ incr sl_bmqueue -1
+ return 0
+}
+
+proc sl_avqueuereset {chan} {
+ global sl_avqueue
+ incr sl_avqueue($chan) -1
+ return 0
+}
+
+proc sl_txqueuereset {} {
+ global sl_txqueue sl_txflood
+ foreach chan [string tolower [channels]] {
+ if {[info exists sl_txqueue($chan)]} {
+ set sl_txqueue($chan) 0
+ }
+ }
+ utimer [lindex $sl_txflood 1] sl_txqueuereset
+ return 0
+}
+
+proc sl_nkqueuereset {chan} {
+ global sl_nkqueue
+ incr sl_nkqueue($chan) -1
+ return 0
+}
+
+proc sl_boqueuereset {chan} {
+ global sl_boqueue
+ incr sl_boqueue($chan) -1
+ return 0
+}
+
+proc sl_jqueuereset {chan} {
+ global sl_jqueue
+ incr sl_jqueue($chan) -1
+ return 0
+}
+
+proc sl_pqueuereset {chan} {
+ global sl_pqueue
+ incr sl_pqueue($chan) -1
+ return 0
+}
+
+proc sl_ccbanqueue {chan} {
+ global sl_ccbanhost sl_ccbannick
+ set sl_ccbannick($chan) [lrange sl_ccbannick($chan) 1 end] ; set sl_ccbanhost($chan) [lrange sl_ccbanhost($chan) 1 end]
+ return 0
+}
+
+proc sl_avbanqueue {chan} {
+ global sl_avbanhost sl_avbannick
+ set sl_avbannick($chan) [lrange sl_avbannick($chan) 1 end] ; set sl_avbanhost($chan) [lrange sl_avbanhost($chan) 1 end]
+ return 0
+}
+
+proc sl_nkbanqueue {chan} {
+ global sl_nkbanhost
+ set sl_nkbanhost($chan) [lrange sl_nkbanhost($chan) 1 end]
+ return 0
+}
+
+proc sl_bobanqueue {chan} {
+ global sl_bobanhost sl_bobannick
+ set sl_bobannick($chan) [lrange sl_bobannick($chan) 1 end] ; set sl_bobanhost($chan) [lrange sl_bobanhost($chan) 1 end]
+ return 0
+}
+
+proc sl_jbanqueue {chan} {
+ global sl_jbanhost sl_jbannick
+ set sl_jbannick($chan) [lrange sl_jbannick($chan) 1 end] ; set sl_jbanhost($chan) [lrange sl_jbanhost($chan) 1 end]
+ return 0
+}
+
+proc sl_pbanqueue {chan} {
+ global sl_pbanhost sl_pbannick
+ set sl_pbannick($chan) [lrange sl_pbannick($chan) 1 end] ; set sl_pbanhost($chan) [lrange sl_pbanhost($chan) 1 end]
+ return 0
+}
+
+proc sl_flud {nick uhost hand type chan} {
+ global sl_flooded
+ set chan [string tolower $chan]
+ if {[validchan $chan] && $sl_flooded($chan)} {return 1}
+ return 0
+}
+
+proc sl_lc {nick uhost hand chan arg} {
+ global sl_lockcmds
+ set chan [string tolower $chan]
+ if {![botisop $chan]} {return 0}
+ if {$sl_lockcmds == 2 && ![isop $nick $chan]} {return 0}
+ sl_quicklock $chan
+ putlog "sentinel: channel lock requested by $hand on $chan"
+ return 0
+}
+
+proc sl_uc {nick uhost hand chan arg} {
+ global sl_lockcmds
+ set chan [string tolower $chan]
+ if {![botisop $chan]} {return 0}
+ if {$sl_lockcmds == 2 && ![isop $nick $chan]} {return 0}
+ putserv "MODE $chan -mi"
+ putlog "sentinel: channel unlock requested by $hand on $chan"
+ return 0
+}
+
+proc sl_dcclc {hand idx arg} {
+ global sl_lockflags
+ putcmdlog "#$hand# lock $arg"
+ set chan [lindex [split $arg] 0]
+ if {$chan == "-all"} {
+ if {![matchattr $hand $sl_lockflags]} {
+ putidx $idx "You're not global +$sl_lockflags." ; return 0
+ }
+ set locklist ""
+ foreach chan [channels] {
+ if {[botisop $chan]} {
+ sl_quicklock $chan
+ lappend locklist $chan
+ }
+ }
+ putidx $idx "Locked [join $locklist ", "]"
+ } else {
+ if {$chan == ""} {
+ set chan [lindex [console $idx] 0]
+ }
+ if {![validchan $chan]} {
+ putidx $idx "No such channel." ; return 0
+ } elseif {![matchattr $hand $sl_lockflags|$sl_lockflags $chan]} {
+ putidx $idx "You're not +$sl_lockflags on $chan." ; return 0
+ } elseif {![botonchan $chan]} {
+ putidx $idx "I'm not on $chan" ; return 0
+ } elseif {![botisop $chan]} {
+ putidx $idx "I'm not opped on $chan" ; return 0
+ }
+ sl_quicklock $chan
+ putidx $idx "Locked $chan"
+ }
+ return 0
+}
+
+proc sl_dccuc {hand idx arg} {
+ global sl_lockflags
+ putcmdlog "#$hand# unlock $arg"
+ set chan [lindex [split $arg] 0]
+ if {$chan == "-all"} {
+ if {![matchattr $hand $sl_lockflags]} {
+ putidx $idx "You're not global +$sl_lockflags." ; return 0
+ }
+ set locklist ""
+ foreach chan [channels] {
+ if {[botisop $chan]} {
+ putserv "MODE $chan -mi"
+ lappend locklist $chan
+ }
+ }
+ putidx $idx "Unlocked [join $locklist ", "]"
+ } else {
+ if {$chan == ""} {
+ set chan [lindex [console $idx] 0]
+ }
+ if {![validchan $chan]} {
+ putidx $idx "No such channel." ; return 0
+ } elseif {![matchattr $hand $sl_lockflags|$sl_lockflags $chan]} {
+ putidx $idx "You're not +$sl_lockflags on $chan." ; return 0
+ } elseif {![botonchan $chan]} {
+ putidx $idx "I'm not on $chan" ; return 0
+ } elseif {![botisop $chan]} {
+ putidx $idx "I'm not opped on $chan" ; return 0
+ }
+ putserv "MODE $chan -mi"
+ putidx $idx "Unlocked $chan"
+ }
+ return 0
+}
+
+proc sl_quicklock {chan} {
+ global numversion
+ if {$numversion < 1050000} {
+ putquick "MODE $chan +mi"
+ } else {
+ putquick "MODE $chan +mi" -next
+ }
+}
+
+proc sl_dcc {hand idx arg} {
+ global sl_avflood sl_ban sl_banmax sl_bcflood sl_boban sl_boflood sl_bmflood sl_bxsimul sl_bfmaxbans sl_ccflood sl_detectquits sl_globalban sl_igtime sl_jflood sl_kicks sl_lockcmds sl_lockflags sl_ilocktime sl_mlocktime sl_nkflood sl_note sl_shortlock sl_tsunami sl_txflood
+ putcmdlog "#$hand# sentinel $arg"
+ putidx $idx "This bot is protected by sentinel.tcl by slennox"
+ putidx $idx "Current settings"
+ if {[lsearch -exact $sl_bcflood 0] != -1} {
+ putidx $idx "- Bot CTCP flood: Off"
+ } else {
+ putidx $idx "- Bot CTCP flood: [lindex $sl_bcflood 0] in [lindex $sl_bcflood 1] secs"
+ }
+ if {[lsearch -exact $sl_bmflood 0] != -1} {
+ putidx $idx "- Bot MSG flood: Off"
+ } else {
+ putidx $idx "- Bot MSG flood: [lindex $sl_bmflood 0] in [lindex $sl_bmflood 1] secs"
+ }
+ if {[lsearch -exact $sl_ccflood 0] != -1} {
+ putidx $idx "- Channel CTCP flood: Off"
+ } else {
+ putidx $idx "- Channel CTCP flood: [lindex $sl_ccflood 0] in [lindex $sl_ccflood 1] secs"
+ }
+ if {[lsearch -exact $sl_avflood 0] != -1} {
+ putidx $idx "- Channel AVALANCHE flood: Off"
+ } else {
+ putidx $idx "- Channel AVALANCHE flood: [lindex $sl_avflood 0] in [lindex $sl_avflood 1] secs"
+ }
+ if {[lsearch -exact $sl_avflood 0] != -1 || !$sl_tsunami} {
+ putidx $idx "- Channel TSUNAMI flood: Off"
+ } else {
+ putidx $idx "- Channel TSUNAMI flood: [lindex $sl_avflood 0] in [lindex $sl_avflood 1] secs ($sl_tsunami ctrl codes / line)"
+ }
+ if {[lsearch -exact $sl_txflood 0] != -1} {
+ putidx $idx "- Channel TEXT flood: Off"
+ } else {
+ putidx $idx "- Channel TEXT flood: [lindex $sl_txflood 0] in [lindex $sl_txflood 1] secs"
+ }
+ if {[lsearch -exact $sl_boflood 0] != -1} {
+ putidx $idx "- Channel BOGUS flood: Off"
+ } else {
+ putidx $idx "- Channel BOGUS flood: [lindex $sl_boflood 0] in [lindex $sl_boflood 1] secs"
+ }
+ if {$sl_detectquits} {
+ set detectquits "quit detection ON"
+ } else {
+ set detectquits "quit detection OFF"
+ }
+ if {[lsearch -exact $sl_jflood 0] != -1} {
+ putidx $idx "- Channel JOIN-PART flood: Off"
+ } else {
+ putidx $idx "- Channel JOIN-PART flood: [lindex $sl_jflood 0] in [lindex $sl_jflood 1] secs ($detectquits)"
+ }
+ if {[lsearch -exact $sl_nkflood 0] != -1} {
+ putidx $idx "- Channel NICK flood: Off"
+ } else {
+ putidx $idx "- Channel NICK flood: [lindex $sl_nkflood 0] in [lindex $sl_nkflood 1] secs"
+ }
+ if {!$sl_ilocktime} {
+ putidx $idx "- Channel +i locktime: Indefinite"
+ } else {
+ putidx $idx "- Channel +i locktime: $sl_ilocktime secs"
+ }
+ if {!$sl_mlocktime} {
+ putidx $idx "- Channel +m locktime: Indefinite"
+ } else {
+ putidx $idx "- Channel +m locktime: $sl_mlocktime secs"
+ }
+ if {$sl_shortlock && $sl_ban} {
+ putidx $idx "- Small flood short lock: Active"
+ } else {
+ putidx $idx "- Small flood short lock: Inactive"
+ }
+ if {$sl_ban && $sl_ban < 120} {
+ putidx $idx "- Channel flood bans: $sl_ban mins"
+ } elseif {$sl_ban >= 120} {
+ putidx $idx "- Channel flood bans: [expr $sl_ban / 60] hrs"
+ } else {
+ putidx $idx "- Channel flood bans: Disabled"
+ }
+ if {!$sl_boban || [lsearch -exact $sl_boflood 0] != -1} {
+ putidx $idx "- Bogus username bans: Disabled"
+ } elseif {$sl_boban > 0 && $sl_boban < 120} {
+ putidx $idx "- Bogus username bans: $sl_boban mins"
+ } elseif {$sl_boban >= 120} {
+ putidx $idx "- Bogus username bans: [expr $sl_boban / 60] hrs"
+ }
+ if {$sl_ban || [lsearch -exact $sl_boflood 0] == -1} {
+ if {$sl_globalban} {
+ putidx $idx "- Ban type: Global [sl_masktype nick@host.domain]"
+ } else {
+ putidx $idx "- Ban type: Channel-specific [sl_masktype nick@host.domain]"
+ }
+ }
+ if {$sl_ban || [lsearch -exact $sl_boflood 0] == -1} {
+ putidx $idx "- Maximum bans: $sl_banmax"
+ }
+ if {$sl_igtime > 0 && $sl_igtime < 120} {
+ putidx $idx "- Flooder ignores: $sl_igtime mins"
+ } elseif {$sl_igtime >= 120} {
+ putidx $idx "- Flooder ignores: [expr $sl_igtime / 60] hrs"
+ } else {
+ putidx $idx "- Flooder ignores: Permanent"
+ }
+ if {$sl_ban} {
+ putidx $idx "- Kicks per line: $sl_kicks"
+ }
+ if {!$sl_bfmaxbans} {
+ putidx $idx "- Maximum channel bans: Disabled"
+ } else {
+ putidx $idx "- Maximum channel bans: $sl_bfmaxbans"
+ }
+ if {$sl_note != ""} {
+ putidx $idx "- Flood notification: Notifying [join $sl_note ", "]"
+ } else {
+ putidx $idx "- Flood notification: Off"
+ }
+ if {!$sl_lockcmds} {
+ putidx $idx "- Public lc/uc commands: Disabled"
+ } elseif {$sl_lockcmds == 1} {
+ putidx $idx "- Public lc/uc commands: Enabled (+$sl_lockflags users, ops not required)"
+ } elseif {$sl_lockcmds == 2} {
+ putidx $idx "- Public lc/uc commands: Enabled (+$sl_lockflags users, ops required)"
+ }
+ if {$sl_bxsimul} {
+ putidx $idx "- BitchX simulation: On"
+ } elseif {!$sl_bxsimul} {
+ putidx $idx "- BitchX simulation: Off"
+ }
+ return 0
+}
+
+if {$sl_bxsimul} {
+ bind raw - 001 sl_bxserverjoin
+ if {![info exists sl_bxonestack]} {
+ set sl_bxonestack 0
+ }
+ if {![info exists sl_bxversion]} {
+ set sl_bxversion [lindex {75p1+ 75p3+} [rand 2]]
+ }
+ set sl_bxsystem "*IX" ; set sl_bxwhoami $username ; set sl_bxmachine ""
+ catch {set sl_bxsystem [exec uname -s -r]}
+ catch {set sl_bxwhoami [exec id -un]}
+ catch {set sl_bxmachine [exec uname -n]}
+ set sl_bxjointime [unixtime]
+ proc sl_bxserverjoin {from keyword arg} {
+ global sl_bxjointime sl_bxisaway
+ set sl_bxjointime [unixtime] ; set sl_bxisaway 0
+ return 0
+ }
+ proc sl_bxaway {} {
+ global sl_bxjointime sl_bxisaway
+ if {!$sl_bxisaway} {
+ puthelp "AWAY :is away: (Auto-Away after 10 mins) \[\002BX\002-MsgLog [lindex {On Off} [rand 2]]\]"
+ set sl_bxisaway 1
+ } else {
+ puthelp "AWAY"
+ set sl_bxisaway 0 ; set sl_bxjointime [unixtime]
+ }
+ if {![string match *sl_bxaway* [timers]]} {
+ timer [expr [rand 300] + 10] sl_bxaway
+ }
+ return 0
+ }
+ if {![info exists sl_bxisaway]} {
+ set sl_bxisaway 0
+ }
+ if {![string match *sl_bxaway* [timers]]} {
+ timer [expr [rand 300] + 10] sl_bxaway
+ }
+}
+
+proc sl_setarray {chan} {
+ global sl_avbanhost sl_avbannick sl_avqueue sl_bfull sl_bobanhost sl_bobannick sl_boqueue sl_ccbanhost sl_ccbannick sl_ccqueue sl_flooded sl_jbanhost sl_jbannick sl_jqueue sl_locked sl_nkbanhost sl_nkflooding sl_nkqueue sl_pbanhost sl_pbannick sl_pqueue sl_txqueue sl_unlocked
+ set chan [string tolower $chan]
+ sl_killutimer "incr sl_*queue($chan) -1"
+ sl_killutimer "sl_*banqueue $chan"
+ sl_killutimer "sl_*queuereset $chan"
+ set sl_flooded($chan) 0 ; set sl_locked($chan) 0 ; set sl_unlocked($chan) [unixtime]
+ set sl_nkflooding($chan) [unixtime]
+ set sl_ccqueue($chan) 0 ; set sl_ccbanhost($chan) "" ; set sl_ccbannick($chan) ""
+ set sl_avqueue($chan) 0 ; set sl_avbanhost($chan) "" ; set sl_avbannick($chan) ""
+ set sl_txqueue($chan) 0
+ set sl_nkqueue($chan) 0 ; set sl_nkbanhost($chan) ""
+ set sl_boqueue($chan) 0 ; set sl_bobanhost($chan) "" ; set sl_bobannick($chan) ""
+ set sl_jqueue($chan) 0 ; set sl_jbanhost($chan) "" ; set sl_jbannick($chan) ""
+ set sl_pqueue($chan) 0 ; set sl_pbanhost($chan) "" ; set sl_pbannick($chan) ""
+ set sl_bfull($chan) 0
+ return 0
+}
+
+proc sl_unsetarray {chan} {
+ global sl_avbanhost sl_avbannick sl_avqueue sl_bfull sl_bobanhost sl_bobannick sl_boqueue sl_ccbanhost sl_ccbannick sl_ccqueue sl_flooded sl_jbanhost sl_jbannick sl_jqueue sl_locked sl_nkbanhost sl_nkflooding sl_nkqueue sl_pbanhost sl_pbannick sl_pqueue sl_txqueue sl_unlocked
+ set chan [string tolower $chan]
+ if {![validchan $chan] && [info exists sl_flooded($chan)]} {
+ unset sl_flooded($chan) sl_locked($chan) sl_unlocked($chan) sl_nkflooding($chan) sl_ccqueue($chan) sl_ccbanhost($chan) sl_ccbannick($chan) sl_avqueue($chan) sl_avbanhost($chan) sl_avbannick($chan) sl_txqueue($chan) sl_nkqueue($chan) sl_nkbanhost($chan) sl_boqueue($chan) sl_bobanhost($chan) sl_bobannick($chan) sl_jqueue($chan) sl_jbanhost($chan) sl_jbannick($chan) sl_pqueue($chan) sl_pbanhost($chan) sl_pbannick($chan) sl_bfull($chan)
+ }
+ return 0
+}
+
+proc sl_settimer {} {
+ foreach chan [channels] {
+ sl_setarray $chan
+ }
+ return 0
+}
+
+proc sl_killutimer {cmd} {
+ set n 0
+ regsub -all -- {\[} $cmd {\[} cmd ; regsub -all -- {\]} $cmd {\]} cmd
+ foreach tmr [utimers] {
+ if {[string match $cmd [join [lindex $tmr 1]]]} {
+ killutimer [lindex $tmr 2]
+ incr n
+ }
+ }
+ return $n
+}
+
+proc sl_masktype {uhost} {
+ global sl_masktype
+ switch -exact -- $sl_masktype {
+ 0 {return *!*[string range $uhost [string first @ $uhost] end]}
+ 1 {return *!*$uhost}
+ 2 {return *!*[lindex [split [maskhost $uhost] "!"] 1]}
+ }
+ return
+}
+
+if {![info exists sl_unlocked] && ![string match *sl_settimer* [utimers]]} {
+ utimer 3 sl_settimer
+}
+
+if {![info exists sl_bflooded]} {
+ set sl_bflooded 0
+}
+if {![info exists sl_bcqueue]} {
+ set sl_bcqueue 0
+}
+if {![info exists sl_bmqueue]} {
+ set sl_bmqueue 0
+}
+if {![info exists sl_nickkick]} {
+ set sl_nickkick 0
+}
+
+set sl_bcflood [split $sl_bcflood :] ; set sl_bmflood [split $sl_bmflood :]
+set sl_ccflood [split $sl_ccflood :] ; set sl_avflood [split $sl_avflood :]
+set sl_txflood [split $sl_txflood :] ; set sl_boflood [split $sl_boflood :]
+set sl_jflood [split $sl_jflood :] ; set sl_nkflood [split $sl_nkflood :]
+set sl_note [split $sl_note]
+
+if {$sl_ilocktime > 0 && $sl_ilocktime < 30} {
+ set sl_ilocktime 30
+}
+if {$sl_mlocktime > 0 && $sl_mlocktime < 30} {
+ set sl_mlocktime 30
+}
+
+set trigger-on-ignore 0
+if {!${kick-method}} {
+ set sl_kicks 8
+} else {
+ set sl_kicks ${kick-method}
+}
+
+if {$numversion <= 1040400} {
+ if {$numversion >= 1032100} {
+ set kick-bogus 0
+ }
+ if {$numversion >= 1032400} {
+ set ban-bogus 0
+ }
+}
+if {$numversion >= 1032400} {
+ set kick-fun 0 ; set ban-fun 0
+}
+if {$numversion >= 1032500} {
+ set ctcp-mode 0
+}
+
+if {![string match *sl_txqueuereset* [utimers]] && [lsearch -exact $sl_txflood 0] == -1} {
+ utimer [lindex $sl_txflood 1] sl_txqueuereset
+}
+
+bind pub $sl_lockflags|$sl_lockflags lc sl_lc
+bind pub $sl_lockflags|$sl_lockflags uc sl_uc
+bind dcc $sl_lockflags|$sl_lockflags lock sl_dcclc
+bind dcc $sl_lockflags|$sl_lockflags unlock sl_dccuc
+if {!$sl_lockcmds} {
+ unbind pub $sl_lockflags|$sl_lockflags lc sl_lc
+ unbind pub $sl_lockflags|$sl_lockflags uc sl_uc
+ rename sl_lc ""
+ rename sl_uc ""
+}
+bind dcc m|m sentinel sl_dcc
+bind raw - NOTICE sl_avflood
+bind raw - PRIVMSG sl_avflood
+if {[lsearch -exact $sl_avflood 0] != -1 && [lsearch -exact $sl_txflood 0] != -1} {
+ unbind raw - NOTICE sl_avflood
+ unbind raw - PRIVMSG sl_avflood
+ rename sl_avflood ""
+}
+bind ctcp - CLIENTINFO sl_ctcp
+bind ctcp - USERINFO sl_ctcp
+bind ctcp - VERSION sl_ctcp
+bind ctcp - FINGER sl_ctcp
+bind ctcp - ERRMSG sl_ctcp
+bind ctcp - ECHO sl_ctcp
+bind ctcp - INVITE sl_ctcp
+bind ctcp - WHOAMI sl_ctcp
+bind ctcp - OP sl_ctcp
+bind ctcp - OPS sl_ctcp
+bind ctcp - UNBAN sl_ctcp
+bind ctcp - PING sl_ctcp
+bind ctcp - TIME sl_ctcp
+bind msgm - * sl_bmflood
+if {[lsearch -exact $sl_bmflood 0] != -1} {
+ unbind msgm - * sl_bmflood
+ rename sl_bmflood ""
+}
+bind nick - * sl_nkflood
+if {[lsearch -exact $sl_nkflood 0] != -1} {
+ unbind nick - * sl_nkflood
+ rename sl_nkflood ""
+}
+bind join - * sl_jflood
+bind part - * sl_pflood
+bind sign - * sl_pflood
+if {![info exists sl_detectquits]} {
+ set sl_detectquits 0
+}
+if {!$sl_detectquits} {
+ unbind sign - * sl_pflood
+}
+bind kick - * sl_pfloodk
+bind flud - * sl_flud
+bind mode - * sl_mode
+
+putlog "Loaded sentinel.tcl v2.70 by slennox"
+
+return
diff --git a/tcl.tcl b/tcl.tcl
new file mode 100644
index 0000000..abc310b
--- /dev/null
+++ b/tcl.tcl
@@ -0,0 +1,69 @@
+# tcl.tcl - evaluate TCL statements in channels right from your eggdrop!
+# use at your own risk lmfao, i accept no liability
+
+# who has access to this command? (ideally you'd bind n|, but this doesn't support services accounts yet)
+set TCLNickServAccount "steering"
+
+bind pub * ,tcl tclchancmd
+bind pub * ,, tclchancmd
+
+proc msgSplit {chan msg} {
+ set max_length 410
+ set constructedMsg "PRIVMSG $chan :$msg"
+ # Check if the message is already within the allowed length
+ if {[string length $constructedMsg] <= $max_length} {
+ putquick "PRIVMSG $chan :$msg"
+ } else {
+ # Split the message into chunks of maximum length
+ set num_chunks [expr {([string length $msg] + $max_length - 1) / $max_length}]
+ for {set i 0} {$i < $num_chunks} {incr i} {
+ set chunk [string range $msg [expr {$i * $max_length}] [expr {($i + 1) * $max_length - 1}]]
+ putquick "PRIVMSG $chan :$chunk ([expr {$i + 1}]/$num_chunks)"
+ }
+ }
+}
+
+proc tclchancmd {nick user hand chan text} {
+ global TCLNickServAccount
+ set acct [getaccount $nick]
+ set accthand [finduser -account $acct]
+ if {$accthand != $TCLNickServAccount} {
+ putserv "PRIVMSG $chan :no."
+ return 1
+ }
+
+ set startTime [clock clicks -milliseconds]
+
+ set result [catch {uplevel #0 $text} resultText]
+
+ set endTime [clock clicks -milliseconds]
+ set timeTaken [expr $endTime - $startTime]
+
+ set lines [split $resultText "\n"]
+ set numLines [llength $lines]
+
+ if {[string trim $resultText] == ""} {
+ set resultText "(no output)"
+ }
+
+ if {$numLines <= 1} {
+ if {$result != 0} {
+ msgSplit $chan "error $result: $resultText -${timeTaken}ms-"
+ } else {
+ msgSplit $chan "ok: $resultText -${timeTaken}ms-"
+ }
+ } else {
+ if {$result != 0} {
+ putquick "PRIVMSG $chan :error $result: ($numLines lines) -${timeTaken}ms-"
+ } else {
+ putquick "PRIVMSG $chan :ok: ($numLines lines) -${timeTaken}ms-"
+ }
+ set outputtedLines 1
+ foreach line $lines {
+ msgSplit $chan "${outputtedLines}/${numLines}: $line"
+ incr outputtedLines
+ }
+ }
+}
+
+putlog "sourced tcl.tcl"
diff --git a/telegrab-chat.tcl b/telegrab-chat.tcl
new file mode 100644
index 0000000..b99dfcf
--- /dev/null
+++ b/telegrab-chat.tcl
@@ -0,0 +1,67 @@
+bind pubm * "#telegrab *" telegrab-privmsg
+
+proc telegrab-privmsg {nick uhost hand chan text} {
+ if {([string index $text 0] == "!") && ([string index $text 1] != " ")} {
+ return 0
+ }
+ if {([string match "*@archiveteam/Aramaki" $uhost]) || ([string match "*@hackint/user/h2ibot" $uhost])} {
+ return 0
+ }
+ if {[string index $text 0] == "\001"} {
+ return 0
+ }
+ if {[isop $nick $chan]} {
+ set nick "@$nick"
+ } elseif {[isvoice $nick $chan]} {
+ set nick "+$nick"
+ }
+
+ putserv "PRIVMSG #telegrab-chat :<$nick> $text"
+}
+
+bind ctcp * "ACTION" telegrab-action
+
+proc telegrab-action {nick uhost hand dest keyword text} {
+ if {$dest == "#telegrab"} {
+ if {[isop $nick $dest]} {
+ set nick "@$nick"
+ } elseif {[isvoice $nick $dest]} {
+ set nick "+$nick"
+ }
+ putserv "PRIVMSG #telegrab-chat :\001ACTION <$nick> $text\001"
+ }
+}
+
+bind notc * "*" telegrab-notice
+
+proc telegrab-notice {nick uhost hand text {dest ""}} {
+ if {$dest == "#telegrab"} {
+ if {[isop $nick $dest]} {
+ set nick "@$nick"
+ } elseif {[isvoice $nick $dest]} {
+ set nick "+$nick"
+ }
+ putserv "NOTICE #telegrab-chat :<$nick> $text"
+ }
+}
+
+bind out * "% sent" telegrab-out
+
+proc telegrab-out {queue text status} {
+ set botnick $::botnick
+ if {[botisop "#telegrab"]} {
+ set botnick "@$botnick"
+ } elseif {[botisvoice "#telegrab"]} {
+ set botnick "+$botnick"
+ }
+ if {[string match "PRIVMSG #telegrab *" $text]} {
+ if {[string match "*\\\[remind\\\]*" $text]} {
+ putserv "PRIVMSG #telegrab-chat :<$botnick> [join [lrange [split $text ":"] 1 end] ":"]"
+ }
+ }
+ if {[string match "NOTICE #telegrab *" $text]} {
+ if {[string match "*\\\[karma\\\]*" $text]} {
+ putserv "NOTICE #telegrab-chat :<$botnick> [join [lrange [split $text ":"] 1 end] ":"]"
+ }
+ }
+}
diff --git a/tell.tcl b/tell.tcl
new file mode 100644
index 0000000..26555fd
--- /dev/null
+++ b/tell.tcl
@@ -0,0 +1,94 @@
+package require sqlite3
+
+set tellfile "./tell.db"
+
+if {![file exists $tellfile]} {
+ sqlite3 tellDB $tellfile
+ tellDB eval {CREATE TABLE IF NOT EXISTS messages (id INTEGER PRIMARY KEY, target TEXT, sender TEXT, channel TEXT, message TEXT)}
+ tellDB close
+}
+
+bind pub * "!tell" tell
+
+proc tell {nick uhost hand chan text} {
+ global tellfile
+ sqlite3 tellDB $tellfile
+
+ set target [lindex [split $text] 0]
+ set lowtarget [string tolower $target]
+ set msg [join [lrange [split $text] 1 end] " "]
+ set message [format "\[%s\] <%s> %s" [clock format [clock seconds] -format "%Y-%m-%dT%H:%M:%SZ" -gmt true] $nick $msg]
+
+ if {$target == ""} {
+ putserv "NOTICE $chan :\[tell\] syntax: !tell <nick/hostmask> <message>"
+ return 0
+ }
+
+ if {$msg == ""} {
+ putserv "NOTICE $chan :\[tell\] syntax: !tell <nick/hostmask> <message>"
+ return 0
+ }
+
+ if {$target == $nick} {
+ putserv "NOTICE $nick :\[tell\] look at the shape you're in, talking to the walls again..."
+ return 0
+ }
+
+ if {[onchan $target $chan]} {
+ putserv "NOTICE $chan :\[tell\] $target is here - they should see your message :)"
+ putserv "PRIVMSG #fire-trail :\[tell\] !tell requested (but skipped, they're on the channel) by $nick in $chan for $target: $msg"
+ } else {
+ tellDB eval {INSERT INTO messages (target, sender, channel, message) VALUES ($lowtarget, $nick, $chan, $message)}
+ putserv "NOTICE $chan :\[tell\] ok, I'll tell $target when they join next"
+ putserv "PRIVMSG #fire-trail :\[tell\] !tell requested by $nick in $chan for $target: $msg"
+ }
+ tellDB close
+}
+
+bind join * * tellJoin
+
+proc tellJoin {nick uhost handle chan} {
+ global tellfile
+ sqlite3 tellDB $tellfile
+
+ set target [string tolower $nick]
+ set result [tellDB eval {SELECT message FROM messages WHERE target = $target AND channel = $chan}]
+
+ if {[llength $result] > 0} {
+ foreach row $result {
+ putserv "PRIVMSG $chan :\[tell\] $nick: $row"
+ }
+ tellDB eval {DELETE FROM messages WHERE target = $target AND channel = $chan}
+ }
+
+ set target [string cat [string tolower $nick] "!" [string tolower $uhost]]
+ set result [tellDB eval {SELECT message FROM messages WHERE $target GLOB LOWER(target) AND channel = $chan}]
+
+ if {[llength $result] > 0} {
+ foreach row $result {
+ putserv "PRIVMSG $chan :\[tell\] $nick: $row"
+ }
+ tellDB eval {DELETE FROM messages WHERE $target GLOB LOWER(target) AND channel = $chan}
+ }
+
+ tellDB close
+}
+
+bind nick * * tellNick
+
+proc tellNick {nick uhost handle chan newnick} {
+ global tellfile
+ sqlite3 tellDB $tellfile
+
+ set target [string tolower $newnick]
+ set result [tellDB eval {SELECT message FROM messages WHERE target = $target AND channel = $chan}]
+
+ if {[llength $result] > 0} {
+ foreach row $result {
+ putserv "PRIVMSG $chan :\[tell\] $newnick: $row"
+ }
+ tellDB eval {DELETE FROM messages WHERE target = $target AND channel = $chan}
+ }
+
+ tellDB close
+}
diff --git a/tiktok2proxitok.tcl b/tiktok2proxitok.tcl
new file mode 100644
index 0000000..49a9b0d
--- /dev/null
+++ b/tiktok2proxitok.tcl
@@ -0,0 +1,49 @@
+# proxitok2proxitok.tcl
+
+setudef flag proxitok
+
+bind pubm * "% *https://tiktok.com/*" outputProxiTokLink
+bind pubm * "% *http://tiktok.com/*" outputProxiTokLink
+bind pubm * "% *https://www.tiktok.com/*" outputProxiTokLink
+bind pubm * "% *http://www.tiktok.com/*" outputProxiTokLink
+
+set proxitokHost "farside.link/proxitok"
+set proxitokAntiFloodSeconds 5
+
+array set ::proxitokAntiFlood {}
+
+proc outputProxiTokLink {nick uhost hand chan text} {
+ if {[channel get $chan proxitok]} {
+ if {[string index $text 0] == "!"} {
+ putlog "\[tiktok2proxitok\] ignoring proxitok link from $nick!$uhost in $chan - line starts with ! ($text)"
+ return 0
+ }
+ global proxitokHost proxitokAntiFlood proxitokAntiFloodSeconds
+ set afbypass 0
+ set accthand [finduser -account [getaccount $nick]]
+ if {[string match $accthand "*"]} {
+ # not needed once eggdrop adds support for nickserv account to nick2hand
+ set accthand [nick2hand $nick]
+ }
+ if {(![string match $accthand "*"]) && ([matchattr $accthand +E])} {
+ putlog "\[proxitok2proxitok\] anti-flood: bypassing anti-floor system for $nick!$uhost in $chan (hand: $accthand), has chattr E"
+ set afbypass 1
+ }
+ # extract just the host from $uhost
+ set atPosition [string last "@" $uhost]
+ set host [string range $uhost [expr {$atPosition + 1}] end]
+ if {([info exists proxitokAntiFlood($host)]) && ($afbypass == 0)} {
+ set currentTime [clock seconds]
+ set elapsedTime [expr {$currentTime - $proxitokAntiFlood($host)}]
+ if {$elapsedTime <= $proxitokAntiFloodSeconds} {
+ putlog "\[proxitok2proxitok\] anti-flood: ignoring proxitok link from $nick!$uhost in $chan, ($elapsedTime/$proxitokAntiFloodSeconds seconds since last conversion for $host)"
+ return 1
+ }
+ }
+ set proxitokLinks [regexp -all -inline {https?://(?:www\.)?(?:tiktok)\.com/[@-_.a-zA-Z0-9/]+} $text]
+ # if someone just sends https://tiktok.com/, for example
+ if {$proxitokLinks == ""} { return }
+ putserv "PRIVMSG $chan :proxitok: [join [string map -nocase [list "@" "%40" "www." "" "http://" "https://" "tiktok.com" $proxitokHost] $proxitokLinks]]"
+ set proxitokAntiFlood($host) [clock seconds]
+ }
+}
diff --git a/transferinliner.tcl b/transferinliner.tcl
new file mode 100644
index 0000000..72813cb
--- /dev/null
+++ b/transferinliner.tcl
@@ -0,0 +1,78 @@
+# transferinliner.tcl
+
+setudef flag transferinliner
+
+bind pubm * "% *https://transfer.archivete.am/*" outputATTLink
+
+set ATTAntiFloodSeconds 5
+set ATTAntiFloodLinkSeconds 120
+set ATTExcludedExtensions {*.zst *.gz *.tar.gz *.tar *.tar.xz}
+
+array set ::ATTAntiFlood {}
+array set ::ATTAntiFloodLinks {}
+
+proc outputATTLink {nick uhost hand chan text} {
+ if {[channel get $chan transferinliner]} {
+ if {[string index $text 0] == "!"} {
+ putlog "\[transferinliner\] ignoring transfer link from $nick!$uhost in $chan - line starts with ! ($text)"
+ return 0
+ }
+ set accthand [finduser -account [getaccount $nick]]
+ if {[string match $accthand "*"]} {
+ # not needed once eggdrop adds support for nickserv account to nick2hand
+ set accthand [nick2hand $nick]
+ }
+ if {![string match $accthand "*"]} {
+ if {[matchattr $accthand +B]} {
+ putlog "\[transferinliner\] ignoring transfer link from $nick!$uhost in $chan (hand: $accthand), has chattr B"
+ return 1
+ }
+ }
+ global ATTAntiFlood ATTAntiFloodLinks ATTAntiFloodSeconds ATTAntiFloodLinkSeconds ATTExcludedExtensions
+ # extract just the host from $uhost
+ set atPosition [string last "@" $uhost]
+ set host [string range $uhost [expr {$atPosition + 1}] end]
+ if {[info exists ATTAntiFlood($host)]} {
+ set currentTime [clock seconds]
+ set elapsedTime [expr {$currentTime - $ATTAntiFlood($host)}]
+ if {$elapsedTime <= $ATTAntiFloodSeconds} {
+ putlog "\[transferinliner\] anti-flood: ignoring transfer link from $nick!$uhost in $chan, ($elapsedTime/$ATTAntiFloodSeconds seconds since last conversion for $host)"
+ return 1
+ }
+ }
+ set ATTLinks [regexp -all -inline {https?://transfer\.archivete\.am/(?!inline/)[^\s]*} $text]
+ set urls [split $ATTLinks " "]
+ set filteredUrls {}
+
+ foreach url $urls {
+ set excludeUrl 0
+ foreach extension $ATTExcludedExtensions {
+ if {[string match $extension $url]} {
+ set excludeUrl 1
+ break
+ }
+ }
+ if {$excludeUrl == 0} {
+ lappend filteredUrls $url
+ }
+ }
+
+ set ATTLinks [join $filteredUrls " "]
+ # if someone just sends https://transfer.archivete.am/, for example
+ if {$ATTLinks == "https://transfer.archivete.am/"} { return }
+ # only inline links were posted
+ if {$ATTLinks == ""} { return }
+ regsub -all {[^[:alnum:]]} $ATTLinks "" ATTLinksAF
+ if {[info exists ATTAntiFloodLinks($ATTLinksAF)]} {
+ set currentTime [clock seconds]
+ set elapsedTime [expr {$currentTime - $ATTAntiFloodLinks($ATTLinksAF)}]
+ if {$elapsedTime <= $ATTAntiFloodLinkSeconds} {
+ putlog "\[transferinliner\] anti-flood: ignoring transfer link from $nick!$uhost in $chan, ($elapsedTime/$ATTAntiFloodLinkSeconds seconds since last conversion for $ATTLinksAF)"
+ return 1
+ }
+ }
+ putserv "PRIVMSG $chan :inline (for browser viewing): [join [string map -nocase [list "http://" "https://" "https://transfer.archivete.am/" "https://transfer.archivete.am/inline/"] $ATTLinks]]"
+ set ATTAntiFlood($host) [clock seconds]
+ set ATTAntiFloodLinks($ATTLinksAF) [clock seconds]
+ }
+}
diff --git a/twitter2nitter-pre-expand.tcl b/twitter2nitter-pre-expand.tcl
new file mode 100644
index 0000000..edfcb44
--- /dev/null
+++ b/twitter2nitter-pre-expand.tcl
@@ -0,0 +1,63 @@
+# nitter2twitter.tcl
+
+setudef flag nitter
+
+bind pubm * "% *https://twitter.com/*" outputNitterLink
+bind pubm * "% *http://twitter.com/*" outputNitterLink
+bind pubm * "% *https://x.com/*" outputNitterLink
+bind pubm * "% *http://x.com/*" outputNitterLink
+
+set nitterHost "nitter.net"
+set nitterAntiFloodSeconds 5
+
+array set ::nitterAntiFlood {}
+
+proc outputNitterLink {nick uhost hand chan text} {
+ if {[channel get $chan nitter]} {
+ if {[string index $text 0] == "!"} {
+ putlog "\[twitter2nitter\] ignoring twitter link from $nick!$uhost in $chan - line starts with ! ($text)"
+ return 0
+ }
+ global nitterHost nitterAntiFlood nitterAntiFloodSeconds
+ # extract just the host from $uhost
+ set atPosition [string last "@" $uhost]
+ set host [string range $uhost [expr {$atPosition + 1}] end]
+ if {[info exists nitterAntiFlood($host)]} {
+ set currentTime [clock seconds]
+ set elapsedTime [expr {$currentTime - $nitterAntiFlood($host)}]
+ if {$elapsedTime <= $nitterAntiFloodSeconds} {
+ putlog "\[twitter2nitter\] anti-flood: ignoring twitter link from $nick!$uhost in $chan, ($elapsedTime/$nitterAntiFloodSeconds seconds since last conversion for $host)"
+ return 1
+ }
+ }
+ set twitterLinks [regexp -all -inline {https?://(?:twitter|x)\.com/[-_.a-zA-Z0-9/]+} $text]
+ # if someone just sends https://twitter.com/, for example
+ if {$twitterLinks == ""} { return }
+ putserv "PRIVMSG $chan :nitter: [join [string map -nocase [list "http://" "https://" "twitter.com" $nitterHost "x.com" $nitterHost] $twitterLinks]]"
+ set nitterAntiFlood($host) [clock seconds]
+ }
+}
+
+# this should NOT be enabled without care, is currently only used in #hackernews since 'rss' doesn't auto-convert twitter links in submissions
+# there are no flood controls etc like above since 'rss' posts fast and this is only to be enabled in special cases
+# note: currently `/notice #channel https://twitter.com/twitter` won't work, there needs to be something in front of it. a wildcard in `bind notc` doesn't seem to allow for `*` to match for nothing as it does in `bind pubm` (also tried `%` for "matches 0 or more non-space characters", no dice)
+
+setudef flag nitternotice
+
+bind notc * "% *https://twitter.com/*" processNitterNotice
+bind notc * "% *http://twitter.com/*" processNitterNotice
+bind notc * "% *https://x.com/*" processNitterNotice
+bind notc * "% *http://x.com/*" processNitterNotice
+
+proc processNitterNotice {nick uhost hand text {chan ""}} {
+ if {$chan == ""} {
+ # notice was delivered directly to the bot, ignore it
+ return 0
+ } else {
+ if {[channel get $chan nitternotice]} {
+ global nitterHost
+ set twitterLinks [regexp -all -inline {https?://(?:twitter|x)\.com/[-_.a-zA-Z0-9/]+} $text]
+ putserv "NOTICE $chan :nitter: [join [string map -nocase [list "http://" "https://" "twitter.com" $nitterHost "x.com" $nitterHost] $twitterLinks]]"
+ }
+ }
+}
diff --git a/twitter2nitter.tcl b/twitter2nitter.tcl
new file mode 100644
index 0000000..0f7478f
--- /dev/null
+++ b/twitter2nitter.tcl
@@ -0,0 +1,90 @@
+# nitter2twitter.tcl
+
+setudef flag nitter
+
+#unbind pubm * "% *https://twitter.com/*" outputNitterLink
+#unbind pubm * "% *http://twitter.com/*" outputNitterLink
+#unbind pubm * "% *https://x.com/*" outputNitterLink
+#unbind pubm * "% *http://x.com/*" outputNitterLink
+
+#unbind pubm * "% *https://fixupx.com/*" outputNitterLink
+#unbind pubm * "% *https://fixvx.com/*" outputNitterLink
+#unbind pubm * "% *https://fxtwitter.com/*" outputNitterLink
+#unbind pubm * "% *https://twittpr.com/*" outputNitterLink
+#unbind pubm * "% *https://vxtwitter.com/*" outputNitterLink
+#unbind pubm * "% *http://fixupx.com/*" outputNitterLink
+#unbind pubm * "% *http://fixvx.com/*" outputNitterLink
+#unbind pubm * "% *http://fxtwitter.com/*" outputNitterLink
+#unbind pubm * "% *http://twittpr.com/*" outputNitterLink
+#unbind pubm * "% *http://vxtwitter.com/*" outputNitterLink
+
+bind pubm * "% *" outputNitterLink
+
+set nitterHost "nitter.lucabased.xyz"
+set nitterAntiFloodSeconds 5
+
+array set ::nitterAntiFlood {}
+
+proc outputNitterLink {nick uhost hand chan text} {
+ if {[channel get $chan nitter]} {
+ if {![regexp -- {https?://(twitter|x|fixupx|fixvx|fxtwitter|twittpr|vxtwitter)\.com} $text]} {
+ # no twitters found, return
+ return 0
+ }
+ if {[string index $text 0] == "!"} {
+ putlog "\[twitter2nitter\] ignoring twitter link from $nick!$uhost in $chan - line starts with ! ($text)"
+ return 0
+ }
+ global nitterHost nitterAntiFlood nitterAntiFloodSeconds
+ set afbypass 0
+ set accthand [finduser -account [getaccount $nick]]
+ if {[string match $accthand "*"]} {
+ # not needed once eggdrop adds support for nickserv account to nick2hand
+ set accthand [nick2hand $nick]
+ }
+ if {(![string match $accthand "*"]) && ([matchattr $accthand +E])} {
+ putlog "\[twitter2nitter\] anti-flood: bypassing anti-flood system for $nick!$uhost in $chan (hand: $accthand), has chattr E"
+ set afbypass 1
+ }
+ # extract just the host from $uhost
+ set atPosition [string last "@" $uhost]
+ set host [string range $uhost [expr {$atPosition + 1}] end]
+ if {([info exists nitterAntiFlood($host)]) && ($afbypass == 0)} {
+ set currentTime [clock seconds]
+ set elapsedTime [expr {$currentTime - $nitterAntiFlood($host)}]
+ if {$elapsedTime <= $nitterAntiFloodSeconds} {
+ putlog "\[twitter2nitter\] anti-flood: ignoring twitter link from $nick!$uhost in $chan, ($elapsedTime/$nitterAntiFloodSeconds seconds since last conversion for $host)"
+ return 1
+ }
+ }
+ set twitterLinks [regexp -all -inline {https?://(?:twitter|x|fixupx|fixvx|fxtwitter|twittpr|vxtwitter)\.com/[-_.a-zA-Z0-9/]+} $text]
+ # if someone just sends https://twitter.com/, for example
+ if {$twitterLinks == ""} { return }
+ putserv "PRIVMSG $chan :nitter: [join [string map -nocase [list "http://" "https://" "fixupx.com" $nitterHost "fixvx.com" $nitterHost "fxtwitter.com" $nitterHost "twittpr.com" $nitterHost "vxtwitter.com" $nitterHost "twitter.com" $nitterHost "x.com" $nitterHost] $twitterLinks]]"
+ set nitterAntiFlood($host) [clock seconds]
+ }
+}
+
+# this should NOT be enabled without care, is currently only used in #hackernews since 'rss' doesn't auto-convert twitter links in submissions
+# there are no flood controls etc like above since 'rss' posts fast and this is only to be enabled in special cases
+# note: currently `/notice #channel https://twitter.com/twitter` won't work, there needs to be something in front of it. a wildcard in `bind notc` doesn't seem to allow for `*` to match for nothing as it does in `bind pubm` (also tried `%` for "matches 0 or more non-space characters", no dice)
+
+setudef flag nitternotice
+
+bind notc * "% *https://twitter.com/*" processNitterNotice
+bind notc * "% *http://twitter.com/*" processNitterNotice
+bind notc * "% *https://x.com/*" processNitterNotice
+bind notc * "% *http://x.com/*" processNitterNotice
+
+proc processNitterNotice {nick uhost hand text {chan ""}} {
+ if {$chan == ""} {
+ # notice was delivered directly to the bot, ignore it
+ return 0
+ } else {
+ if {[channel get $chan nitternotice]} {
+ global nitterHost
+ set twitterLinks [regexp -all -inline {https?://(?:twitter|x)\.com/[-_.a-zA-Z0-9/]+} $text]
+ putserv "NOTICE $chan :nitter: [join [string map -nocase [list "http://" "https://" "twitter.com" $nitterHost "x.com" $nitterHost] $twitterLinks]]"
+ }
+ }
+}
diff --git a/userinfo.tcl b/userinfo.tcl
new file mode 100644
index 0000000..84629ad
--- /dev/null
+++ b/userinfo.tcl
@@ -0,0 +1,286 @@
+# userinfo.tcl v1.08 for Eggdrop 1.4.3 and higher
+# Scott G. Taylor -- ButchBub!staylor@mrynet.com
+#
+# v1.00 ButchBub 14 July 1997 -Original release. Based on
+# whois.tcl "URL" commands.
+# v1.01 Beldin 11 November 1997 -1.3 only version
+# v1.02 Kirk 19 June 1998 -extremely small fixes
+# v1.03 guppy 17 March 1999 -small fixes again
+# v1.04 Ernst 15 June 1999 -fix for egg 1.3.x + TCL 8.0
+# v1.05 Dude 14 July 1999 -small cosmetic/typo fixes
+# -$lastbind bug work around fix
+# -added userinfo_loaded var
+# -fixed bug in dcc_chuserinfo proc
+# -unbinds removed user fields
+# -dcc .showfields command added
+# -deletes removed userinfo fields
+# from the whois-fields list.
+# v1.06 guppy 19 March 2000 -removed lastbind workaround since
+# lastbind is fixed in eggdrop1.4.3
+# v1.07 TaKeDa 20 August 2001 -now script works also on bots,
+# which didn't have server module loaded
+# -added new fields PHONE and ICQ
+# v1.08 mortmann 16 July 2020 -added new fields YOUTUBE and TWITCH
+#
+# TO USE: o Set the desired userinfo field keywords to the
+# `userinfo-fields' line below where indicated.
+# o Load this script on a 1.4.3 or later Eggdrop bot.
+# o Begin having users save the desired information. If you
+# choose to add the default "IRL" field, they just use
+# the IRC command: /MSG <botnick> irl Joe Blow.
+# o See the new information now appear with the whois command.
+#
+# This script enhances the `whois' output utilizing the `whois-fields'
+# option of eggdrop 1.1-grant and later versions. It adds the functionality
+# of whois.tcl used in pre-1.1-grant versions.
+#
+# The fields desired to be maintained in the userfile `xtra' information
+# should be put in `userinfo-fields'. This is different than the Eggdrop
+# configuration variable `whois-fields' in that this script will add the
+# commands to change these fields. It will also add these desired fields
+# to the `whois-fields' itself, so do not define them there as well. The
+# fields added in `userinfo-fields' will be converted to upper case for
+# aesthetics in the `whois' command output.
+#
+# The commands that will be added to the running eggdrop are:
+# (<info> will be the respective userfile field added in `userinfo-fields')
+#
+# TYPE COMMAND USAGE
+# ====== ============== ========================================
+# msg <info> To change your <info> via /MSG.
+# dcc .<info> To change your <info> via DCC.
+# dcc .ch<info> To change someone else's <info> via DCC.
+#
+# Currently supported fields and commands:
+#
+# FIELD USAGE
+# ===== =====================
+# URL WWW page URL
+# IRL In Real Life name
+# BF Boyfriend
+# GF Girlfriend
+# DOB Birthday (Date Of Birth)
+# EMAIL Email address
+# PHONE Phone number
+# ICQ ICQ number
+# YOUTUBE YouTube channel
+# TWITCH Twitch channel
+
+
+################################
+# Set your desired fields here #
+################################
+
+set userinfo-fields "URL BF GF IRL EMAIL DOB PHONE ICQ YOUTUBE TWITCH"
+
+# This script's identification
+
+set userinfover "Userinfo TCL v1.08"
+
+# This script is NOT for pre-1.4.3 versions.
+
+catch { set numversion }
+if {![info exists numversion] || ($numversion < 1040300)} {
+ putlog "*** Can't load $userinfover -- At least Eggdrop v1.4.3 required"
+ return 0
+}
+
+# Make sure we don't bail because whois and/or userinfo-fields aren't set.
+
+if { ![info exists whois-fields]} { set whois-fields "" }
+if { ![info exists userinfo-fields]} { set userinfo-fields "" }
+
+# Add only the userinfo-fields not already in the whois-fields list.
+
+foreach field [string tolower ${userinfo-fields}] {
+ if { [lsearch -exact [string tolower ${whois-fields}] $field] == -1 } { append whois-fields " " [string toupper $field] }
+}
+
+# If olduserinfo-fields doesn't exist, create it.
+
+if { ![info exists olduserinfo-fields] } { set olduserinfo-fields ${userinfo-fields} }
+
+# Delete only the userinfo-fields that have been removed but are still
+# listed in the whois-fields list.
+
+foreach field [string tolower ${olduserinfo-fields}] {
+ if { [lsearch -exact [string tolower ${whois-fields}] $field] >= 0 && [lsearch -exact [string tolower ${userinfo-fields}] $field] == -1 } {
+ set fieldtmp [lsearch -exact [string tolower ${whois-fields}] $field]
+ set whois-fields [lreplace ${whois-fields} $fieldtmp $fieldtmp]
+ }
+}
+
+# If olduserinfo-fields don't equal userinfo-fields, lets run through the
+# old list of user fields and compare them with the current list. this way
+# any fields that have been removed that were originally in the list will
+# have their msg/dcc commands unbinded so you don't have to do a restart.
+
+if {[info commands putserv] == ""} {
+ set isservermod 0
+} else {
+ set isservermod 1
+}
+
+if { [string tolower ${olduserinfo-fields}] != [string tolower ${userinfo-fields}] } {
+ foreach field [string tolower ${olduserinfo-fields}] {
+ if { [lsearch -exact [string tolower ${userinfo-fields}] $field] == -1 } {
+ if $isservermod {unbind msg - $field msg_setuserinfo}
+ unbind dcc - $field dcc_setuserinfo
+ unbind dcc m ch$field dcc_chuserinfo
+ }
+ }
+ set olduserinfo-fields ${userinfo-fields}
+}
+
+# Run through the list of user info fields and bind their commands
+
+if { ${userinfo-fields} != "" } {
+ foreach field [string tolower ${userinfo-fields}] {
+ if $isservermod {bind msg - $field msg_setuserinfo}
+ bind dcc - $field dcc_setuserinfo
+ bind dcc m ch$field dcc_chuserinfo
+ }
+}
+
+# This is the `/msg <info>' procedure
+if $isservermod {
+
+proc msg_setuserinfo {nick uhost hand arg} {
+ global lastbind quiet-reject userinfo-fields
+ set userinfo [string toupper $lastbind]
+ set arg [cleanarg $arg]
+ set ignore 1
+ foreach channel [channels] {
+ if {[onchan $nick $channel]} {
+ set ignore 0
+ }
+ }
+ if {$ignore} {
+ return 0
+ }
+ if {$hand != "*"} {
+ if {$arg != ""} {
+ if {[string tolower $arg] == "none"} {
+ putserv "NOTICE $nick :Removed your $userinfo line."
+ setuser $hand XTRA $userinfo ""
+ } {
+ putserv "NOTICE $nick :Now: $arg"
+ setuser $hand XTRA $userinfo "[string range $arg 0 159]"
+ }
+ } {
+ if {[getuser $hand XTRA $userinfo] == ""} {
+ putserv "NOTICE $nick :You have no $userinfo set."
+ } {
+ putserv "NOTICE $nick :Currently: [getuser $hand XTRA $userinfo]"
+ }
+ }
+ } else {
+ if {${quiet-reject} != 1} {
+ putserv "NOTICE $nick :You must be a registered user to use this feature."
+ }
+ }
+ putcmdlog "($nick!$uhost) !$hand! $userinfo $arg"
+ return 0
+}
+
+#checking for server module
+}
+
+# This is the dcc '.<info>' procedure.
+
+proc dcc_setuserinfo {hand idx arg} {
+ global lastbind userinfo-fields
+ set userinfo [string toupper $lastbind]
+ set arg [cleanarg $arg]
+ if {$arg != ""} {
+ if {[string tolower $arg] == "none"} {
+ putdcc $idx "Removed your $userinfo line."
+ setuser $hand XTRA $userinfo ""
+ } {
+ putdcc $idx "Now: $arg"
+ setuser $hand XTRA $userinfo "[string range $arg 0 159]"
+ }
+ } {
+ if {[getuser $hand XTRA $userinfo] == ""} {
+ putdcc $idx "You have no $userinfo set."
+ } {
+ putdcc $idx "Currently: [getuser $hand XTRA $userinfo]"
+ }
+ }
+ putcmdlog "#$hand# [string tolower $userinfo] $arg"
+ return 0
+}
+
+# This is the DCC `.ch<info>' procedure
+
+proc dcc_chuserinfo {hand idx arg} {
+ global lastbind userinfo-fields
+ set userinfo [string toupper [string range $lastbind 2 end]]
+ set arg [cleanarg $arg]
+ if { $arg == "" } {
+ putdcc $idx "syntax: .ch[string tolower $userinfo] <who> \[<[string tolower $userinfo]>|NONE\]"
+ return 0
+ }
+ set who [lindex [split $arg] 0]
+ if {![validuser $who]} {
+ putdcc $idx "$who is not a valid user."
+ return 0
+ }
+ if {[llength [split $arg]] == 1} {
+ set info ""
+ } {
+ set info [lrange [split $arg] 1 end]
+ }
+ if {$info != ""} {
+ if {[string tolower $info] == "none"} {
+ putdcc $idx "Removed $who's $userinfo line."
+ setuser $who XTRA $userinfo ""
+ putcmdlog "$userinfo for $who removed by $hand"
+ } {
+ putdcc $idx "Now: $info"
+ setuser $who XTRA $userinfo "$info"
+ putcmdlog "$userinfo for $who set to \"$info\" by $hand"
+ }
+ } {
+ if {[getuser $who XTRA $userinfo] == ""} {
+ putdcc $idx "$who has no $userinfo set."
+ } {
+ putdcc $idx "Currently: [getuser $who XTRA $userinfo]"
+ }
+ }
+ return 0
+}
+
+bind dcc m showfields showfields
+
+proc showfields {hand idx arg} {
+ global userinfo-fields
+ if { ${userinfo-fields} == "" } {
+ putdcc $idx "There are no user info fields set."
+ return 0
+ }
+ putdcc $idx "Currently: [string toupper ${userinfo-fields}]"
+ putcmdlog "#$hand# showfields"
+ return 0
+}
+
+proc cleanarg {arg} {
+ set response ""
+ for {set i 0} {$i < [string length $arg]} {incr i} {
+ set char [string index $arg $i]
+ if {($char != "\12") && ($char != "\15")} {
+ append response $char
+ }
+ }
+ return $response
+}
+
+# Set userinfo_loaded variable to indicate that the script was successfully
+# loaded. this can be used in scripts that make use of the userinfo tcl.
+
+set userinfo_loaded 1
+
+# Announce that we've loaded the script.
+
+putlog "$userinfover loaded (${userinfo-fields})."
+putlog "use '.help userinfo' for commands."
diff --git a/weed b/weed
new file mode 100755
index 0000000..d836043
--- /dev/null
+++ b/weed
@@ -0,0 +1,591 @@
+#! /bin/sh
+# This trick is borrowed from Tothwolf's Wolfpack \
+# Check for working 'grep -E' before using 'egrep' \
+if echo a | (grep -E '(a|b)') >/dev/null 2>&1; \
+then \
+ egrep="grep -E"; \
+else \
+ egrep=egrep; \
+fi; \
+# Search for tclsh[0-9].[0-9] in each valid dir in PATH \
+for dir in $(echo $PATH | sed 's/:/ /g'); \
+do \
+ if test -d $dir; \
+ then \
+ files=$(/bin/ls $dir | $egrep '^tclsh[0-9]\.[0-9]$'); \
+ if test "$files" != ""; \
+ then \
+ versions="${versions:+$versions }$(echo $files | sed 's/tclsh//g')"; \
+ fi; \
+ fi; \
+done; \
+for ver in $versions; \
+do \
+ tmpver=$(echo $ver | sed 's/\.//g'); \
+ if test "$lasttmpver" != ""; \
+ then \
+ if test "$tmpver" -gt "$lasttmpver"; \
+ then \
+ lastver=$ver; \
+ lasttmpver=$tmpver; \
+ fi; \
+ else \
+ lastver=$ver; \
+ lasttmpver=$tmpver; \
+ fi; \
+done; \
+exec tclsh$lastver "$0" ${1+"$@"}
+#
+# weed out certain undesirables from an eggdrop userlist
+# try just typing 'tclsh weed' to find out the options
+# Robey Pointer, November 1994
+#
+# <cmwagner@gate.net>:
+# I did a few bug fixes to the original weed script, things changed...
+#
+# when specifying other weed options they would unset the User() field and
+# a maxlast weed would try and weed again and cause the script to stop due
+# to User() being already unset (array nonexistent)
+#
+# when loadUserFile encountered an xtra field it would try and use the $info
+# variable, which was supposed to be $xtra (something overlooked when the
+# line was cut and pasted -- I hate it when that happens)
+#
+# changed the formatting of the saved weed file to match more closely to
+# eggdrop 0.9tp (so this may cause incompatibilities), but when a hostmask
+# field exactly matched 40 characters it would save it with no spaces after
+# it and eggdrop would reject the user record. I know I could have easily
+# changed one character, but I couldn't help myself. <grin>
+# 5 march 1996
+#
+# <robey, 23jul1996>:
+# upgrade for v2 userfiles
+# <bruce s, 04sep1996>:
+# fixed xtra field from getting truncated
+# <robey, 20sep1996>:
+# stopped it from mangling channel ban lists
+# <Ec|ipse & dtM, 10jun1997>:
+# upgrade for v3 userfiles
+# <Ec|ipse 18jun1997>:
+# added an option to remove users from unwanted channels
+# <Ec|ipse 28oct1997>:
+# upgrade for v4 userfiles, with v3 converter
+# <Ernst 8mar1998>:
+# fixed bug "list element in braces followed by X instead of space"
+# (the use of "lrange" where you aren't sure if it's a list is bad)
+# fixed --CONSOLE item not being included, creating "user" --CONSOLE
+# <Ernst 1apr1998>:
+# two more improper occurrences of "lrange" removed
+# <rtc 20sep1999>:
+# removed ancient way of determining the current time.
+# <Tothwolf 21oct1999>:
+# [clock] isn't in all versions of Tcl...
+# <guppy 12Apr2001>:
+# borrowed code from Tothwolf's Wolfpack to find tclsh better
+#
+
+set exempt {*ban *ignore}
+set exemptops 0 ; set exemptmasters 0 ; set exemptfriends 0
+set exemptparty 0 ; set exemptfile 0 ; set exemptchanm 0
+set exemptbotm 0 ; set exemptchann 0 ; set exemptchanf 0
+set exemptchano 0
+set maxlast 0 ; set maxban 0 ; set maxignore 0
+set weedops 0 ; set weedmasters 0 ; set weednopw 0
+set stripops 0 ; set stripmasters 0 ; set weedlurkers 0
+set chanrem {}
+set convert 0
+
+if {![string compare "" [info commands clock]]} then {
+ set fd [open "/tmp/egg.timer." w]
+ close $fd
+ set CURRENT [file atime "/tmp/egg.timer."]
+ exec rm -f /tmp/egg.timer.
+} else {
+ set CURRENT [clock seconds]
+}
+
+if {$argc < 1} {
+ puts stdout "\nUsage: weed <userfile> \[options\]"
+ puts stdout " (weeds out users from an eggdrop userlist)"
+ puts stdout "Output goes to <userfile>.weed"
+ puts stdout "Possible options:"
+ puts stdout " -<nick> exempt this user from weeding"
+ puts stdout " ^o ^m ^f exempt ops or masters or friends"
+ puts stdout " ^co ^cm ^cf exempt chanops or chanmasters or chanfriends"
+ puts stdout " ^cn exempt chanowner"
+ puts stdout " ^p ^x exempt party-line or file-area users"
+ puts stdout " ^b exempt botnet master"
+ puts stdout " +<days> weed: haven't been seen in N days"
+ puts stdout " :n weed: haven't EVER been seen"
+ puts stdout " :o :m weed: ops or masters with no password set"
+ puts stdout " :a weed: anyone with no password set"
+ puts stdout " o m unop/unmaster: ops or masters with no pass."
+ puts stdout " b<days> weed: bans not used in N days"
+ puts stdout " i<days> weed: ignores created over N days ago"
+ puts stdout " =<chan> weed: channels no longer supported"
+ puts stdout " c convert v3 eggdrop userfile"
+ puts stdout ""
+ exit
+}
+puts stdout "\nWEED 18jun97, robey\n"
+
+set filename [lindex $argv 0]
+for {set i 1} {$i < $argc} {incr i} {
+ set carg [lindex $argv $i]
+ if {$carg == ":n"} {
+ set weedlurkers 1
+ } elseif {$carg == ":o"} {
+ set weedops 1 ; set stripops 0 ; set weednopw 0
+ } elseif {$carg == ":m"} {
+ set weedmasters 1 ; set stripmasters 0 ; set weednopw 0
+ } elseif {$carg == ":a"} {
+ set weednopw 1 ; set weedops 0 ; set weedmasters 0
+ set stripops 0 ; set stripmasters 0
+ } elseif {$carg == "o"} {
+ set stripops 1 ; set weedops 0 ; set weednopw 0
+ } elseif {$carg == "m"} {
+ set stripmasters 1 ; set weedmasters 0 ; set weednopw 0
+ } elseif {$carg == "^m"} {
+ set exemptmasters 1
+ } elseif {$carg == "^o"} {
+ set exemptops 1
+ } elseif {$carg == "^f"} {
+ set exemptfriends 1
+ } elseif {$carg == "^p"} {
+ set exemptparty 1
+ } elseif {$carg == "^x"} {
+ set exemptfile 1
+ } elseif {$carg == "^cf"} {
+ set exemptchanf 1
+ } elseif {$carg == "^cm"} {
+ set exemptchanm 1
+ } elseif {$carg == "^cn"} {
+ set exemptchann 1
+ } elseif {$carg == "^b"} {
+ set exemptbotm 1
+ } elseif {$carg == "^co"} {
+ set exemptchano 1
+ } elseif {$carg == "c"} {
+ set convert 1
+ } elseif {[string index $carg 0] == "-"} {
+ lappend exempt [string range $carg 1 end]
+ } elseif {[string index $carg 0] == "+"} {
+ set maxlast [expr 60*60*24* [string range $carg 1 end]]
+ } elseif {[string index $carg 0] == "b"} {
+ set maxban [expr 60*60*24* [string range $carg 1 end]]
+ } elseif {[string index $carg 0] == "i"} {
+ set maxignore [expr 60*60*24* [string range $carg 1 end]]
+ } elseif {[string index $carg 0] == "="} {
+ lappend chanrem [string tolower [string range $carg 1 end]]
+ } else {
+ puts stderr "UNKNOWN OPTION: '$carg'\n"
+ exit
+ }
+}
+
+if {(!$weedlurkers) && (!$weedops) && (!$weedmasters) && (!$weednopw) &&
+ (!$stripops) && (!$stripmasters) && ($maxlast == 0) && ($convert == 0) &&
+ ($maxban == 0) && ($maxignore == 0) && ($chanrem == {})} {
+ puts stderr "PROBLEM: You didn't specify anything to weed out.\n"
+ exit
+}
+
+set weeding { } ; set strip { } ; set exempting { }
+if {$weedlurkers} { lappend weeding "lurkers" }
+if {$weedops} { lappend weeding "pwdless-ops" }
+if {$weedmasters} { lappend weeding "pwdless-masters" }
+if {$weednopw} { lappend weeding "pwdless-users" }
+if {$chanrem != {}} { lappend weeding "unwanted-channel" }
+if {$maxlast > 0} { lappend weeding ">[expr $maxlast /(60*60*24)]-days" }
+if {$maxban > 0} { lappend weeding "bans>[expr $maxban /(60*60*24)]-days" }
+if {$maxignore > 0} { lappend weeding "ign>[expr $maxignore /(60*60*24)]-days" }
+if {$weeding != { }} { puts stdout "Weeding:$weeding" }
+
+if {$stripops} { lappend strip "pwdless-ops" }
+if {$stripmasters} { lappend strip "pwdless-masters" }
+if {$strip != { }} { puts stdout "Stripping:$strip" }
+
+if {$exemptops} { lappend exempting "(ops)" }
+if {$exemptmasters} { lappend exempting "(masters)" }
+if {$exemptfriends} { lappend exempting "(friends)" }
+if {$exemptparty} { lappend exempting "(party-line)" }
+if {$exemptfile} { lappend exempting "(file-area)" }
+if {$exemptchann} { lappend exempting "(channel-owners)" }
+if {$exemptchanm} { lappend exempting "(channel-masters)" }
+if {$exemptchano} { lappend exempting "(channel-ops)" }
+if {$exemptchanf} { lappend exempting "(channel-friends)" }
+if {$exemptbotm} { lappend exempting "(botnet masters)" }
+if {[llength $exempt]>2} { lappend exempting "[lrange $exempt 2 end]" }
+if {$exempting != { }} { puts stdout "Exempt:$exempting" }
+
+puts stdout "\nReading $filename ..."
+
+proc convertUserFile {fname} {
+ global User Hostmask Channel Botflag LastOn BotAddr Xtra convert
+ puts stdout "\nRunning Converter on $fname"
+ set oldhandle {}
+ if {[catch {set fd [open $fname r]}] != 0} { return 0 }
+ set line [string trim [gets $fd]]
+ if {[string range $line 1 2] == "3v"} {
+ set convert 1
+ } elseif {[string range $line 1 2] == "4v"} {
+ return 0
+ }
+ while {![eof $fd]} {
+ set line [string trim [gets $fd]]
+ if {([string index $line 0] != "#") && ([string length $line] > 0)} {
+ scan $line "%s" handle
+ if {$handle == "-"} {
+ # hostmask
+ set hmList [split [string range $line 2 end] ,]
+ for {set i 0} {$i < [llength $hmList]} {incr i} {
+ lappend Hostmask($oldhandle) [string trim [lindex $hmList $i]]
+ }
+ } elseif {$handle == "!"} {
+ # channel
+ set chList [string trim [string range $line 1 end]]
+ lappend Channel($oldhandle) "[lrange $chList 0 1] [string trim [lindex $chList end] 0123456789]"
+ } elseif {$handle == "*"} {
+ # dccdir
+ set dccdir [string trim [string range $line 2 end]]
+ set User($oldhandle) [lreplace $User($oldhandle) 2 2 $dccdir]
+ } elseif {$handle == "+"} {
+ # email
+ set email [string trim [string range $line 2 end]]
+ set User($oldhandle) [lreplace $User($oldhandle) 3 3 $email]
+ } elseif {$handle == "="} {
+ # comment
+ set comment [string trim [string range $line 2 end]]
+ set User($oldhandle) [lreplace $User($oldhandle) 4 4 $comment]
+ } elseif {$handle == ":"} {
+ # user info line / bot addresses
+ if {[lsearch [split [lindex $User($oldhandle) 0] ""] b] == -1} {
+ set info [string trim [string range $line 1 end]]
+ set User($oldhandle) [lreplace $User($oldhandle) 5 5 $info]
+ } else {
+ set BotAddr($oldhandle) [string trim [string range $line 1 end]]
+ }
+ } elseif {$handle == "."} {
+ # xtra field start
+ if {![info exists xtraList($oldhandle)]} {
+ set xtraList($oldhandle) [string trim [string range $line 1 end]]
+ } {
+ set xtraList($oldhandle) "$xtraList($oldhandle) [string trim [string range $line 1 end]]"
+ }
+ } elseif {$handle == "!!"} {
+ # laston
+ set LastOn($oldhandle) [lindex $line 1]
+ } else {
+ # finish up xtra field first
+ if {[info exists xtraList($oldhandle)]} {
+ for {set i 0} {$i < [llength $xtraList($oldhandle)]} {incr i} {
+ lappend Xtra($oldhandle) [string trim [lindex $xtraList($oldhandle) $i] \{]
+ }
+ }
+ # begin of new user
+ scan $line "%s %s %s %s" handle pass attr ts
+ if {$convert == 1 && $attr != ""} {
+ regsub -all {B} $attr {t} attr
+ set botflags "s h a l r" ; set Botflag($handle) ""
+ set nattr [split [string trim $attr 0123456789] ""] ; set attr ""
+ foreach flag $botflags {
+ if {[lsearch -exact $nattr $flag] != -1} {append Botflag($handle) $flag}
+ }
+ foreach flag $nattr {
+ if {[lsearch -exact $botflags $flag] == -1} {append attr $flag}
+ }
+ }
+ set User($handle) [list $attr $pass {} {} {} {}]
+ set Hostmask($handle) {}
+ set Channel($handle) {}
+ set oldhandle $handle
+ }
+ }
+ }
+ return 1
+}
+
+proc loadUserFile {fname} {
+ global User Hostmask Channel Botflag LastOn BotAddr Xtra
+ set oldhandle {}
+ if {[catch {set fd [open $fname r]}] != 0} { return 0 }
+ set line [string trim [gets $fd]]
+ if {[string range $line 1 2] != "4v"} {
+ if {[string range $line 1 2] == "3v"} {
+ convertUserFile $fname
+ return 1
+ } else {
+ puts stderr "Unknown userfile version! (not v4)\n"
+ exit
+ }
+ }
+ while {![eof $fd]} {
+ set line [string trim [gets $fd]]
+ if {([string index $line 0] != "#") && ([string length $line] > 0)} {
+ scan $line "%s" handle
+ if {$handle == "--HOSTS"} {
+ # hostmask
+ set hmList [lindex $line 1]
+ lappend Hostmask($oldhandle) [string trim $hmList]
+ } elseif {$handle == "-"} {
+ # hostmask
+ set hmList [join [lrange $line 1 end]]
+ lappend Hostmask($oldhandle) [string trim $hmList]
+ } elseif {$handle == "!"} {
+ # channel
+ set chList [string trim [string range $line 1 end]]
+ lappend Channel($oldhandle) $chList
+ } elseif {$handle == "--BOTADDR"} {
+ # botaddr
+ set BotAddr($oldhandle) [lindex $line 1]
+ } elseif {$handle == "--PASS"} {
+ # pass
+ set pass [string trim [string range $line [expr [string first " " $line] + 1] end]]
+ set User($oldhandle) [lreplace $User($oldhandle) 1 1 $pass]
+ } elseif {$handle == "--DCCDIR"} {
+ # dccdir
+ set first [string first " " $line]
+ if {$first != -1} {
+ set dccdir [string trim [string range $line [expr $first + 1] end]]
+ } {
+ set dccdir ""
+ }
+ set User($oldhandle) [lreplace $User($oldhandle) 2 2 $dccdir]
+ } elseif {$handle == "--COMMENT"} {
+ # comment
+ set first [string first " " $line]
+ if {$first != -1} {
+ set comment [string trim [string range $line [expr $first + 1] end]]
+ } {
+ set comment ""
+ }
+ set User($oldhandle) [lreplace $User($oldhandle) 4 4 $comment]
+ } elseif {$handle == "--INFO"} {
+ # user info line
+ set first [string first " " $line]
+ if {$first != -1} {
+ set info [string trim [string range $line [expr $first + 1] end]]
+ } {
+ set info ""
+ }
+ set User($oldhandle) [lreplace $User($oldhandle) 5 5 $info]
+ } elseif {$handle == "--CONSOLE"} {
+ # console
+ set first [string first " " $line]
+ if {$first != -1} {
+ set console [string trim [string range $line [expr $first + 1] end]]
+ } {
+ set console ""
+ }
+ set User($oldhandle) [lreplace $User($oldhandle) 6 6 $console]
+ } elseif {$handle == "--XTRA"} {
+ # xtra field
+ set first [string first " " $line]
+ if {$first != -1} {
+ set xtraList [string trim [string range $line [expr $first + 1] end]]
+ } {
+ set xtraList ""
+ }
+ lappend Xtra($oldhandle) $xtraList
+ } elseif {$handle == "--LASTON"} {
+ # laston
+ set LastOn($oldhandle) [lindex $line 1]
+ } elseif {$handle == "--BOTFL"} {
+ # botflags
+ set Botflag($oldhandle) [string trim [string range $line 1 end]]
+ } else {
+ # begin of new user
+ scan $line "%s %s %s" handle dash attr
+ set User($handle) [list $attr {} {} {} {} {} {}]
+ set Hostmask($handle) {}
+ set Channel($handle) {}
+ set oldhandle $handle
+ }
+ }
+ }
+ return 1
+}
+
+proc saveUserFile fname {
+ global User Hostmask Channel Botflag LastOn BotAddr Xtra
+ if {[catch {set fd [open $fname w]}] != 0} { return 0 }
+ puts $fd "#4v: weed! now go away."
+ foreach i [array names User] {
+ set hmask "none"
+ set hmloop 0 ; set chloop 0 ; set loloop 0 ; set xloop 0 ; set aloop 0
+ if {[lindex $User($i) 1] == "bans"} {set plug "bans"} {set plug "-"}
+ set attr [lindex $User($i) 0]
+ set ts [lindex $User($i) 2]
+ puts $fd [format "%-9s %-20s %-24s" $i $plug $attr]
+ for {} {$hmloop < [llength $Hostmask($i)]} {incr hmloop} {
+ if {[string index $i 0] == "*" || [string range $i 0 1] == "::"} {
+ set hmask [lindex $Hostmask($i) $hmloop]
+ regsub -all {~} $hmask { } hmask
+ puts $fd "- $hmask"
+ } else {
+ puts $fd "--HOSTS [lindex $Hostmask($i) $hmloop]"
+ }
+ }
+ if {[info exists BotAddr($i)]} {
+ puts $fd "--BOTADDR [lindex $BotAddr($i) 0]"
+ }
+ if {[info exists Xtra($i)]} {
+ for {} {$xloop < [llength $Xtra($i)]} {incr xloop} {
+ puts $fd "--XTRA [lindex $Xtra($i) $xloop]"
+ }
+ }
+ if {[info exists Channel($i)]} {
+ for {} {$chloop < [llength $Channel($i)]} {incr chloop} {
+ puts $fd "! [lindex $Channel($i) $chloop]"
+ }
+ }
+ if {[info exists Botflag($i)]} {
+ if {$Botflag($i) != ""} { puts $fd "--BOTFL [lindex $Botflag($i) 0]" }
+ }
+ if {[string index $i 0] == "*" || [string range $i 0 1] == "::"} {
+ set User($i) [lreplace $User($i) 1 1 {}]
+ }
+ if {[lindex $User($i) 1] != {}} { puts $fd "--PASS [lindex $User($i) 1]" }
+ if {[lindex $User($i) 2] != {}} { puts $fd "--DCCDIR [lindex $User($i) 2]" }
+ if {[lindex $User($i) 3] != {}} { puts $fd "--XTRA EMAIL [lindex $User($i) 3]" }
+ if {[lindex $User($i) 4] != {}} { puts $fd "--COMMENT [lindex $User($i) 4]" }
+ if {[lindex $User($i) 5] != {}} { puts $fd "--INFO [lindex $User($i) 5]" }
+ if {[lindex $User($i) 6] != {}} { puts $fd "--CONSOLE [lindex $User($i) 6]" }
+ if {[info exists LastOn($i)]} {
+ puts $fd "--LASTON [lindex $LastOn($i) 0]"
+ }
+ }
+ close $fd
+ return 1
+}
+
+if {![loadUserFile $filename]} {
+ puts stdout "* Couldn't load userfile!\n"
+ exit
+}
+
+if {$convert == 0} {
+ puts stdout "Loaded. Weeding..."
+ puts stdout "(pwd = no pass, -o/-m = removed op/master, lrk = never seen, exp = expired)"
+ puts stdout "(uwc = unwanted channel)\n"
+} else {
+ puts stdout "Loaded. Converting..."
+}
+
+set total 0
+set weeded 0
+foreach i [array names User] {
+ incr total
+ set attr [lindex $User($i) 0]
+ set chanattr [lindex [lindex $Channel($i) 0] 2]
+ if {([lsearch -exact $exempt $i] == -1) &&
+ ([string range $i 0 1] != "::") &&
+ ([string range $i 0 1] != "--") &&
+ (([string first o $attr] == -1) || (!$exemptops)) &&
+ (([string first m $attr] == -1) || (!$exemptmasters)) &&
+ (([string first f $attr] == -1) || (!$exemptfriends)) &&
+ (([string first p $attr] == -1) || (!$exemptparty)) &&
+ (([string first x $attr] == -1) || (!$exemptfile)) &&
+ (([string first t $attr] == -1) || (!$exemptbotm)) &&
+ (([string first f $chanattr] == -1) || (!$exemptchanf)) &&
+ (([string first m $chanattr] == -1) || (!$exemptchanm)) &&
+ (([string first n $chanattr] == -1) || (!$exemptchann)) &&
+ (([string first o $chanattr] == -1) || (!$exemptchano))} {
+ set pass [lindex $User($i) 1]
+ if {[info exists LastOn($i)]} { set ts [lindex $LastOn($i) 0] } { set ts 0 }
+ if {([string compare $pass "-"] == 0) && ([string first b $attr] == -1)} {
+ if {$weednopw == 1} {
+ unset User($i) ; incr weeded
+ puts -nonewline stdout "[format "pwd: %-10s " $i]"
+ } elseif {([string first o $attr] != -1) && ($weedops == 1)} {
+ unset User($i) ; incr weeded
+ puts -nonewline stdout "[format "pwd: %-10s " $i]"
+ } elseif {([string first m $attr] != -1) && ($weedmasters == 1)} {
+ unset User($i) ; incr weeded
+ puts -nonewline stdout "[format "pwd: %-10s " $i]"
+ }
+ if {([string first o $attr] != -1) && ($stripops == 1)} {
+ set nattr {}
+ for {set x 0} {$x < [string length $attr]} {incr x} {
+ if {[string index $attr $x] != "o"} {
+ set nattr [format "%s%s" $nattr [string index $attr $x]]
+ }
+ }
+ if {$nattr == {}} { set nattr "-" }
+ set User($i) [lreplace $User($i) 0 0 $nattr]
+ puts -nonewline stdout "[format " -o: %-10s " $i]"
+ }
+ if {([string first m $attr] != -1) && ($stripmasters == 1)} {
+ set nattr {}
+ for {set x 0} {$x < [string length $attr]} {incr x} {
+ if {[string index $attr $x] != "m"} {
+ set nattr [format "%s%s" $nattr [string index $attr $x]]
+ }
+ }
+ if {$nattr == {}} { set nattr "-" }
+ set User($i) [lreplace $User($i) 0 0 $nattr]
+ puts -nonewline stdout "[format " -m: %-10s " $i]"
+ }
+ }
+ if {($ts==0) && ($weedlurkers==1) && ([string first b $attr] == -1) && [info exists User($i)]} {
+ unset User($i) ; incr weeded
+ puts -nonewline stdout "[format "lrk: %-10s " $i]"
+ }
+ if {($ts > 0) && ($maxlast > 0) && ($CURRENT-$ts > $maxlast && [info exists User($i)])} {
+ unset User($i) ; incr weeded
+ puts -nonewline stdout "[format "exp: %-10s " $i]"
+ }
+ if {$chanrem != {} && [info exists Channel($i)]} {
+ foreach unchan $chanrem {
+ set id [lsearch [string tolower $Channel($i)] *$unchan*]
+ if {$id != -1} {
+ set Channel($i) [lreplace $Channel($i) $id $id] ; incr weeded
+ puts -nonewline stdout "[format "uwc: %-10s " $i]"
+ }
+ }
+ }
+ }
+ flush stdout
+}
+if {$weeded == 0 && $convert == 0} { puts -nonewline stdout "uNF... Nothing to weed!" }
+puts stdout "\n"
+
+foreach i [array names User] {
+ if {([string range $i 0 1] == "::") || ($i == "*ban")} {
+ for {set j 0} {$j < [llength $Hostmask($i)]} {incr j} {
+ set ban [split [lindex $Hostmask($i) $j] :]
+ if {[string range [lindex $ban 2] 0 0] == "+"} {
+ set lastused [lindex $ban 3]
+ if {($maxban > 0) && ($CURRENT-$lastused > $maxban)} {
+ if {$i == "*ban"} {
+ puts stdout "Expired ban: [lindex $ban 0]"
+ } {
+ puts stdout "Expired ban on [string range $i 2 end]: [lindex $ban 0]"
+ }
+ set Hostmask($i) [lreplace $Hostmask($i) $j $j]
+ incr j -1
+ }
+ }
+ }
+ }
+ if {$i == "*ignore"} {
+ for {set j 0} {$j < [llength $Hostmask($i)]} {incr j} {
+ set ign [split [lindex $Hostmask($i) $j] :]
+ set lastused [lindex $ign 3]
+ if {($maxignore > 0) && ($CURRENT-$lastused > $maxignore)} {
+ puts stdout "Expired ignore: [lindex $ign 0]"
+ set Hostmask($i) [lreplace $Hostmask($i) $j $j]
+ incr j -1
+ }
+ }
+ }
+}
+
+puts stdout "\nFinished scan."
+puts stdout "Original total ($total), new total ([expr $total-$weeded]), zapped ($weeded)"
+
+if {![saveUserFile $filename.weed]} {
+ puts stdout "* uNF... Couldn't save new userfile!\n"
+ exit
+}
+puts stdout "Wrote $filename.weed"
diff --git a/wiki.tcl b/wiki.tcl
new file mode 100644
index 0000000..4e9c203
--- /dev/null
+++ b/wiki.tcl
@@ -0,0 +1,111 @@
+# Requires Tcl 8.5+ and tcllib
+# To enable you must .chanset #channel +wiki
+
+package require http
+package require htmlparse
+package require tls
+http::register https 443 [list tls::socket -tls1 1]
+
+namespace eval wiki {
+ variable max_lines 1
+ variable max_chars 400
+ variable url "https://en.wikipedia.org/wiki/"
+
+ bind pub -|- ",wiki" wiki::search
+
+ #variable parse_regexp {(<table class.*?<p>.*?</p>.*?</table>)??.*?<p>(.*?)</p>\n<table id="toc"}
+ #variable parse_regexp {(?:</table>)?.*?<p>(.*)((</ul>)|(</p>)).*?((<table id="toc")|(<h2>)|(<table id="disambigbox"))}
+ variable parse_regexp {<\/table>.*?<p>(.*?)<\/p>}
+ setudef flag wiki
+}
+
+proc wiki::fetch {term {url {}}} {
+ if {$url != ""} {
+ set token [http::geturl $url -timeout 10000]
+ } else {
+ set query [regsub -all -- {\s} $term "_"]
+ set token [http::geturl ${wiki::url}${query} -timeout 10000]
+ }
+ set data [http::data $token]
+ set ncode [http::ncode $token]
+ set meta [http::meta $token]
+ upvar #0 $token state
+ set fetched_url $state(url)
+ http::cleanup $token
+
+ # debug
+ putlog "Fetch! term: $term url: $url fetched: $fetched_url"
+ set fid [open "w-debug.txt" w]
+ puts $fid $data
+ close $fid
+
+ # Follow redirects
+ if {[regexp -- {^3\d{2}$} $ncode]} {
+ return [wiki::fetch $term [dict get $meta Location]]
+ }
+ if {$ncode != 200} {
+ error "HTTP query failed ($ncode): $data: $meta"
+ }
+
+ # If page returns list of results, choose the first one and fetch that
+ #if {[regexp -- {<p>.*?((may refer to:)|(in one of the following senses:))</p>} $data]} {
+ # regexp -- {<ul>.*?<li>.*? title="(.*?)">.*?</li>} $data -> new_query
+ # return [wiki::fetch $new_query]
+ #}
+
+ if {![regexp -- $wiki::parse_regexp $data -> out]} {
+ error "Parse error"
+ }
+ return [list url $fetched_url result [wiki::sanitise $out]]
+}
+
+proc wiki::sanitise {raw} {
+ set raw [::htmlparse::mapEscapes $raw]
+ # Remove some help links
+ set raw [regsub -- {<small class="metadata">.*?</small>} $raw ""]
+ set raw [regsub -all -- {<(.*?)>} $raw ""]
+ set raw [regsub -all -- {\[.*?\]} $raw ""]
+ set raw [regsub -all -- {\n} $raw " "]
+ return $raw
+}
+
+proc wiki::search {nick uhost hand chan argv} {
+ if {![channel get $chan wiki]} { return }
+ if {[string length $argv] == 0} {
+ puthelp "PRIVMSG $chan :Please provide a term."
+ return
+ }
+ set argv [string trim $argv]
+ # Upper case first character
+ set argv [string toupper [string index $argv 0]][string range $argv 1 end]
+ if {[catch {wiki::fetch $argv} data]} {
+ puthelp "PRIVMSG $chan :Error: $data"
+ return
+ }
+ foreach line [wiki::split_line $wiki::max_chars [dict get $data result]] {
+ if {[incr count] > $wiki::max_lines} {
+ puthelp "PRIVMSG $chan :Output truncated. [dict get $data url]"
+ break
+ }
+ putserv [encoding convertfrom utf-8 "PRIVMSG $chan :$line"]
+ }
+}
+
+# by fedex
+proc wiki::split_line {max str} {
+ set last [expr {[string length $str] -1}]
+ set start 0
+ set end [expr {$max -1}]
+ set lines []
+ while {$start <= $last} {
+ if {$last >= $end} {
+ set end [string last { } $str $end]
+ }
+ lappend lines [string trim [string range $str $start $end]]
+ set start $end
+ set end [expr {$start + $max}]
+ }
+ return $lines
+}
+
+putlog "wiki.tcl loaded"
diff --git a/youtube.tcl b/youtube.tcl
new file mode 100644
index 0000000..7462833
--- /dev/null
+++ b/youtube.tcl
@@ -0,0 +1,247 @@
+#########################################################################################
+# Name m00nie::youtube
+# Description Uses youtube v3 API to search and return videos
+#
+# Version 3.1 - Yet more duration decoding fixes. Specific case of 00s videos less than
+# an hour too. It also adds checks for videos <24 hours long (who knew)
+# Hope this is the last of the duration crap
+# 3.0 - Again trying to fix or cover all scenarios for how duration is returned
+# in ISO format without needing to load another library. Hopefully correct..
+# Also reverted some encoding changed back to 2.6 as these were added in
+# error. Live stream detection should also be working again as that value
+# seems to have changed.
+# 2.9 - OMG the time
+# 2.8 - New variable "out" to more easily change the (I think lovely ;D) YouTube
+# output at the beginning of any spam in this script.
+# 2.7 - Handles API problems (e.g. having an incorrect key) nicer. Also since
+# v1.9 of eggdrop utf8 is nativley handled so tries to accomodate that
+# within the script.
+# 2.6 - Add likes to auto spam (Thanks to ComputerTech for the suggestion)
+# also a small update to time formating
+# 2.5 - Correctly returns results for channels rather than videos for !yt
+# 2.4 - Fixing bug that meant date was again reported wrongly. Thanks
+# to caesar for suggesting the fix
+# 2.3 - Fix for new date/time results from the API
+# 2.2 - Reodering throttling to make it work better....
+# 2.1 - Adds throttling to spammed links themselves (link_throt)
+# Also includes a fix on some character output with !yt searching
+# Thanks to <AlbozZ> for this spot and fix!
+# 2.0 - Adds seperate flag for search and autoinfo (suggestion from m4s)
+# This is a change from previous versions
+# .chanset #chan +youtube = Enabled auto info grabbing on a URL spam
+# .chanset #chan +youtubesearch = Enabled access to search via !yt
+# 1.9 - Adding throttling controls (per user and per chan)
+# 1.8 - Chanset +youtube now controls search access!
+# 1.7 - Modify SSL params (fixes issues on some systems)
+# 1.6 - Small correction to "stream" categorisation.....
+# 1.5 - Added UTF-8 support thanks to CatboxParadox (Requires eggdrop
+# to be compiled with UTF-8 support)
+# 1.4 - Correct time format and live streams gaming etc
+# 1.3 - Updated output to be RFC compliant for some IRCDs
+# 1.2 - Added auto info grabber for spammed links
+# 1.1 - Fixing regex!
+# 1.0 - Initial release
+# Website https://www.m00nie.com/youtube-eggdrop-script-using-api-v3/
+# Notes Grab your own key @ https://developers.google.com/youtube/v3/
+#########################################################################################
+namespace eval m00nie {
+ namespace eval youtube {
+ # ----- CHANGE these variables -----
+ # This key is your own and shoudl remain a secret (e.g please dont email it to me! Obtain it on the link above in the notes)
+ # Set this in the config, not here
+ #set m00nie::youtube::key "..."
+
+ # Some people dont like coloured output! :O
+ # (you can see an example of the coloured output @ https://www.m00nie.com/youtube-eggdrop-script-using-api-v3/)
+ # Whatever text (if any) you set the below variable to will be spammed prior to the first result of search or autoinfo
+ #variable out "\002\00301,00You\00300,04Tube\003\002"
+ variable out "\[youtube\]"
+
+ # The two variables below control throttling in seconds. First is per user, second is per channel third is per link
+ variable user_throt 5
+ variable chan_throt 5
+ variable link_throt 10
+
+
+ # ---- Dont change things below this line -----
+ package require http
+ package require json
+ # We need to verify the revision of TLS since prior to this version is missing auto host for SNI
+ if { [catch {package require tls 1.7.11}] } {
+ # We dont have an autoconfigure option for SNI
+ putlog "m00nie::youtube *** WARNING *** OLD Version of TLS package installed please update to 1.7.11+ ... "
+ http::register https 443 [list ::tls::socket -servername www.googleapis.com]
+ } else {
+ package require tls 1.7.11
+ http::register https 443 [list ::tls::socket -autoservername true]
+ }
+ bind pub - !yt m00nie::youtube::search
+ bind pubm - * m00nie::youtube::autoinfo
+ variable version "3.1"
+ setudef flag youtube
+ setudef flag youtubesearch
+ variable regex {(?:http(?:s|).{3}|)(?:www.|)(?:youtube.com\/watch\?.*v=|youtu.be\/)([\w-]{11})}
+ ::http::config -useragent "Mozilla/5.0 (X11; Fedora; Linux x86_64; rv:129.0) Gecko/20100101 Firefox/129.0"
+ variable throttled
+
+#### Script Starts here #####
+
+proc autoinfo {nick uhost hand chan text} {
+ if {[channel get $chan youtube] && [regexp -nocase -- $m00nie::youtube::regex $text url id]} {
+ if {[throttlecheck $nick $chan $id]} { return 0 }
+ putlog "m00nie::youtube::autoinfo is running"
+ putlog "m00nie::youtube::autoinfo url is: $url and id is: $id"
+ set url "https://www.googleapis.com/youtube/v3/videos?id=$id&key=$m00nie::youtube::key&part=snippet,statistics,contentDetails&fields=items(snippet(title,channelTitle,publishedAt),statistics(viewCount,likeCount,dislikeCount),contentDetails(duration))"
+ set ids [getinfo $url]
+
+ # Catch probs...
+ if {$ids eq "0"} {
+ putlog "m00nie::youtube::autoinfo there was a problem :("
+ return
+ }
+ set title [encoding convertfrom [lindex $ids 0 1 3]]
+
+ set pubiso [lindex $ids 0 1 1]
+ set pubiso [string map {"T" " " ".000Z" "" "Z" ""} $pubiso]
+ set pubtime [clock format [clock scan $pubiso] -format {%Y-%m-%dT%H:%M:%SZ}]
+
+ set user [encoding convertfrom [lindex $ids 0 1 5]]
+ # Yes all quite horrible...
+ # ... it gets worse. ISO time format really isnt my friend...
+ # ... be sick on yourself.. I have
+ set isotime [lindex $ids 0 3 1]
+ regsub -all {P} $isotime "" isotime
+ # First off check for videos that are days and non 0 hours
+ if {[regexp {^[0-9]*DT[0-9]*H} $isotime]} {
+ regexp {(^[0-9]*DT)?([0-9]*H)} $isotime x days hours
+ regsub -all {DT} $days "" days
+ regsub -all {H} $hours "" hours
+ set hours [expr ($days * 24) + $hours]
+ set nh "${hours}H"
+ regsub -all {^.*H} $isotime "$nh" isotime
+ # Now check for videos that are days and "0" hours
+ } elseif {[regexp {^[0-9]*D} $isotime]} {
+ regexp {^[0-9]*D} $isotime days
+ regsub -all {D} $days "" days
+ set hours [expr $days * 24]
+ set nh "${hours}H"
+ regsub -all {^.*D} $isotime "$nh" isotime
+ }
+ # Everything else should be <24 hours and >24 shoud be formatted (maybe)
+
+ regsub -all {T} $isotime "" isotime
+ # No M or S returned e.g. PT10H for a 10:00:00 video...
+ if {[regexp {^[0-9].*H$} $isotime]} {
+ set isotime "${isotime}00M00S"
+ # No M but we do have S e.g. PT10H01S for a 10:00:01 video...
+ } elseif {[regexp {^[0-9]*H[0-9]*S$} $isotime]} {
+ regsub {H} $isotime "H00M" isotime
+ # No S but we do have M e.g. PT10M for a 10:00 video...
+ } elseif {[regexp {^[0-9]*M$} $isotime]} {
+ regsub {M} $isotime "M00S" isotime
+ }
+ # Now we should have some kind of "standard" to mangle as before
+ regsub -all {H|M} $isotime ":" isotime
+ regsub -all {S} $isotime "" isotime
+ # Seems it can now return P0D (it use to be 0)
+ if { [string index $isotime 0] == "0" || $isotime == "P0D" } {
+ set isotime "live"
+ } elseif { [string index $isotime end-1] == ":" } {
+ set sec [string index $isotime end]
+ set trim [string range $isotime 0 end-1]
+ set isotime ${trim}0$sec
+ } elseif { [string index $isotime end-2] != ":" } {
+ set isotime "${isotime}s"
+ }
+ set views [lindex $ids 0 5 1]
+ set like [lindex $ids 0 5 3]
+ # At the moment not used (it looked a little messy)
+ set dis [lindex $ids 0 5 5]
+ puthelp "PRIVMSG $chan :$m00nie::youtube::out \002$title\002 by $user (duration: $isotime) on $pubtime, $views views \[Likes: $like\]"
+ }
+}
+
+proc b0rkcheck {results} {
+ putlog "m00nie::youtube::b0rkcheck is running"
+ if {!([lindex $results 0 0] eq "items")} {
+ putlog "m00nie::youtube::b0rkcheck looks to be a problem with the API - [lindex $results 0 0] [lindex $results 1 1]: [lindex $results 1 3]"
+ return 0
+ } else {
+ return 1
+ }
+}
+
+proc throttlecheck {nick chan link} {
+ if {[info exists m00nie::youtube::throttled($link)]} {
+ putlog "m00nie::youtube::throttlecheck search term or video id: $link, is throttled at the moment"
+ return 1
+ } elseif {[info exists m00nie::youtube::throttled($chan)]} {
+ putlog "m00nie::youtube::throttlecheck Channel $chan is throttled at the moment"
+ return 1
+ } elseif {[info exists m00nie::youtube::throttled($nick)]} {
+ putlog "m00nie::youtube::throttlecheck User $nick is throttled at the moment"
+ return 1
+ } else {
+ set m00nie::youtube::throttled($nick) [utimer $m00nie::youtube::user_throt [list unset m00nie::youtube::throttled($nick)]]
+ set m00nie::youtube::throttled($chan) [utimer $m00nie::youtube::chan_throt [list unset m00nie::youtube::throttled($chan)]]
+ set m00nie::youtube::throttled($link) [utimer $m00nie::youtube::link_throt [list unset m00nie::youtube::throttled($link)]]
+ return 0
+ }
+}
+
+proc getinfo { url } {
+ for { set i 1 } { $i <= 5 } { incr i } {
+ set rawpage [::http::data [::http::geturl "$url" -timeout 5000]]
+ if {[string length rawpage] > 0} { break }
+ }
+ putlog "m00nie::youtube::getinfo Rawpage length is: [string length $rawpage]"
+ if {[string length $rawpage] == 0} { error "youtube returned ZERO no data :( or we couldnt connect properly" }
+ set results [json::json2dict $rawpage]
+ # Check is we have errors
+ if {[b0rkcheck $results]} {
+ # We dont :)
+ set ids [dict get $results items]
+ return $ids
+ } else {
+ # We do have errors :(
+ return 0
+ }
+}
+
+proc search {nick uhost hand chan text} {
+ if {![channel get $chan youtubesearch] } {
+ return
+ }
+ putlog "m00nie::youtube::search is running"
+ regsub -all {\s+} $text "%20" text
+ if {[throttlecheck $nick $chan $text]} { return 0 }
+ set url "https://www.googleapis.com/youtube/v3/search?part=snippet&fields=items(id(videoId),id(channelId),snippet(title))&key=$m00nie::youtube::key&q=$text"
+ set ids [getinfo $url]
+
+ # Catch probs...
+ if {$ids eq "0"} {
+ putlog "m00nie::youtube::autoinfo there was a problem :("
+ return
+ }
+
+ set output "$m00nie::youtube::out "
+ for {set i 0} {$i < 5} {incr i} {
+ set id [lindex $ids $i 1 1]
+ set type [lindex $ids $i 1 0]
+ # Catch Channels rather than videos (youtu.be doesnt work for channels)
+ if {$type eq "channelId"} {
+ set yout "https://www.youtube.com/channel/$id"
+ } else {
+ set yout "https://youtu.be/$id"
+ }
+ set desc [encoding convertfrom [lindex $ids $i 3 1]]
+ set desc [string map -nocase [list "&amp;" "&" "&#39;" "'" "&quot;" "\""] $desc ]
+ append output "\002" $desc "\002 - " $yout " | "
+ }
+ set output [string range $output 0 end-2]
+ puthelp "PRIVMSG $chan :$output"
+}
+}
+}
+putlog "m00nie::youtube $m00nie::youtube::version loaded"
+