contact.py 5.88 KB
Newer Older
louiz’'s avatar
louiz’ committed
1
# Copyright 2010-2011 Florent Le Coz <louiz@louiz.org>
2 3 4 5
#
# This file is part of Poezio.
#
# Poezio is free software: you can redistribute it and/or modify
6
# it under the terms of the zlib license. See the COPYING file.
7
"""
8
Defines the Resource and Contact classes, which are used in
mathieui's avatar
mathieui committed
9
the roster.
10
"""
11

mathieui's avatar
mathieui committed
12
from collections import defaultdict
13
import logging
14 15 16 17 18 19 20 21
from typing import (
    Any,
    Dict,
    Iterator,
    List,
    Optional,
    Union,
)
22

Maxime Buquet's avatar
Maxime Buquet committed
23
from slixmpp import InvalidJID, JID
mathieui's avatar
mathieui committed
24 25

log = logging.getLogger(__name__)
26

mathieui's avatar
mathieui committed
27

28
class Resource:
29
    """
30 31
    Defines a roster item.
    It's a precise resource.
32
    """
mathieui's avatar
mathieui committed
33

mathieui's avatar
mathieui committed
34
    def __init__(self, jid, data):
mathieui's avatar
mathieui committed
35 36 37
        """
        data: the dict to use as a source
        """
mathieui's avatar
mathieui committed
38
        # Full JID
39 40
        self._jid: str = jid
        self._data: Dict[str, Union[str, int]] = data
41

42
    @property
mathieui's avatar
mathieui committed
43
    def jid(self) -> str:
44
        return self._jid
45

46
    @property
mathieui's avatar
mathieui committed
47
    def priority(self) -> int:
48
        return self._data.get('priority') or 0
49

50
    @property
mathieui's avatar
mathieui committed
51
    def presence(self) -> str:
52
        return self._data.get('show') or ''
53 54

    @property
mathieui's avatar
mathieui committed
55
    def status(self) -> str:
56
        return self._data.get('status') or ''
57

mathieui's avatar
mathieui committed
58
    def __repr__(self) -> str:
mathieui's avatar
mathieui committed
59
        return '<%s>' % self._jid
60

mathieui's avatar
mathieui committed
61
    def __eq__(self, value: object) -> bool:
62 63 64 65
        if not isinstance(value, Resource):
            return False
        return self.jid == value.jid and self._data == value._data

mathieui's avatar
mathieui committed
66

67
class Contact:
68 69
    """
    This a way to gather multiple resources from the same bare JID.
70
    This class contains zero or more Resource object and useful methods
71 72
    to get the resource with the highest priority, etc
    """
mathieui's avatar
mathieui committed
73

mathieui's avatar
mathieui committed
74 75
    def __init__(self, item):
        """
louiz’'s avatar
louiz’ committed
76
        item: a slixmpp RosterItem pointing to that contact
mathieui's avatar
mathieui committed
77 78
        """
        self.__item = item
79
        self.folded_states: Dict[str, bool] = defaultdict(lambda: True)
mathieui's avatar
mathieui committed
80
        self._name = ''
81
        self.avatar = None
mathieui's avatar
mathieui committed
82
        self.error = None
83
        self.rich_presence: Dict[str, Any] = defaultdict(lambda: None)
84

85
    @property
mathieui's avatar
mathieui committed
86
    def groups(self) -> List[str]:
mathieui's avatar
mathieui committed
87 88
        """Name of the groups the contact is in"""
        return self.__item['groups'] or ['none']
89

90
    @property
mathieui's avatar
mathieui committed
91
    def bare_jid(self) -> JID:
mathieui's avatar
mathieui committed
92
        """The bare jid of the contact"""
mathieui's avatar
mathieui committed
93
        return self.__item.jid
94

mathieui's avatar
mathieui committed
95 96
    @property
    def name(self):
mathieui's avatar
mathieui committed
97
        """The name of the contact or an empty string."""
mathieui's avatar
mathieui committed
98 99 100 101 102 103
        return self.__item['name'] or self._name or ''

    @name.setter
    def name(self, value):
        """Set the name of the contact with user nickname"""
        self._name = value
104

mathieui's avatar
mathieui committed
105 106 107 108
    @property
    def ask(self):
        if self.__item['pending_out']:
            return 'asked'
109

mathieui's avatar
mathieui committed
110 111
    @property
    def pending_in(self):
mathieui's avatar
mathieui committed
112
        """We received a subscribe stanza from this contact."""
mathieui's avatar
mathieui committed
113
        return self.__item['pending_in']
