Compare commits

..

8 Commits

4 changed files with 94 additions and 14 deletions

View File

@ -60,15 +60,10 @@ class Markov(Plugin):
def handle_chatter(self, connection, event): def handle_chatter(self, connection, event):
"""Learn from IRC chatter.""" """Learn from IRC chatter."""
what = event.arguments[0] what = event.arguments[0]
if connection.server_config.additional_addressed_nicks: who = irc.client.NickMask(event.source).nick
all_nicks = '|'.join(connection.server_config.additional_addressed_nicks.split('\n') +
[connection.get_nickname()])
else:
all_nicks = connection.get_nickname()
trimmed_what = re.sub(r'^(({nicks})[:,]|@({nicks}))\s+'.format(nicks=all_nicks), '', what)
nick = irc.client.NickMask(event.source).nick
target = reply_destination_for_event(event) target = reply_destination_for_event(event)
log.debug("what: '%s', who: '%s', target: '%s'", what, who, target)
# check to see whether or not we should learn from this channel # check to see whether or not we should learn from this channel
channel = None channel = None
if irc.client.is_channel(target): if irc.client.is_channel(target):
@ -78,11 +73,23 @@ class Markov(Plugin):
log.debug("not learning from %s as i've been told to ignore it", channel) log.debug("not learning from %s as i've been told to ignore it", channel)
else: else:
# learn the line # learn the line
# remove our own nick and aliases from what we learn
if connection.server_config.additional_addressed_nicks:
all_nicks = '|'.join(connection.server_config.additional_addressed_nicks.split('\n') +
[connection.get_nickname()])
else:
all_nicks = connection.get_nickname()
what = re.sub(r'^(({nicks})[:,]|@({nicks}))\s+'.format(nicks=all_nicks), '', what)
# don't learn the speaker's nick if this came over a bridge
if channel and who == channel.discord_bridge:
what = ' '.join(what.split(' ')[1:])
recursing = getattr(event, 'recursing', False) recursing = getattr(event, 'recursing', False)
if not recursing: if not recursing:
log.debug("learning %s", trimmed_what) log.debug("learning %s", what)
context = markovlib.get_or_create_target_context(target) context = markovlib.get_or_create_target_context(target)
markovlib.learn_line(trimmed_what, context) markovlib.learn_line(what, context)
log.debug("searching '%s' for '%s'", what, all_nicks) log.debug("searching '%s' for '%s'", what, all_nicks)
if re.search(all_nicks, what, re.IGNORECASE) is not None: if re.search(all_nicks, what, re.IGNORECASE) is not None:
@ -96,7 +103,7 @@ class Markov(Plugin):
topics = [x for x in match.group('addressed_msg').split(' ') if len(x) >= 3] topics = [x for x in match.group('addressed_msg').split(' ') if len(x) >= 3]
return self.bot.reply(event, "{0:s}: {1:s}" return self.bot.reply(event, "{0:s}: {1:s}"
"".format(nick, " ".join(markovlib.generate_line(context, topics=topics)))) "".format(who, " ".join(markovlib.generate_line(context, topics=topics))))
else: else:
# i wasn't addressed directly, so just respond # i wasn't addressed directly, so just respond
topics = [x for x in what.split(' ') if len(x) >= 3] topics = [x for x in what.split(' ') if len(x) >= 3]

View File

