Procházet zdrojové kódy

dictionary: Respond to our nick

When someone says the bot's nickname it will output a random chatty
response. Previously it did not respond at all. Note this will only work
for cases where the bot is not directly addressed, as we expect those
cases to indicate a command to the bot.

This also resolves a potential bug where string match metacharacters
were unescaped when looking for terms in messages.
Will Storey před 9 roky
rodič
revize
ea45a1fdfd
2 změnil soubory, kde provedl 143 přidání a 9 odebrání
  1. 46 9
      dictionary.tcl
  2. 97 0
      dictionary_test.tcl

+ 46 - 9
dictionary.tcl

@@ -72,14 +72,11 @@ namespace eval dictionary {
   # this is for throttling term outputs.
   # this is for throttling term outputs.
   variable flood [dict create]
   variable flood [dict create]
 
 
-  # Binds
   bind pubm -|- "*"          ::[namespace current]::public
   bind pubm -|- "*"          ::[namespace current]::public
   bind pubm -|- "*"          ::[namespace current]::publearn
   bind pubm -|- "*"          ::[namespace current]::publearn
   bind evnt -|- "save"       ::[namespace current]::save
   bind evnt -|- "save"       ::[namespace current]::save
 
 
-  # Miscellaneous
   setudef flag dictionary
   setudef flag dictionary
-  namespace export *
 }
 }
 
 
 # respond to terms in the channel
 # respond to terms in the channel
@@ -107,19 +104,25 @@ proc ::dictionary::public {nick host hand chan argv} {
   }
   }
 
 
   # look for a word we know about.
   # look for a word we know about.
+  set term ""
   foreach word [dict keys $terms] {
   foreach word [dict keys $terms] {
-    if {[string match -nocase "* $word *" $argv] \
-      || [string match -nocase "$word *" $argv] \
-      || [string match -nocase "* $word" $argv] \
-      || [string match -nocase "$word" $argv]} \
-    {
+    if {[::dictionary::string_contains_term $argv $word]} {
      set term $word
      set term $word
      break
      break
     }
     }
   }
   }
-  if {![info exists term]} {
+
+  if {$term == ""} {
+    # They didn't say a term we know something about. In this case the only
+    # response we'll send is if they said our name. Send them a chatty response
+    # if so.
+    if {[::dictionary::string_contains_term $argv $botnick]} {
+      set response [::dictionary::get_chatty_response $nick]
+      putserv "PRIVMSG $chan :$response"
+    }
     return
     return
   }
   }
+
   set term_dict [dict get $terms $term]
   set term_dict [dict get $terms $term]
 
 
   # check if we've responded to the word recently so we don't
   # check if we've responded to the word recently so we don't
@@ -180,12 +183,14 @@ proc ::dictionary::publearn {nick host hand chan argv} {
       set include_term_in_def 0
       set include_term_in_def 0
       set description "[string range $argv [expr [string first "," $argv] + 2] end]"
       set description "[string range $argv [expr [string first "," $argv] + 2] end]"
     }
     }
+
     if {[dict exists $terms $term]} {
     if {[dict exists $terms $term]} {
       set term_dict [dict get $terms $term]
       set term_dict [dict get $terms $term]
       set def [dict get $term_dict def]
       set def [dict get $term_dict def]
       putserv "PRIVMSG $chan :$term is already $def"
       putserv "PRIVMSG $chan :$term is already $def"
       return
       return
     }
     }
