| 1 | ## common/gajim.py |
|---|
| 2 | ## |
|---|
| 3 | ## Contributors for this file: |
|---|
| 4 | ## - Yann Le Boulanger <asterix@lagaule.org> |
|---|
| 5 | ## - Nikos Kouremenos <kourem@gmail.com> |
|---|
| 6 | ## |
|---|
| 7 | ## Copyright (C) 2003-2004 Yann Le Boulanger <asterix@lagaule.org> |
|---|
| 8 | ## Vincent Hanquez <tab@snarc.org> |
|---|
| 9 | ## Copyright (C) 2005 Yann Le Boulanger <asterix@lagaule.org> |
|---|
| 10 | ## Vincent Hanquez <tab@snarc.org> |
|---|
| 11 | ## Nikos Kouremenos <nkour@jabber.org> |
|---|
| 12 | ## Dimitur Kirov <dkirov@gmail.com> |
|---|
| 13 | ## Travis Shirk <travis@pobox.com> |
|---|
| 14 | ## Norman Rasmussen <norman@rasmussen.co.za> |
|---|
| 15 | ## |
|---|
| 16 | ## This program is free software; you can redistribute it and/or modify |
|---|
| 17 | ## it under the terms of the GNU General Public License as published |
|---|
| 18 | ## by the Free Software Foundation; version 2 only. |
|---|
| 19 | ## |
|---|
| 20 | ## This program is distributed in the hope that it will be useful, |
|---|
| 21 | ## but WITHOUT ANY WARRANTY; without even the implied warranty of |
|---|
| 22 | ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|---|
| 23 | ## GNU General Public License for more details. |
|---|
| 24 | ## |
|---|
| 25 | |
|---|
| 26 | import os |
|---|
| 27 | import sys |
|---|
| 28 | import logging |
|---|
| 29 | import mutex |
|---|
| 30 | |
|---|
| 31 | import config |
|---|
| 32 | |
|---|
| 33 | |
|---|
| 34 | interface = None # The actual interface (the gtk one for the moment) |
|---|
| 35 | version = '0.9' |
|---|
| 36 | config = config.Config() |
|---|
| 37 | connections = {} |
|---|
| 38 | verbose = False |
|---|
| 39 | |
|---|
| 40 | h = logging.StreamHandler() |
|---|
| 41 | f = logging.Formatter('%(asctime)s %(name)s: %(message)s', '%d %b %Y %H:%M:%S') |
|---|
| 42 | h.setFormatter(f) |
|---|
| 43 | log = logging.getLogger('Gajim') |
|---|
| 44 | log.addHandler(h) |
|---|
| 45 | |
|---|
| 46 | import logger |
|---|
| 47 | logger = logger.Logger() # init the logger |
|---|
| 48 | |
|---|
| 49 | if os.name == 'nt': |
|---|
| 50 | DATA_DIR = os.path.join('..', 'data') |
|---|
| 51 | try: |
|---|
| 52 | # Documents and Settings\[User Name]\Application Data\Gajim |
|---|
| 53 | LOGPATH = os.path.join(os.environ['appdata'], 'Gajim', 'Logs') # deprecated |
|---|
| 54 | VCARDPATH = os.path.join(os.environ['appdata'], 'Gajim', 'Vcards') |
|---|
| 55 | except KeyError: |
|---|
| 56 | # win9x, in cwd |
|---|
| 57 | LOGPATH = 'Logs' # deprecated |
|---|
| 58 | VCARDPATH = 'Vcards' |
|---|
| 59 | else: # Unices |
|---|
| 60 | DATA_DIR = '../data' |
|---|
| 61 | LOGPATH = os.path.expanduser('~/.gajim/logs') # deprecated |
|---|
| 62 | VCARDPATH = os.path.expanduser('~/.gajim/vcards') |
|---|
| 63 | |
|---|
| 64 | try: |
|---|
| 65 | LOGPATH = LOGPATH.decode(sys.getfilesystemencoding()) |
|---|
| 66 | VCARDPATH = VCARDPATH.decode(sys.getfilesystemencoding()) |
|---|
| 67 | except: |
|---|
| 68 | pass |
|---|
| 69 | |
|---|
| 70 | LANG = os.getenv('LANG') # en_US, fr_FR, el_GR etc.. |
|---|
| 71 | if LANG: |
|---|
| 72 | LANG = LANG[:2] # en, fr, el etc.. |
|---|
| 73 | else: |
|---|
| 74 | LANG = 'en' |
|---|
| 75 | |
|---|
| 76 | last_message_time = {} # list of time of the latest incomming message |
|---|
| 77 | # {acct1: {jid1: time1, jid2: time2}, } |
|---|
| 78 | encrypted_chats = {} # list of encrypted chats {acct1: [jid1, jid2], ..} |
|---|
| 79 | |
|---|
| 80 | contacts = {} # list of contacts {acct: {jid1: [C1, C2]}, } one Contact per resource |
|---|
| 81 | gc_contacts = {} # list of contacts that are in gc {acct: {room_jid: {nick: C}}} |
|---|
| 82 | gc_connected = {} # tell if we are connected to the room or not {acct: {room_jid: True}} |
|---|
| 83 | gc_passwords = {} # list of the pass required to enter a room {room_jid: password} |
|---|
| 84 | |
|---|
| 85 | groups = {} # list of groups |
|---|
| 86 | newly_added = {} # list of contacts that has just signed in |
|---|
| 87 | to_be_removed = {} # list of contacts that has just signed out |
|---|
| 88 | |
|---|
| 89 | awaiting_events = {} # list of messages/FT reveived but not printed |
|---|
| 90 | # awaiting_events[jid] = (type, (data1, data2, ...)) |
|---|
| 91 | # if type in ('chat', 'normal'): data = (message, subject, kind, time, |
|---|
| 92 | # encrypted, resource) |
|---|
| 93 | # kind can be (incoming, error) |
|---|
| 94 | # if type in file-request, file-request-error, file-send-error, file-error, |
|---|
| 95 | # file-completed, file-stopped: |
|---|
| 96 | # data = file_props |
|---|
| 97 | nicks = {} # list of our nick names in each account |
|---|
| 98 | allow_notifications = {} # do we allow notifications for each account ? |
|---|
| 99 | con_types = {} # type of each connection (ssl, tls, tcp, ...) |
|---|
| 100 | |
|---|
| 101 | sleeper_state = {} # whether we pass auto away / xa or not |
|---|
| 102 | #'off': don't use sleeper for this account |
|---|
| 103 | #'online': online and use sleeper |
|---|
| 104 | #'autoaway': autoaway and use sleeper |
|---|
| 105 | #'autoxa': autoxa and use sleeper |
|---|
| 106 | status_before_autoaway = {} |
|---|
| 107 | #queues of events from connections... |
|---|
| 108 | events_for_ui = {} |
|---|
| 109 | #... and its mutex |
|---|
| 110 | mutex_events_for_ui = mutex.mutex() |
|---|
| 111 | |
|---|
| 112 | SHOW_LIST = ['offline', 'connecting', 'online', 'chat', 'away', 'xa', 'dnd', |
|---|
| 113 | 'invisible'] |
|---|
| 114 | |
|---|
| 115 | def get_nick_from_jid(jid): |
|---|
| 116 | pos = jid.find('@') |
|---|
| 117 | return jid[:pos] |
|---|
| 118 | |
|---|
| 119 | def get_server_from_jid(jid): |
|---|
| 120 | pos = jid.find('@') + 1 # after @ |
|---|
| 121 | return jid[pos:] |
|---|
| 122 | |
|---|
| 123 | def get_nick_from_fjid(jid): |
|---|
| 124 | # fake jid is the jid for a contact in a room |
|---|
| 125 | # gaim@conference.jabber.no/nick/nick-continued |
|---|
| 126 | return jid.split('/', 1)[1] |
|---|
| 127 | |
|---|
| 128 | def get_room_name_and_server_from_room_jid(jid): |
|---|
| 129 | room_name = get_nick_from_jid(jid) |
|---|
| 130 | server = get_server_from_jid(jid) |
|---|
| 131 | return room_name, server |
|---|
| 132 | |
|---|
| 133 | def get_room_and_nick_from_fjid(jid): |
|---|
| 134 | # fake jid is the jid for a contact in a room |
|---|
| 135 | # gaim@conference.jabber.no/nick/nick-continued |
|---|
| 136 | # return ('gaim@conference.jabber.no', 'nick/nick-continued') |
|---|
| 137 | l = jid.split('/', 1) |
|---|
| 138 | if len(l) == 1: # No nick |
|---|
| 139 | l.append('') |
|---|
| 140 | return l |
|---|
| 141 | |
|---|
| 142 | def get_real_jid_from_fjid(account, fjid): |
|---|
| 143 | '''returns real jid or returns None |
|---|
| 144 | if we don't know the real jid''' |
|---|
| 145 | room_jid, nick = get_room_and_nick_from_fjid(fjid) |
|---|
| 146 | if not nick: # It's not a fake_jid, it is a real jid |
|---|
| 147 | return fjid # we return the real jid |
|---|
| 148 | real_jid = fjid |
|---|
| 149 | gcs = interface.instances[account]['gc'] |
|---|
| 150 | if gcs.has_key(room_jid): |
|---|
| 151 | # It's a pm, so if we have real jid it's in contact.jid |
|---|
| 152 | if not gc_contacts[account][room_jid].has_key(nick): |
|---|
| 153 | return |
|---|
| 154 | contact = gc_contacts[account][room_jid][nick] |
|---|
| 155 | # contact.jid is None when it's not a real jid (we don't know real jid) |
|---|
| 156 | real_jid = contact.jid |
|---|
| 157 | return real_jid |
|---|
| 158 | |
|---|
| 159 | def get_room_from_fjid(jid): |
|---|
| 160 | return get_room_and_nick_from_fjid(jid)[0] |
|---|
| 161 | |
|---|
| 162 | def get_contact_instances_from_jid(account, jid): |
|---|
| 163 | ''' we may have two or more resources on that jid ''' |
|---|
| 164 | if jid in contacts[account]: |
|---|
| 165 | contacts_instances = contacts[account][jid] |
|---|
| 166 | return contacts_instances |
|---|
| 167 | |
|---|
| 168 | def get_first_contact_instance_from_jid(account, jid): |
|---|
| 169 | contact = None |
|---|
| 170 | if jid in contacts[account]: |
|---|
| 171 | contact = contacts[account][jid][0] |
|---|
| 172 | else: # it's fake jid |
|---|
| 173 | #FIXME: problem see comment in next line |
|---|
| 174 | room, nick = \ |
|---|
| 175 | get_room_and_nick_from_fjid(jid) # if we ban/kick we now real jid |
|---|
| 176 | if gc_contacts[account].has_key(room) and \ |
|---|
| 177 | nick in gc_contacts[account][room]: |
|---|
| 178 | contact = gc_contacts[account][room][nick] |
|---|
| 179 | return contact |
|---|
| 180 | |
|---|
| 181 | def get_contact_instance_with_highest_priority(account, jid): |
|---|
| 182 | contact_instances = contacts[account][jid] |
|---|
| 183 | return get_highest_prio_contact_from_contacts(contact_instances) |
|---|
| 184 | |
|---|
| 185 | def get_contact_name_from_jid(account, jid): |
|---|
| 186 | return contacts[account][jid][0].name |
|---|
| 187 | |
|---|
| 188 | def get_highest_prio_contact_from_contacts(contacts): |
|---|
| 189 | prim_contact = None # primary contact |
|---|
| 190 | for contact in contacts: |
|---|
| 191 | if prim_contact == None or int(contact.priority) > \ |
|---|
| 192 | int(prim_contact.priority): |
|---|
| 193 | prim_contact = contact |
|---|
| 194 | return prim_contact |
|---|
| 195 | |
|---|
| 196 | def get_jid_without_resource(jid): |
|---|
| 197 | return jid.split('/')[0] |
|---|
| 198 | |
|---|
| 199 | def construct_fjid(room_jid, nick): |
|---|
| 200 | ''' nick is in utf8 (taken from treeview); room_jid is in unicode''' |
|---|
| 201 | # fake jid is the jid for a contact in a room |
|---|
| 202 | # gaim@conference.jabber.org/nick |
|---|
| 203 | if isinstance(nick, str): |
|---|
| 204 | nick = unicode(nick, 'utf-8') |
|---|
| 205 | return room_jid + '/' + nick |
|---|
| 206 | |
|---|
| 207 | def get_resource_from_jid(jid): |
|---|
| 208 | jids = jid.split('/', 1) |
|---|
| 209 | if len(jids) > 1: |
|---|
| 210 | return jids[1] # abc@doremi.org/res/res-continued |
|---|
| 211 | else: |
|---|
| 212 | return '' |
|---|
| 213 | '''\ |
|---|
| 214 | [15:34:28] <asterix> we should add contact.fake_jid I think |
|---|
| 215 | [15:34:46] <asterix> so if we know real jid, it wil be in contact.jid, or we look in contact.fake_jid |
|---|
| 216 | [15:32:54] <asterix> they can have resource if we know the real jid |
|---|
| 217 | [15:33:07] <asterix> and that resource is in contact.resource |
|---|
| 218 | ''' |
|---|
| 219 | |
|---|
| 220 | def get_number_of_accounts(): |
|---|
| 221 | return len(connections.keys()) |
|---|
| 222 | |
|---|
| 223 | def get_transport_name_from_jid(jid, use_config_setting = True): |
|---|
| 224 | '''returns 'aim', 'gg', 'irc' etc''' |
|---|
| 225 | #FIXME: jid can be None! one TB I saw had this problem: |
|---|
| 226 | # in the code block # it is a groupchat presence in handle_event_notify |
|---|
| 227 | # jid was None. Yann why? |
|---|
| 228 | if not jid or (use_config_setting and not config.get('use_transports_iconsets')): |
|---|
| 229 | return |
|---|
| 230 | host = jid.split('@')[-1] |
|---|
| 231 | if host.startswith('aim'): |
|---|
| 232 | return 'aim' |
|---|
| 233 | elif host.startswith('gg'): |
|---|
| 234 | return 'gadugadu' |
|---|
| 235 | elif host.startswith('irc'): |
|---|
| 236 | return 'irc' |
|---|
| 237 | # abc@icqsucks.org will match as ICQ, but what to do.. |
|---|
| 238 | elif host.startswith('icq'): |
|---|
| 239 | return 'icq' |
|---|
| 240 | elif host.startswith('msn'): |
|---|
| 241 | return 'msn' |
|---|
| 242 | elif host.startswith('sms'): |
|---|
| 243 | return 'sms' |
|---|
| 244 | elif host.startswith('tlen'): |
|---|
| 245 | return 'tlen' |
|---|
| 246 | elif host.startswith('weather'): |
|---|
| 247 | return 'weather' |
|---|
| 248 | elif host.startswith('yahoo'): |
|---|
| 249 | return 'yahoo' |
|---|
| 250 | else: |
|---|
| 251 | return None |
|---|
| 252 | |
|---|
| 253 | def jid_is_transport(jid): |
|---|
| 254 | aim = jid.startswith('aim') |
|---|
| 255 | gg = jid.startswith('gg') # gadugadu |
|---|
| 256 | irc = jid.startswith('irc') |
|---|
| 257 | icq = jid.startswith('icq') |
|---|
| 258 | msn = jid.startswith('msn') |
|---|
| 259 | sms = jid.startswith('sms') |
|---|
| 260 | tlen = jid.startswith('tlen') |
|---|
| 261 | yahoo = jid.startswith('yahoo') |
|---|
| 262 | |
|---|
| 263 | if aim or gg or irc or icq or msn or sms or yahoo or tlen: |
|---|
| 264 | is_transport = True |
|---|
| 265 | else: |
|---|
| 266 | is_transport = False |
|---|
| 267 | |
|---|
| 268 | return is_transport |
|---|
| 269 | |
|---|
| 270 | def get_jid_from_account(account_name): |
|---|
| 271 | name = config.get_per('accounts', account_name, 'name') |
|---|
| 272 | hostname = config.get_per('accounts', account_name, 'hostname') |
|---|
| 273 | jid = name + '@' + hostname |
|---|
| 274 | return jid |
|---|
| 275 | |
|---|
| 276 | def get_hostname_from_account(account_name, use_srv = False): |
|---|
| 277 | '''returns hostname (if custom hostname is used, that is returned)''' |
|---|
| 278 | if use_srv and connections[account_name].connected_hostname: |
|---|
| 279 | return connections[account_name].connected_hostname |
|---|
| 280 | if config.get_per('accounts', account_name, 'use_custom_host'): |
|---|
| 281 | return config.get_per('accounts', account_name, 'custom_host') |
|---|
| 282 | return config.get_per('accounts', account_name, 'hostname') |
|---|
| 283 | |
|---|
| 284 | def get_first_event(account, jid, typ = None): |
|---|
| 285 | '''returns the first event of the given type from the awaiting_events queue''' |
|---|
| 286 | if not awaiting_events[account].has_key(jid): |
|---|
| 287 | return None |
|---|
| 288 | q = awaiting_events[account][jid] |
|---|
| 289 | if not typ: |
|---|
| 290 | return q[0] |
|---|
| 291 | for ev in q: |
|---|
| 292 | if ev[0] == typ: |
|---|
| 293 | return ev |
|---|
| 294 | return None |
|---|
| 295 | |
|---|
| 296 | def popup_window(account): |
|---|
| 297 | autopopup = config.get('autopopup') |
|---|
| 298 | autopopupaway = config.get('autopopupaway') |
|---|
| 299 | if autopopup and (autopopupaway or connections[account].connected > 3): |
|---|
| 300 | return True |
|---|
| 301 | return False |
|---|
| 302 | |
|---|
| 303 | def show_notification(account): |
|---|
| 304 | if config.get('notify_on_new_message'): |
|---|
| 305 | # check OUR status and if we allow notifications for that status |
|---|
| 306 | if config.get('autopopupaway'): # always show notification |
|---|
| 307 | return True |
|---|
| 308 | if connections[account].connected in (2, 3): # we're online or chat |
|---|
| 309 | return True |
|---|
| 310 | return False |
|---|