historically, Libervia (SàT before) was using camelCase as allowed by PEP8 when using a pre-PEP8 code, to use the same coding style as in Twisted. However, snake_case is more readable and it's better to follow PEP8 best practices, so it has been decided to move on full snake_case. Because Libervia has a huge codebase, this ended with a ugly mix of camelCase and snake_case. To fix that, this patch does a big refactoring by renaming every function and method (including bridge) that are not coming from Twisted or Wokkel, to use fully snake_case. This is a massive change, and may result in some bugs.
308 lines
11 KiB
Python
308 lines
11 KiB
Python
#!/usr/bin/env python3
|
|
|
|
|
|
# SAT standard user interface for managing contacts
|
|
# Copyright (C) 2009-2021 Jérôme Poisson (goffi@goffi.org)
|
|
|
|
# This program is free software: you can redistribute it and/or modify
|
|
# it under the terms of the GNU Affero General Public License as published by
|
|
# the Free Software Foundation, either version 3 of the License, or
|
|
# (at your option) any later version.
|
|
|
|
# This program is distributed in the hope that it will be useful,
|
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
# GNU Affero General Public License for more details.
|
|
|
|
# You should have received a copy of the GNU Affero General Public License
|
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
from sat.core.i18n import _, D_
|
|
from sat.core.constants import Const as C
|
|
from sat.tools import xml_tools
|
|
from twisted.words.protocols.jabber import jid
|
|
from xml.dom.minidom import Element
|
|
|
|
|
|
class ContactList(object):
|
|
"""Add, update and remove contacts."""
|
|
|
|
def __init__(self, host):
|
|
self.host = host
|
|
self.__add_id = host.register_callback(self._add_contact, with_data=True)
|
|
self.__update_id = host.register_callback(self._update_contact, with_data=True)
|
|
self.__confirm_delete_id = host.register_callback(
|
|
self._get_confirm_remove_xmlui, with_data=True
|
|
)
|
|
|
|
host.import_menu(
|
|
(D_("Contacts"), D_("Add contact")),
|
|
self._get_add_dialog_xmlui,
|
|
security_limit=2,
|
|
help_string=D_("Add contact"),
|
|
)
|
|
host.import_menu(
|
|
(D_("Contacts"), D_("Update contact")),
|
|
self._get_update_dialog_xmlui,
|
|
security_limit=2,
|
|
help_string=D_("Update contact"),
|
|
)
|
|
host.import_menu(
|
|
(D_("Contacts"), D_("Remove contact")),
|
|
self._get_remove_dialog_xmlui,
|
|
security_limit=2,
|
|
help_string=D_("Remove contact"),
|
|
)
|
|
|
|
# FIXME: a plugin should not be used here, and current profile's jid host would be better than installation wise host
|
|
if "MISC-ACCOUNT" in self.host.plugins:
|
|
self.default_host = self.host.plugins["MISC-ACCOUNT"].account_domain_new_get()
|
|
else:
|
|
self.default_host = "example.net"
|
|
|
|
def contacts_get(self, profile):
|
|
"""Return a sorted list of the contacts for that profile
|
|
|
|
@param profile: %(doc_profile)s
|
|
@return: list[string]
|
|
"""
|
|
client = self.host.get_client(profile)
|
|
ret = [contact.full() for contact in client.roster.get_jids()]
|
|
ret.sort()
|
|
return ret
|
|
|
|
def get_groups(self, new_groups=None, profile=C.PROF_KEY_NONE):
|
|
"""Return a sorted list of the groups for that profile
|
|
|
|
@param new_group (list): add these groups to the existing ones
|
|
@param profile: %(doc_profile)s
|
|
@return: list[string]
|
|
"""
|
|
client = self.host.get_client(profile)
|
|
ret = client.roster.get_groups()
|
|
ret.sort()
|
|
ret.extend([group for group in new_groups if group not in ret])
|
|
return ret
|
|
|
|
def get_groups_of_contact(self, user_jid_s, profile):
|
|
"""Return all the groups of the given contact
|
|
|
|
@param user_jid_s (string)
|
|
@param profile: %(doc_profile)s
|
|
@return: list[string]
|
|
"""
|
|
client = self.host.get_client(profile)
|
|
return client.roster.get_item(jid.JID(user_jid_s)).groups
|
|
|
|
def get_groups_of_all_contacts(self, profile):
|
|
"""Return a mapping between the contacts and their groups
|
|
|
|
@param profile: %(doc_profile)s
|
|
@return: dict (key: string, value: list[string]):
|
|
- key: the JID userhost
|
|
- value: list of groups
|
|
"""
|
|
client = self.host.get_client(profile)
|
|
return {item.jid.userhost(): item.groups for item in client.roster.get_items()}
|
|
|
|
def _data2elts(self, data):
|
|
"""Convert a contacts data dict to minidom Elements
|
|
|
|
@param data (dict)
|
|
@return list[Element]
|
|
"""
|
|
elts = []
|
|
for key in data:
|
|
key_elt = Element("jid")
|
|
key_elt.setAttribute("name", key)
|
|
for value in data[key]:
|
|
value_elt = Element("group")
|
|
value_elt.setAttribute("name", value)
|
|
key_elt.childNodes.append(value_elt)
|
|
elts.append(key_elt)
|
|
return elts
|
|
|
|
def get_dialog_xmlui(self, options, data, profile):
|
|
"""Generic method to return the XMLUI dialog for adding or updating a contact
|
|
|
|
@param options (dict): parameters for the dialog, with the keys:
|
|
- 'id': the menu callback id
|
|
- 'title': deferred localized string
|
|
- 'contact_text': deferred localized string
|
|
@param data (dict)
|
|
@param profile: %(doc_profile)s
|
|
@return dict
|
|
"""
|
|
form_ui = xml_tools.XMLUI("form", title=options["title"], submit_id=options["id"])
|
|
if "message" in data:
|
|
form_ui.addText(data["message"])
|
|
form_ui.addDivider("dash")
|
|
|
|
form_ui.addText(options["contact_text"])
|
|
if options["id"] == self.__add_id:
|
|
contact = data.get(
|
|
xml_tools.form_escape("contact_jid"), "@%s" % self.default_host
|
|
)
|
|
form_ui.addString("contact_jid", value=contact)
|
|
elif options["id"] == self.__update_id:
|
|
contacts = self.contacts_get(profile)
|
|
list_ = form_ui.addList("contact_jid", options=contacts, selected=contacts[0])
|
|
elts = self._data2elts(self.get_groups_of_all_contacts(profile))
|
|
list_.set_internal_callback(
|
|
"groups_of_contact", fields=["contact_jid", "groups_list"], data_elts=elts
|
|
)
|
|
|
|
form_ui.addDivider("blank")
|
|
|
|
form_ui.addText(_("Select in which groups your contact is:"))
|
|
selected_groups = []
|
|
if "selected_groups" in data:
|
|
selected_groups = data["selected_groups"]
|
|
elif options["id"] == self.__update_id:
|
|
try:
|
|
selected_groups = self.get_groups_of_contact(contacts[0], profile)
|
|
except IndexError:
|
|
pass
|
|
groups = self.get_groups(selected_groups, profile)
|
|
form_ui.addList(
|
|
"groups_list", options=groups, selected=selected_groups, styles=["multi"]
|
|
)
|
|
|
|
adv_list = form_ui.change_container("advanced_list", columns=3, selectable="no")
|
|
form_ui.addLabel(D_("Add group"))
|
|
form_ui.addString("add_group")
|
|
button = form_ui.addButton("", value=D_("Add"))
|
|
button.set_internal_callback("move", fields=["add_group", "groups_list"])
|
|
adv_list.end()
|
|
|
|
form_ui.addDivider("blank")
|
|
return {"xmlui": form_ui.toXml()}
|
|
|
|
def _get_add_dialog_xmlui(self, data, profile):
|
|
"""Get the dialog for adding contact
|
|
|
|
@param data (dict)
|
|
@param profile: %(doc_profile)s
|
|
@return dict
|
|
"""
|
|
options = {
|
|
"id": self.__add_id,
|
|
"title": D_("Add contact"),
|
|
"contact_text": D_("New contact identifier (JID):"),
|
|
}
|
|
return self.get_dialog_xmlui(options, {}, profile)
|
|
|
|
def _get_update_dialog_xmlui(self, data, profile):
|
|
"""Get the dialog for updating contact
|
|
|
|
@param data (dict)
|
|
@param profile: %(doc_profile)s
|
|
@return dict
|
|
"""
|
|
if not self.contacts_get(profile):
|
|
_dialog = xml_tools.XMLUI("popup", title=D_("Nothing to update"))
|
|
_dialog.addText(_("Your contact list is empty."))
|
|
return {"xmlui": _dialog.toXml()}
|
|
|
|
options = {
|
|
"id": self.__update_id,
|
|
"title": D_("Update contact"),
|
|
"contact_text": D_("Which contact do you want to update?"),
|
|
}
|
|
return self.get_dialog_xmlui(options, {}, profile)
|
|
|
|
def _get_remove_dialog_xmlui(self, data, profile):
|
|
"""Get the dialog for removing contact
|
|
|
|
@param data (dict)
|
|
@param profile: %(doc_profile)s
|
|
@return dict
|
|
"""
|
|
if not self.contacts_get(profile):
|
|
_dialog = xml_tools.XMLUI("popup", title=D_("Nothing to delete"))
|
|
_dialog.addText(_("Your contact list is empty."))
|
|
return {"xmlui": _dialog.toXml()}
|
|
|
|
form_ui = xml_tools.XMLUI(
|
|
"form",
|
|
title=D_("Who do you want to remove from your contacts?"),
|
|
submit_id=self.__confirm_delete_id,
|
|
)
|
|
form_ui.addList("contact_jid", options=self.contacts_get(profile))
|
|
return {"xmlui": form_ui.toXml()}
|
|
|
|
def _get_confirm_remove_xmlui(self, data, profile):
|
|
"""Get the confirmation dialog for removing contact
|
|
|
|
@param data (dict)
|
|
@param profile: %(doc_profile)s
|
|
@return dict
|
|
"""
|
|
if C.bool(data.get("cancelled", "false")):
|
|
return {}
|
|
contact = data[xml_tools.form_escape("contact_jid")]
|
|
|
|
def delete_cb(data, profile):
|
|
if not C.bool(data.get("cancelled", "false")):
|
|
self._delete_contact(jid.JID(contact), profile)
|
|
return {}
|
|
|
|
delete_id = self.host.register_callback(delete_cb, with_data=True, one_shot=True)
|
|
form_ui = xml_tools.XMLUI("form", title=D_("Delete contact"), submit_id=delete_id)
|
|
form_ui.addText(
|
|
D_("Are you sure you want to remove %s from your contact list?") % contact
|
|
)
|
|
return {"xmlui": form_ui.toXml()}
|
|
|
|
def _add_contact(self, data, profile):
|
|
"""Add the selected contact
|
|
|
|
@param data (dict)
|
|
@param profile: %(doc_profile)s
|
|
@return dict
|
|
"""
|
|
if C.bool(data.get("cancelled", "false")):
|
|
return {}
|
|
contact_jid_s = data[xml_tools.form_escape("contact_jid")]
|
|
try:
|
|
contact_jid = jid.JID(contact_jid_s)
|
|
except (RuntimeError, jid.InvalidFormat, AttributeError):
|
|
# TODO: replace '\t' by a constant (see tools.xmlui.XMLUI.on_form_submitted)
|
|
data["selected_groups"] = data[xml_tools.form_escape("groups_list")].split(
|
|
"\t"
|
|
)
|
|
options = {
|
|
"id": self.__add_id,
|
|
"title": D_("Add contact"),
|
|
"contact_text": D_('Please enter a valid JID (like "contact@%s"):')
|
|
% self.default_host,
|
|
}
|
|
return self.get_dialog_xmlui(options, data, profile)
|
|
self.host.contact_add(contact_jid, profile_key=profile)
|
|
return self._update_contact(data, profile) # after adding, updating
|
|
|
|
def _update_contact(self, data, profile):
|
|
"""Update the selected contact
|
|
|
|
@param data (dict)
|
|
@param profile: %(doc_profile)s
|
|
@return dict
|
|
"""
|
|
client = self.host.get_client(profile)
|
|
if C.bool(data.get("cancelled", "false")):
|
|
return {}
|
|
contact_jid = jid.JID(data[xml_tools.form_escape("contact_jid")])
|
|
# TODO: replace '\t' by a constant (see tools.xmlui.XMLUI.on_form_submitted)
|
|
groups = data[xml_tools.form_escape("groups_list")].split("\t")
|
|
self.host.contact_update(client, contact_jid, name="", groups=groups)
|
|
return {}
|
|
|
|
def _delete_contact(self, contact_jid, profile):
|
|
"""Delete the selected contact
|
|
|
|
@param contact_jid (JID)
|
|
@param profile: %(doc_profile)s
|
|
@return dict
|
|
"""
|
|
self.host.contact_del(contact_jid, profile_key=profile)
|
|
return {}
|