| 1 | ## common/gajim.py |
|---|
| 2 | ## |
|---|
| 3 | ## Copyright (C) 2003-2006 Yann Le Boulanger <asterix@lagaule.org> |
|---|
| 4 | ## Copyright (C) 2005-2006 Nikos Kouremenos <kourem@gmail.com> |
|---|
| 5 | ## Copyright (C) 2005-2006 Dimitur Kirov <dkirov@gmail.com> |
|---|
| 6 | ## Copyright (C) 2005-2006 Travis Shirk <travis@pobox.com> |
|---|
| 7 | ## |
|---|
| 8 | ## This program 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 2 only. |
|---|
| 11 | ## |
|---|
| 12 | ## This program 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 | |
|---|
| 18 | import sys |
|---|
| 19 | import logging |
|---|
| 20 | import locale |
|---|
| 21 | |
|---|
| 22 | import config |
|---|
| 23 | from contacts import Contacts |
|---|
| 24 | from events import Events |
|---|
| 25 | |
|---|
| 26 | try: |
|---|
| 27 | import defs |
|---|
| 28 | except ImportError: |
|---|
| 29 | print >> sys.stderr, '''defs.py is missing! |
|---|
| 30 | |
|---|
| 31 | If you start gajim from svn: |
|---|
| 32 | * Make sure you have GNU autotools installed. |
|---|
| 33 | This includes the following packages: |
|---|
| 34 | automake >= 1.8 |
|---|
| 35 | autoconf >= 2.59 |
|---|
| 36 | intltool-0.35 |
|---|
| 37 | libtool |
|---|
| 38 | * Run |
|---|
| 39 | $ sh autogen.sh |
|---|
| 40 | * Optionally, install gajim |
|---|
| 41 | $ make |
|---|
| 42 | $ sudo make install |
|---|
| 43 | |
|---|
| 44 | **** Note for translators **** |
|---|
| 45 | You can get the latest string updates, by running: |
|---|
| 46 | $ cd po/ |
|---|
| 47 | $ make update-po |
|---|
| 48 | |
|---|
| 49 | ''' |
|---|
| 50 | sys.exit(1) |
|---|
| 51 | |
|---|
| 52 | interface = None # The actual interface (the gtk one for the moment) |
|---|
| 53 | config = config.Config() |
|---|
| 54 | version = config.get('version') |
|---|
| 55 | connections = {} # 'account name': 'account (connection.Connection) instance' |
|---|
| 56 | verbose = False |
|---|
| 57 | |
|---|
| 58 | h = logging.StreamHandler() |
|---|
| 59 | f = logging.Formatter('%(asctime)s %(name)s: %(message)s', '%d %b %Y %H:%M:%S') |
|---|
| 60 | h.setFormatter(f) |
|---|
| 61 | log = logging.getLogger('Gajim') |
|---|
| 62 | log.addHandler(h) |
|---|
| 63 | |
|---|
| 64 | import logger |
|---|
| 65 | logger = logger.Logger() # init the logger |
|---|
| 66 | |
|---|
| 67 | import configpaths |
|---|
| 68 | gajimpaths = configpaths.gajimpaths |
|---|
| 69 | |
|---|
| 70 | LOGPATH = gajimpaths['LOG'] # deprecated |
|---|
| 71 | VCARD_PATH = gajimpaths['VCARD'] |
|---|
| 72 | AVATAR_PATH = gajimpaths['AVATAR'] |
|---|
| 73 | MY_EMOTS_PATH = gajimpaths['MY_EMOTS'] |
|---|
| 74 | TMP = gajimpaths['TMP'] |
|---|
| 75 | DATA_DIR = gajimpaths['DATA'] |
|---|
| 76 | HOME_DIR = gajimpaths['HOME'] |
|---|
| 77 | |
|---|
| 78 | try: |
|---|
| 79 | LANG = locale.getdefaultlocale()[0] # en_US, fr_FR, el_GR etc.. |
|---|
| 80 | except (ValueError, locale.Error): |
|---|
| 81 | # unknown locale, use en is better than fail |
|---|
| 82 | LANG = None |
|---|
| 83 | if LANG is None: |
|---|
| 84 | LANG = 'en' |
|---|
| 85 | else: |
|---|
| 86 | LANG = LANG[:2] # en, fr, el etc.. |
|---|
| 87 | |
|---|
| 88 | gmail_domains = ['gmail.com', 'googlemail.com'] |
|---|
| 89 | |
|---|
| 90 | transport_type = {} # list the type of transport |
|---|
| 91 | |
|---|
| 92 | last_message_time = {} # list of time of the latest incomming message |
|---|
| 93 | # {acct1: {jid1: time1, jid2: time2}, } |
|---|
| 94 | encrypted_chats = {} # list of encrypted chats {acct1: [jid1, jid2], ..} |
|---|
| 95 | |
|---|
| 96 | contacts = Contacts() |
|---|
| 97 | gc_connected = {} # tell if we are connected to the room or not {acct: {room_jid: True}} |
|---|
| 98 | gc_passwords = {} # list of the pass required to enter a room {room_jid: password} |
|---|
| 99 | automatic_rooms = {} # list of rooms that must be automaticaly configured and for which we have a list of invities {account: {room_jid: {'invities': []}}} |
|---|
| 100 | |
|---|
| 101 | groups = {} # list of groups |
|---|
| 102 | newly_added = {} # list of contacts that has just signed in |
|---|
| 103 | to_be_removed = {} # list of contacts that has just signed out |
|---|
| 104 | |
|---|
| 105 | events = Events() |
|---|
| 106 | |
|---|
| 107 | nicks = {} # list of our nick names in each account |
|---|
| 108 | # should we block 'contact signed in' notifications for this account? |
|---|
| 109 | # this is only for the first 30 seconds after we change our show |
|---|
| 110 | # to something else than offline |
|---|
| 111 | # can also contain account/transport_jid to block notifications for contacts |
|---|
| 112 | # from this transport |
|---|
| 113 | block_signed_in_notifications = {} |
|---|
| 114 | con_types = {} # type of each connection (ssl, tls, tcp, ...) |
|---|
| 115 | |
|---|
| 116 | sleeper_state = {} # whether we pass auto away / xa or not |
|---|
| 117 | #'off': don't use sleeper for this account |
|---|
| 118 | #'online': online and use sleeper |
|---|
| 119 | #'autoaway': autoaway and use sleeper |
|---|
| 120 | #'autoxa': autoxa and use sleeper |
|---|
| 121 | status_before_autoaway = {} |
|---|
| 122 | |
|---|
| 123 | # jid of transport contacts for which we need to ask avatar when transport will |
|---|
| 124 | # be online |
|---|
| 125 | transport_avatar = {} # {transport_jid: [jid_list]} |
|---|
| 126 | |
|---|
| 127 | SHOW_LIST = ['offline', 'connecting', 'online', 'chat', 'away', 'xa', 'dnd', |
|---|
| 128 | 'invisible'] |
|---|
| 129 | |
|---|
| 130 | # zeroconf account name |
|---|
| 131 | ZEROCONF_ACC_NAME = 'Local' |
|---|
| 132 | priority_dict = {} |
|---|
| 133 | for status in ('online', 'chat', 'away', 'xa', 'dnd', 'invisible'): |
|---|
| 134 | priority_dict[status] = config.get('autopriority' + status) |
|---|
| 135 | |
|---|
| 136 | def get_nick_from_jid(jid): |
|---|
| 137 | pos = jid.find('@') |
|---|
| 138 | return jid[:pos] |
|---|
| 139 | |
|---|
| 140 | def get_server_from_jid(jid): |
|---|
| 141 | pos = jid.find('@') + 1 # after @ |
|---|
| 142 | return jid[pos:] |
|---|
| 143 | |
|---|
| 144 | def get_nick_from_fjid(jid): |
|---|
| 145 | # fake jid is the jid for a contact in a room |
|---|
| 146 | # gaim@conference.jabber.no/nick/nick-continued |
|---|
| 147 | return jid.split('/', 1)[1] |
|---|
| 148 | |
|---|
| 149 | def get_name_and_server_from_jid(jid): |
|---|
| 150 | name = get_nick_from_jid(jid) |
|---|
| 151 | server = get_server_from_jid(jid) |
|---|
| 152 | return name, server |
|---|
| 153 | |
|---|
| 154 | def get_room_and_nick_from_fjid(jid): |
|---|
| 155 | # fake jid is the jid for a contact in a room |
|---|
| 156 | # gaim@conference.jabber.no/nick/nick-continued |
|---|
| 157 | # return ('gaim@conference.jabber.no', 'nick/nick-continued') |
|---|
| 158 | l = jid.split('/', 1) |
|---|
| 159 | if len(l) == 1: # No nick |
|---|
| 160 | l.append('') |
|---|
| 161 | return l |
|---|
| 162 | |
|---|
| 163 | def get_real_jid_from_fjid(account, fjid): |
|---|
| 164 | '''returns real jid or returns None |
|---|
| 165 | if we don't know the real jid''' |
|---|
| 166 | room_jid, nick = get_room_and_nick_from_fjid(fjid) |
|---|
| 167 | if not nick: # It's not a fake_jid, it is a real jid |
|---|
| 168 | return fjid # we return the real jid |
|---|
| 169 | real_jid = fjid |
|---|
| 170 | if interface.msg_win_mgr.get_control(room_jid, account): |
|---|
| 171 | # It's a pm, so if we have real jid it's in contact.jid |
|---|
| 172 | gc_contact = contacts.get_gc_contact(account, room_jid, nick) |
|---|
| 173 | if not gc_contact: |
|---|
| 174 | return |
|---|
| 175 | # gc_contact.jid is None when it's not a real jid (we don't know real jid) |
|---|
| 176 | real_jid = gc_contact.jid |
|---|
| 177 | return real_jid |
|---|
| 178 | |
|---|
| 179 | def get_room_from_fjid(jid): |
|---|
| 180 | return get_room_and_nick_from_fjid(jid)[0] |
|---|
| 181 | |
|---|
| 182 | def get_contact_name_from_jid(account, jid): |
|---|
| 183 | c = contacts.get_first_contact_from_jid(account, jid) |
|---|
| 184 | return c.name |
|---|
| 185 | |
|---|
| 186 | def get_jid_without_resource(jid): |
|---|
| 187 | return jid.split('/')[0] |
|---|
| 188 | |
|---|
| 189 | def construct_fjid(room_jid, nick): |
|---|
| 190 | ''' nick is in utf8 (taken from treeview); room_jid is in unicode''' |
|---|
| 191 | # fake jid is the jid for a contact in a room |
|---|
| 192 | # gaim@conference.jabber.org/nick |
|---|
| 193 | if isinstance(nick, str): |
|---|
| 194 | nick = unicode(nick, 'utf-8') |
|---|
| 195 | return room_jid + '/' + nick |
|---|
| 196 | |
|---|
| 197 | def get_resource_from_jid(jid): |
|---|
| 198 | jids = jid.split('/', 1) |
|---|
| 199 | if len(jids) > 1: |
|---|
| 200 | return jids[1] # abc@doremi.org/res/res-continued |
|---|
| 201 | else: |
|---|
| 202 | return '' |
|---|
| 203 | '''\ |
|---|
| 204 | [15:34:28] <asterix> we should add contact.fake_jid I think |
|---|
| 205 | [15:34:46] <asterix> so if we know real jid, it wil be in contact.jid, or we look in contact.fake_jid |
|---|
| 206 | [15:32:54] <asterix> they can have resource if we know the real jid |
|---|
| 207 | [15:33:07] <asterix> and that resource is in contact.resource |
|---|
| 208 | ''' |
|---|
| 209 | |
|---|
| 210 | def get_number_of_accounts(): |
|---|
| 211 | '''returns the number of ALL accounts''' |
|---|
| 212 | return len(connections.keys()) |
|---|
| 213 | |
|---|
| 214 | def get_number_of_connected_accounts(accounts_list = None): |
|---|
| 215 | '''returns the number of CONNECTED accounts |
|---|
| 216 | you can optionally pass an accounts_list |
|---|
| 217 | and if you do those will be checked, else all will be checked''' |
|---|
| 218 | connected_accounts = 0 |
|---|
| 219 | if accounts_list is None: |
|---|
| 220 | accounts = connections.keys() |
|---|
| 221 | else: |
|---|
| 222 | accounts = accounts_list |
|---|
| 223 | for account in accounts: |
|---|
| 224 | if account_is_connected(account): |
|---|
| 225 | connected_accounts = connected_accounts + 1 |
|---|
| 226 | return connected_accounts |
|---|
| 227 | |
|---|
| 228 | def account_is_connected(account): |
|---|
| 229 | if account not in connections: |
|---|
| 230 | return False |
|---|
| 231 | if connections[account].connected > 1: # 0 is offline, 1 is connecting |
|---|
| 232 | return True |
|---|
| 233 | else: |
|---|
| 234 | return False |
|---|
| 235 | |
|---|
| 236 | def account_is_disconnected(account): |
|---|
| 237 | return not account_is_connected(account) |
|---|
| 238 | |
|---|
| 239 | def get_number_of_securely_connected_accounts(): |
|---|
| 240 | '''returns the number of the accounts that are SSL/TLS connected''' |
|---|
| 241 | num_of_secured = 0 |
|---|
| 242 | for account in connections: |
|---|
| 243 | if account_is_securely_connected(account): |
|---|
| 244 | num_of_secured += 1 |
|---|
| 245 | return num_of_secured |
|---|
| 246 | |
|---|
| 247 | def account_is_securely_connected(account): |
|---|
| 248 | if account in con_types and con_types[account] in ('tls', 'ssl'): |
|---|
| 249 | return True |
|---|
| 250 | else: |
|---|
| 251 | return False |
|---|
| 252 | |
|---|
| 253 | def get_transport_name_from_jid(jid, use_config_setting = True): |
|---|
| 254 | '''returns 'aim', 'gg', 'irc' etc |
|---|
| 255 | if JID is not from transport returns None''' |
|---|
| 256 | #FIXME: jid can be None! one TB I saw had this problem: |
|---|
| 257 | # in the code block # it is a groupchat presence in handle_event_notify |
|---|
| 258 | # jid was None. Yann why? |
|---|
| 259 | if not jid or (use_config_setting and not config.get('use_transports_iconsets')): |
|---|
| 260 | return |
|---|
| 261 | |
|---|
| 262 | host = get_server_from_jid(jid) |
|---|
| 263 | if host in transport_type: |
|---|
| 264 | return transport_type[host] |
|---|
| 265 | |
|---|
| 266 | # host is now f.e. icq.foo.org or just icq (sometimes on hacky transports) |
|---|
| 267 | host_splitted = host.split('.') |
|---|
| 268 | if len(host_splitted) != 0: |
|---|
| 269 | # now we support both 'icq.' and 'icq' but not icqsucks.org |
|---|
| 270 | host = host_splitted[0] |
|---|
| 271 | |
|---|
| 272 | if host in ('aim', 'irc', 'icq', 'msn', 'sms', 'tlen', 'weather', 'yahoo'): |
|---|
| 273 | return host |
|---|
| 274 | elif host == 'gg': |
|---|
| 275 | return 'gadu-gadu' |
|---|
| 276 | elif host == 'jit': |
|---|
| 277 | return 'icq' |
|---|
| 278 | else: |
|---|
| 279 | return None |
|---|
| 280 | |
|---|
| 281 | def jid_is_transport(jid): |
|---|
| 282 | # if not '@' or '@' starts the jid then it is transport |
|---|
| 283 | if jid.find('@') <= 0: |
|---|
| 284 | return True |
|---|
| 285 | return False |
|---|
| 286 | |
|---|
| 287 | def get_jid_from_account(account_name): |
|---|
| 288 | '''return the jid we use in the given account''' |
|---|
| 289 | name = config.get_per('accounts', account_name, 'name') |
|---|
| 290 | hostname = config.get_per('accounts', account_name, 'hostname') |
|---|
| 291 | jid = name + '@' + hostname |
|---|
| 292 | return jid |
|---|
| 293 | |
|---|
| 294 | def get_our_jids(): |
|---|
| 295 | '''returns a list of the jids we use in our accounts''' |
|---|
| 296 | our_jids = list() |
|---|
| 297 | for account in contacts.get_accounts(): |
|---|
| 298 | our_jids.append(get_jid_from_account(account)) |
|---|
| 299 | return our_jids |
|---|
| 300 | |
|---|
| 301 | def get_hostname_from_account(account_name, use_srv = False): |
|---|
| 302 | '''returns hostname (if custom hostname is used, that is returned)''' |
|---|
| 303 | if use_srv and connections[account_name].connected_hostname: |
|---|
| 304 | return connections[account_name].connected_hostname |
|---|
| 305 | if config.get_per('accounts', account_name, 'use_custom_host'): |
|---|
| 306 | return config.get_per('accounts', account_name, 'custom_host') |
|---|
| 307 | return config.get_per('accounts', account_name, 'hostname') |
|---|
| 308 | |
|---|
| 309 | def get_notification_image_prefix(jid): |
|---|
| 310 | '''returns the prefix for the notification images''' |
|---|
| 311 | transport_name = get_transport_name_from_jid(jid) |
|---|
| 312 | if transport_name in ('aim', 'icq', 'msn', 'yahoo'): |
|---|
| 313 | prefix = transport_name |
|---|
| 314 | else: |
|---|
| 315 | prefix = 'jabber' |
|---|
| 316 | return prefix |
|---|
| 317 | |
|---|
| 318 | def get_name_from_jid(account, jid): |
|---|
| 319 | '''returns from JID's shown name and if no contact returns jids''' |
|---|
| 320 | contact = contacts.get_first_contact_from_jid(account, jid) |
|---|
| 321 | if contact: |
|---|
| 322 | actor = contact.get_shown_name() |
|---|
| 323 | else: |
|---|
| 324 | actor = jid |
|---|
| 325 | return actor |
|---|
| 326 | |
|---|
| 327 | def get_priority(account, show): |
|---|
| 328 | '''return the priority an account must have''' |
|---|
| 329 | if not show: |
|---|
| 330 | show = 'online' |
|---|
| 331 | |
|---|
| 332 | if show in ('online', 'chat', 'away', 'xa', 'dnd', 'invisible') and \ |
|---|
| 333 | config.get_per('accounts', account, 'adjust_priority_with_status'): |
|---|
| 334 | return config.get_per('accounts', account, 'autopriority_' + show) |
|---|
| 335 | return config.get_per('accounts', account, 'priority') |
|---|