зеркало из https://github.com/mozilla/pjs.git
Implement Infobot functionality as a bot module. b=123692, r=timeless
This commit is contained in:
Родитель
c85d99ace2
Коммит
e19f38e577
|
@ -0,0 +1,762 @@
|
|||
# -*- Mode: perl; tab-width: 4; indent-tabs-mode: nil; -*-
|
||||
################################
|
||||
# Infobot Module #
|
||||
################################
|
||||
# some of these ideas are stolen from infobot, of course.
|
||||
# see www.infobot.org
|
||||
|
||||
package BotModules::Infobot;
|
||||
use vars qw(@ISA);
|
||||
@ISA = qw(BotModules);
|
||||
use AnyDBM_File;
|
||||
use Fcntl;
|
||||
1;
|
||||
|
||||
# XXX "mozbot is a bot" fails (gets handled as a Tell of "is a bot" :-/)
|
||||
# XXX "who is foo" responds "I don't know what is foo" (should respond "I don't know _who_ is foo")
|
||||
|
||||
# it seems tie() works on scope and not on reference counting, so as
|
||||
# soon as the thing it is tying goes out of scope (even if the variable
|
||||
# in question still has active references) it loses its magic.
|
||||
our $factoids = {'is' => {}, 'are' => {}};
|
||||
tie(%{$factoids->{'is'}}, 'AnyDBM_File', 'factoids-is', O_RDWR|O_CREAT, 0666);
|
||||
tie(%{$factoids->{'are'}}, 'AnyDBM_File', 'factoids-are', O_RDWR|O_CREAT, 0666);
|
||||
|
||||
sub Help {
|
||||
my $self = shift;
|
||||
my ($event) = @_;
|
||||
return {
|
||||
'' => 'Keeps track of factoids and returns them on request. '.
|
||||
'To set factoids, just tell me something in the form \'apple is a company\' or \'apples are fruit\'. '.
|
||||
'To find out about something, say \'apple?\' or \'what are apples\'. '.
|
||||
'To correct me, you can use any of: \'no, apple is a fruit\', \'apple =~ s/company/fruit/\', or \'apple is also a fruit\'. '.
|
||||
'To make me forget a factoid, \'forget apple\'. '.
|
||||
'You can use \'|\' to separate several alternative answers.',
|
||||
'who' => 'If a definition contains $who, then it will be replaced by the name of the person who asked the question.',
|
||||
'reply' => 'If a definition starts with <reply> then when responding the initial prefix will be skipped. '.
|
||||
'e.g., \'apples are <reply>mm, apples\' will mean that \'what are apples\' will get the response \'mm, apples\'.',
|
||||
'action' => 'If a definition starts with <action> then when responding the definition will be used as an action. '.
|
||||
'e.g., \'apples are <action>eats one\' will mean that \'what are apples\' will get the response \'* bot eats one\'.',
|
||||
'alias' => 'If a definition starts with <alias> then it will be treated as a symlink to whatever follows. '.
|
||||
'e.g., \'crab apples are <alias>apples\' and \'apples are fruit\' will mean that \'what are crab apples\' will get the response \'apples are fruit\'.',
|
||||
'status' => 'Reports on how many factoids are in the database.',
|
||||
'tell' => 'Make me tell someone something. e.g., \'tell pikachu what apples are\' or \'tell fred about me\'.',
|
||||
'literal' => 'To find out exactly what is stored for an entry apples, you would say to me: literal apples',
|
||||
'remember' => 'If you are having trouble making me remember something (for example \'well, foo is bar\' '.
|
||||
'getting treated as \'foo\' is \'bar\'), then you can prefix your statement with \'remember:\' '.
|
||||
'(following the \'no,\' if you are changing an entry). For example, \'remember: well, foo is bar\'. '.
|
||||
'Note that \'well, foo?\' is treated as \'what is foo\' not is \'what is well, foo\', so this is not always useful.',
|
||||
'no' => 'To correct an entry, prefix your statement with \'no,\'. '.
|
||||
'For example, \'no, I am good\' to correct your entry from \'is bad\' to \'is good\'. :-)',
|
||||
};
|
||||
}
|
||||
|
||||
# RegisterConfig - Called when initialised, should call registerVariables
|
||||
sub RegisterConfig {
|
||||
my $self = shift;
|
||||
$self->SUPER::RegisterConfig(@_);
|
||||
$self->registerVariables(
|
||||
# [ name, save?, settable? ]
|
||||
['autoLearn', 1, 1, ['*']], # in the auto* variables, '*' means 'all channels'
|
||||
['autoHelp', 1, 1, ['*']],
|
||||
['autoEdit', 1, 1, []],
|
||||
['neverLearn', 1, 1, []], # the never* variables override the auto* variables
|
||||
['neverHelp', 1, 1, []],
|
||||
['neverEdit', 1, 1, []],
|
||||
['autoIgnore', 1, 1, []], # list of nicks for which to always turn off auto*
|
||||
['teachers', 1, 1, []], # list of users who may teach, leave blank to allow anyone to teach
|
||||
['factoidPositions', 0, 0, {'is' => {}, 'are' => {}}],
|
||||
['friendBots', 1, 1, []],
|
||||
['researchNotes', 0, 0, {}],
|
||||
['pruneDelay', 1, 1, 120], # how frequently to look through the research notes and remove expired items
|
||||
['queryTimeToLive', 1, 1, 600], # queries can be remembered up to ten minutes by default
|
||||
['dunnoTimeToLive', 1, 1, 604800], # DUNNO queries can be remembered up to a week by default
|
||||
['noIdeaDelay', 1, 1, 2], # how long to wait before admitting lack of knowledge
|
||||
['questions', 0, 0, 0], # how many questions there have been since the last load
|
||||
['edits', 0, 0, 0], # how many edits (learning, editing, forgetting) there have been since the last load
|
||||
['interbots', 0, 0, 0], # how many times we have spoken with other bots
|
||||
['maxInChannel', 1, 1, 200], # beyond this answers are /msged
|
||||
);
|
||||
}
|
||||
|
||||
# Schedule - called when bot connects to a server, to install any schedulers
|
||||
# use $self->schedule($event, $delay, $times, $data)
|
||||
# where $times is 1 for a single event, -1 for recurring events,
|
||||
# and a positive number for an event that occurs that many times.
|
||||
sub Schedule {
|
||||
my $self = shift;
|
||||
my ($event) = @_;
|
||||
$self->schedule($event, \$self->{'pruneDelay'}, -1, 'pruneInfobot');
|
||||
$self->SUPER::Schedule($event);
|
||||
}
|
||||
|
||||
sub unload {
|
||||
my $self = shift;
|
||||
$self->SUPER::unload(@_);
|
||||
# just to make sure...
|
||||
untie(%{$factoids->{'is'}});
|
||||
untie(%{$factoids->{'are'}});
|
||||
}
|
||||
|
||||
sub Told {
|
||||
my $self = shift;
|
||||
my ($event, $message) = @_;
|
||||
if ($message =~ /^\s*status[?\s]*$/osi) {
|
||||
my $sum = $self->countFactoids();
|
||||
my $questions = $self->{'questions'} == 1 ? "$self->{'questions'} question" : "$self->{'questions'} questions";
|
||||
my $edits = $self->{'edits'} == 1 ? "$self->{'edits'} edit" : "$self->{'edits'} edits";
|
||||
my $interbots = $self->{'interbots'} == 1 ? "$self->{'interbots'} time" : "$self->{'interbots'} times";
|
||||
my $friends = @{$self->{'friendBots'}} == 1 ? (scalar(@{$self->{'friendBots'}}).' bot friend') : (scalar(@{$self->{'friendBots'}}).' bot friends');
|
||||
$self->targettedSay($event, "I have $sum factoids in my database and $friends to help me answer questions. ".
|
||||
"Since the last reload, I've been asked $questions, performed $edits, and spoken with other bots $interbots.", 1);
|
||||
} elsif ($event->{'channel'} eq '' and $message =~ /^:INFOBOT:DUNNO <(\S+)> (.*)$/) {
|
||||
$self->ReceivedDunno($event, $1, $2);
|
||||
} elsif ($event->{'channel'} eq '' and $message =~ /^:INFOBOT:QUERY <(\S+)> (.*)$/) {
|
||||
$self->ReceivedQuery($event, $2, $1);
|
||||
} elsif ($event->{'channel'} eq '' and $message =~ /^:INFOBOT:REPLY <(\S+)> (.+?) =(is|are)?=> (.*)$/) {
|
||||
$self->ReceivedReply($event, $3, $2, $1, $4);
|
||||
} elsif ($message =~ /^\s*literal\s+(.+?)\s*$/) {
|
||||
$self->Literal($event, $1);
|
||||
} elsif (not $self->DoFactoidCheck($event, $message, 1)) {
|
||||
return $self->SUPER::Told(@_);
|
||||
}
|
||||
return 0; # we've dealt with it, no need to do anything else.
|
||||
}
|
||||
|
||||
sub Baffled {
|
||||
my $self = shift;
|
||||
my ($event, $message) = @_;
|
||||
if (not $self->DoFactoidCheck($event, $message, 2)) {
|
||||
return $self->SUPER::Heard(@_);
|
||||
}
|
||||
return 0; # we've dealt with it, no need to do anything else.
|
||||
}
|
||||
|
||||
sub Heard {
|
||||
my $self = shift;
|
||||
my ($event, $message) = @_;
|
||||
if (not $self->DoFactoidCheck($event, $message, 0)) {
|
||||
return $self->SUPER::Heard(@_);
|
||||
}
|
||||
return 0; # we've dealt with it, no need to do anything else.
|
||||
}
|
||||
|
||||
sub DoFactoidCheck {
|
||||
my $self = shift;
|
||||
my ($event, $message, $direct) = @_;
|
||||
# $direct is one of: 0 = heard, 1 = told, 2 = baffled
|
||||
|
||||
my $shortMessage;
|
||||
if ($message =~ /^\s* (?:(?:well|and|or|yes|[uh]+m*|o+[oh]*[k]+(?:a+y+)?|still|well|so|[ah]+|[oh]+)[:,.!?\s]+|)*
|
||||
(?:(?:geez?|boy|du+des?|golly|gosh|wow|whee|wo+ho+)?[:,.!\s]+|)*
|
||||
(?:(?:heya?|hello|hi)(?:\s+there)?(?:\s+peoples?|\s+kids?|\s+folks?)?[:,!.?\s]+)*
|
||||
(?:(?:geez?|boy|du+des?|golly|gosh|wow|whee|wo+ho+)?[:,.!\s]+|)*
|
||||
(?:(?:(?:stupid\s+)?q(?:uestion)?|basically)[:,.!\s]+)*
|
||||
(?:(?:does\s+)?(?:any|ne)\s*(?:1|one|body)\s+know[,\s]+|)?
|
||||
(.*)
|
||||
\s*$/osix) {
|
||||
$shortMessage = $1;
|
||||
}
|
||||
|
||||
if ($message =~ /^\s*tell\s+(\S+)\s+about\s+me(?:[,\s]+please)?[\s!?.]*$/osi) {
|
||||
$self->GiveFactoid($event,
|
||||
undef, # database
|
||||
$event->{'from'}, # what
|
||||
$direct,
|
||||
$1); # who
|
||||
} elsif ($message =~ /^\s*tell\s+(\S+)\s+about\s+(.+?)(?:[,\s]+please)?[\s!?.]*$/osi) {
|
||||
$self->GiveFactoid($event,
|
||||
undef, # database
|
||||
$2, # what
|
||||
$direct,
|
||||
$1); # who
|
||||
} elsif ($message =~ /^\s*tell\s+(\S+)\s+(?:what|who|where)\s+(?:am\s+I|I\s+am)(?:[,\s]+please)?[\s!?.]*$/osi) {
|
||||
$self->GiveFactoid($event,
|
||||
'is', # database
|
||||
$event->{'from'}, # what
|
||||
$direct,
|
||||
$1); # who
|
||||
} elsif ($message =~ /^\s*tell\s+(\S+)\s+(?:what|who|where)\s+(is|are)\s+(.+?)(?:[,\s]+please)?[\s!?.]*$/osi) {
|
||||
$self->GiveFactoid($event,
|
||||
lc($2), # database
|
||||
$3, # what
|
||||
$direct,
|
||||
$1); # who
|
||||
} elsif ($message =~ /^\s*tell\s+(\S+)\s+(?:what|who|where)\s+(.+?)\s+(is|are)(?:[,\s]+please)?[\s!?.]*$/osi) {
|
||||
$self->GiveFactoid($event,
|
||||
lc($3), # database
|
||||
$2, # what
|
||||
$direct,
|
||||
$1); # who
|
||||
} elsif ($message =~ /^\s*(.+?)\s*=~\s*s?\/(.+?)\/(.*?)\/(i)?(g)?(i)?\s*$/osi) {
|
||||
$self->EditFactoid($event,
|
||||
$1, # subject
|
||||
$2, # first part to remove
|
||||
$3, # second part to remove
|
||||
defined($5), # global?
|
||||
defined($4) || defined($6), # case insensitive?
|
||||
$direct);
|
||||
} elsif ($message =~ /^\s*forget\s+(?:about\s+)?me\s*$/osi) {
|
||||
$self->ForgetFactoid($event, $event->{'from'}, $direct);
|
||||
} elsif ($message =~ /^\s*forget\s+(?:about\s+)?(.+?)\s*$/osi) {
|
||||
$self->ForgetFactoid($event, $1, $direct);
|
||||
} elsif ($shortMessage =~ /^(?:what|where|who) \s+ (is|are) \s+ (.+?) [?\s]* $/osix) {
|
||||
$self->GiveFactoid($event,
|
||||
lc($1), # is/are (optional)
|
||||
$2, # subject
|
||||
$direct);
|
||||
} elsif ($shortMessage =~ /^(?:(?:where|how)\s+can\s+(?:i|one|s?he|we)\s+(?:find|learn|read)(?:\s+about)? | how\s+about | what\'?s) \s+ (.+?) [?\s]* $/osix) {
|
||||
$self->GiveFactoid($event,
|
||||
undef, # is/are (optional)
|
||||
$1, # subject
|
||||
$direct);
|
||||
} elsif ($shortMessage =~ /^(?:what|where|who)\s+ (.+?) (is|are) [?\s]* $/osix) {
|
||||
$self->GiveFactoid($event,
|
||||
lc($2), # is/are (optional)
|
||||
$1, # subject
|
||||
$direct);
|
||||
} elsif ($shortMessage =~ /^(?:what|where|who) \s+ (?:am\s+I|I\s+am) [?\s]* $/osix) {
|
||||
$self->GiveFactoid($event,
|
||||
'is', # am => is
|
||||
$event->{'from'}, # subject
|
||||
$direct);
|
||||
} elsif ($shortMessage =~ /^(no\s*, (\s*$event->{'nick'}\s*,)? \s+)? (?:remember\s*:\s+)? (.+?) \s+ (is|are) \s+ (also\s+)? (.+?) $/six) {
|
||||
# the "remember:" prefix can be used to delimit the start of the actual content, if necessary.
|
||||
$self->SetFactoid($event,
|
||||
defined($1), # replace existing answer?
|
||||
$3, # subject
|
||||
lc($4), # is/are
|
||||
defined($5), # add to existing answer?
|
||||
$6, # object
|
||||
($direct or defined($2)));
|
||||
} elsif ($shortMessage =~ /^(no\s*, (\s*$event->{'nick'}\s*,)? \s+)? (?:remember\s*:\s+)? I \s+ am \s+ (also\s+)? (.+?) $/osix) {
|
||||
# the "remember:" prefix can be used to delimit the start of the actual content, if necessary.
|
||||
$self->SetFactoid($event,
|
||||
defined($1), # replace existing answer?
|
||||
$event->{'from'}, # subject
|
||||
'is', # I am = Foo is
|
||||
defined($2), # add to existing answer?
|
||||
$3, # object
|
||||
$direct);
|
||||
} elsif ((not $direct or $direct == 2) and $shortMessage =~ /^(.+?)\s+(is|are)[?\s]*\?[?\s]*$/osi) {
|
||||
$self->GiveFactoid($event,
|
||||
lc($2), # is/are (optional)
|
||||
$1, # subject
|
||||
$direct);
|
||||
} elsif ((not $direct or $direct == 2) and $shortMessage =~ /^(.+?)[?\s]*\?[?\s]*$/osi) {
|
||||
$self->GiveFactoid($event,
|
||||
undef, # is/are (optional)
|
||||
$1, # subject
|
||||
$direct);
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
sub SetFactoid {
|
||||
my $self = shift;
|
||||
my($event, $replace, $subject, $database, $add, $object, $direct, $fromBot) = @_;
|
||||
if ($direct or $self->allowed($event, 'Learn')) {
|
||||
|
||||
teacher: if (@{$self->{'teachers'}}) {
|
||||
foreach my $user (@{$self->{'teachers'}}) {
|
||||
if ($user eq $event->{'userName'}) {
|
||||
last teacher;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
# update the database
|
||||
if (not $replace) {
|
||||
$subject = $self->CanonicalizeFactoid($database, $subject);
|
||||
} else {
|
||||
my $oldSubject = $self->CanonicalizeFactoid($database, $subject);
|
||||
if (defined($factoids->{$database}->{$oldSubject})) {
|
||||
delete($factoids->{$database}->{$oldSubject});
|
||||
}
|
||||
}
|
||||
if ($replace or not defined($factoids->{$database}->{$subject})) {
|
||||
$self->debug("Learning that $subject $database '$object'.");
|
||||
$factoids->{$database}->{$subject} = $object;
|
||||
} elsif (not $add) {
|
||||
my @what = split(/\|/o, $factoids->{$database}->{$subject});
|
||||
local $" = '\' or \'';
|
||||
if (not defined($fromBot)) {
|
||||
if (@what == 1 and $what[0] eq $object) {
|
||||
$self->targettedSay($event, 'Yep, that\'s what I thought. Thanks for confirming it.', $direct);
|
||||
} else {
|
||||
# XXX "that's one of the alternatives, sure..."
|
||||
$self->targettedSay($event, "But $subject $database '@what'...", $direct);
|
||||
}
|
||||
}
|
||||
return 0; # failed to update database
|
||||
} else {
|
||||
$self->debug("Learning that $subject $database also '$object'.");
|
||||
$factoids->{$database}->{$subject} .= "|$object";
|
||||
}
|
||||
if (not defined($fromBot)) {
|
||||
$self->targettedSay($event, 'ok', $direct);
|
||||
}
|
||||
if (defined($self->{'researchNotes'}->{lc($subject)})) {
|
||||
my @queue = @{$self->{'researchNotes'}->{lc($subject)}};
|
||||
foreach my $entry (@queue) {
|
||||
my($eventE, $typeE, $databaseE, $subjectE, $targetE, $directE, $visitedAliasesE, $timeE) = @$entry;
|
||||
if ($typeE eq 'QUERY') {
|
||||
if ((defined($targetE) and $event->{'from'} ne $targetE) or
|
||||
($event->{'from'} ne $eventE->{'from'} and
|
||||
($event->{'channel'} eq '' or $event->{'channel'} ne $eventE->{'channel'}))) {
|
||||
my($how, $what, $propagated) = $self->GetFactoid($eventE, $databaseE, $subjectE,
|
||||
$targetE, $directE, $visitedAliasesE, $event->{'from'});
|
||||
if (defined($how)) {
|
||||
if (defined($targetE)) {
|
||||
$self->debug("I now know what '$subject' $database, so telling $targetE, since $eventE->{'from'} told me to.");
|
||||
} else {
|
||||
$self->debug("I now know what '$subject' $database, so telling $eventE->{'from'} who wanted to know.");
|
||||
}
|
||||
$self->factoidSay($eventE, $how, $what, $directE, $targetE);
|
||||
$entry->[1] = 'OLD';
|
||||
} else {
|
||||
# either $propagated, or database doesn't match requested database, or internal error
|
||||
$self->debug("I now know what '$subject' $database, but for some reason that ".
|
||||
"didn't help me help $eventE->{'from'} who needed to know what '$subjectE' $databaseE.");
|
||||
}
|
||||
}
|
||||
} elsif ($typeE eq 'DUNNO') {
|
||||
my $who = defined($targetE) ? $targetE : $eventE->{'from'};
|
||||
$self->directSay($eventE, ":INFOBOT:REPLY <$who> $subject =$database=> $factoids->{$database}->{$subject}");
|
||||
$entry->[1] = 'OLD';
|
||||
}
|
||||
}
|
||||
}
|
||||
$self->{'edits'}++;
|
||||
return 1;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
sub GiveFactoid {
|
||||
my $self = shift;
|
||||
my($event, $database, $subject, $direct, $target) = @_;
|
||||
if ($direct or $self->allowed($event, 'Help')) {
|
||||
if ($target eq $event->{'nick'}) {
|
||||
$self->targettedSay($event, 'Oh, yeah, great idea, get me to talk to myself.', $direct);
|
||||
} else {
|
||||
if (lc($subject) eq 'you') {
|
||||
# first, skip some words that are handled by other commonly-used modules
|
||||
# in particular, 'who are you' is handled by Greeting.bm
|
||||
return;
|
||||
}
|
||||
$self->{'questions'}++;
|
||||
my($how, $what, $propagated) = $self->GetFactoid($event, $database, $subject, $target, $direct);
|
||||
if (not defined($how)) {
|
||||
$self->scheduleNoIdea($event, $database, $subject, $direct, $propagated);
|
||||
} else {
|
||||
$self->debug("Telling $event->{'from'} about $subject.");
|
||||
$self->factoidSay($event, $how, $what, $direct, $target);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sub Literal {
|
||||
my $self = shift;
|
||||
my($event, $subject) = @_;
|
||||
my $is = $self->CanonicalizeFactoid('is', $subject);
|
||||
my $are = $self->CanonicalizeFactoid('are', $subject);
|
||||
if (defined($is) or defined($are)) {
|
||||
local $" = '\' or \'';
|
||||
if (defined($factoids->{'is'}->{$is})) {
|
||||
my @what = split(/\|/o, $factoids->{'is'}->{$is});
|
||||
$self->targettedSay($event, "$is is '@what'.", 1);
|
||||
}
|
||||
if (defined($factoids->{'are'}->{$are})) {
|
||||
my @what = split(/\|/o, $factoids->{'are'}->{$is});
|
||||
$self->targettedSay($event, "$are are '@what'.", 1);
|
||||
}
|
||||
} else {
|
||||
$self->targettedSay($event, "I have no record of anything called '$subject'.", 1);
|
||||
}
|
||||
}
|
||||
|
||||
sub scheduleNoIdea {
|
||||
my $self = shift;
|
||||
my($event, $database, $subject, $direct, $propagated) = @_;
|
||||
if (ref($propagated)) {
|
||||
$self->schedule($event, \$self->{'noIdeaDelay'}, 1, 'noIdea', $database, $subject, $direct, $propagated);
|
||||
} else {
|
||||
$self->noIdea($event, $database, $subject, $direct);
|
||||
}
|
||||
}
|
||||
|
||||
sub GetFactoid {
|
||||
my $self = shift;
|
||||
my($event, $originalDatabase, $subject, $target, $direct, $visitedAliases, $friend) = @_;
|
||||
if (not defined($visitedAliases)) {
|
||||
$visitedAliases = {};
|
||||
}
|
||||
my $database;
|
||||
($database, $subject) = $self->FindFactoid($originalDatabase, $subject);
|
||||
if (defined($factoids->{$database}->{$subject})) {
|
||||
my @alternatives = split(/\|/o, $factoids->{$database}->{$subject});
|
||||
my $answer;
|
||||
if (@alternatives) {
|
||||
if (not defined($self->{'factoidPositions'}->{$database}->{$subject})
|
||||
or $self->{'factoidPositions'}->{$database}->{$subject} >= scalar(@alternatives)) {
|
||||
$self->{'factoidPositions'}->{$database}->{$subject} = 0;
|
||||
}
|
||||
$answer = @alternatives[$self->{'factoidPositions'}->{$database}->{$subject}];
|
||||
$self->{'factoidPositions'}->{$database}->{$subject}++;
|
||||
} else {
|
||||
$answer = @alternatives[0];
|
||||
}
|
||||
my $who = defined($target) ? $target : $event->{'from'};
|
||||
$answer =~ s/\$who/$who/go;
|
||||
if ($answer =~ /^<alias>(.*)$/o) {
|
||||
if ($visitedAliases->{$1}) {
|
||||
return ('msg', "see $subject", 0);
|
||||
} else {
|
||||
$visitedAliases->{$subject}++;
|
||||
my($how, $what, $propagated) = $self->GetFactoid($event, undef, $1, $target, $direct, $visitedAliases);
|
||||
if (not defined($how)) {
|
||||
return ('msg', "see $1", $propagated);
|
||||
} else {
|
||||
return ($how, $what, $propagated);
|
||||
}
|
||||
}
|
||||
} elsif ($answer =~ /^<action>/o) {
|
||||
$answer =~ s/^<action>\s*//o;
|
||||
return ('me', $answer, 0);
|
||||
} else {
|
||||
if ($answer =~ /^<reply>/o) {
|
||||
$answer =~ s/^<reply>\s*//o;
|
||||
} else {
|
||||
if (lc($who) eq lc($subject)) {
|
||||
$answer = "you are $answer";
|
||||
} else {
|
||||
$answer = "$subject $database $answer";
|
||||
}
|
||||
if (defined($friend)) {
|
||||
$answer = "$friend knew: $answer";
|
||||
}
|
||||
}
|
||||
return ('msg', $answer, 0);
|
||||
}
|
||||
} else {
|
||||
# we have no idea what this is
|
||||
return (undef, undef, $self->Research($event, $originalDatabase, $subject, $target, $direct, $visitedAliases));
|
||||
}
|
||||
}
|
||||
|
||||
sub CanonicalizeFactoid {
|
||||
my $self = shift;
|
||||
my($database, $subject) = @_;
|
||||
if (not defined($factoids->{$database}->{$subject})) {
|
||||
while (my $key = each %{$factoids->{$database}}) {
|
||||
if (lc($key) eq lc($subject)) {
|
||||
$subject = $key;
|
||||
# can't return or 'each' iterator won't be reset XXX
|
||||
}
|
||||
}
|
||||
}
|
||||
return $subject;
|
||||
}
|
||||
|
||||
sub FindFactoid {
|
||||
my $self = shift;
|
||||
my($database, $subject) = @_;
|
||||
if (not defined($database)) {
|
||||
$database = 'is';
|
||||
$subject = $self->CanonicalizeFactoid('is', $subject);
|
||||
if (not defined($factoids->{'is'}->{$subject})) {
|
||||
$subject = $self->CanonicalizeFactoid('are', $subject);
|
||||
if (defined($factoids->{'are'}->{$subject})) {
|
||||
$database = 'are';
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$subject = $self->CanonicalizeFactoid($database, $subject);
|
||||
}
|
||||
return ($database, $subject);
|
||||
}
|
||||
|
||||
sub EditFactoid {
|
||||
my $self = shift;
|
||||
my($event, $subject, $search, $replace, $global, $caseInsensitive, $direct) = @_;
|
||||
if ($direct or $self->allowed($event, 'Edit')) {
|
||||
my $database;
|
||||
($database, $subject) = $self->FindFactoid($database, $subject);
|
||||
if (not defined($factoids->{$database}->{$subject})) {
|
||||
$self->targettedSay($event, "Er, I don't know about this $subject thingy...", $direct);
|
||||
return;
|
||||
}
|
||||
$self->debug("Editing the $subject entry.");
|
||||
my @output;
|
||||
foreach my $factoid (split(/\|/o, $factoids->{$database}->{$subject})) {
|
||||
$search = $self->sanitizeRegexp($search);
|
||||
if ($global and $caseInsensitive) {
|
||||
$factoid =~ s/$search/$replace/gi;
|
||||
} elsif ($global) {
|
||||
$factoid =~ s/$search/$replace/g;
|
||||
} elsif ($caseInsensitive) {
|
||||
$factoid =~ s/$search/$replace/i;
|
||||
} else {
|
||||
$factoid =~ s/$search/$replace/;
|
||||
}
|
||||
push(@output, $factoid);
|
||||
}
|
||||
$factoids->{$database}->{$subject} = join('|', @output);
|
||||
$self->targettedSay($event, 'ok', $direct);
|
||||
$self->{'edits'}++;
|
||||
}
|
||||
}
|
||||
|
||||
sub ForgetFactoid {
|
||||
my $self = shift;
|
||||
my($event, $subject, $direct) = @_;
|
||||
if ($direct or $self->allowed($event, 'Edit')) {
|
||||
my $count = 0;
|
||||
my $database;
|
||||
foreach my $db ('is', 'are') {
|
||||
($database, $subject) = $self->FindFactoid($db, $subject);
|
||||
if (defined($factoids->{$database}->{$subject})) {
|
||||
delete($factoids->{$database}->{$subject});
|
||||
$count++;
|
||||
}
|
||||
}
|
||||
if ($count) {
|
||||
$self->targettedSay($event, "I've forgotten what I knew about '$subject'.", $direct);
|
||||
$self->{'edits'}++;
|
||||
} else {
|
||||
$self->targettedSay($event, "I never knew anything about '$subject' in the first place!", $direct);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# interbot communications
|
||||
sub Research {
|
||||
my $self = shift;
|
||||
my($event, $database, $subject, $target, $direct, $visitedAliases) = @_;
|
||||
if (not @{$self->{'friendBots'}}) {
|
||||
# no bots to ask, bail out
|
||||
return 0;
|
||||
}
|
||||
# now check that we need to ask the bots about it:
|
||||
my $asked = 0;
|
||||
if (not defined($self->{'researchNotes'}->{$subject})) {
|
||||
$self->{'researchNotes'}->{$subject} = [];
|
||||
} else {
|
||||
entry: foreach my $entry (@{$self->{'researchNotes'}->{lc($subject)}}) {
|
||||
my($eventE, $typeE, $databaseE, $subjectE, $targetE, $directE, $visitedAliasesE, $timeE) = @$entry;
|
||||
if ($typeE eq 'QUERY') {
|
||||
$asked++; # at least one bot was already asked quite recently
|
||||
if ((defined($targetE) and lc($targetE) eq lc($targetE)) or
|
||||
(not defined($targetE) and lc($event->{'from'}) eq lc($eventE->{'from'}))) {
|
||||
# already queued
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
# remember to tell these people about $subject if we ever find out about it:
|
||||
my $entry = [$event, 'QUERY', $database, $subject, $target, $direct, $visitedAliases, time()];
|
||||
push(@{$self->{'researchNotes'}->{lc($subject)}}, $entry);
|
||||
my $who = defined($target) ? $target : $event->{'from'};
|
||||
if (not $asked) {
|
||||
# not yet asked, so ask each bot about $subject
|
||||
foreach my $bot (@{$self->{'friendBots'}}) {
|
||||
local $event->{'from'} = $bot;
|
||||
$self->directSay($event, ":INFOBOT:QUERY <$who> $subject");
|
||||
}
|
||||
$self->{'interbots'}++;
|
||||
return $entry; # return reference to entry so that we can check if it has been replied or not
|
||||
} else {
|
||||
return $asked;
|
||||
}
|
||||
}
|
||||
|
||||
sub ReceivedReply {
|
||||
my $self = shift;
|
||||
my($event, $database, $subject, $target, $object) = @_;
|
||||
$self->{'interbots'}++;
|
||||
if (not $self->SetFactoid($event, 0, $subject, $database, 0, $object, 1, 1) and
|
||||
defined($self->{'researchNotes'}->{lc($subject)})) {
|
||||
# we didn't believe $event->{'from'}, but we might as well
|
||||
# tell any users that were wondering.
|
||||
foreach my $entry (@{$self->{'researchNotes'}->{lc($subject)}}) {
|
||||
my($eventE, $typeE, $databaseE, $subjectE, $targetE, $directE, $visitedAliasesE, $timeE) = @$entry;
|
||||
if ($typeE eq 'QUERY') {
|
||||
$self->factoidSay($eventE, 'msg', "According to $event->{'from'}, $subject $database '$object'.", $directE, $targetE);
|
||||
} elsif ($typeE eq 'DUNNO') {
|
||||
my $who = defined($targetE) ? $targetE : $eventE->{'from'};
|
||||
$self->directSay($eventE, ":INFOBOT:REPLY <$who> $subject =$database=> $object");
|
||||
}
|
||||
$entry->[1] = 'OLD';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sub ReceivedQuery {
|
||||
my $self = shift;
|
||||
my($event, $subject, $target) = @_;
|
||||
$self->{'interbots'}++;
|
||||
if (not $self->tellBot($event, $subject, $target)) {
|
||||
# in the spirit of embrace-and-extend, we're going to say that
|
||||
# :INFOBOT:DUNNO means "I don't know, but if you ever find
|
||||
# out, please tell me".
|
||||
$self->directSay($event, ":INFOBOT:DUNNO <$event->{'nick'}> $subject");
|
||||
}
|
||||
}
|
||||
|
||||
sub ReceivedDunno {
|
||||
my $self = shift;
|
||||
my($event, $target, $subject) = @_;
|
||||
$self->{'interbots'}++;
|
||||
if (not $self->tellBot($event, $subject, $target)) {
|
||||
# store the request
|
||||
push(@{$self->{'researchNotes'}->{lc($subject)}}, [$event, 'DUNNO', undef, $1, $target, 0, {}, time()]);
|
||||
}
|
||||
}
|
||||
|
||||
sub tellBot {
|
||||
my $self = shift;
|
||||
my($event, $subject, $target) = @_;
|
||||
my $count = 0;
|
||||
my $database;
|
||||
foreach my $db ('is', 'are') {
|
||||
($database, $subject) = $self->FindFactoid($db, $subject);
|
||||
if (defined($factoids->{$database}->{$subject})) {
|
||||
$self->directSay($event, ":INFOBOT:REPLY <$target> $subject =$database=> $factoids->{$database}->{$subject}");
|
||||
$count++;
|
||||
}
|
||||
}
|
||||
return $count;
|
||||
}
|
||||
|
||||
sub Scheduled {
|
||||
my $self = shift;
|
||||
my ($event, @data) = @_;
|
||||
if ($data[0] eq 'pruneInfobot') {
|
||||
my $now = time();
|
||||
foreach my $key (keys %{$self->{'researchNotes'}}) {
|
||||
my @new;
|
||||
foreach my $entry (@{$self->{'researchNotes'}->{$key}}) {
|
||||
my($eventE, $typeE, $databaseE, $subjectE, $targetE, $directE, $visitedAliasesE, $timeE) = @$entry;
|
||||
if (($typeE eq 'QUERY' and ($now - $timeE) < $self->{'queryTimeToLive'}) or
|
||||
($typeE eq 'DUNNO' and ($now - $timeE) < $self->{'dunnoTimeToLive'})) {
|
||||
push(@new, $entry);
|
||||
}
|
||||
}
|
||||
if (@new) {
|
||||
$self->{'researchNotes'}->{$key} = \@new;
|
||||
} else {
|
||||
delete($self->{'researchNotes'}->{$key});
|
||||
}
|
||||
}
|
||||
} elsif ($data[0] eq 'noIdea') {
|
||||
my(undef, $database, $subject, $direct, $propagated) = @data;
|
||||
my($eventE, $typeE, $databaseE, $subjectE, $targetE, $directE, $visitedAliasesE, $timeE) = @$propagated;
|
||||
# in theory, $eventE = $event, $databaseE = $database,
|
||||
# $subjectE = $subject, $targetE depends on if this was
|
||||
# triggered by a tell, $directE = $direct, $visitedAliasesE is
|
||||
# opaque, and $timeE is opaque.
|
||||
if ($typeE ne 'OLD') {
|
||||
$self->noIdea($event, $database, $subject, $direct);
|
||||
}
|
||||
} else {
|
||||
$self->SUPER::Scheduled($event, @data);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
# internal helper routines
|
||||
|
||||
sub factoidSay {
|
||||
my $self = shift;
|
||||
my($event, $how, $what, $direct, $target) = @_;
|
||||
if (defined($target)) {
|
||||
$self->targettedSay($event, "told $target", 1);
|
||||
my $helper = $event->{'from'};
|
||||
local $event->{'from'} = $target;
|
||||
if ($how eq 'me') {
|
||||
$self->directEmote($event, $what);
|
||||
} else {
|
||||
if (length($what)) {
|
||||
$self->directSay($event, "$helper wanted you to know: $what");
|
||||
}
|
||||
}
|
||||
} elsif ($how eq 'me') {
|
||||
$self->emote($event, $what);
|
||||
} else {
|
||||
if ($event->{'channel'} eq '' or length($what) < $self->{'maxInChannel'}) {
|
||||
$self->targettedSay($event, $what, 1);
|
||||
} else {
|
||||
if ($direct) {
|
||||
$self->targettedSay($event, substr($what, 0, $self->{'maxInChannel'}) . '... (rest /msged)' , 1);
|
||||
$self->directSay($event, $what);
|
||||
} else {
|
||||
$self->targettedSay($event, substr($what, 0, $self->{'maxInChannel'}) . '... (there is more; ask me in a /msg)' , 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sub targettedSay {
|
||||
my $self = shift;
|
||||
my($event, $message, $direct) = @_;
|
||||
if ($direct and length($message)) {
|
||||
if ($event->{'channel'} ne '') {
|
||||
$message = "$event->{'from'}: $message";
|
||||
}
|
||||
$self->say($event, $message);
|
||||
}
|
||||
}
|
||||
|
||||
sub countFactoids {
|
||||
my $self = shift;
|
||||
# don't want to use keys() as that would load the whole database index into memory.
|
||||
my $sum = 0;
|
||||
while (my $factoid = each %{$factoids->{'is'}}) { $sum++; }
|
||||
while (my $factoid = each %{$factoids->{'are'}}) { $sum++; }
|
||||
return $sum;
|
||||
}
|
||||
|
||||
sub allowed {
|
||||
my $self = shift;
|
||||
my($event, $type) = @_;
|
||||
if ($event->{'channel'} ne '') {
|
||||
foreach my $user (@{$self->{'autoIgnore'}}) {
|
||||
if ($user eq $event->{'from'}) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
foreach my $channel (@{$self->{"never$type"}}) {
|
||||
if ($channel eq $event->{'channel'} or
|
||||
$channel eq '*') {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
foreach my $channel (@{$self->{"auto$type"}}) {
|
||||
if ($channel eq $event->{'channel'} or
|
||||
$channel eq '*') {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
sub noIdea {
|
||||
my $self = shift;
|
||||
my($event, $database, $subject, $direct) = @_;
|
||||
if (lc($subject) eq lc($event->{'from'})) {
|
||||
$self->targettedSay($event, "Sorry, I've no idea who you are.", $direct);
|
||||
} else {
|
||||
if (not defined($database)) {
|
||||
$database = 'might be';
|
||||
}
|
||||
$self->targettedSay($event, "Sorry, I've no idea what '$subject' $database.", $direct);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,69 @@
|
|||
#!/usr/bin/perl -w
|
||||
######################################
|
||||
# Infobot Factoid Import/Export Tool #
|
||||
######################################
|
||||
|
||||
use strict;
|
||||
use AnyDBM_File;
|
||||
use Fcntl;
|
||||
|
||||
if (not @ARGV == 2) {
|
||||
&use();
|
||||
} else {
|
||||
my $command = shift @ARGV;
|
||||
my $filename = shift @ARGV;
|
||||
if ($command eq '-d') {
|
||||
&dump($filename);
|
||||
} elsif ($command eq '-i') {
|
||||
&import($filename);
|
||||
} else {
|
||||
&use();
|
||||
}
|
||||
}
|
||||
|
||||
sub use {
|
||||
print "\n";
|
||||
print " usage: $0 -d dbname\n";
|
||||
print " prints out an ascii flat file of the database listed.\n";
|
||||
print " dbname should be the basename of the db, e.g.\n";
|
||||
print " $0 -d ../factoids-is > is.fact\n";
|
||||
print " $0 -d ../factoids-are > are.fact\n";
|
||||
print "\n";
|
||||
print " $0 -i dbname\n";
|
||||
print " prints out an ascii flat file of the database listed.\n";
|
||||
print " dbname should be the basename of the db, e.g.\n";
|
||||
print " $0 -i ../factoids-is < chemicals.fact\n";
|
||||
print " $0 -i ../factoids-is < is.fact\n";
|
||||
print " $0 -i ../factoids-are < are.fact\n";
|
||||
print "\n";
|
||||
exit(1);
|
||||
}
|
||||
|
||||
sub dump {
|
||||
my %db;
|
||||
tie(%db, 'AnyDBM_File', shift, O_RDONLY, 0666);
|
||||
while (my ($key, $val) = each %db) {
|
||||
chomp $val;
|
||||
print "$key => $val\n";
|
||||
}
|
||||
}
|
||||
|
||||
sub import {
|
||||
my %db;
|
||||
tie(%db, 'AnyDBM_File', shift, O_WRONLY|O_CREAT, 0666);
|
||||
while (<STDIN>) {
|
||||
chomp;
|
||||
unless (m/\s*(.+?)\s+=(?:is=|are=)?>\s+(.+?)\s*$/o) {
|
||||
m/\s*(.+?)\s+(?:is|are)?\s+(.+?)\s*$/o;
|
||||
}
|
||||
if (length($1) and length($2)) {
|
||||
if (defined($db{$1})) {
|
||||
if (not $db{$1} =~ m/^(|.*\|)\Q$2\E(|.*\|)$/s) {
|
||||
$db{$1} .= "|$2";
|
||||
}
|
||||
} else {
|
||||
$db{$1} = $2;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,195 @@
|
|||
The Infobot Protocol
|
||||
====================
|
||||
|
||||
Reverse engineered from infobot 0.45.3 by Ian Hickson.
|
||||
|
||||
|
||||
QUERY
|
||||
-----
|
||||
|
||||
If a bot is asked something by a user and does not know the answer, it
|
||||
may send queries to all the bots it knows. Queries must be in private
|
||||
messages and should have the following form:
|
||||
|
||||
:INFOBOT:QUERY <target> subject
|
||||
|
||||
...where "target" is the name of the user who sent the query in the
|
||||
first place, and "subject" is the question that was asked.
|
||||
|
||||
In reality, "target" may be any string of non-whitespace character, so
|
||||
it could be used as an internal ID.
|
||||
|
||||
A bot receiving a QUERY message must not try to contact the user given
|
||||
by "target" (that string should be treated as opaque) and must not
|
||||
make any assumptions about the "subject" string (it could contain
|
||||
*anything*, including high bit characters and the works).
|
||||
|
||||
It is an error for the "subject" string to contain either "=is=>" or
|
||||
"=are=>". Receiving bots may ignore this error, however.
|
||||
|
||||
Bot authors should carefully consider the potential for cascades
|
||||
before writing bots that chain QUERY messages. (As in, send out QUERY
|
||||
messages if they are unable to respond to a QUERY message themselves).
|
||||
In general, this is not a recommended behaviour.
|
||||
|
||||
Bot authors are urged to write protection into their bots to avoid
|
||||
being affected by poorly written bots that cause cascades.
|
||||
|
||||
|
||||
REPLY
|
||||
-----
|
||||
|
||||
Upon receiving a QUERY message, a bot may, if it has information on
|
||||
"subject", opt to send a private message back to the originating bot
|
||||
in the form of a REPLY message. Bots must not send unsolicited REPLY
|
||||
messages. The form of the REPLY message is:
|
||||
|
||||
:INFOBOT:REPLY <target> subject =database=> object
|
||||
|
||||
...where "target" is the string of the same name from the original
|
||||
QUERY message, "subject" is the second string from the original QUERY
|
||||
message, "database" is one of "is" or "are" depending on the whether
|
||||
"subject" is determined to be singular or plural respectively, and
|
||||
"object" is the string that should be assumed to be the answer to
|
||||
"subject". The string may contain special formatting codes, these are
|
||||
described below.
|
||||
|
||||
Upon receiving a REPLY message, bots should first check that they are
|
||||
expecting one. If they are, the user identified by the "target" string
|
||||
should be contacted and given the information represented by the
|
||||
"object" string. (Remember that the "target" string need not actually
|
||||
be the nick of the original user; it could be an internal key that
|
||||
indirectly identifies a user.)
|
||||
|
||||
Bots should carefully check the integrity and authenticity of the
|
||||
"target" string, and must check that "database" is one of "is" or
|
||||
"are". The "subject" string ends at the first occurance of either
|
||||
"=is=>" or "=are=>". It is *not* an error for the "object" string to
|
||||
contain either of those substrings.
|
||||
|
||||
Bots may opt to store the information given by a REPLY request so that
|
||||
future questions may be answered without depending on other bots.
|
||||
|
||||
It is suggested that bots credit which bot actually knew the
|
||||
information when reporting back to the user.
|
||||
|
||||
|
||||
DUNNO
|
||||
-----
|
||||
|
||||
(This is not part of the original infobot protocol. And is, as of
|
||||
2002-02-05, only supported by the mozbot2 Infobot module.)
|
||||
|
||||
Upon receiving a QUERY message, a bot may, if it has no information on
|
||||
the "subject" in question, reply with a DUNNO message. This message
|
||||
has basically the same form as the QUERY message:
|
||||
|
||||
:INFOBOT:DUNNO <target> subject
|
||||
|
||||
The DUNNO message indicates that the bot is not aware of the answer to
|
||||
the question, but would like to be informed of the answer, should the
|
||||
first bot ever find out about it. The "target" string should, as with
|
||||
the QUERY string, be considered opaque.
|
||||
|
||||
Upon receiving a DUNNO message, there are several possible responses.
|
||||
If the bot is aware of the answer to "subject", then it should treat
|
||||
the DUNNO message as if it was a QUERY message (typically resulting in
|
||||
a REPLY message). This can occur if, for example, another bot has sent
|
||||
a REPLY to the original QUERY before this bot has had the chance to
|
||||
send the DUNNO message.
|
||||
|
||||
If the first bot still doesn't know the answer, however, it may store
|
||||
the DUNNO request internally. If, at a future time, the bot is
|
||||
informed (either directly by a user or through a REPLY message) about
|
||||
the answer to "subject", then it may send a REPLY message to the bot
|
||||
that sent the DUNNO request, informing the bot of the value it learnt.
|
||||
|
||||
|
||||
SPECIAL STRINGS
|
||||
---------------
|
||||
|
||||
The "object" string in the REPLY message may contain several special
|
||||
flags.
|
||||
|
||||
$who
|
||||
If the string contains the string "$who" then, when the string is
|
||||
given to the user, it should be replaced by the name of the user.
|
||||
|
||||
|
|
||||
Multiple alternative replies may be encoded in one reply, those
|
||||
should be separated by a vertical bar.
|
||||
|
||||
<reply>
|
||||
If the string is prefixed by "<reply>" then the string should not
|
||||
be prefixed by "subject is" or "subject are" as usual.
|
||||
|
||||
<action>
|
||||
The string should be returned via a CTCP ACTION.
|
||||
|
||||
<alias>
|
||||
The string should be taken as the name of another entry to look up.
|
||||
|
||||
|
||||
EXAMPLES
|
||||
--------
|
||||
|
||||
In these examples, A, B and C are bots, and x, y and z are people.
|
||||
|
||||
The first example shows a simple case of one bots asking two other
|
||||
bots for help, one of which gives a reply and the other of which says
|
||||
it has no idea.
|
||||
|
||||
+-------- originator of private message
|
||||
|
|
||||
| +--- target of private message
|
||||
| |
|
||||
V V
|
||||
z -> A: what is foo?
|
||||
A -> z: I have no idea.
|
||||
A -> B: :INFOBOT:QUERY <z> foo
|
||||
A -> C: :INFOBOT:QUERY <z> foo
|
||||
B -> A: :INFOBOT:REPLY <x> foo =is=> bar
|
||||
C -> A: :INFOBOT:DUNNO <C> foo
|
||||
A -> x: B knew: foo is bar
|
||||
A -> C: :INFOBOT:REPLY <C> foo =is=> bar
|
||||
|
||||
Note how the DUNNO in this case comes after the REPLY and thus is
|
||||
immediately answered.
|
||||
|
||||
The next example uses <alias>. One bot knows the answer to the
|
||||
question as an alias to another word, but when the original bot asks
|
||||
about _that_ word, it is the second bot that can help.
|
||||
|
||||
z -> A: what is foo?
|
||||
A -> z: I have no idea.
|
||||
A -> B: :INFOBOT:QUERY <z> foo
|
||||
A -> C: :INFOBOT:QUERY <z> foo
|
||||
B -> A: :INFOBOT:REPLY <x> foo =is=> <alias>bar
|
||||
C -> A: :INFOBOT:DUNNO <C> foo
|
||||
A -> B: :INFOBOT:QUERY <z> bar
|
||||
A -> C: :INFOBOT:QUERY <z> bar
|
||||
A -> C: :INFOBOT:REPLY <C> foo =is=> <alias>bar
|
||||
B -> A: :INFOBOT:DUNNO <B> bar
|
||||
C -> A: :INFOBOT:REPLY <x> bar =is=> baz
|
||||
A -> z: C knew: bar is baz
|
||||
A -> B: :INFOBOT:REPLY <B> bar =is=> baz
|
||||
|
||||
Note how the credit actually goes to the second bot. A better bot
|
||||
might remember all the bots involved and credit all of them. A better
|
||||
bot might also remember what the original question was and reply "foo
|
||||
is baz" instead of "bar is baz".
|
||||
|
||||
Next we have some examples of special codes. If we have:
|
||||
|
||||
foo is bar|<alias>baz|<reply>foo to you too|<action>foos|$who
|
||||
baz is foo
|
||||
|
||||
...then the following are valid responses when asked about foo:
|
||||
|
||||
<A> foo is bar
|
||||
<A> baz is foo
|
||||
<A> foo to you too
|
||||
* A foos
|
||||
<A> foo is z
|
||||
|
||||
-- end --
|
Загрузка…
Ссылка в новой задаче