1# Copyright (C) 2008-2014 Yann Leboulanger <asterix AT lagaule.org> 2# Copyright (C) 2008 Brendan Taylor <whateley AT gmail.com> 3# Jonathan Schleifer <js-gajim AT webkeks.org> 4# Stephan Erb <steve-e AT h3c.de> 5# 6# This file is part of Gajim. 7# 8# Gajim is free software; you can redistribute it and/or modify 9# it under the terms of the GNU General Public License as published 10# by the Free Software Foundation; version 3 only. 11# 12# Gajim is distributed in the hope that it will be useful, 13# but WITHOUT ANY WARRANTY; without even the implied warranty of 14# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15# GNU General Public License for more details. 16# 17# You should have received a copy of the GNU General Public License 18# along with Gajim. If not, see <http://www.gnu.org/licenses/>. 19 20import string 21import random 22import itertools 23 24from gajim.common import helpers 25from gajim.common import events 26from gajim.common import app 27from gajim.common import contacts 28from gajim.common import ged 29from gajim.common.helpers import AdditionalDataDict 30from gajim.common.const import KindConstant 31from gajim.gui.util import get_show_in_roster 32from gajim.gui.util import get_show_in_systray 33 34 35class ChatControlSession: 36 def __init__(self, conn, jid, thread_id, type_='chat'): 37 self.conn = conn 38 self.jid = jid 39 self.type_ = type_ 40 self.resource = jid.resource 41 self.control = None 42 43 if thread_id: 44 self.received_thread_id = True 45 self.thread_id = thread_id 46 else: 47 self.received_thread_id = False 48 if type_ == 'normal': 49 self.thread_id = None 50 else: 51 self.thread_id = self.generate_thread_id() 52 53 self.loggable = True 54 55 self.last_send = 0 56 self.last_receive = 0 57 58 app.ged.register_event_handler('decrypted-message-received', 59 ged.PREGUI, 60 self._nec_decrypted_message_received) 61 62 def generate_thread_id(self): 63 return ''.join( 64 [f(string.ascii_letters) for f in itertools.repeat( 65 random.choice, 32)] 66 ) 67 68 def is_loggable(self): 69 return helpers.should_log(self.conn.name, self.jid.bare) 70 71 def get_to(self): 72 bare_jid = self.jid.bare 73 if not self.resource: 74 return bare_jid 75 return bare_jid + '/' + self.resource 76 77 def _nec_decrypted_message_received(self, obj): 78 """ 79 Dispatch a received <message> stanza 80 """ 81 if obj.session != self: 82 return 83 84 if obj.properties.is_muc_pm: 85 contact = app.contacts.get_gc_contact( 86 self.conn.name, obj.jid, obj.resource) 87 else: 88 contact = app.contacts.get_contact( 89 self.conn.name, obj.jid, obj.resource) 90 if self.resource != obj.resource: 91 self.resource = obj.resource 92 if self.control: 93 if isinstance(contact, contacts.GC_Contact): 94 self.control.gc_contact = contact 95 self.control.contact = contact.as_contact() 96 else: 97 self.control.contact = contact 98 if self.control.resource: 99 self.control.change_resource(self.resource) 100 101 if not obj.msgtxt: 102 return 103 104 log_type = KindConstant.CHAT_MSG_RECV 105 if obj.properties.is_sent_carbon: 106 log_type = KindConstant.CHAT_MSG_SENT 107 108 if self.is_loggable() and obj.msgtxt: 109 jid = obj.fjid 110 if not obj.properties.is_muc_pm: 111 jid = obj.jid 112 113 obj.msg_log_id = app.storage.archive.insert_into_logs( 114 self.conn.name, 115 jid, 116 obj.properties.timestamp, 117 log_type, 118 message=obj.msgtxt, 119 subject=obj.properties.subject, 120 additional_data=obj.additional_data, 121 stanza_id=obj.unique_id, 122 message_id=obj.properties.id) 123 124 if obj.properties.is_muc_pm and not obj.gc_control: 125 # This is a carbon of a PM from a MUC we are not currently 126 # joined. We log it silently without notification. 127 return True 128 129 if not obj.msgtxt: # empty message text 130 return True 131 132 if not self.control: 133 ctrl = app.interface.msg_win_mgr.search_control(obj.jid, 134 obj.conn.name, obj.resource) 135 if ctrl: 136 self.control = ctrl 137 self.control.set_session(self) 138 if isinstance(contact, contacts.GC_Contact): 139 self.control.gc_contact = contact 140 self.control.contact = contact.as_contact() 141 else: 142 self.control.contact = contact 143 144 if not obj.properties.is_muc_pm: 145 self.roster_message2(obj) 146 147 def roster_message2(self, obj): 148 """ 149 Display the message or show notification in the roster 150 """ 151 contact = None 152 jid = obj.jid 153 resource = obj.resource 154 155 fjid = jid 156 157 # Try to catch the contact with correct resource 158 if resource: 159 fjid = jid + '/' + resource 160 contact = app.contacts.get_contact(obj.conn.name, jid, resource) 161 162 highest_contact = app.contacts.get_contact_with_highest_priority( 163 obj.conn.name, jid) 164 if not contact: 165 # If there is another resource, it may be a message from an 166 # invisible resource 167 lcontact = app.contacts.get_contacts(obj.conn.name, jid) 168 if (len(lcontact) > 1 or (lcontact and lcontact[0].resource and \ 169 lcontact[0].show != 'offline')) and jid.find('@') > 0: 170 contact = app.contacts.copy_contact(highest_contact) 171 contact.resource = resource 172 contact.priority = 0 173 contact.show = 'offline' 174 contact.status = '' 175 app.contacts.add_contact(obj.conn.name, contact) 176 177 else: 178 # Default to highest prio 179 fjid = jid 180 contact = highest_contact 181 182 if not contact: 183 # contact is not in roster 184 contact = app.interface.roster.add_to_not_in_the_roster( 185 obj.conn.name, jid, obj.properties.nickname) 186 187 if not self.control: 188 ctrl = app.interface.msg_win_mgr.search_control(obj.jid, 189 obj.conn.name, obj.resource) 190 if ctrl: 191 self.control = ctrl 192 self.control.set_session(self) 193 else: 194 fjid = jid 195 196 obj.popup = helpers.allow_popup_window(self.conn.name) 197 198 event_t = events.ChatEvent 199 event_type = 'message_received' 200 201 if self.control: 202 # We have a ChatControl open 203 obj.show_in_roster = False 204 obj.show_in_systray = False 205 do_event = False 206 elif obj.properties.is_sent_carbon: 207 # Its a Carbon Copied Message we sent 208 obj.show_in_roster = False 209 obj.show_in_systray = False 210 unread_events = app.events.get_events( 211 self.conn.name, fjid, types=['chat']) 212 read_ids = [] 213 for msg in unread_events: 214 read_ids.append(msg.msg_log_id) 215 app.storage.archive.set_read_messages(read_ids) 216 app.events.remove_events(self.conn.name, fjid, types=['chat']) 217 do_event = False 218 else: 219 # Everything else 220 obj.show_in_roster = get_show_in_roster(event_type, self) 221 obj.show_in_systray = get_show_in_systray(event_type, 222 obj.conn.name, 223 contact.jid) 224 do_event = True 225 if do_event: 226 kind = obj.properties.type.value 227 event = event_t( 228 obj.msgtxt, 229 obj.properties.subject, 230 kind, 231 obj.properties.timestamp, 232 obj.resource, 233 obj.msg_log_id, 234 correct_id=obj.correct_id, 235 message_id=obj.properties.id, 236 session=self, 237 displaymarking=obj.displaymarking, 238 sent_forwarded=obj.properties.is_sent_carbon, 239 show_in_roster=obj.show_in_roster, 240 show_in_systray=obj.show_in_systray, 241 additional_data=obj.additional_data) 242 243 app.events.add_event(self.conn.name, fjid, event) 244 245 def roster_message(self, jid, msg, tim, msg_type='', 246 subject=None, resource='', msg_log_id=None, user_nick='', 247 displaymarking=None, additional_data=None): 248 """ 249 Display the message or show notification in the roster 250 """ 251 contact = None 252 fjid = jid 253 254 if additional_data is None: 255 additional_data = AdditionalDataDict() 256 257 # Try to catch the contact with correct resource 258 if resource: 259 fjid = jid + '/' + resource 260 contact = app.contacts.get_contact(self.conn.name, jid, resource) 261 262 highest_contact = app.contacts.get_contact_with_highest_priority( 263 self.conn.name, jid) 264 if not contact: 265 # If there is another resource, it may be a message from an invisible 266 # resource 267 lcontact = app.contacts.get_contacts(self.conn.name, jid) 268 if (len(lcontact) > 1 or (lcontact and lcontact[0].resource and \ 269 lcontact[0].show != 'offline')) and jid.find('@') > 0: 270 contact = app.contacts.copy_contact(highest_contact) 271 contact.resource = resource 272 if resource: 273 fjid = jid + '/' + resource 274 contact.priority = 0 275 contact.show = 'offline' 276 contact.status = '' 277 app.contacts.add_contact(self.conn.name, contact) 278 279 else: 280 # Default to highest prio 281 fjid = jid 282 contact = highest_contact 283 284 if not contact: 285 # contact is not in roster 286 contact = app.interface.roster.add_to_not_in_the_roster( 287 self.conn.name, jid, user_nick) 288 289 if not self.control: 290 ctrl = app.interface.msg_win_mgr.get_control(fjid, self.conn.name) 291 if ctrl: 292 self.control = ctrl 293 self.control.set_session(self) 294 else: 295 fjid = jid 296 297 # Do we have a queue? 298 no_queue = len(app.events.get_events(self.conn.name, fjid)) == 0 299 300 popup = helpers.allow_popup_window(self.conn.name) 301 302 # We print if window is opened and it's not a single message 303 if self.control: 304 typ = '' 305 306 if msg_type == 'error': 307 typ = 'error' 308 309 self.control.add_message(msg, 310 typ, 311 tim=tim, 312 subject=subject, 313 displaymarking=displaymarking, 314 additional_data=additional_data) 315 316 if msg_log_id: 317 app.storage.archive.set_read_messages([msg_log_id]) 318 319 return 320 321 # We save it in a queue 322 event_t = events.ChatEvent 323 event_type = 'message_received' 324 325 show_in_roster = get_show_in_roster(event_type, self) 326 show_in_systray = get_show_in_systray(event_type, 327 self.conn.name, 328 contact.jid) 329 330 event = event_t(msg, subject, msg_type, tim, resource, 331 msg_log_id, session=self, 332 displaymarking=displaymarking, sent_forwarded=False, 333 show_in_roster=show_in_roster, show_in_systray=show_in_systray, 334 additional_data=additional_data) 335 336 app.events.add_event(self.conn.name, fjid, event) 337 338 if popup: 339 if not self.control: 340 self.control = app.interface.new_chat(contact, 341 self.conn.name, session=self) 342 343 if app.events.get_events(self.conn.name, fjid): 344 self.control.read_queue() 345 else: 346 if no_queue: # We didn't have a queue: we change icons 347 app.interface.roster.draw_contact(jid, self.conn.name) 348 349 app.interface.roster.show_title() # we show the * or [n] 350 # Select the big brother contact in roster, it's visible because it has 351 # events. 352 family = app.contacts.get_metacontacts_family(self.conn.name, jid) 353 if family: 354 _nearby_family, bb_jid, bb_account = \ 355 app.contacts.get_nearby_family_and_big_brother(family, 356 self.conn.name) 357 else: 358 bb_jid, bb_account = jid, self.conn.name 359 app.interface.roster.select_contact(bb_jid, bb_account) 360