+
     set term [string trim $term]
     set term [string trim $term]
     set description [string trim $description]
     set description [string trim $description]
     if {[string length $term] == 0 || [string length $description] == 0} {
     if {[string length $term] == 0 || [string length $description] == 0} {
@@ -233,6 +238,38 @@ proc ::dictionary::publearn {nick host hand chan argv} {
   putserv "PRIVMSG $chan :$response"
   putserv "PRIVMSG $chan :$response"
 }
 }
 
 
+# Return 1 if the string contains the term. This is tested case insensitively.
+#
+# The term is present only if it is by itself surrounded whitespace or
+# punctuation.
+#
+# e.g. if the term is 'test' then these strings contain it:
+#
+# hi test hi
+# hi test, hi
+# test
+#
+# But these do not:
+#
+# hi testing hi
+# hitest
+proc ::dictionary::string_contains_term {s term} {
+  set term_lc [string tolower $term]
+
+  set term_quoted [::dictionary::quotemeta $term_lc]
+
+  # \m matches at the beginning of a word, \M at the end.
+  return [regexp -nocase -- \\m$term_quoted\\M $s]
+}
+
+# Escape/quote metacharacters so that the string becomes suitable for placing in
+# a regular expression. This makes it so any regex metacharacter is quoted.
+#
+# See http://stackoverflow.com/questions/4346750/regular-expression-literal-text-span/4352893#4352893
+proc ::dictionary::quotemeta {s} {
+  return [regsub -all {\W} $s {\\&}]
+}
+
 proc ::dictionary::get_random_response {responses nick} {
 proc ::dictionary::get_random_response {responses nick} {
   # we assume we have responses in the list.
   # we assume we have responses in the list.
 
 

+ 97 - 0
dictionary_test.tcl

@@ -0,0 +1,97 @@
+#
+# Unit tests for dictionary.tcl
+#
+
+# Dummy some eggdrop functions.
+proc ::bind {a b c d} {}
+proc ::setudef {a b} {}
+proc ::putlog {s} {}
+
+source dictionary.tcl
+
+proc ::tests {} {
+	puts "Running tests..."
+
+	set success 1
+
+	if {![::test_quotemeta]} {
+		set success 0
+	}
+
+	if {![::test_string_contains_term]} {
+		set success 0
+	}
+
+	if {$success} {
+		puts "Success!"
+	} else {
+		puts "Failure."
+		exit 1
+	}
+}
+
+proc ::test_quotemeta {} {
+	set tests [list \
+		[dict create input hi! output hi\\!] \
+		[dict create input hi output hi] \
+		[dict create input hi*+ output hi\\*\\+] \
+		[dict create input hi\{\}\\ output hi\\\{\\\}\\\\] \
+	]
+
+	set failed 0
+
+	foreach test $tests {
+		set output [::dictionary::quotemeta [dict get $test input]]
+		if {$output != [dict get $test output]} {
+			puts [format "FAILURE: quotemeta(%s) = %s, wanted %s" \
+				[dict get $test input] $output [dict get $test output]]
+
+			incr failed
+		}
+	}
+
+	if {$failed != 0} {
+		puts [format "quotemeta: %d/%d tests failed" $failed [llength $tests]]
+	}
+
+	return [expr $failed == 0]
+}
+
+proc ::test_string_contains_term {} {
+	set tests [list \
+		[dict create s "hi test hi" term "test" want 1] \
+		[dict create s "hi testing hi" term "test" want 0] \
+		[dict create s "hi test, hi" term "test" want 1] \
+		[dict create s "hi test. hi" term "test" want 1] \
+		[dict create s "test" term "test" want 1] \
+		[dict create s "hi test" term "test" want 1] \
+		[dict create s "test hi" term "test" want 1] \
+		[dict create s "test hi" term "TEST" want 1] \
+		[dict create s "TEST hi" term "test" want 1] \
+	]
+
+	set failed 0
+
+	foreach test $tests {
+		set s [dict get $test s]
+		set term [dict get $test term]
+		set want [dict get $test want]
+
+		set output [::dictionary::string_contains_term $s $term]
+		if {$output != $want} {
+			puts [format "FAILURE: string_contains_term(\"%s\", \"%s\") = %d, wanted %d" \
+				$s $term $output $want]
+
+			incr failed
+		}
+	}
+
+	if {$failed != 0} {
+		puts [format "string_contains_term: %d/%d tests failed" $failed \
+			[llength $tests]]
+	}
+
+	return [expr $failed == 0]
+}
+
+::tests