iability to show captcha when joining a groupchat with captcha. (TODO: download image when it's not in the message itself)

This commit is contained in:
Yann Leboulanger 2010-06-27 23:09:07 +02:00
parent 90411cad12
commit f9e8b46e6c
8 changed files with 294 additions and 126 deletions

View File

@ -5,6 +5,7 @@
<object class="GtkVBox" id="groupchat_control_vbox"> <object class="GtkVBox" id="groupchat_control_vbox">
<property name="can_focus">True</property> <property name="can_focus">True</property>
<property name="border_width">3</property> <property name="border_width">3</property>
<property name="orientation">vertical</property>
<child> <child>
<object class="GtkAlignment" id="alignment"> <object class="GtkAlignment" id="alignment">
<property name="visible">True</property> <property name="visible">True</property>
@ -34,6 +35,7 @@
<property name="visible">True</property> <property name="visible">True</property>
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property> <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
<property name="border_width">5</property> <property name="border_width">5</property>
<property name="orientation">vertical</property>
<child> <child>
<object class="GtkLabel" id="banner_name_label"> <object class="GtkLabel" id="banner_name_label">
<property name="visible">True</property> <property name="visible">True</property>
@ -73,6 +75,85 @@
<property name="position">0</property> <property name="position">0</property>
</packing> </packing>
</child> </child>
<child>
<object class="GtkHPaned" id="hpaned">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="border_width">3</property>
<property name="position">495</property>
<property name="position_set">True</property>
<child>
<object class="GtkVBox" id="gc_textviews_vbox">
<property name="width_request">0</property>
<property name="visible">True</property>
<property name="orientation">vertical</property>
<property name="spacing">6</property>
<child>
<object class="GtkScrolledWindow" id="conversation_scrolledwindow">
<property name="width_request">200</property>
<property name="height_request">60</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="hscrollbar_policy">automatic</property>
<property name="vscrollbar_policy">automatic</property>
<property name="shadow_type">in</property>
<child>
<placeholder/>
</child>
</object>
<packing>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkScrolledWindow" id="message_scrolledwindow">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="hscrollbar_policy">never</property>
<property name="vscrollbar_policy">never</property>
<property name="shadow_type">in</property>
<child>
<placeholder/>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="position">1</property>
</packing>
</child>
</object>
<packing>
<property name="resize">False</property>
<property name="shrink">False</property>
</packing>
</child>
<child>
<object class="GtkScrolledWindow" id="list_scrolledwindow">
<property name="width_request">100</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="hscrollbar_policy">never</property>
<property name="vscrollbar_policy">automatic</property>
<property name="shadow_type">in</property>
<child>
<object class="GtkTreeView" id="list_treeview">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="border_width">1</property>
<property name="headers_visible">False</property>
</object>
</child>
</object>
<packing>
<property name="resize">False</property>
<property name="shrink">False</property>
</packing>
</child>
</object>
<packing>
<property name="position">1</property>
</packing>
</child>
<child> <child>
<object class="GtkHBox" id="actions_hbox"> <object class="GtkHBox" id="actions_hbox">
<property name="visible">True</property> <property name="visible">True</property>
@ -339,92 +420,5 @@
<property name="position">2</property> <property name="position">2</property>
</packing> </packing>
</child> </child>
<child>
<object class="GtkHPaned" id="hpaned">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="border_width">3</property>
<property name="position">495</property>
<property name="position_set">True</property>
<child>
<object class="GtkVBox" id="vbox108">
<property name="width_request">0</property>
<property name="visible">True</property>
<property name="spacing">6</property>
<child>
<object class="GtkVBox" id="vbox109">
<property name="visible">True</property>
<property name="spacing">6</property>
<child>
<object class="GtkScrolledWindow" id="conversation_scrolledwindow">
<property name="width_request">200</property>
<property name="height_request">60</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="hscrollbar_policy">automatic</property>
<property name="vscrollbar_policy">automatic</property>
<property name="shadow_type">in</property>
<child>
<placeholder/>
</child>
</object>
<packing>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkScrolledWindow" id="message_scrolledwindow">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="hscrollbar_policy">never</property>
<property name="vscrollbar_policy">never</property>
<property name="shadow_type">in</property>
<child>
<placeholder/>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="position">1</property>
</packing>
</child>
</object>
<packing>
<property name="position">0</property>
</packing>
</child>
</object>
<packing>
<property name="resize">False</property>
<property name="shrink">False</property>
</packing>
</child>
<child>
<object class="GtkScrolledWindow" id="list_scrolledwindow">
<property name="width_request">100</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="hscrollbar_policy">never</property>
<property name="vscrollbar_policy">automatic</property>
<property name="shadow_type">in</property>
<child>
<object class="GtkTreeView" id="list_treeview">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="border_width">1</property>
<property name="headers_visible">False</property>
</object>
</child>
</object>
<packing>
<property name="resize">False</property>
<property name="shrink">False</property>
</packing>
</child>
</object>
<packing>
<property name="position">1</property>
</packing>
</child>
</object> </object>
</interface> </interface>

View File

@ -288,33 +288,37 @@ class CommonConnection:
gajim.thread_interface(encrypt_thread, [msg, keyID, gajim.thread_interface(encrypt_thread, [msg, keyID,
True], _on_encrypted, []) True], _on_encrypted, [])
else: else:
self._message_encrypted_cb(output, type_, msg, msgtxt, self._message_encrypted_cb(output, type_, msg,
original_message, fjid, resource, jid, xhtml, msgtxt, original_message, fjid, resource,
subject, chatstate, composing_xep, label, forward_from, jid, xhtml, subject, chatstate, msg_id,
delayed, session, form_node, user_nick, keyID, composing_xep, label, forward_from, delayed,
callback) session, form_node, user_nick, keyID,
callback)
self.dispatch('GPG_ALWAYS_TRUST', _on_always_trust) self.dispatch('GPG_ALWAYS_TRUST', _on_always_trust)
else: else:
self._message_encrypted_cb(output, type_, msg, msgtxt, self._message_encrypted_cb(output, type_, msg, msgtxt,
original_message, fjid, resource, jid, xhtml, subject, original_message, fjid, resource, jid, xhtml,
chatstate, composing_xep, label, forward_from, delayed, session, subject, chatstate, msg_id, composing_xep, label,
form_node, user_nick, keyID, callback) forward_from, delayed, session, form_node,
user_nick, keyID, callback)
gajim.thread_interface(encrypt_thread, [msg, keyID, False], gajim.thread_interface(encrypt_thread, [msg, keyID, False],
_on_encrypted, []) _on_encrypted, [])
return return
self._message_encrypted_cb(('', error), type_, msg, msgtxt, self._message_encrypted_cb(('', error), type_, msg, msgtxt,
original_message, fjid, resource, jid, xhtml, subject, chatstate, original_message, fjid, resource, jid, xhtml, subject,
composing_xep, label, forward_from, delayed, session, form_node, user_nick, chatstate, msg_id, composing_xep, label, forward_from, delayed,
keyID, callback) session, form_node, user_nick, keyID, callback)
self._on_continue_message(type_, msg, msgtxt, original_message, fjid, self._on_continue_message(type_, msg, msgtxt, original_message, fjid,
resource, jid, xhtml, subject, msgenc, keyID, chatstate, composing_xep, resource, jid, xhtml, subject, msgenc, keyID, chatstate, msg_id,
label, forward_from, delayed, session, form_node, user_nick, callback) composing_xep, label, forward_from, delayed, session, form_node,
user_nick, callback)
def _message_encrypted_cb(self, output, type_, msg, msgtxt, original_message, def _message_encrypted_cb(self, output, type_, msg, msgtxt,
fjid, resource, jid, xhtml, subject, chatstate, composing_xep, label, forward_from, original_message, fjid, resource, jid, xhtml, subject, chatstate, msg_id,
delayed, session, form_node, user_nick, keyID, callback): composing_xep, label, forward_from, delayed, session, form_node, user_nick,
keyID, callback):
msgenc, error = output msgenc, error = output
if msgenc and not error: if msgenc and not error:
@ -324,19 +328,19 @@ class CommonConnection:
# one in locale and one en # one in locale and one en
msgtxt = _('[This message is *encrypted* (See :XEP:`27`]') + \ msgtxt = _('[This message is *encrypted* (See :XEP:`27`]') + \
' (' + msgtxt + ')' ' (' + msgtxt + ')'
self._on_continue_message(type_, msg, msgtxt, original_message, fjid, self._on_continue_message(type_, msg, msgtxt, original_message,
resource, jid, xhtml, subject, msgenc, keyID, chatstate, fjid, resource, jid, xhtml, subject, msgenc, keyID,
composing_xep, label, forward_from, delayed, session, form_node, user_nick, chatstate, msg_id, composing_xep, label, forward_from, delayed,
callback) session, form_node, user_nick, callback)
return return
# Encryption failed, do not send message # Encryption failed, do not send message
tim = localtime() tim = localtime()
self.dispatch('MSGNOTSENT', (jid, error, msgtxt, tim, session)) self.dispatch('MSGNOTSENT', (jid, error, msgtxt, tim, session))
def _on_continue_message(self, type_, msg, msgtxt, original_message, fjid, def _on_continue_message(self, type_, msg, msgtxt, original_message, fjid,
resource, jid, xhtml, subject, msgenc, keyID, chatstate, composing_xep, resource, jid, xhtml, subject, msgenc, keyID, chatstate, msg_id,
label, composing_xep, label, forward_from, delayed, session, form_node, user_nick,
forward_from, delayed, session, form_node, user_nick, callback): callback):
if type_ == 'chat': if type_ == 'chat':
msg_iq = common.xmpp.Message(to=fjid, body=msgtxt, typ=type_, msg_iq = common.xmpp.Message(to=fjid, body=msgtxt, typ=type_,
xhtml=xhtml) xhtml=xhtml)
@ -347,6 +351,10 @@ class CommonConnection:
else: else:
msg_iq = common.xmpp.Message(to=fjid, body=msgtxt, typ='normal', msg_iq = common.xmpp.Message(to=fjid, body=msgtxt, typ='normal',
xhtml=xhtml) xhtml=xhtml)
if msg_id:
msg_iq.setID(msg_id)
if msgenc: if msgenc:
msg_iq.setTag(common.xmpp.NS_ENCRYPTED + ' x').setData(msgenc) msg_iq.setTag(common.xmpp.NS_ENCRYPTED + ' x').setData(msgenc)
@ -1607,11 +1615,11 @@ class Connection(CommonConnection, ConnectionHandlers):
self.connection.send(msg_iq) self.connection.send(msg_iq)
def send_message(self, jid, msg, keyID, type_='chat', subject='', def send_message(self, jid, msg, keyID=None, type_='chat', subject='',
chatstate=None, msg_id=None, composing_xep=None, resource=None, chatstate=None, msg_id=None, composing_xep=None, resource=None,
user_nick=None, xhtml=None, label=None, session=None, forward_from=None, form_node=None, user_nick=None, xhtml=None, label=None, session=None, forward_from=None,
original_message=None, delayed=None, callback=None, callback_args=[], form_node=None, original_message=None, delayed=None, callback=None,
now=False): callback_args=[], now=False):
def cb(jid, msg, keyID, forward_from, session, original_message, def cb(jid, msg, keyID, forward_from, session, original_message,
subject, type_, msg_iq): subject, type_, msg_iq):
@ -1625,11 +1633,10 @@ class Connection(CommonConnection, ConnectionHandlers):
subject, type_) subject, type_)
self._prepare_message(jid, msg, keyID, type_=type_, subject=subject, self._prepare_message(jid, msg, keyID, type_=type_, subject=subject,
chatstate=chatstate, msg_id=msg_id, composing_xep=composing_xep, chatstate=chatstate, msg_id=msg_id, composing_xep=composing_xep,
resource=resource, user_nick=user_nick, xhtml=xhtml, label=label, resource=resource, user_nick=user_nick, xhtml=xhtml, label=label,
session=session, session=session, forward_from=forward_from, form_node=form_node,
forward_from=forward_from, form_node=form_node, original_message=original_message, delayed=delayed, callback=cb)
original_message=original_message, delayed=delayed, callback=cb)
def send_contacts(self, contacts, jid): def send_contacts(self, contacts, jid):
""" """
@ -1987,6 +1994,14 @@ class Connection(CommonConnection, ConnectionHandlers):
p = self.add_sha(p, ptype != 'unavailable') p = self.add_sha(p, ptype != 'unavailable')
self.connection.send(p) self.connection.send(p)
def send_captcha(self, jid, form_node):
if not gajim.account_is_connected(self.name):
return
iq = common.xmpp.Iq(typ='set', to=jid)
captcha = iq.addChild(name='captcha', namespace=common.xmpp.NS_CAPTCHA)
captcha.addChild(node=form_node)
self.connection.send(iq)
def check_unique_room_id_support(self, server, instance): def check_unique_room_id_support(self, server, instance):
if not gajim.account_is_connected(self.name): if not gajim.account_is_connected(self.name):
return return

