Commit 83e54f77 authored by Guillaume BINET's avatar Guillaume BINET

Removed the notion of "connection" internally

parent e933f5a7
......@@ -426,11 +426,6 @@ class Presence(object):
return str(self.__str__())
class Connection(object):
def send_message(self, mess):
raise NotImplementedError("It should be implemented specifically for your backend")
def build_text_html_message_pair(source):
node = None
text_plain = None
......@@ -502,8 +497,7 @@ class Backend(object):
self.bot_alt_prefixes = BOT_ALT_PREFIXES
def send_message(self, mess):
"""Send a message"""
self.connect().send_message(mess)
"""Should be overridden by backends"""
def send_simple_reply(self, mess, text, private=False):
"""Send a simple response to a message"""
......@@ -534,13 +528,13 @@ class Backend(object):
response.type = 'chat' if private else msg_type
return response
def callback_presence(self, conn, presence):
def callback_presence(self, presence):
"""
Implemented by errBot.
"""
pass
def callback_message(self, conn, mess):
def callback_message(self, mess):
"""
Needs to return False if we want to stop further treatment
"""
......
......@@ -20,18 +20,6 @@ from config import CHATROOM_PRESENCE
class CampfireConnection(Connection, pyfire.Campfire):
rooms = {} # keep track of joined room so we can send messages directly to them
def send_message(self, mess):
# we only reply to rooms in reality in campfire so we need to find one or a default one at least
room_name = mess.to.domain
if not room_name:
room_name = mess.frm.domain
if room_name in self.rooms:
room = self.rooms[room_name][0]
room.speak(mess.body) # Basic text support for the moment
else:
logging.info(
"Attempted to send a message to a not connected room yet Room %s : %s" % (room_name, mess.body))
def join_room(self, name, msg_callback, error_callback):
room = self.get_room_by_name(name)
room.join()
......@@ -55,6 +43,19 @@ class CampfireBackend(ErrBot):
self.password = password
self.ssl = ssl
def send_message(self, mess):
super(CampfireBackend, self).send_message(mess)
# we only reply to rooms in reality in campfire so we need to find one or a default one at least
room_name = mess.to.domain
if not room_name:
room_name = mess.frm.domain
if room_name in self.conn.rooms:
room = self.conn.rooms[room_name][0]
room.speak(mess.body) # Basic text support for the moment
else:
logging.info(
"Attempted to send a message to a not connected room yet Room %s : %s" % (room_name, mess.body))
def serve_forever(self):
self.exit_lock.acquire()
self.connect() # be sure we are "connected" before the first command
......@@ -96,7 +97,7 @@ class CampfireBackend(ErrBot):
msg = Message(message.body, type_='groupchat') # it is always a groupchat in campfire
msg.frm = user + '@' + message.room.get_data()['name'] + '/' + user
msg.to = self.jid # assume it is for me
self.callback_message(self.conn, msg)
self.callback_message(msg)
def error_callback(self, error, room):
logging.error("Stream STOPPED due to ERROR: %s in room %s" % (error, room))
......
......@@ -213,7 +213,7 @@ class GraphicBackend(ErrBot):
msg = Message(text)
msg.frm = config.BOT_ADMINS[0] # assume this is the admin talking
msg.to = self.jid # To me only
self.callback_message(self.conn, msg)
self.callback_message(msg)
self.app.input.clear()
def build_message(self, text):
......
......@@ -2,7 +2,7 @@ from __future__ import absolute_import
import logging
import sys
import config
from errbot.backends.base import Message, build_message, build_text_html_message_pair
from errbot.backends.base import Message, build_message, build_text_html_message_pair, Identifier
from errbot.errBot import ErrBot
from errbot.utils import RateLimited
......@@ -61,17 +61,6 @@ class IRCConnection(SingleServerIRCBot):
msg.to = e.target
self.callback.callback_message(self, msg)
def send_message(self, mess):
msg_func = self.send_private_message if mess.typ == 'chat' else self.send_public_message
# If this is a response in private of a public message take the recipient in
# the resource instead of the incoming chatroom
if mess.type == 'chat' and mess.to.resource:
to = mess.to.resource
else:
to = mess.to.node
for line in build_text_html_message_pair(mess.body)[0].split('\n'):
msg_func(to, line)
@RateLimited(config.__dict__.get('IRC_PRIVATE_RATE', 1))
def send_private_message(self, to, line):
self.connection.privmsg(to, line)
......@@ -87,6 +76,18 @@ class IRCBackend(ErrBot):
super(IRCBackend, self).__init__()
self.conn = IRCConnection(self, nickname, server, port, ssl, password, username)
def send_message(self, mess):
super(IRCBackend, self).send_message(mess)
msg_func = self.conn.send_private_message if mess.typ == 'chat' else self.conn.send_public_message
# If this is a response in private of a public message take the recipient in
# the resource instead of the incoming chatroom
if mess.type == 'chat' and mess.to.resource:
to = mess.to.resource
else:
to = mess.to.node
for line in build_text_html_message_pair(mess.body)[0].split('\n'):
msg_func(to, line)
def serve_forever(self):
try:
self.conn.start()
......
......@@ -36,22 +36,16 @@ STZ_PRE = 2
STZ_IQ = 3
class ConnectionMock():
def send(self, mess):
outgoing_message_queue.put(mess.body)
class TestBackend(ErrBot):
def send_message(self, mess):
self.send(mess)
class TestBackend(ErrBot):
conn = ConnectionMock()
super(TestBackend, self).send_message(mess)
outgoing_message_queue.put(mess.body)
def serve_forever(self):
import config
self.jid = Identifier('Err') # whatever
self.connect() # be sure we are "connected" before the first command
self.connect_callback() # notify that the connection occured
self.sender = config.BOT_ADMINS[0] # By default, assume this is the admin talking
try:
......@@ -64,7 +58,7 @@ class TestBackend(ErrBot):
msg = Message(entry)
msg.frm = self.sender
msg.to = self.jid # To me only
self.callback_message(self.conn, msg)
self.callback_message(msg)
elif stanza_type is STZ_PRE:
logging.info("Presence stanza received.")
self.callback_presence(entry)
......@@ -84,9 +78,7 @@ class TestBackend(ErrBot):
self.shutdown()
def connect(self):
if not self.conn:
self.conn = ConnectionMock()
return self.conn
return
def build_message(self, text):
return build_message(text, Message)
......
......@@ -4,27 +4,16 @@ import config
from errbot.backends.base import Message, build_message, Identifier, Presence, ONLINE, OFFLINE
from errbot.errBot import ErrBot
class ConnectionMock(object):
def send(self, mess):
print(mess.body)
def send_message(self, mess):
self.send(mess)
ENCODING_INPUT = sys.stdin.encoding
class TextBackend(ErrBot):
conn = ConnectionMock()
def serve_forever(self):
self.jid = Identifier('Err')
me = Identifier(config.BOT_ADMINS[0])
self.connect() # be sure we are "connected" before the first command
self.connect_callback() # notify that the connection occured
self.callback_presence(self.conn, Presence(identifier=me, status=ONLINE))
self.callback_presence(Presence(identifier=me, status=ONLINE))
try:
while True:
entry = input("Talk to me >>")
......@@ -44,10 +33,9 @@ class TextBackend(ErrBot):
logging.debug("Trigger shutdown")
self.shutdown()
def connect(self):
if not self.conn:
self.conn = ConnectionMock()
return self.conn
def send_message(self, mess):
super(TextBackend, self).send_message(mess)
print(mess.body)
def build_message(self, text):
return build_message(text, Message)
......
......@@ -72,22 +72,6 @@ class ToxConnection(Tox, Connection):
logging.info('TOX: connecting...')
self.bootstrap_from_address(*TOX_BOOTSTRAP_SERVER)
def send_message(self, mess):
body = mess.body
number = int(mess.to.node)
subparts = [body[i:i+TOX_MAX_MESS_LENGTH] for i in range(0, len(body), TOX_MAX_MESS_LENGTH)]
if mess.type == 'groupchat':
logging.debug('TOX: sending to group number %i', number)
for subpart in subparts:
super(ToxConnection, self).group_message_send(number, subpart)
sleep(0.5) # antiflood
else:
logging.debug('TOX: sending to friend number %i', number)
for subpart in subparts:
# yup this is an horrible clash on names !
super(ToxConnection, self).send_message(number, subpart)
sleep(0.5) # antiflood
def on_friend_request(self, friend_pk, message):
logging.info('TOX: Friend request from %s: %s' % (friend_pk, message))
self.add_friend_norequest(friend_pk)
......@@ -158,6 +142,22 @@ class ToxBackend(ErrBot):
logging.debug("Check if %s is admin" % pk)
return any(pka.startswith(pk) for pka in config.BOT_ADMINS)
def send_message(self, mess):
super(ToxBackend, self).send_message(mess)
body = mess.body
number = int(mess.to.node)
subparts = [body[i:i+TOX_MAX_MESS_LENGTH] for i in range(0, len(body), TOX_MAX_MESS_LENGTH)]
if mess.type == 'groupchat':
logging.debug('TOX: sending to group number %i', number)
for subpart in subparts:
self.conn.group_message_send(number, subpart)
sleep(0.5) # antiflood
else:
logging.debug('TOX: sending to friend number %i', number)
for subpart in subparts:
self.conn.send_message(number, subpart)
sleep(0.5) # antiflood
def serve_forever(self):
checked = False
......
......@@ -97,12 +97,6 @@ class XMPPConnection(Connection):
self.client.add_event_handler("session_start", self.session_start)
self.client.add_event_handler("ssl_invalid_cert", self.ssl_invalid_cert)
def send_message(self, mess):
self.client.send_message(mto=mess.to,
mbody=mess.body,
mtype=mess.type,
mhtml=mess.html)
def session_start(self, _):
self.client.send_presence()
self.client.get_roster()
......@@ -219,19 +213,19 @@ class XMPPBackend(ErrBot):
msg.type = xmppmsg['type']
msg.nick = xmppmsg['mucnick']
msg.delayed = bool(xmppmsg['delay']._get_attr('stamp')) # this is a bug in sleekxmpp it should be ['from']
self.callback_message(self.conn, msg)
self.callback_message(msg)
def contact_online(self, event):
logging.debug("contact_online %s" % event)
p = Presence(identifier=Identifier(str(event['from'])),
status=ONLINE)
self.callback_presence(self.conn, p)
self.callback_presence(p)
def contact_offline(self, event):
logging.debug("contact_offline %s" % event)
p = Presence(identifier=Identifier(str(event['from'])),
status=OFFLINE)
self.callback_presence(self.conn, p)
self.callback_presence(p)
def user_joined_chat(self, event):
logging.debug("user_join_chat %s" % event)
......@@ -239,7 +233,7 @@ class XMPPBackend(ErrBot):
p = Presence(chatroom=idd,
nick=idd.resource,
status=ONLINE)
self.callback_presence(self.conn, p)
self.callback_presence(p)
def user_left_chat(self, event):
logging.debug("user_left_chat %s" % event)
......@@ -247,7 +241,7 @@ class XMPPBackend(ErrBot):
p = Presence(chatroom=idd,
nick=idd.resource,
status=OFFLINE)
self.callback_presence(self.conn, p)
self.callback_presence(p)
def user_changed_status(self, event):
logging.debug("user_changed_status %s" % event)
......@@ -258,7 +252,7 @@ class XMPPBackend(ErrBot):
p = Presence(identifier=Identifier(str(event['from'])),
status=errstatus, message=message)
self.callback_presence(self.conn, p)
self.callback_presence(p)
def connected(self, data):
"""Callback for connection events"""
......@@ -268,8 +262,15 @@ class XMPPBackend(ErrBot):
"""Callback for disconnection events"""
self.disconnect_callback()
def send_message(self, mess):
super(XMPPBackend, self).send_message(mess)
self.conn.client.send_message(mto=mess.to,
mbody=mess.body,
mtype=mess.type,
mhtml=mess.html)
def serve_forever(self):
self.connect() # be sure we are "connected" before the first command
self.conn.connect()
try:
self.conn.serve_forever()
......@@ -279,9 +280,6 @@ class XMPPBackend(ErrBot):
logging.debug("Trigger shutdown")
self.shutdown()
def connect(self):
return self.conn.connect()
def build_message(self, text):
return build_message(text, Message)
......
......@@ -199,6 +199,16 @@ class BotPlugin(BotPluginBase):
"""
pass
def callback_presence(self, presence):
"""
Triggered on every presence change.
:param message:
An instance of :class:`~errbot.backends.base.Presence`
representing the new presence state that was received.
"""
pass
def callback_botmessage(self, message):
"""
Triggered on every message coming from the bot itself.
......
......@@ -64,7 +64,7 @@ class ChatRoom(BotPlugin):
for room in rooms:
self.send(room, body, message_type='groupchat')
elif mess_type == 'groupchat':
fr = mess.from_
fr = mess.frm
chat_room = fr.node + '@' + fr.domain if fr.domain else fr.node
if chat_room in REVERSE_CHATROOM_RELAY:
users_to_relay_to = REVERSE_CHATROOM_RELAY[chat_room]
......
......@@ -56,6 +56,8 @@ BL_PLUGINS = b'bl_plugins' if PY2 else 'bl_plugins'
class ErrBot(Backend, StoreMixin):
""" ErrBot is the layer of Err that takes care of the plugin management and dispatching
"""
__errdoc__ = """ Commands related to the bot administration """
MSG_ERROR_OCCURRED = 'Computer says nooo. See logs for details.'
MSG_UNKNOWN_COMMAND = 'Unknown command: "%(command)s". '
......@@ -131,7 +133,6 @@ class ErrBot(Backend, StoreMixin):
def send_message(self, mess):
super(ErrBot, self).send_message(mess)
# Act only in the backend tells us that this message is OK to broadcast
for bot in get_all_active_plugin_objects():
# noinspection PyBroadException
try:
......@@ -139,18 +140,18 @@ class ErrBot(Backend, StoreMixin):
except Exception as _:
logging.exception("Crash in a callback_botmessage handler")
def callback_message(self, conn, mess):
if super(ErrBot, self).callback_message(conn, mess):
def callback_message(self, mess):
if super(ErrBot, self).callback_message(mess):
# Act only in the backend tells us that this message is OK to broadcast
for bot in get_all_active_plugin_objects():
# noinspection PyBroadException
try:
logging.debug('callback_message for %s' % bot.__class__.__name__)
bot.callback_message(conn, mess)
bot.callback_message(None, mess)
except Exception as _:
logging.exception("Crash in a callback_message handler")
def callback_presence(self, conn, pres):
def callback_presence(self, pres):
for bot in get_all_active_plugin_objects():
# noinspection PyBroadException
try:
......
......@@ -285,43 +285,43 @@ class BotCmds(unittest.TestCase):
self.assertFalse(len(self.dummy.re_commands))
def test_callback_message(self):
self.dummy.callback_message(None, self.makemessage("!return_args_as_str one two"))
self.dummy.callback_message(self.makemessage("!return_args_as_str one two"))
self.assertEquals("one two", self.dummy.pop_message().body)
@patch('errbot.backends.base.BOT_PREFIX_OPTIONAL_ON_CHAT', new=True)
def test_callback_message_with_prefix_optional(self):
m = self.makemessage("return_args_as_str one two")
self.dummy.callback_message(None, m)
self.dummy.callback_message(m)
self.assertEquals("one two", self.dummy.pop_message().body)
# Groupchat should still require the prefix
m.type = "groupchat"
self.dummy.callback_message(None, m)
self.dummy.callback_message(m)
self.assertRaises(Empty, self.dummy.pop_message, *[], **{'block': False})
m = self.makemessage("!return_args_as_str one two", type="groupchat")
self.dummy.callback_message(None, m)
self.dummy.callback_message(m)
self.assertEquals("one two", self.dummy.pop_message().body)
@patch('errbot.backends.base.BOT_ALT_PREFIXES', new=('Err',))
@patch('errbot.backends.base.BOT_ALT_PREFIX_SEPARATORS', new=(',', ';'))
def test_callback_message_with_bot_alt_prefixes(self):
self.dummy = DummyBackend()
self.dummy.callback_message(None, self.makemessage("Err return_args_as_str one two"))
self.dummy.callback_message(self.makemessage("Err return_args_as_str one two"))
self.assertEquals("one two", self.dummy.pop_message().body)
self.dummy.callback_message(None, self.makemessage("Err, return_args_as_str one two"))
self.dummy.callback_message(self.makemessage("Err, return_args_as_str one two"))
self.assertEquals("one two", self.dummy.pop_message().body)
def test_callback_message_with_re_botcmd(self):
self.dummy.callback_message(None, self.makemessage("!regex command with prefix"))
self.dummy.callback_message(self.makemessage("!regex command with prefix"))
self.assertEquals("Regex command", self.dummy.pop_message().body)
self.dummy.callback_message(None, self.makemessage("regex command without prefix"))
self.dummy.callback_message(self.makemessage("regex command without prefix"))
self.assertEquals("Regex command", self.dummy.pop_message().body)
self.dummy.callback_message(None, self.makemessage("!regex command with capture group: Captured text"))
self.dummy.callback_message(self.makemessage("!regex command with capture group: Captured text"))
self.assertEquals("Captured text", self.dummy.pop_message().body)
self.dummy.callback_message(None, self.makemessage("regex command with capture group: Captured text"))
self.dummy.callback_message(self.makemessage("regex command with capture group: Captured text"))
self.assertEquals("Captured text", self.dummy.pop_message().body)
self.dummy.callback_message(None, self.makemessage(
self.dummy.callback_message(self.makemessage(
"This command also allows extra text in front - regex command with capture group: Captured text"))
self.assertEquals("Captured text", self.dummy.pop_message().body)
......@@ -329,34 +329,34 @@ class BotCmds(unittest.TestCase):
@patch('errbot.backends.base.BOT_ALT_PREFIX_SEPARATORS', new=(',', ';'))
def test_callback_message_with_re_botcmd_and_alt_prefixes(self):
self.dummy = DummyBackend()
self.dummy.callback_message(None, self.makemessage("!regex command with prefix"))
self.dummy.callback_message(self.makemessage("!regex command with prefix"))
self.assertEquals("Regex command", self.dummy.pop_message().body)
self.dummy.callback_message(None, self.makemessage("Err regex command with prefix"))
self.dummy.callback_message(self.makemessage("Err regex command with prefix"))
self.assertEquals("Regex command", self.dummy.pop_message().body)
self.dummy.callback_message(None, self.makemessage("Err, regex command with prefix"))
self.dummy.callback_message(self.makemessage("Err, regex command with prefix"))
self.assertEquals("Regex command", self.dummy.pop_message().body)
self.dummy.callback_message(None, self.makemessage("regex command without prefix"))
self.dummy.callback_message(self.makemessage("regex command without prefix"))
self.assertEquals("Regex command", self.dummy.pop_message().body)
self.dummy.callback_message(None, self.makemessage("!regex command with capture group: Captured text"))
self.dummy.callback_message(self.makemessage("!regex command with capture group: Captured text"))
self.assertEquals("Captured text", self.dummy.pop_message().body)
self.dummy.callback_message(None, self.makemessage("regex command with capture group: Captured text"))
self.dummy.callback_message(self.makemessage("regex command with capture group: Captured text"))
self.assertEquals("Captured text", self.dummy.pop_message().body)
self.dummy.callback_message(None, self.makemessage(
self.dummy.callback_message(self.makemessage(
"This command also allows extra text in front - regex command with capture group: Captured text"))
self.assertEquals("Captured text", self.dummy.pop_message().body)
self.dummy.callback_message(None, self.makemessage("Err, regex command with capture group: Captured text"))
self.dummy.callback_message(self.makemessage("Err, regex command with capture group: Captured text"))
self.assertEquals("Captured text", self.dummy.pop_message().body)
self.dummy.callback_message(None, self.makemessage(
self.dummy.callback_message(self.makemessage(
"Err This command also allows extra text in front - regex command with capture group: Captured text"))
self.assertEquals("Captured text", self.dummy.pop_message().body)
def test_regex_commands_can_overlap(self):
self.dummy.callback_message(None, self.makemessage("!matched by two commands"))
self.dummy.callback_message(self.makemessage("!matched by two commands"))
response = (self.dummy.pop_message().body, self.dummy.pop_message().body)
self.assertTrue(response == ("one", "two") or response == ("two", "one"))
def test_regex_commands_allow_passing_re_flags(self):
self.dummy.callback_message(None, self.makemessage("!MaTcHeD By TwO cOmMaNdS"))
self.dummy.callback_message(self.makemessage("!MaTcHeD By TwO cOmMaNdS"))
self.assertEquals("two", self.dummy.pop_message().body)
self.assertRaises(Empty, self.dummy.pop_message, **{'timeout': 1})
......@@ -440,7 +440,7 @@ class BotCmds(unittest.TestCase):
logger.info("** message: {}".format(test['message'].body))
logger.info("** acl: {!r}".format(test['acl']))
logger.info("** acl_default: {!r}".format(test['acl_default']))
self.dummy.callback_message(None, test['message'])
self.dummy.callback_message(test['message'])
self.assertEqual(
test['expected_response'],
self.dummy.pop_message().body
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment