summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorWill Thompson <will.thompson@collabora.co.uk>2011-11-04 08:58:45 +0000
committerWill Thompson <will.thompson@collabora.co.uk>2011-11-07 17:44:39 +0000
commit6895340badaff9f97683230848ae8e0f9f6f99d8 (patch)
tree7e0d4b518f6fec73542784e2db8b6554ed59025e
parent4312e1f6789227e658ee55349b91b0980128ab51 (diff)
ImFactory: handle Facebook's own-message extension
Facebook's XMPP server sends us IQs containing messages which we send to contacts, whether on this connection or via another connection (such as the web interface). We can expose these as Delivery_Status_Accepted delivery reports, allowing UIs to log them and show them up or whatever. This could help keep things coherent if the user hops between chatting in the browser and/or Empathy, say. https://bugs.freedesktop.org/show_bug.cgi?id=41417
-rw-r--r--src/im-factory.c95
-rw-r--r--src/namespaces.h8
-rw-r--r--tests/twisted/Makefile.am1
-rw-r--r--tests/twisted/constants.py8
-rw-r--r--tests/twisted/text/facebook-own-message.py70
5 files changed, 181 insertions, 1 deletions
diff --git a/src/im-factory.c b/src/im-factory.c
index 0cd805610..b695a601d 100644
--- a/src/im-factory.c
+++ b/src/im-factory.c
@@ -29,6 +29,7 @@
#include <telepathy-glib/dbus.h>
#include <telepathy-glib/gtypes.h>
#include <telepathy-glib/interfaces.h>
+#include <wocky/wocky-c2s-porter.h>
#define DEBUG_FLAG GABBLE_DEBUG_IM
@@ -40,6 +41,7 @@
#include "disco.h"
#include "im-channel.h"
#include "message-util.h"
+#include "namespaces.h"
static void channel_manager_iface_init (gpointer, gpointer);
static void caps_channel_manager_iface_init (gpointer, gpointer);
@@ -86,7 +88,10 @@ gabble_im_factory_init (GabbleImFactory *self)
static void connection_status_changed_cb (GabbleConnection *conn,
guint status, guint reason, GabbleImFactory *self);
-
+static void porter_available_cb (
+ GabbleConnection *conn,
+ WockyPorter *porter,
+ gpointer user_data);
static void
gabble_im_factory_constructed (GObject *obj)
@@ -99,6 +104,8 @@ gabble_im_factory_constructed (GObject *obj)
self->priv->status_changed_id = g_signal_connect (self->priv->conn,
"status-changed", (GCallback) connection_status_changed_cb, obj);
+ tp_g_signal_connect_object (self->priv->conn,
+ "porter-available", (GCallback) porter_available_cb, obj, 0);
}
@@ -457,6 +464,92 @@ connection_status_changed_cb (GabbleConnection *conn,
}
}
+static gboolean
+im_factory_own_message_cb (
+ WockyPorter *porter,
+ WockyStanza *stanza,
+ gpointer user_data)
+{
+ GabbleImFactory *self = GABBLE_IM_FACTORY (user_data);
+ WockyNode *own_message, *body;
+ const gchar *to;
+ gboolean sent_locally;
+ GabbleIMChannel *chan;
+
+ /* Our stanza filter should guarantee that these are present. */
+ own_message = wocky_node_get_child (wocky_stanza_get_top_node (stanza),
+ "own-message");
+ g_return_val_if_fail (own_message != NULL, FALSE);
+ body = wocky_node_get_child (own_message, "body");
+ g_return_val_if_fail (body != NULL, FALSE);
+
+ to = wocky_node_get_attribute (own_message, "to");
+ if (to == NULL)
+ {
+ DEBUG ("own-message missing to='' attribute; ignoring");
+ return FALSE;
+ }
+
+ /* If self='true', the message was sent by the local user on this machine,
+ * rather than by the local user on some other machine. We don't really have
+ * a good way to show this in Messages. Also we don't get told the id='' of
+ * the original message, which is annoying.
+ */
+ sent_locally = !tp_strdiff ("true",
+ wocky_node_get_attribute (own_message, "self"));
+ DEBUG ("this report is for a message to '%s', sent %s",
+ to, sent_locally ? "locally" : "remotely");
+
+ /* Don't create a channel for the sole purpose of reporting an own-message.
+ * This is consistent with not creating a channel to report send errors
+ * (given that both are delivery reports).
+ */
+ chan = get_channel_for_incoming_message (self, to, FALSE);
+ if (chan != NULL)
+ _gabble_im_channel_report_delivery (chan,
+ TP_CHANNEL_TEXT_MESSAGE_TYPE_NORMAL, 0, NULL, body->content,
+ GABBLE_TEXT_CHANNEL_SEND_NO_ERROR, TP_DELIVERY_STATUS_ACCEPTED);
+ else
+ DEBUG ("no channel for '%s'; not spawning one just for the delivery report",
+ to);
+
+ wocky_porter_acknowledge_iq (porter, stanza, NULL);
+ return TRUE;
+}
+
+static void
+porter_available_cb (
+ GabbleConnection *conn,
+ WockyPorter *porter,
+ gpointer user_data)
+{
+ GabbleImFactory *self = GABBLE_IM_FACTORY (user_data);
+ gchar *stream_server;
+
+ g_object_get (conn, "stream-server", &stream_server, NULL);
+
+ if (!tp_strdiff (stream_server, "chat.facebook.com"))
+ {
+ wocky_porter_register_handler_from (
+ porter, WOCKY_STANZA_TYPE_IQ, WOCKY_STANZA_SUB_TYPE_SET,
+ /* We could use _from_server() if that accepted messages from our
+ * JID's domain, not just from bare JID, full JID, or no sender
+ * specified—which would allow the extension to work on other servers
+ * too—but it doesn't.
+ */
+ stream_server,
+ WOCKY_PORTER_HANDLER_PRIORITY_NORMAL,
+ im_factory_own_message_cb, self,
+ '(',
+ "own-message", ':', NS_FACEBOOK_MESSAGES,
+ '(', "body", ')',
+ ')', NULL);
+ }
+
+ g_free (stream_server);
+}
+
+
static void
gabble_im_factory_get_contact_caps (GabbleCapsChannelManager *manager,
TpHandle handle,
diff --git a/src/namespaces.h b/src/namespaces.h
index bacbb2ac5..59aded50a 100644
--- a/src/namespaces.h
+++ b/src/namespaces.h
@@ -123,4 +123,12 @@
#define NS_TEMPPRES "urn:xmpp:temppres:0"
#define NS_GOOGLE_SHARED_STATUS "google:shared-status"
+/* This is used by the extension Facebook uses to push you messages you send
+ * using other devices (or the website).
+ *
+ * http://www.youtube.com/watch?v=rSnXE2791yg is not the song I was looking
+ * for, but it's not bad.
+ */
+#define NS_FACEBOOK_MESSAGES "http://www.facebook.com/xmpp/messages"
+
#endif /* __GABBLE_NAMESPACES__H__ */
diff --git a/tests/twisted/Makefile.am b/tests/twisted/Makefile.am
index debb6591b..50cf68313 100644
--- a/tests/twisted/Makefile.am
+++ b/tests/twisted/Makefile.am
@@ -105,6 +105,7 @@ TWISTED_TESTS = \
test-register.py \
text/destroy.py \
text/ensure.py \
+ text/facebook-own-message.py \
text/initiate.py \
text/initiate-requestotron.py \
text/respawn.py \
diff --git a/tests/twisted/constants.py b/tests/twisted/constants.py
index bbb10caa7..2a879d810 100644
--- a/tests/twisted/constants.py
+++ b/tests/twisted/constants.py
@@ -444,6 +444,14 @@ DELIVERY_REPORTING_SUPPORT_FLAGS_RECEIVE_SUCCESSES = 2
DELIVERY_REPORTING_SUPPORT_FLAGS_RECEIVE_READ = 4
DELIVERY_REPORTING_SUPPORT_FLAGS_RECEIVE_DELETED = 8
+DELIVERY_STATUS_UNKNOWN = 0
+DELIVERY_STATUS_DELIVERED = 1
+DELIVERY_STATUS_TEMPORARILY_FAILED = 2
+DELIVERY_STATUS_PERMANENTLY_FAILED = 3
+DELIVERY_STATUS_ACCEPTED = 4
+DELIVERY_STATUS_READ = 5
+DELIVERY_STATUS_DELETED = 6
+
MEDIA_STREAM_ERROR_UNKNOWN = 0
MEDIA_STREAM_ERROR_EOS = 1
MEDIA_STREAM_ERROR_CODEC_NEGOTIATION_FAILED = 2
diff --git a/tests/twisted/text/facebook-own-message.py b/tests/twisted/text/facebook-own-message.py
new file mode 100644
index 000000000..a695b3d92
--- /dev/null
+++ b/tests/twisted/text/facebook-own-message.py
@@ -0,0 +1,70 @@
+"""
+Tests exposing Facebook's own-message extension via delivery reports.
+
+I would say that this has been reverse-engineered, but reading the completely
+trivial protocol out of debug logs is hardly reverse-engineering. It isn't
+documented anywhere I can find, mind you.
+"""
+from servicetest import (
+ assertEquals, assertLength, assertContains, wrap_channel, EventPattern,
+ sync_dbus,
+ )
+from gabbletest import exec_test, elem, elem_iq
+import constants as cs
+
+NS_FACEBOOK_MESSAGES = "http://www.facebook.com/xmpp/messages"
+
+def test(q, bus, conn, stream):
+ def send_own_message(to, text):
+ iq = elem_iq(stream, 'set', from_='chat.facebook.com')(
+ elem(NS_FACEBOOK_MESSAGES, 'own-message', to=to, self='false')(
+ elem('body')(text)
+ )
+ )
+ stream.send(iq)
+ q.expect('stream-iq', iq_type='result', iq_id=iq['id'])
+
+ # First, test receiving an own-message stanza for a message sent to a
+ # contact we have an open channel for.
+ jid = '-5678@chat.facebook.com'
+ _, path, props = conn.Requests.EnsureChannel({
+ cs.CHANNEL_TYPE: cs.CHANNEL_TYPE_TEXT,
+ cs.TARGET_HANDLE_TYPE: cs.HT_CONTACT,
+ cs.TARGET_ID: jid,
+ })
+ channel = wrap_channel(bus.get_object(conn.bus_name, path),
+ 'Text', ['Messages'])
+ handle = props[cs.TARGET_HANDLE]
+
+ text = u'omg omg its ur birthdayy <3 <3 xoxoxoxo'
+ send_own_message(to=jid, text=text)
+ e = q.expect('dbus-signal', signal='MessageReceived')
+ message, = e.args
+ assertLength(1, message)
+ header = message[0]
+
+ assertEquals(handle, header['message-sender'])
+ assertEquals(cs.MT_DELIVERY_REPORT, header['message-type'])
+ assertEquals(cs.DELIVERY_STATUS_ACCEPTED, header['delivery-status'])
+
+ assertContains('delivery-echo', header)
+ echo = header['delivery-echo']
+ echo_header, echo_body = echo
+
+ assertEquals(conn.GetSelfHandle(), echo_header['message-sender'])
+ assertEquals('text/plain', echo_body['content-type'])
+ assertEquals(text, echo_body['content'])
+
+ channel.Text.AcknowledgePendingMessages([header['pending-message-id']])
+ channel.Close()
+
+ # Now test receiving an own-message stanza for a message sent to a contact
+ # we don't have a channel open for. It should be ignored (but acked). This
+ # is consistent with delivery failure reports.
+ q.forbid_events([EventPattern('dbus-signal', signal='MessageReceived')])
+ send_own_message(to='-393939@chat.facebook.com',
+ text=u'please ignore this message')
+ sync_dbus(bus, q, conn)
+
+if __name__ == '__main__':
+ exec_test(test, params={'account': 'test@chat.facebook.com'})