View File

@ -1667,8 +1667,21 @@ ConnectionCaps, ConnectionHandlersBase, ConnectionJingle):
if jid not in self.last_history_time: if jid not in self.last_history_time:
return return
self.dispatch('GC_MSG', (frm, msgtxt, tim, has_timestamp, msg.getXHTML(), captcha = msg.getTag('captcha', namespace=common.xmpp.NS_CAPTCHA)
statusCode, displaymarking)) if captcha:
captcha = captcha.getTag('x', namespace=common.xmpp.NS_DATA)
for field in captcha.getTags('field'):
for media in field.getTags('media'):
for uri in media.getTags('uri'):
uri_data = uri.getData()
if uri_data.startswith('cid:'):
uri_data = uri_data[4:]
for data in msg.getTags('data',
namespace=common.xmpp.NS_BOB):
if data.getAttr('cid') == uri_data:
uri.setData(data.getData())
self.dispatch('GC_MSG', (frm, msgtxt, tim, has_timestamp,
msg.getXHTML(), statusCode, displaymarking, captcha))
tim_int = int(float(mktime(tim))) tim_int = int(float(mktime(tim)))
if gajim.config.should_log(self.name, jid) and not \ if gajim.config.should_log(self.name, jid) and not \

View File

@ -227,6 +227,93 @@ class DataField(ExtendedNode):
return locals() return locals()
@nested_property
def media():
"""
Media data
"""
def fget(self):
media = self.getTag('media', namespace=xmpp.NS_DATA_MEDIA)
if media:
return Media(media)
return None
def fset(self, value):
assert isinstance(value, Media)
fdel(self)
self.addChild(node=value)
def fdel(self):
t = self.getTag('media')
if t is not None:
self.delChild(t)
return locals()
class Uri(xmpp.Node):
def __init__(self, uri_tag):
xmpp.Node.__init__(self, node=uri_tag)
@nested_property
def type_():
"""
uri type
"""
def fget(self):
return self.getAttr('type')
def fset(self, value):
assert isinstance(value, basestring)
self.setAttr('type', value)
def fdel(self):
self.delAttr('type')
return locals()
@nested_property
def uri_data():
"""
uri data
"""
def fget(self):
return self.getData()
def fset(self, value):
assert isinstance(value, basestring)
self.setData(value)
def fdel(self):
self.setData(None)
return locals()
class Media(xmpp.Node):
def __init__(self, media_tag):
xmpp.Node.__init__(self, node=media_tag)
@nested_property
def uris():
"""
URIs of the media element.
"""
def fget(self):
uris = []
for element in self.getTags('uri'):
uris.append(Uri(element))
return uris
def fset(self, value):
fdel(self)
for uri in values:
self.addChild(node=uri)
def fdel(self, value):
for element in self.getTags('uri'):
self.delChild(element)
return locals()
class BooleanField(DataField): class BooleanField(DataField):
@nested_property @nested_property
def value(): def value():
@ -543,12 +630,13 @@ class SimpleDataForm(DataForm, DataRecord):
if f.required: if f.required:
# Keep all required fields # Keep all required fields
continue continue
if (hasattr(f, 'value') and not f.value) or (hasattr(f, 'values') and \ if (hasattr(f, 'value') and not f.value) or (hasattr(f, 'values') \
len(f.values) == 0): and len(f.values) == 0):
to_be_removed.append(f) to_be_removed.append(f)
else: else:
del f.label del f.label
del f.description del f.description
del f.media
for f in to_be_removed: for f in to_be_removed:
c.delChild(f) c.delChild(f)
return c return c

