Skip to content

Commit d8f2c1f

Browse files
authored
Merge branch 'master' into polls
2 parents dc1d2ac + 6f1046f commit d8f2c1f

File tree

5 files changed

+286
-114
lines changed

5 files changed

+286
-114
lines changed

.travis.yml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,14 @@
11
language: python
2+
3+
# In order to run Python 3.7 or higher, we need xenial
4+
# https://docs.travis-ci.com/user/languages/python/#python-37-and-higher
5+
dist: xenial
6+
27
python:
38
- "3.4"
49
- "3.5"
10+
- "3.6"
11+
- "3.7"
512

613
sudo: false
714
cache: pip

botogram/objects/messages.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -335,6 +335,8 @@ def from_(self):
335335
"forward_from": User,
336336
"forward_from_chat": Chat,
337337
"forward_from_message_id": int,
338+
"forward_sender_name": str,
339+
"forward_signature": str,
338340
"forward_date": int,
339341
"reply_to_message": _itself,
340342
"text": str,
@@ -371,6 +373,7 @@ def from_(self):
371373
# Those are provided dynamically by self.forward_from
372374
"forward_from": "_forward_from",
373375
"forward_from_chat": "_forward_from_chat",
376+
"forward_sender_name": "_forward_sender_name",
374377
}
375378
_check_equality_ = "message_id"
376379

@@ -392,12 +395,24 @@ def forward_from(self):
392395
"""Get from where the message was forwarded"""
393396
# Provide either _forward_from or _forward_from_chat
394397
# _forward_from_chat is checked earlier because it's more correct
398+
# _forward_sender_name is returned if the original sender
399+
# has opted to hide his account
400+
395401
if self._forward_from_chat is not None:
396402
return self._forward_from_chat
397403

398404
if self._forward_from is not None:
399405
return self._forward_from
400406

407+
if self._forward_sender_name is not None:
408+
return self._forward_sender_name
409+
410+
@property
411+
def forward_hidden(self):
412+
"""Check if the original sender is hidden or not"""
413+
414+
return isinstance(self.forward_from, str)
415+
401416
@property
402417
def channel_post_author(self):
403418
"""Get the author of the channel post"""

botogram/objects/mixins.py

Lines changed: 136 additions & 98 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,24 @@ def _get_call_args(self, reply_to, extra, attach, notify):
7777

7878
return args
7979

