class Main: STATUS_JOINING = 'joining' AUCTION_RESOURCE = 'Auction' ITEM_ID_AS_LOGIN = '******' AUCTION_ID_FORMAT = ITEM_ID_AS_LOGIN + '@{}' def __init__(self): self.client = None self.chatManager = None self.chat = None def main(self, xmpp_hostname, sniper_id, sniper_password, item_id): self.connectTo(xmpp_hostname, sniper_id, sniper_password, item_id) main_window = MainWindow() return main_window def connectTo(self, xmpp_hostname, username, password, item_id): self.createClient(xmpp_hostname, username, password) #self.client.connect() #self.client.send(nbxmpp.Presence()) self.chatManager = ChatManager(self.client) self.chat = self.chatManager.create_chat( self.auctionId(item_id, xmpp_hostname)) def auctionId(self, itemId, xmpp_hostname): return self.AUCTION_ID_FORMAT.format(itemId, xmpp_hostname) def createClient(self, xmpp_hosntame, username, password): self.client = Client() self.client.set_domain(xmpp_hosntame) self.client.set_username(username) self.client.set_password(password) self.client.set_resource(self.AUCTION_RESOURCE) self.client.set_ignore_tls_errors(True) self.client.subscribe('connection-failed', self._on_signal) def _on_signal(self, _client, signal_name, *args, **kwargs): print('%s, Error: %s', signal_name, self.client.get_error())
class Client(ConnectionHandlers): def __init__(self, account): self._client = None self._account = account self.name = account self._hostname = app.settings.get_account_setting( self._account, 'hostname') self._user = app.settings.get_account_setting(self._account, 'name') self.password = None self._priority = 0 self._connect_machine_calls = 0 self.addressing_supported = False self.is_zeroconf = False self.pep = {} self.roster_supported = True self._state = ClientState.DISCONNECTED self._status_sync_on_resume = False self._status = 'online' self._status_message = '' self._idle_status = 'online' self._idle_status_enabled = True self._idle_status_message = '' self._reconnect = True self._reconnect_timer_source = None self._destroy_client = False self._remove_account = False self._destroyed = False self.available_transports = {} modules.register_modules(self) self._create_client() if Monitor.is_available(): self._idle_handler_id = Monitor.connect('state-changed', self._idle_state_changed) self._screensaver_handler_id = app.app.connect( 'notify::screensaver-active', self._screensaver_state_changed) ConnectionHandlers.__init__(self) def _set_state(self, state): log.info('State: %s', state) self._state = state @property def state(self): return self._state @property def account(self): return self._account @property def status(self): return self._status @property def status_message(self): if self._idle_status_active(): return self._idle_status_message return self._status_message @property def priority(self): return self._priority @property def certificate(self): return self._client.peer_certificate[0] @property def features(self): return self._client.features @property def local_address(self): address = self._client.local_address if address is not None: return address.to_string().split(':')[0] return None def set_remove_account(self, value): # Used by the RemoveAccount Assistant to make the Client # not react to any stream errors that happen while the # account is removed by the server and the connection is killed self._remove_account = value def _create_client(self): if self._destroyed: # If we disable an account cleanup() is called and all # modules are unregistered. Because disable_account() does not wait # for the client to properly disconnect, handlers of the # nbxmpp.Client() are emitted after we called cleanup(). # After nbxmpp.Client() disconnects and is destroyed we create a # new instance with this method but modules.get_handlers() fails # because modules are already unregistered. # TODO: Make this nicer return log.info('Create new nbxmpp client') self._client = NBXMPPClient(log_context=self._account) self.connection = self._client self._client.set_domain(self._hostname) self._client.set_username(self._user) self._client.set_resource(get_resource(self._account)) pass_saved = app.settings.get_account_setting(self._account, 'savepass') if pass_saved: # Request password from keyring only if the user chose to save # his password self.password = passwords.get_password(self._account) self._client.set_password(self.password) self._client.set_accepted_certificates( app.cert_store.get_certificates()) self._client.subscribe('resume-failed', self._on_resume_failed) self._client.subscribe('resume-successful', self._on_resume_successful) self._client.subscribe('disconnected', self._on_disconnected) self._client.subscribe('connection-failed', self._on_connection_failed) self._client.subscribe('connected', self._on_connected) self._client.subscribe('stanza-sent', self._on_stanza_sent) self._client.subscribe('stanza-received', self._on_stanza_received) for handler in modules.get_handlers(self): self._client.register_handler(handler) def _on_resume_failed(self, _client, _signal_name): log.info('Resume failed') app.nec.push_incoming_event( NetworkEvent('our-show', account=self._account, show='offline')) self.get_module('Chatstate').enabled = False def _on_resume_successful(self, _client, _signal_name): self._set_state(ClientState.CONNECTED) self._set_client_available() if self._status_sync_on_resume: self._status_sync_on_resume = False self.update_presence() else: # Normally show is updated when we receive a presence reflection. # On resume, if show has not changed while offline, we don’t send # a new presence so we have to trigger the event here. app.nec.push_incoming_event( NetworkEvent('our-show', account=self._account, show=self._status)) def _set_client_available(self): self._set_state(ClientState.AVAILABLE) app.nec.push_incoming_event( NetworkEvent('account-connected', account=self._account)) def disconnect(self, gracefully, reconnect, destroy_client=False): if self._state.is_disconnecting: log.warning('Disconnect already in progress') return self._set_state(ClientState.DISCONNECTING) self._reconnect = reconnect self._destroy_client = destroy_client log.info('Starting to disconnect %s', self._account) self._client.disconnect(immediate=not gracefully) def _on_disconnected(self, _client, _signal_name): log.info('Disconnect %s', self._account) self._set_state(ClientState.DISCONNECTED) domain, error, text = self._client.get_error() if self._remove_account: # Account was removed via RemoveAccount Assistant. self._reconnect = False elif domain == StreamError.BAD_CERTIFICATE: self._reconnect = False self._destroy_client = True cert, errors = self._client.peer_certificate open_window('SSLErrorDialog', account=self._account, client=self, cert=cert, error=errors.pop()) elif domain in (StreamError.STREAM, StreamError.BIND): if error == 'conflict': # Reset resource app.settings.set_account_setting(self._account, 'resource', 'gajim.$rand') elif domain == StreamError.SASL: self._reconnect = False self._destroy_client = True if error in ('not-authorized', 'no-password'): def _on_password(password): self.password = password self._client.set_password(password) self._prepare_for_connect() app.nec.push_incoming_event( NetworkEvent('password-required', conn=self, on_password=_on_password)) app.nec.push_incoming_event( NetworkEvent('simple-notification', account=self._account, type_='connection-failed', title=_('Authentication failed'), text=text or error)) if self._reconnect: self._after_disconnect() self._schedule_reconnect() app.nec.push_incoming_event( NetworkEvent('our-show', account=self._account, show='error')) else: self.get_module('Chatstate').enabled = False app.nec.push_incoming_event( NetworkEvent('our-show', account=self._account, show='offline')) self._after_disconnect() def _after_disconnect(self): self._disable_reconnect_timer() self.get_module('VCardAvatars').avatar_advertised = False app.proxy65_manager.disconnect(self._client) self.terminate_sessions() self.get_module('Bytestream').remove_all_transfers() if self._destroy_client: self._client.destroy() self._client = None self._destroy_client = False self._create_client() app.nec.push_incoming_event( NetworkEvent('account-disconnected', account=self._account)) def _on_connection_failed(self, _client, _signal_name): self._schedule_reconnect() def _on_connected(self, _client, _signal_name): self._set_state(ClientState.CONNECTED) self.get_module('MUC').get_manager().reset_state() self.get_module('Discovery').discover_server_info() self.get_module('Discovery').discover_account_info() self.get_module('Discovery').discover_server_items() self.get_module('Chatstate').enabled = True self.get_module('MAM').reset_state() def _on_stanza_sent(self, _client, _signal_name, stanza): app.nec.push_incoming_event( NetworkEvent('stanza-sent', account=self._account, stanza=stanza)) def _on_stanza_received(self, _client, _signal_name, stanza): app.nec.push_incoming_event( NetworkEvent('stanza-received', account=self._account, stanza=stanza)) def get_own_jid(self): """ Return the last full JID we received on a bind event. In case we were never connected it returns the bare JID from config. """ if self._client is not None: jid = self._client.get_bound_jid() if jid is not None: return jid # This returns the bare jid return nbxmpp.JID.from_string(app.get_jid_from_account(self._account)) def change_status(self, show, message): if not message: message = '' self._idle_status_enabled = show == 'online' self._status_message = message if show != 'offline': self._status = show if self._state.is_disconnecting: log.warning('Can\'t change status while ' 'disconnect is in progress') return if self._state.is_disconnected: if show == 'offline': return self._prepare_for_connect() return if self._state.is_connecting: if show == 'offline': self.disconnect(gracefully=False, reconnect=False, destroy_client=True) return if self._state.is_reconnect_scheduled: if show == 'offline': self._destroy_client = True self._abort_reconnect() else: self._prepare_for_connect() return # We are connected if show == 'offline': self.set_user_activity(None) self.set_user_mood(None) self.set_user_tune(None) self.set_user_location(None) presence = self.get_module('Presence').get_presence( typ='unavailable', status=message, caps=False) self.send_stanza(presence) self.disconnect(gracefully=True, reconnect=False, destroy_client=True) return self.update_presence() def update_presence(self, include_muc=True): status, message, idle = self.get_presence_state() self._priority = app.get_priority(self._account, status) self.get_module('Presence').send_presence(priority=self._priority, show=status, status=message, idle_time=idle) if include_muc: self.get_module('MUC').update_presence() def set_user_activity(self, activity): self.get_module('UserActivity').set_activity(activity) def set_user_mood(self, mood): self.get_module('UserMood').set_mood(mood) def set_user_tune(self, tune): self.get_module('UserTune').set_tune(tune) def set_user_location(self, location): self.get_module('UserLocation').set_location(location) def get_module(self, name): return modules.get(self._account, name) @helpers.call_counter def connect_machine(self): log.info('Connect machine state: %s', self._connect_machine_calls) if self._connect_machine_calls == 1: self.get_module('MetaContacts').get_metacontacts() elif self._connect_machine_calls == 2: self.get_module('Delimiter').get_roster_delimiter() elif self._connect_machine_calls == 3: self.get_module('Roster').request_roster() elif self._connect_machine_calls == 4: self._finish_connect() def _finish_connect(self): self._status_sync_on_resume = False self._set_client_available() # We did not resume the stream, so we are not joined any MUCs self.update_presence(include_muc=False) self.get_module('Bookmarks').request_bookmarks() self.get_module('SoftwareVersion').set_enabled(True) self.get_module('LastActivity').set_enabled(True) self.get_module('Annotations').request_annotations() self.get_module('Blocking').get_blocking_list() # Inform GUI we just signed in app.nec.push_incoming_event( NetworkEvent('signed-in', account=self._account, conn=self)) modules.send_stored_publish(self._account) def send_stanza(self, stanza): """ Send a stanza untouched """ return self._client.send_stanza(stanza) def send_message(self, message): if not self._state.is_available: log.warning('Trying to send message while offline') return stanza = self.get_module('Message').build_message_stanza(message) message.stanza = stanza if message.contact is None: # Only Single Message should have no contact self._send_message(message) return method = message.contact.settings.get('encryption') if not method: self._send_message(message) return # TODO: Make extension point return encrypted message extension = 'encrypt' if message.is_groupchat: extension = 'gc_encrypt' app.plugin_manager.extension_point(extension + method, self, message, self._send_message) def _send_message(self, message): message.set_sent_timestamp() message.message_id = self.send_stanza(message.stanza) app.nec.push_incoming_event( MessageSentEvent(None, jid=message.jid, **vars(message))) if message.is_groupchat: return self.get_module('Message').log_message(message) def send_messages(self, jids, message): if not self._state.is_available: log.warning('Trying to send message while offline') return for jid in jids: message = message.copy() message.contact = app.contacts.create_contact(jid, message.account) stanza = self.get_module('Message').build_message_stanza(message) message.stanza = stanza self._send_message(message) def _prepare_for_connect(self): custom_host = get_custom_host(self._account) if custom_host is not None: self._client.set_custom_host(*custom_host) gssapi = app.settings.get_account_setting(self._account, 'enable_gssapi') if gssapi: self._client.set_mechs(['GSSAPI']) anonymous = app.settings.get_account_setting(self._account, 'anonymous_auth') if anonymous: self._client.set_mechs(['ANONYMOUS']) if app.settings.get_account_setting(self._account, 'use_plain_connection'): self._client.set_connection_types([ConnectionType.PLAIN]) proxy = get_user_proxy(self._account) if proxy is not None: self._client.set_proxy(proxy) self.connect() def connect(self, ignored_tls_errors=None): if self._state not in (ClientState.DISCONNECTED, ClientState.RECONNECT_SCHEDULED): # Do not try to reco while we are already trying return log.info('Connect') self._client.set_ignored_tls_errors(ignored_tls_errors) self._reconnect = True self._disable_reconnect_timer() self._set_state(ClientState.CONNECTING) if warn_about_plain_connection(self._account, self._client.connection_types): app.nec.push_incoming_event( NetworkEvent('plain-connection', account=self._account, connect=self._client.connect, abort=self._abort_reconnect)) return self._client.connect() def _schedule_reconnect(self): self._set_state(ClientState.RECONNECT_SCHEDULED) log.info("Reconnect to %s in 3s", self._account) self._reconnect_timer_source = GLib.timeout_add_seconds( 3, self._prepare_for_connect) def _abort_reconnect(self): self._set_state(ClientState.DISCONNECTED) self._disable_reconnect_timer() app.nec.push_incoming_event( NetworkEvent('our-show', account=self._account, show='offline')) if self._destroy_client: self._client.destroy() self._client = None self._destroy_client = False self._create_client() def _disable_reconnect_timer(self): if self._reconnect_timer_source is not None: GLib.source_remove(self._reconnect_timer_source) self._reconnect_timer_source = None def _idle_state_changed(self, monitor): state = monitor.state.value if monitor.is_awake(): self._idle_status = state self._idle_status_message = '' self._update_status() return if not app.settings.get(f'auto{state}'): return if (state in ('away', 'xa') and self._status == 'online' or state == 'xa' and self._idle_status == 'away'): self._idle_status = state self._idle_status_message = get_idle_status_message( state, self._status_message) self._update_status() def _update_status(self): if not self._idle_status_enabled: return self._status = self._idle_status if self._state.is_available: self.update_presence() else: self._status_sync_on_resume = True def _idle_status_active(self): if not Monitor.is_available(): return False if not self._idle_status_enabled: return False return self._idle_status != 'online' def get_presence_state(self): if self._idle_status_active(): return self._idle_status, self._idle_status_message, True return self._status, self._status_message, False @staticmethod def _screensaver_state_changed(application, _param): active = application.get_property('screensaver-active') Monitor.set_extended_away(active) def cleanup(self): self._destroyed = True if Monitor.is_available(): Monitor.disconnect(self._idle_handler_id) app.app.disconnect(self._screensaver_handler_id) if self._client is not None: # cleanup() is called before nbmxpp.Client has disconnected, # when we disable the account. So we need to unregister # handlers here. # TODO: cleanup() should not be called before disconnect is finished for handler in modules.get_handlers(self): self._client.unregister_handler(handler) modules.unregister_modules(self) def quit(self, kill_core): if kill_core and self._state in (ClientState.CONNECTING, ClientState.CONNECTED, ClientState.AVAILABLE): self.disconnect(gracefully=True, reconnect=False)
class TestClient(Gtk.Window): def __init__(self): Gtk.Window.__init__(self, title='Test Client') self.set_default_size(500, 500) self._builder = Builder('client.ui') self._builder.connect_signals(self) self.add(self._builder.grid) self._client = None self._scroll_timeout = None self._create_paths() self._load_config() def _create_client(self): self._client = Client(log_context='TEST') self._client.set_domain(self.address.domain) self._client.set_username(self.address.localpart) self._client.set_resource('test') proxy_ip = self._builder.proxy_ip.get_text() if proxy_ip: proxy_port = int(self._builder.proxy_port.get_text()) proxy_host = '%s:%s' % (proxy_ip, proxy_port) proxy = ProxyData( self._builder.proxy_type.get_active_text().lower(), proxy_host, self._builder.proxy_username.get_text() or None, self._builder.proxy_password.get_text() or None) self._client.set_proxy(proxy) self._client.set_connection_types(self._get_connection_types()) self._client.set_protocols(self._get_connection_protocols()) self._client.set_password(self.password) self._client.subscribe('resume-failed', self._on_signal) self._client.subscribe('resume-successful', self._on_signal) self._client.subscribe('disconnected', self._on_signal) self._client.subscribe('connection-lost', self._on_signal) self._client.subscribe('connection-failed', self._on_signal) self._client.subscribe('connected', self._on_connected) self._client.subscribe('stanza-sent', self._on_stanza_sent) self._client.subscribe('stanza-received', self._on_stanza_received) self._client.register_handler( StanzaHandler('message', self._on_message)) @property def password(self): return self._builder.password.get_text() @property def address(self): return JID.from_string(self._builder.address.get_text()) @property def xml_box(self): return self._builder.xml_box def scroll_to_end(self): adj_v = self._builder.scrolledwin.get_vadjustment() if adj_v is None: # This can happen when the Widget is already destroyed when called # from GLib.idle_add self._scroll_timeout = None return max_scroll_pos = adj_v.get_upper() - adj_v.get_page_size() adj_v.set_value(max_scroll_pos) adj_h = self._builder.scrolledwin.get_hadjustment() adj_h.set_value(0) self._scroll_timeout = None def _on_signal(self, _client, signal_name, *args, **kwargs): log.info('%s, Error: %s', signal_name, self._client.get_error()) if signal_name == 'disconnected': if self._client.get_error() is None: return domain, error, text = self._client.get_error() if domain == StreamError.BAD_CERTIFICATE: self._client.set_ignore_tls_errors(True) self._client.connect() def _on_connected(self, _client, _signal_name): self.send_presence() def _on_message(self, _stream, stanza, _properties): log.info('Message received') log.info(stanza.getBody()) def _on_stanza_sent(self, _stream, _signal_name, data): self.xml_box.add(StanzaRow(data, False)) self._add_scroll_timeout() def _on_stanza_received(self, _stream, _signal_name, data): self.xml_box.add(StanzaRow(data, True)) self._add_scroll_timeout() def _add_scroll_timeout(self): if self._scroll_timeout is not None: return self._scroll_timeout = GLib.timeout_add(50, self.scroll_to_end) def _connect_clicked(self, *args): if self._client is None: self._create_client() self._client.connect() def _disconnect_clicked(self, *args): if self._client is not None: self._client.disconnect() def _clear_clicked(self, *args): self.xml_box.foreach(self._remove) def _on_reconnect_clicked(self, *args): if self._client is not None: self._client.reconnect() def _get_connection_types(self): types = [] if self._builder.directtls.get_active(): types.append(ConnectionType.DIRECT_TLS) if self._builder.starttls.get_active(): types.append(ConnectionType.START_TLS) if self._builder.plain.get_active(): types.append(ConnectionType.PLAIN) return types def _get_connection_protocols(self): protocols = [] if self._builder.tcp.get_active(): protocols.append(ConnectionProtocol.TCP) if self._builder.websocket.get_active(): protocols.append(ConnectionProtocol.WEBSOCKET) return protocols def _on_save_clicked(self, *args): data = {} data['jid'] = self._builder.address.get_text() data['password'] = self._builder.password.get_text() data['proxy_type'] = self._builder.proxy_type.get_active_text() data['proxy_ip'] = self._builder.proxy_ip.get_text() data['proxy_port'] = self._builder.proxy_port.get_text() data['proxy_username'] = self._builder.proxy_username.get_text() data['proxy_password'] = self._builder.proxy_password.get_text() data['directtls'] = self._builder.directtls.get_active() data['starttls'] = self._builder.starttls.get_active() data['plain'] = self._builder.plain.get_active() data['tcp'] = self._builder.tcp.get_active() data['websocket'] = self._builder.websocket.get_active() path = self._get_config_dir() / 'config' with path.open('w') as fp: json.dump(data, fp) def _load_config(self): path = self._get_config_dir() / 'config' if not path.exists(): return with path.open('r') as fp: data = json.load(fp) self._builder.address.set_text(data.get('jid', '')) self._builder.password.set_text(data.get('password', '')) self._builder.proxy_type.set_active_id(data.get('proxy_type', 'HTTP')) self._builder.proxy_ip.set_text(data.get('proxy_ip', '')) self._builder.proxy_port.set_text(data.get('proxy_port', '')) self._builder.proxy_username.set_text(data.get('proxy_username', '')) self._builder.proxy_password.set_text(data.get('proxy_password', '')) self._builder.directtls.set_active(data.get('directtls', False)) self._builder.starttls.set_active(data.get('starttls', False)) self._builder.plain.set_active(data.get('plain', False)) self._builder.tcp.set_active(data.get('tcp', False)) self._builder.websocket.set_active(data.get('websocket', False)) @staticmethod def _get_config_dir(): if os.name == 'nt': return Path(os.path.join(os.environ['appdata'], 'nbxmpp')) expand = os.path.expanduser base = os.getenv('XDG_CONFIG_HOME') if base is None or base[0] != '/': base = expand('~/.config') return Path(os.path.join(base, 'nbxmpp')) def _create_paths(self): path_ = self._get_config_dir() if not path_.exists(): for parent_path in reversed(path_.parents): # Create all parent folders # don't use mkdir(parent=True), as it ignores `mode` # when creating the parents if not parent_path.exists(): print('creating %s directory' % parent_path) parent_path.mkdir(mode=0o700) print('creating %s directory' % path_) path_.mkdir(mode=0o700) def _remove(self, item): self.xml_box.remove(item) item.destroy() def send_presence(self): presence = nbxmpp.Presence() self._client.send_stanza(presence)