114

mathieui's avatar
mathieui committed
115 116 117
    @pending_in.setter
    def pending_in(self, value):
        self.__item['pending_in'] = value
118

119
    @property
mathieui's avatar
mathieui committed
120
    def pending_out(self):
mathieui's avatar
mathieui committed
121
        """We sent a subscribe stanza to this contact."""
mathieui's avatar
mathieui committed
122
        return self.__item['pending_out']
123

mathieui's avatar
mathieui committed
124 125 126
    @pending_out.setter
    def pending_out(self, value):
        self.__item['pending_out'] = value
127

128
    @property
mathieui's avatar
mathieui committed
129
    def resources(self) -> Iterator[Resource]:
mathieui's avatar
mathieui committed
130
        """List of the available resources as Resource objects"""
mathieui's avatar
mathieui committed
131 132 133
        return (Resource('%s%s' % (self.bare_jid, ('/' + key)
                                   if key else ''), self.__item.resources[key])
                for key in self.__item.resources.keys())
134

135
    @property
mathieui's avatar
mathieui committed
136
    def subscription(self) -> str:
mathieui's avatar
mathieui committed
137
        return self.__item['subscription']
138

mathieui's avatar
mathieui committed
139
    def __contains__(self, value):
Maxime Buquet's avatar
Maxime Buquet committed
140 141 142 143 144 145
        try:
            resource = JID(value).resource
        except InvalidJID:
            resource = None
        return value in self.__item.resources or \
            (resource is not None and resource in self.__item.resources)
146

mathieui's avatar
mathieui committed
147
    def __len__(self) -> int:
mathieui's avatar
mathieui committed
148 149 150
        """Number of resources"""
        return len(self.__item.resources)

mathieui's avatar
mathieui committed
151 152
    def __bool__(self) -> bool:
        """This contact exists even when he has no resources"""
mathieui's avatar
mathieui committed
153 154
        return True

mathieui's avatar
mathieui committed
155
    def __getitem__(self, key) -> Optional[Resource]:
mathieui's avatar
mathieui committed
156
        """Return the corresponding Resource object, or None"""
Maxime Buquet's avatar
Maxime Buquet committed
157 158 159 160
        try:
            res = JID(key).resource
        except InvalidJID:
            return None
mathieui's avatar
mathieui committed
161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180
        resources = self.__item.resources
        item = resources.get(res, None) or resources.get(key, None)
        return Resource(key, item) if item else None

    def subscribe(self):
        """Subscribe to this JID"""
        self.__item.subscribe()

    def authorize(self):
        """Authorize this JID"""
        self.__item.authorize()

    def unauthorize(self):
        """Unauthorize this JID"""
        self.__item.unauthorize()

    def unsubscribe(self):
        """Unsubscribe from this JID"""
        self.__item.unsubscribe()

mathieui's avatar
mathieui committed
181 182
    def get(self, key: str,
            default: Optional[Resource] = None) -> Optional[Resource]:
mathieui's avatar
mathieui committed
183 184
        """Same as __getitem__, but with a configurable default"""
        return self[key] or default
185

mathieui's avatar
mathieui committed
186
    def get_resources(self) -> List[Resource]:
mathieui's avatar
mathieui committed
187 188
        """Return all resources, sorted by priority """
        compare_resources = lambda x: x.priority
189
        return sorted(self.resources, key=compare_resources, reverse=True)
mathieui's avatar
mathieui committed
190

mathieui's avatar
mathieui committed
191
    def get_highest_priority_resource(self) -> Optional[Resource]:
mathieui's avatar
mathieui committed
192 193 194
        """Return the resource with the highest priority"""
        resources = self.get_resources()
        if resources:
195
            return resources[0]
mathieui's avatar
mathieui committed
196 197
        return None

mathieui's avatar
mathieui committed
198
    def folded(self, group_name='none') -> bool:
199 200 201 202 203 204
        """
        Return the Folded state of a contact for this group
        """
        return self.folded_states[group_name]

    def toggle_folded(self, group='none'):
205
        """
mathieui's avatar
mathieui committed
206
        Fold if it's unfolded, and vice versa
207
        """
208
        self.folded_states[group] = not self.folded_states[group]
209

mathieui's avatar
mathieui committed
210
    def __repr__(self) -> str:
mathieui's avatar
mathieui committed
211 212
        ret = '<Contact: %s' % self.bare_jid
        for resource in self.resources:
213
            ret += '\n\t\t%s' % resource
214
        return ret + ' />\n'