1# Copyright (C) 2020 Philipp Hörist <philipp AT hoerist.com>
2#
3# This file is part of nbxmpp.
4#
5# This program is free software; you can redistribute it and/or
6# modify it under the terms of the GNU General Public License
7# as published by the Free Software Foundation; either version 3
8# of the License, or (at your option) any later version.
9#
10# This program is distributed in the hope that it will be useful,
11# but WITHOUT ANY WARRANTY; without even the implied warranty of
12# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13# GNU General Public License for more details.
14#
15# You should have received a copy of the GNU General Public License
16# along with this program; If not, see <http://www.gnu.org/licenses/>.
17
18import hashlib
19from dataclasses import dataclass
20from dataclasses import field
21
22from nbxmpp.task import iq_request_task
23from nbxmpp.util import b64decode
24from nbxmpp.util import b64encode
25from nbxmpp.errors import StanzaError
26from nbxmpp.errors import MalformedStanzaError
27from nbxmpp.protocol import Iq
28from nbxmpp.simplexml import Node
29from nbxmpp.namespaces import Namespace
30from nbxmpp.modules.base import BaseModule
31from nbxmpp.modules.util import process_response
32
33
34class VCardTemp(BaseModule):
35    def __init__(self, client):
36        BaseModule.__init__(self, client)
37
38        self._client = client
39        self.handlers = []
40
41    @iq_request_task
42    def request_vcard(self, jid=None):
43        _task = yield
44
45        response = yield _make_vcard_request(jid)
46
47        if response.isError():
48            raise StanzaError(response)
49
50        vcard_node = _get_vcard_node(response)
51        yield VCard.from_node(vcard_node)
52
53    @iq_request_task
54    def set_vcard(self, vcard, jid=None):
55        _task = yield
56
57        response = yield _make_vcard_publish(jid, vcard)
58        yield process_response(response)
59
60
61def _make_vcard_request(jid):
62    iq = Iq(typ='get', to=jid)
63    iq.addChild('vCard', namespace=Namespace.VCARD)
64    return iq
65
66
67def _get_vcard_node(response):
68    vcard_node = response.getTag('vCard', namespace=Namespace.VCARD)
69    if vcard_node is None:
70        raise MalformedStanzaError('vCard node missing', response)
71    return vcard_node
72
73
74def _make_vcard_publish(jid, vcard):
75    iq = Iq(typ='set', to=jid)
76    iq.addChild(node=vcard.to_node())
77    return iq
78
79
80@dataclass
81class VCard:
82    data: dict = field(default_factory=dict)
83
84    @classmethod
85    def from_node(cls, node):
86        dict_ = {}
87        for info in node.getChildren():
88            name = info.getName()
89            if name in ('ADR', 'TEL', 'EMAIL'):
90                dict_.setdefault(name, [])
91                entry = {}
92                for child in info.getChildren():
93                    entry[child.getName()] = child.getData()
94                dict_[name].append(entry)
95            elif info.getChildren() == []:
96                dict_[name] = info.getData()
97            else:
98                dict_[name] = {}
99                for child in info.getChildren():
100                    dict_[name][child.getName()] = child.getData()
101
102        return cls(data=dict_)
103
104    def to_node(self):
105        vcard = Node(tag='vCard', attrs={'xmlns': Namespace.VCARD})
106        for i in self.data:
107            if i == 'jid':
108                continue
109            if isinstance(self.data[i], dict):
110                child = vcard.addChild(i)
111                for j in self.data[i]:
112                    child.addChild(j).setData(self.data[i][j])
113            elif isinstance(self.data[i], list):
114                for j in self.data[i]:
115                    child = vcard.addChild(i)
116                    for k in j:
117                        child.addChild(k).setData(j[k])
118            else:
119                vcard.addChild(i).setData(self.data[i])
120        return vcard
121
122    def set_avatar(self, avatar, type_=None):
123        avatar = b64encode(avatar)
124        if 'PHOTO' not in self.data:
125            self.data['PHOTO'] = {}
126
127        self.data['PHOTO']['BINVAL'] = avatar
128
129        if type_ is not None:
130            self.data['PHOTO']['TYPE'] = type_
131
132    def get_avatar(self):
133        try:
134            avatar = self.data['PHOTO']['BINVAL']
135        except Exception:
136            return None, None
137
138        if not avatar:
139            return None, None
140
141        avatar = b64decode(avatar, return_type=bytes)
142        avatar_sha = hashlib.sha1(avatar).hexdigest()
143        return avatar, avatar_sha
144