View File

@ -32,10 +32,12 @@ NS_ATOM ='http://www.w3.org/2005/Atom'
NS_AUTH ='jabber:iq:auth' NS_AUTH ='jabber:iq:auth'
NS_AVATAR ='http://www.xmpp.org/extensions/xep-0084.html#ns-metadata' NS_AVATAR ='http://www.xmpp.org/extensions/xep-0084.html#ns-metadata'
NS_BIND ='urn:ietf:params:xml:ns:xmpp-bind' NS_BIND ='urn:ietf:params:xml:ns:xmpp-bind'
NS_BOB ='urn:xmpp:bob' #XEP-0231
NS_BROWSE ='jabber:iq:browse' NS_BROWSE ='jabber:iq:browse'
NS_BROWSING ='http://jabber.org/protocol/browsing' # XEP-0195 NS_BROWSING ='http://jabber.org/protocol/browsing' # XEP-0195
NS_BYTESTREAM ='http://jabber.org/protocol/bytestreams' # JEP-0065 NS_BYTESTREAM ='http://jabber.org/protocol/bytestreams' # JEP-0065
NS_CAPS ='http://jabber.org/protocol/caps' # JEP-0115 NS_CAPS ='http://jabber.org/protocol/caps' # JEP-0115
NS_CAPTCHA ='urn:xmpp:captcha' # XEP-0158
NS_CHATSTATES ='http://jabber.org/protocol/chatstates' # JEP-0085 NS_CHATSTATES ='http://jabber.org/protocol/chatstates' # JEP-0085
NS_CHATTING ='http://jabber.org/protocol/chatting' # XEP-0194 NS_CHATTING ='http://jabber.org/protocol/chatting' # XEP-0194
NS_CLIENT ='jabber:client' NS_CLIENT ='jabber:client'
@ -45,6 +47,7 @@ NS_COMPONENT_1 ='http://jabberd.jabberstudio.org/ns/component/1.0'
NS_COMPRESS ='http://jabber.org/protocol/compress' # XEP-0138 NS_COMPRESS ='http://jabber.org/protocol/compress' # XEP-0138
NS_CONFERENCE ='jabber:x:conference' NS_CONFERENCE ='jabber:x:conference'
NS_DATA ='jabber:x:data' # XEP-0004 NS_DATA ='jabber:x:data' # XEP-0004
NS_DATA_MEDIA ='urn:xmpp:media-element' # XEP-0221
NS_DELAY ='jabber:x:delay' NS_DELAY ='jabber:x:delay'
NS_DELAY2 ='urn:xmpp:delay' NS_DELAY2 ='urn:xmpp:delay'
NS_DIALBACK ='jabber:server:dialback' NS_DIALBACK ='jabber:server:dialback'