80+
@staticmethod
81+
def _get_file_args(path, file_id, url):
82+
args = None
83+
if path is not None and file_id is None and url is None:
84+
file = open(path, "rb")
85+
elif file_id is not None and path is None and url is None:
86+
args = file_id
87+
file = None
88+
elif url is not None and file_id is None and path is None:
89+
args = url
90+
file = None
91+
elif path is None and file_id is None and url is None:
92+
raise TypeError("path or file_id or URL is missing")
93+
else:
94+
raise TypeError("Only one among path, file_id and URL must be" +
95+
"passed")
96+
return args, file
97+
8098
@_require_api
8199
def send(self, message, preview=True, reply_to=None, syntax=None,
82100
extra=None, attach=None, notify=True):
@@ -102,26 +120,19 @@ def send_photo(self, path=None, file_id=None, url=None, caption=None,
102120
if syntax is not None:
103121
syntax = syntaxes.guess_syntax(caption, syntax)
104122
args["parse_mode"] = syntax
105-
if path is not None and file_id is None and url is None:
106-
files = {"photo": open(path, "rb")}
107-
elif file_id is not None and path is None and url is None:
108-
args["photo"] = file_id
109-
files = None
110-
elif url is not None and file_id is None and path is None:
111-
args["photo"] = url
112-
files = None
113-
elif path is None and file_id is None and url is None:
114-
raise TypeError("path or file_id or URL is missing")
115-
else:
116-
raise TypeError("Only one among path, file_id and URL must be" +
117-
"passed")
123+
files = dict()
124+
args["photo"], files["photo"] = self._get_file_args(path,
125+
file_id,
126+
url)
127+
if files["photo"] is None:
128+
del files["photo"]
118129

119130
return self._api.call("sendPhoto", args, files,
120131
expect=_objects().Message)
121132

122133
@_require_api
123134
def send_audio(self, path=None, file_id=None, url=None, duration=None,
124-
performer=None, title=None, reply_to=None,
135+
thumb=None, performer=None, title=None, reply_to=None,
125136
extra=None, attach=None, notify=True, caption=None, *,
126137
syntax=None):
127138
"""Send an audio track"""
@@ -138,19 +149,14 @@ def send_audio(self, path=None, file_id=None, url=None, duration=None,
138149
if title is not None:
139150
args["title"] = title
140151

141-
if path is not None and file_id is None and url is None:
142-
files = {"audio": open(path, "rb")}
143-
elif file_id is not None and path is None and url is None:
144-
files = None
145-
args["audio"] = file_id
146-
elif url is not None and file_id is None and path is None:
147-
args["audio"] = url
148-
files = None
149-
elif path is None and file_id is None and url is None:
150-
raise TypeError("path or file_id or URL is missing")
151-
else:
152-
raise TypeError("Only one among path, file_id and URL must be" +
153-
"passed")
152+
files = dict()
153+
args["audio"], files["audio"] = self._get_file_args(path,
154+
file_id,
155+
url)
156+
if files["audio"] is None:
157+
del files["audio"]
158+
if thumb is not None:
159+
files["thumb"] = thumb
154160

155161
return self._api.call("sendAudio", args, files,
156162
expect=_objects().Message)
@@ -168,30 +174,25 @@ def send_voice(self, path=None, file_id=None, url=None, duration=None,
168174
args["parse_mode"] = syntax
169175
if duration is not None:
170176
args["duration"] = duration
177+
if title is not None:
178+
args["title"] = title
171179
syntax = syntaxes.guess_syntax(caption, syntax)
172180
if syntax is not None:
173181
args["parse_mode"] = syntax
174182

175-
if path is not None and file_id is None and url is None:
176-
files = {"voice": open(path, "rb")}
177-
elif file_id is not None and path is None and url is None:
178-
files = None
179-
args["voice"] = file_id
180-
elif url is not None and file_id is None and path is None:
181-
args["voice"] = url
182-
files = None
183-
elif path is None and file_id is None and url is None:
184-
raise TypeError("path or file_id or URL is missing")
185-
else:
186-
raise TypeError("Only one among path, file_id and URL must be" +
187-
"passed")
183+
files = dict()
184+
args["voice"], files["voice"] = self._get_file_args(path,
185+
file_id,
186+
url)
187+
if files["voice"] is None:
188+
del files["voice"]
188189

189190
return self._api.call("sendVoice", args, files,
190191
expect=_objects().Message)
191192

192193
@_require_api
193194
def send_video(self, path=None, file_id=None, url=None,
194-
duration=None, caption=None, streaming=True,
195+
duration=None, caption=None, streaming=True, thumb=None,
195196
reply_to=None, extra=None, attach=None,
196197
notify=True, *, syntax=None):
197198
"""Send a video"""
@@ -204,83 +205,81 @@ def send_video(self, path=None, file_id=None, url=None,
204205
if syntax is not None:
205206
syntax = syntaxes.guess_syntax(caption, syntax)
206207
args["parse_mode"] = syntax
207-
if path is not None and file_id is None and url is None:
208-
files = {"video": open(path, "rb")}
209-
elif file_id is not None and path is None and url is None:
210-
files = None
211-
args["video"] = file_id
212-
elif url is not None and file_id is None and path is None:
213-
args["video"] = url
214-
files = None
215-
elif path is None and file_id is None and url is None:
216-
raise TypeError("path or file_id or URL is missing")
217-
else:
218-
raise TypeError("Only one among path, file_id and URL must be" +
219-
"passed")
208+
209+
files = dict()
210+
args["video"], files["video"] = self._get_file_args(path,
211+
file_id,
212+
url)
213+
if files["video"] is None:
214+
del files["video"]
215+
if thumb is not None:
216+
files["thumb"] = thumb
220217

221218
return self._api.call("sendVideo", args, files,
222219
expect=_objects().Message)
223220

224221
@_require_api
225222
def send_video_note(self, path=None, file_id=None, duration=None,
226-
diameter=None, reply_to=None, extra=None,
223+
diameter=None, thumb=None, reply_to=None, extra=None,
227224
attach=None, notify=True):
228225
"""Send a video note"""
229226
args = self._get_call_args(reply_to, extra, attach, notify)
230227
if duration is not None:
231228
args["duration"] = duration
232229
if diameter is not None:
233230
args["length"] = diameter
234-
if path is not None and file_id is None:
235-
files = {"video_note": open(path, "rb")}
236-
elif file_id is not None and path is None:
237-
files = None
238-
args["video_note"] = file_id
239-
elif path is None and file_id is None:
240-
raise TypeError("Path or file_id or URL is missing")
241-
else:
242-
raise TypeError("Only one among path and file_id must be" +
243-
"passed")
231+
232+
files = dict()
233+
args["video_note"], files["video_note"] = self._get_file_args(path,
234+
file_id,
235+
None)
236+
if files["video_note"] is None:
237+
del files["video_note"]
238+
if thumb is not None:
239+
files["thumb"] = thumb
244240

245241
return self._api.call("sendVideoNote", args, files,
246242
expect=_objects().Message)
247243

248244
@_require_api
249-
def send_file(self, path=None, file_id=None, url=None, reply_to=None,
250-
extra=None, attach=None, notify=True, caption=None, *,
251-
syntax=None):
245+
def send_file(self, path=None, file_id=None, url=None, thumb=None,
246+
reply_to=None, extra=None, attach=None,
247+
notify=True, caption=None, *, syntax=None):
252248
"""Send a generic file"""
253249
args = self._get_call_args(reply_to, extra, attach, notify)
254250
if caption is not None:
255251
args["caption"] = caption
256252
if syntax is not None:
257253
syntax = syntaxes.guess_syntax(caption, syntax)
258254
args["parse_mode"] = syntax
259-
if path is not None and file_id is None and url is None:
260-
files = {"document": open(path, "rb")}
261-
elif file_id is not None and path is None and url is None:
262-
files = None
263-
args["document"] = file_id
264-
elif url is not None and file_id is None and path is None:
265-
args["document"] = url
266-
files = None
267-
elif path is None and file_id is None and url is None:
268-
raise TypeError("path or file_id or URL is missing")
269-
else:
270-
raise TypeError("Only one among path, file_id and URL must be" +
271-
"passed")
255+
256+
files = dict()
257+
args["document"], files["document"] = self._get_file_args(path,
258+
file_id,
259+
url)
260+
if files["document"] is None:
261+
del files["document"]
262+
if thumb is not None:
263+
files["thumb"] = thumb
272264

273265
return self._api.call("sendDocument", args, files,
274266
expect=_objects().Message)
275267

276268
@_require_api
277-
def send_location(self, latitude, longitude, reply_to=None, extra=None,
278-
attach=None, notify=True):
279-
"""Send a geographic location"""
269+
def send_location(self, latitude, longitude, live_period=None,
270+
reply_to=None, extra=None, attach=None, notify=True):
271+
"""Send a geographic location, set live_period to a number between 60
272+
and 86400 if it's a live location"""
280273
args = self._get_call_args(reply_to, extra, attach, notify)
281274
args["latitude"] = latitude
282275
args["longitude"] = longitude
283276

277+
if live_period:
278+
if live_period < 60 or live_period > 86400:
279+
raise ValueError(
280+
"live_period must be a number between 60 and 86400")
281+
args["live_period"] = live_period
282+
284283
return self._api.call("sendLocation", args,
285284
expect=_objects().Message)
286285

@@ -313,19 +312,13 @@ def send_sticker(self, sticker=None, reply_to=None, extra=None,
313312
)
314313

315314
args = self._get_call_args(reply_to, extra, attach, notify)
316-
if path is not None and file_id is None and url is None:
317-
files = {"sticker": open(path, "rb")}
318-
elif file_id is not None and path is None and url is None:
319-
files = None
320-
args["sticker"] = file_id
321-
elif url is not None and file_id is None and path is None:
322-
args["sticker"] = url
323-
files = None
324-
elif path is None and file_id is None and url is None:
325-
raise TypeError("path or file_id or URL is missing")
326-
else:
327-
raise TypeError("Only one among path, file_id and URL must be " +
328-
"passed")
315+
316+
files = dict()
317+
args["sticker"], files["sticker"] = self._get_file_args(path,
318+
file_id,
319+
url)
320+
if files["sticker"] is None:
321+
del files["sticker"]
329322

330323
return self._api.call("sendSticker", args, files,
331324
expect=_objects().Message)
@@ -451,10 +444,55 @@ def edit_caption(self, caption, extra=None, attach=None, *, syntax=None):
451444
def edit_attach(self, attach):
452445
"""Edit this message's attachment"""
453446
args = {"message_id": self.id, "chat_id": self.chat.id}
454-
args["reply_markup"] = attach
447+
if not hasattr(attach, "_serialize_attachment"):
448+
raise ValueError("%s is not an attachment" % attach)
449+
args["reply_markup"] = json.dumps(attach._serialize_attachment(
450+
self.chat
451+
))
455452

456453
self._api.call("editMessageReplyMarkup", args)
457454

455+
@_require_api
456+
def edit_live_location(self, latitude, longitude, extra=None, attach=None):
457+
"""Edit this message's live location position"""
458+
args = {"message_id": self.id, "chat_id": self.chat.id}
459+
args["latitude"] = latitude
460+
args["longitude"] = longitude
461+
462+
if extra is not None:
463+
_deprecated_message(
464+
"The extra parameter", "1.0", "use the attach parameter", -3
465+
)
466+
args["reply_markup"] = json.dumps(extra.serialize())
467+
468+
if attach is not None:
469+
if not hasattr(attach, "_serialize_attachment"):
470+
raise ValueError("%s is not an attachment" % attach)
471+
args["reply_markup"] = json.dumps(attach._serialize_attachment(
472+
self.chat
473+
))
474+
475+
self._api.call("editMessageLiveLocation", args)
476+
477+
@_require_api
478+
def stop_live_location(self, extra=None, attach=None):
479+
"""Stop this message's live location"""
480+
args = {"message_id": self.id, "chat_id": self.chat.id}
481+
482+
if extra is not None:
483+
_deprecated_message(
484+
"The extra parameter", "1.0", "use the attach parameter", -3
485+
)
486+
args["reply_markup"] = json.dumps(extra.serialize())
487+
488+
if attach is not None:
489+
if not hasattr(attach, "_serialize_attachment"):
490+
raise ValueError("%s is not an attachment" % attach)
491+
args["reply_markup"] = json.dumps(attach._serialize_attachment(
492+
self.chat
493+
))
494+
self._api.call("stopMessageLiveLocation", args)
495+
458496
@_require_api
459497
def reply(self, *args, **kwargs):
460498
"""Reply to the current message"""

0 commit comments

Comments
 (0)