Просмотр исходного кода

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 9 лет назад
Родитель
Сommit
ea45a1fdfd
2 измененных файлов с 143 добавлено и 9 удалено
  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.
   variable flood [dict create]
 
-  # Binds
   bind pubm -|- "*"          ::[namespace current]::public
   bind pubm -|- "*"          ::[namespace current]::publearn
   bind evnt -|- "save"       ::[namespace current]::save
 
-  # Miscellaneous
   setudef flag dictionary
-  namespace export *
 }
 
 # 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.
+  set term ""
   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
      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
   }
+
   set term_dict [dict get $terms $term]
 
   # 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 description "[string range $argv [expr [string first "," $argv] + 2] end]"
     }
+
     if {[dict exists $terms $term]} {
       set term_dict [dict get $terms $term]
       set def [dict get $term_dict def]
       putserv "PRIVMSG $chan :$term is already $def"
       return
     }
+
     set term [string trim $term]
     set description [string trim $description]
     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"
 }
 
+# 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} {
   # 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