Skip to content

Commit 6e3c5d7

Browse files
author
Pietro Albini
committed
Merge branch 'avatars'
2 parents 840d537 + 77ec3d6 commit 6e3c5d7

File tree

4 files changed

+222
-5
lines changed

4 files changed

+222
-5
lines changed

botogram/objects/__init__.py

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,43 @@ def name(self):
3434

3535
return result
3636

37+
@property
38+
@mixins._require_api
39+
def avatar(self):
40+
"""Get the avatar of the user"""
41+
# This is lazy loaded and cached, so it won't affect performances if
42+
# you don't need avatars
43+
if not hasattr(self, "_avatar"):
44+
avatars = self._api.call("getUserProfilePhotos", {
45+
"user_id": self.id,
46+
"limit": 1,
47+
}, expect=UserProfilePhotos)
48+
49+
# If the user has no avatars just use None
50+
self._avatar = None
51+
if len(avatars.photos):
52+
self._avatar = avatars.photos[0] # Take the most recent one
53+
54+
return self._avatar
55+
56+
@mixins._require_api
57+
def avatar_history(self):
58+
"""Get all the avatars of the user"""
59+
avatars = []
60+
61+
while True:
62+
chunk = self._api.call("getUserProfilePhotos", {
63+
"user_id": self.id,
64+
"offset": len(avatars),
65+
"limit": 1,
66+
}, expect=UserProfilePhotos)
67+
68+
avatars += chunk.photos
69+
if len(avatars) >= chunk.total_count:
70+
break
71+
72+
return avatars
73+
3774