@ -1,6 +1,6 @@
"""Provide methods for manipulating markov chain processing.""" """Provide methods for manipulating markov chain processing."""
import logging import logging
from random import SystemRandom as sysrand import random
from django.db.models import Sum from django.db.models import Sum
@ -22,7 +22,7 @@ def generate_line(context, topics=None, min_words=15, max_words=30, sentence_bia
else: else:
if len(line) > 0: if len(line) > 0:
if line[-1][-1] not in [',', '.', '!', '?', ':']: if line[-1][-1] not in [',', '.', '!', '?', ':']:
line[-1] += sysrand.choice(['?', '.', '!']) line[-1] += random.SystemRandom().choice(['?', '.', '!'])
tries += 1 tries += 1
@ -53,7 +53,7 @@ def generate_sentence(context, topics=None, min_words=15, max_words=30):
words = [] words = []
# if we have topics, try to work from it and work backwards # if we have topics, try to work from it and work backwards
if topics: if topics:
topic_word = sysrand.choice(topics) topic_word = random.SystemRandom().choice(topics)
topics.remove(topic_word) topics.remove(topic_word)
log.debug("looking for topic '%s'", topic_word) log.debug("looking for topic '%s'", topic_word)
new_states = MarkovState.objects.filter(context=context, v=topic_word) new_states = MarkovState.objects.filter(context=context, v=topic_word)
@ -100,7 +100,7 @@ def generate_sentence(context, topics=None, min_words=15, max_words=30):
words.append(MarkovState._stop) words.append(MarkovState._stop)
elif len(target_hits) > 0: elif len(target_hits) > 0:
# if there's a target word in the states, pick it # if there's a target word in the states, pick it
target_hit = sysrand.choice(target_hits) target_hit = random.SystemRandom().choice(target_hits)
log.debug("found a topic hit %s, using it", target_hit) log.debug("found a topic hit %s, using it", target_hit)
topics.remove(target_hit) topics.remove(target_hit)
words.append(target_hit) words.append(target_hit)

View File

@ -0,0 +1,72 @@
"""Test IRC behavior of the markov plugin."""
from unittest import mock
from django.test import TestCase
from ircbot.models import IrcServer
from markov.ircplugin import Markov
class MarkovTestCase(TestCase):
"""Test the markov plugin."""
fixtures = ['tests/fixtures/irc_server_fixture.json']
def setUp(self):
"""Create common objects."""
self.mock_bot = mock.MagicMock()
self.mock_connection = mock.MagicMock()
self.mock_connection.get_nickname.return_value = 'test_bot'
self.mock_connection.server_config = IrcServer.objects.get(pk=1)
self.plugin = Markov(self.mock_bot, self.mock_connection, mock.MagicMock())
def test_learn(self):
"""Test that an IRC event triggers learning as expected."""
mock_event = mock.MagicMock()
mock_event.arguments = ['hello this is a test message']
mock_event.target = '#test'
mock_event.recursing = False
with mock.patch('markov.lib.learn_line') as mock_learn_line:
self.plugin.handle_chatter(self.mock_connection, mock_event)
self.assertEqual(mock_learn_line.call_args.args[0], 'hello this is a test message')
def test_learn_self_edit(self):
"""Test that we don't learn our own name when learning something addressed to us."""
mock_event = mock.MagicMock()
mock_event.arguments = ['test_bot: hello this is a test message']
mock_event.target = '#test'
mock_event.recursing = False
with mock.patch('markov.lib.learn_line') as mock_learn_line:
self.plugin.handle_chatter(self.mock_connection, mock_event)
self.assertEqual(mock_learn_line.call_args.args[0], 'hello this is a test message')
def test_learn_variant_self_edit(self):
"""Test that we don't learn our own name when learning something addressed to us, discord style."""
mock_event = mock.MagicMock()
mock_event.arguments = ['@test_bot hello this is a test message']
mock_event.target = '#test'
mock_event.recursing = False
with mock.patch('markov.lib.learn_line') as mock_learn_line:
self.plugin.handle_chatter(self.mock_connection, mock_event)
self.assertEqual(mock_learn_line.call_args.args[0], 'hello this is a test message')
def test_learn_bridge_edit(self):
"""Test that we don't learn the speaker's nick when learning a message from the bridge."""
mock_event = mock.MagicMock()
mock_event.arguments = ['<tester> hello this is a test message']
mock_event.target = '#test'
mock_event.recursing = False
mock_event.source = 'bridge!bridge@localhost'
with mock.patch('markov.lib.learn_line') as mock_learn_line:
self.plugin.handle_chatter(self.mock_connection, mock_event)
self.assertEqual(mock_learn_line.call_args.args[0], 'hello this is a test message')

View File

@ -194,5 +194,6 @@ python_files =
*_tests.py *_tests.py
tests.py tests.py
test_*.py test_*.py
log_level=DEBUG
DJANGO_SETTINGS_MODULE = dr_botzo.settings DJANGO_SETTINGS_MODULE = dr_botzo.settings
django_find_project = false django_find_project = false