View File

@ -27,6 +27,7 @@ multiple - these which may contain more data (with <reported/> element).'''
import gtk import gtk
import gobject import gobject
import base64
import gtkgui_helpers import gtkgui_helpers
import dialogs import dialogs
@ -529,6 +530,25 @@ class SingleForm(gtk.Table, object):
self.attach(label, 0, 1, linecounter, linecounter+1, self.attach(label, 0, 1, linecounter, linecounter+1,
xoptions=gtk.FILL, yoptions=gtk.FILL) xoptions=gtk.FILL, yoptions=gtk.FILL)
if field.media is not None:
for uri in field.media.uris:
if uri.type_.startswith('image/'):
try:
img_data = base64.decodestring(uri.uri_data)
pixbuf_l = gtk.gdk.PixbufLoader()
pixbuf_l.write(img_data)
pixbuf_l.close()
media = gtk.image_new_from_pixbuf(pixbuf_l.\
get_pixbuf())
except Exception:
media = gtk.Label(_('Unable to load image'))
else:
media = gtk.Label(_('Media type not supported: %s') % \
uri.type_)
linecounter += 1
self.attach(media, 0, 1, linecounter, linecounter+1,
xoptions=gtk.FILL, yoptions=gtk.FILL)
if commonwidget: if commonwidget:
assert widget is not None assert widget is not None
widget.set_sensitive(readwrite) widget.set_sensitive(readwrite)

View File

@ -40,9 +40,11 @@ import dialogs
import config import config
import vcard import vcard
import cell_renderer_image import cell_renderer_image
import dataforms_widget
from common import gajim from common import gajim
from common import helpers from common import helpers
from common import dataforms
from chat_control import ChatControl from chat_control import ChatControl
from chat_control import ChatControlBase from chat_control import ChatControlBase
@ -787,7 +789,39 @@ class GroupchatControl(ChatControlBase):
menu.destroy() menu.destroy()
def on_message(self, nick, msg, tim, has_timestamp=False, xhtml=None, def on_message(self, nick, msg, tim, has_timestamp=False, xhtml=None,
status_code=[], displaymarking=None): status_code=[], displaymarking=None, captcha=None):
if captcha:
dataform = dataforms.ExtendForm(node=captcha)
self.form_widget = dataforms_widget.DataFormWidget(dataform)
self.form_widget.show_all()
vbox = self.xml.get_object('gc_textviews_vbox')
vbox.pack_start(self.form_widget, expand=False, fill=False)
def on_send_dataform_clicked(widget):
if not self.form_widget:
return
form_node = self.form_widget.data_form.get_purged()
form_node.type = 'submit'
gajim.connections[self.account].send_captcha(self.room_jid,
form_node)
self.form_widget.hide()
self.form_widget.destroy()
self.btn_box.destroy()
del self.form_widget
del self.btn_box
valid_button = gtk.Button(stock=gtk.STOCK_OK)
valid_button.connect('clicked', on_send_dataform_clicked)
self.btn_box = gtk.HButtonBox()
self.btn_box.set_layout(gtk.BUTTONBOX_END)
self.btn_box.pack_start(valid_button)
self.btn_box.show_all()
vbox.pack_start(self.btn_box, expand=False, fill=False)
if self.parent_win:
self.parent_win.redraw_tab(self, 'attention')
else:
self.attention_flag = True
helpers.play_sound('muc_message_received')
if '100' in status_code: if '100' in status_code:
# Room is not anonymous # Room is not anonymous
self.is_anonymous = False self.is_anonymous = False

View File

@ -956,7 +956,7 @@ class Interface:
def handle_event_gc_msg(self, account, array): def handle_event_gc_msg(self, account, array):
# ('GC_MSG', account, (jid, msg, time, has_timestamp, htmlmsg, # ('GC_MSG', account, (jid, msg, time, has_timestamp, htmlmsg,
# [status_codes], displaymarking)) # [status_codes], displaymarking, captcha))
jids = array[0].split('/', 1) jids = array[0].split('/', 1)
room_jid = jids[0] room_jid = jids[0]
@ -980,7 +980,8 @@ class Interface:
# message from someone # message from someone
nick = jids[1] nick = jids[1]
gc_control.on_message(nick, msg, array[2], array[3], xhtml, array[5], displaymarking=array[6]) gc_control.on_message(nick, msg, array[2], array[3], xhtml, array[5],
displaymarking=array[6], captcha=array[7])
if self.remote_ctrl: if self.remote_ctrl:
highlight = gc_control.needs_visual_notification(msg) highlight = gc_control.needs_visual_notification(msg)