3875
class Chat(BaseObject, mixins.ChatMixin):
3976
"""Telegram API representation of a chat
@@ -257,7 +294,7 @@ class UserProfilePhotos(BaseObject):
257294

258295
required = {
259296
"total_count": int,
260-
"photos": multiple(multiple(PhotoSize)),
297+
"photos": multiple(Photo),
261298
}
262299

263300

docs/api/telegram.rst

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,26 @@ about its business.
7171

7272
.. versionadded:: 0.2
7373

74+
.. py:attribute:: avatar
75+
76+
This attribute contains the user's avatar, represented as a
77+
:py:class:`~botogram.Photo` object. If the user has no avatar, this
78+
attribute will be ``None``.
79+
80+
In order to improve performances, this attribute's content is dynamically
81+
requested to Telegram the first time you access it, so there will be some
82+
delay.
83+
84+
.. versionadded:: 0.2
85+
86+
.. py:method:: avatar_history()
87+
88+
Get the user's avatar history. This returns a list of the current and all
89+
the past avatars for the user, represented as :py:class:`~botogram.Photo`
90+
objects. If the user has no avatars this returns an empty list.
91+
92+
.. versionadded:: 0.2
93+
7494
.. py:method:: send(message, [preview=True, reply_to=None, syntax=None, extra=None])
7595
7696
Send the textual *message* to the user. You may optionally stop clients

docs/changelog.rst

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,10 @@ botogram 0.2
1616

1717
*Alpha release, not yet released*
1818

19-
* Added the :py:attr:`~botogram.User.name` computed attribute on the
20-
:py:class:`~botogram.User` objects
21-
* Added the :py:attr:`~botogram.Chat.name` computed attribute on the
22-
:py:class:`~botogram.Chat` objects
19+
* Added the :py:attr:`botogram.User.name` computed attribute
20+
* Added the :py:attr:`botogram.Chat.name` computed attribute
21+
* Added the :py:attr:`botogram.User.avatar` attribute
22+
* Added the :py:meth:`botogram.User.avatar_history` method
2323
* Renamed ``Bot.init_shared_memory`` to ``Bot.prepare_memory``
2424
* Renamed ``Component.add_shared_memory_initializer`` to
2525
``Component.add_memory_preparer``

tests/test_objects.py

Lines changed: 160 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,166 @@
1010
import botogram.objects
1111

1212

13+
def test_user_avatar(api, mock_req):
14+
mock_req({
15+
"getUserProfilePhotos": {
16+
"ok": True,
17+
"result": {
18+
"total_count": 1,
19+
"photos": [
20+
[
21+
{
22+
"file_id": "aaaaaa",
23+
"width": 50,
24+
"height": 50,
25+
"file_size": 128,
26+
},
27+
{
28+
"file_id": "bbbbbb",
29+
"width": 25,
30+
"height": 25,
31+
"file_size": 64,
32+
},
33+
],
34+
],
35+
},
36+
},
37+
})
38+
39+
# First of all, make sure the API wrapper is required to fetch avatars
40+
user = botogram.objects.User({"id": 123, "first_name": "Bob"})
41+
with pytest.raises(RuntimeError):
42+
user.avatar # Access the avatar without an API wrapper
43+
44+
# Now use an API
45+
user = botogram.objects.User({"id": 123, "first_name": "Bob"}, api)
46+
47+
# Be sure the avatar isn't loaded yet
48+
assert not hasattr(user, "_avatar")
49+
50+
# Now fetch the avatar
51+
avatar = user.avatar
52+
assert avatar.file_id == "aaaaaa"
53+
54+
# And be sure it's cached
55+
assert hasattr(user, "_avatar")
56+
assert user._avatar == avatar
57+
58+
59+
def test_user_avatar_with_no_photos(api, mock_req):
60+
mock_req({
61+
"getUserProfilePhotos": {
62+
"ok": True,
63+
"result": {
64+
"total_count": 0,
65+
"photos": [],
66+
},
67+
},
68+
})
69+
70+
user = botogram.objects.User({"id": 123, "first_name": "Bob"}, api)
71+
assert user.avatar is None
72+
73+
74+
def test_user_avatar_history(api, mock_req):
75+
mock_req({
76+
"getUserProfilePhotos": {
77+
"ok": True,
78+
"result": {
79+
"total_count": 3,
80+
"photos": [
81+
[
82+
{
83+
"file_id": "aaaaaa",
84+
"width": 50,
85+
"height": 50,
86+
"file_size": 128,
87+
},
88+
],
89+
[
90+
{
91+
"file_id": "bbbbbb",
92+
"width": 50,
93+
"height": 50,
94+
"file_size": 128,
95+
},
96+
],
97+
[
98+
{
99+
"file_id": "cccccc",
100+
"width": 50,
101+
"height": 50,
102+
"file_size": 128,
103+
},
104+
],
105+
],
106+
},
107+
},
108+
})
109+
110+
# First of all, make sure the API wrapper is required to fetch avatars
111+
user = botogram.objects.User({"id": 123, "first_name": "Bob"})
112+
with pytest.raises(RuntimeError):
113+
user.avatar_history() # Access the avatar without an API wrapper
114+
115+
# Now use an API
116+
user = botogram.objects.User({"id": 123, "first_name": "Bob"}, api)
117+
118+
files = [avatar.file_id for avatar in user.avatar_history()]
119+
assert files == ["aaaaaa", "bbbbbb", "cccccc"]
120+
121+
122+
def test_user_avatar_history_multiple_requests(api, mock_req):
123+
mock_req({
124+
"getUserProfilePhotos": {
125+
"ok": True,
126+
"result": {
127+
# This is the double of the avatars provided with this request
128+
# This simulates if the user has more than 100 avatars
129+
"total_count": 4,
130+
"photos": [
131+
[
132+
{
133+
"file_id": "aaaaaa",
134+
"width": 50,
135+
"height": 50,
136+
"file_size": 128,
137+
},
138+
],
139+
[
140+
{
141+
"file_id": "bbbbbb",
142+
"width": 50,
143+
"height": 50,
144+
"file_size": 128,
145+
},
146+
],
147+
],
148+
},
149+
},
150+
})
151+
152+
user = botogram.objects.User({"id": 123, "first_name": "Bob"}, api)
153+
154+
files = [avatar.file_id for avatar in user.avatar_history()]
155+
assert files == ["aaaaaa", "bbbbbb", "aaaaaa", "bbbbbb"]
156+
157+
158+
def test_user_avatar_history_no_photos(api, mock_req):
159+
mock_req({
160+
"getUserProfilePhotos": {
161+
"ok": True,
162+
"result": {
163+
"total_count": 0,
164+
"photos": [],
165+
},
166+
},
167+
})
168+
169+
user = botogram.objects.User({"id": 123, "first_name": "Bob"}, api)
170+
assert user.avatar_history() == []
171+
172+
13173
def test_photo_object():
14174
# The Photo object is custom-made, so it's better to ensure all it's
15175
# working as expected

0 commit comments

Comments
 (0)