File: inlinebuilder.py

package info (click to toggle)
python-telethon 1.42.0-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 2,520 kB
  • sloc: python: 16,285; javascript: 200; makefile: 16; sh: 11
file content (450 lines) | stat: -rw-r--r-- 16,993 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
import hashlib

from .. import functions, types
from ... import utils

_TYPE_TO_MIMES = {
    'gif': ['image/gif'],  # 'video/mp4' too, but that's used for video
    'article': ['text/html'],
    'audio': ['audio/mpeg'],
    'contact': [],
    'file': ['application/pdf', 'application/zip'],  # actually any
    'geo': [],
    'photo': ['image/jpeg'],
    'sticker': ['image/webp', 'application/x-tgsticker'],
    'venue': [],
    'video': ['video/mp4'],  # tdlib includes text/html for some reason
    'voice': ['audio/ogg'],
}


class InlineBuilder:
    """
    Helper class to allow defining `InlineQuery
    <telethon.events.inlinequery.InlineQuery>` ``results``.

    Common arguments to all methods are
    explained here to avoid repetition:

        text (`str`, optional):
            If present, the user will send a text
            message with this text upon being clicked.

        link_preview (`bool`, optional):
            Whether to show a link preview in the sent
            text message or not.

        geo (:tl:`InputGeoPoint`, :tl:`GeoPoint`, :tl:`InputMediaVenue`, :tl:`MessageMediaVenue`, optional):
            If present, it may either be a geo point or a venue.

        period (int, optional):
            The period in seconds to be used for geo points.

        contact (:tl:`InputMediaContact`, :tl:`MessageMediaContact`, optional):
            If present, it must be the contact information to send.

        game (`bool`, optional):
            May be `True` to indicate that the game will be sent.

        buttons (`list`, `custom.Button <telethon.tl.custom.button.Button>`, :tl:`KeyboardButton`, optional):
            Same as ``buttons`` for `client.send_message()
            <telethon.client.messages.MessageMethods.send_message>`.

        parse_mode (`str`, optional):
            Same as ``parse_mode`` for `client.send_message()
            <telethon.client.messageparse.MessageParseMethods.parse_mode>`.

        id (`str`, optional):
            The string ID to use for this result. If not present, it
            will be the SHA256 hexadecimal digest of converting the
            created :tl:`InputBotInlineResult` with empty ID to ``bytes()``,
            so that the ID will be deterministic for the same input.

            .. note::

                If two inputs are exactly the same, their IDs will be the same
                too. If you send two articles with the same ID, it will raise
                ``ResultIdDuplicateError``. Consider giving them an explicit
                ID if you need to send two results that are the same.
    """
    def __init__(self, client):
        self._client = client

    # noinspection PyIncorrectDocstring
    async def article(
            self, title, description=None,
            *, url=None, thumb=None, content=None,
            id=None, text=None, parse_mode=(), link_preview=True,
            geo=None, period=60, contact=None, game=False, buttons=None
    ):
        """
        Creates new inline result of article type.

        Args:
            title (`str`):
                The title to be shown for this result.

            description (`str`, optional):
                Further explanation of what this result means.

            url (`str`, optional):
                The URL to be shown for this result.

            thumb (:tl:`InputWebDocument`, optional):
                The thumbnail to be shown for this result.
                For now it has to be a :tl:`InputWebDocument` if present.

            content (:tl:`InputWebDocument`, optional):
                The content to be shown for this result.
                For now it has to be a :tl:`InputWebDocument` if present.

        Example:
            .. code-block:: python

                results = [
                    # Option with title and description sending a message.
                    builder.article(
                        title='First option',
                        description='This is the first option',
                        text='Text sent after clicking this option',
                    ),
                    # Option with title URL to be opened when clicked.
                    builder.article(
                        title='Second option',
                        url='https://example.com',
                        text='Text sent if the user clicks the option and not the URL',
                    ),
                    # Sending a message with buttons.
                    # You can use a list or a list of lists to include more buttons.
                    builder.article(
                        title='Third option',
                        text='Text sent with buttons below',
                        buttons=Button.url('https://example.com'),
                    ),
                ]
        """
        # TODO Does 'article' work always?
        # article, photo, gif, mpeg4_gif, video, audio,
        # voice, document, location, venue, contact, game
        result = types.InputBotInlineResult(
            id=id or '',
            type='article',
            send_message=await self._message(
                text=text, parse_mode=parse_mode, link_preview=link_preview,
                geo=geo, period=period,
                contact=contact,
                game=game,
                buttons=buttons
            ),
            title=title,
            description=description,
            url=url,
            thumb=thumb,
            content=content
        )
        if id is None:
            result.id = hashlib.sha256(bytes(result)).hexdigest()

        return result

    # noinspection PyIncorrectDocstring
    async def photo(
            self, file, *, id=None, include_media=True,
            text=None, parse_mode=(), link_preview=True,
            geo=None, period=60, contact=None, game=False, buttons=None
    ):
        """
        Creates a new inline result of photo type.

        Args:
            include_media (`bool`, optional):
                Whether the photo file used to display the result should be
                included in the message itself or not. By default, the photo
                is included, and the text parameter alters the caption.

            file (`obj`, optional):
                Same as ``file`` for `client.send_file()
                <telethon.client.uploads.UploadMethods.send_file>`.

        Example:
            .. code-block:: python

                results = [
                    # Sending just the photo when the user selects it.
                    builder.photo('/path/to/photo.jpg'),

                    # Including a caption with some in-memory photo.
                    photo_bytesio = ...
                    builder.photo(
                        photo_bytesio,
                        text='This will be the caption of the sent photo',
                    ),

                    # Sending just the message without including the photo.
                    builder.photo(
                        photo,
                        text='This will be a normal text message',
                        include_media=False,
                    ),
                ]
        """
        try:
            fh = utils.get_input_photo(file)
        except TypeError:
            _, media, _ = await self._client._file_to_media(
                file, allow_cache=True, as_image=True
            )
            if isinstance(media, types.InputPhoto):
                fh = media
            else:
                r = await self._client(functions.messages.UploadMediaRequest(
                    types.InputPeerSelf(), media=media
                ))
                fh = utils.get_input_photo(r.photo)

        result = types.InputBotInlineResultPhoto(
            id=id or '',
            type='photo',
            photo=fh,
            send_message=await self._message(
                text=text or '',
                parse_mode=parse_mode,
                link_preview=link_preview,
                media=include_media,
                geo=geo,
                period=period,
                contact=contact,
                game=game,
                buttons=buttons
            )
        )
        if id is None:
            result.id = hashlib.sha256(bytes(result)).hexdigest()

        return result

    # noinspection PyIncorrectDocstring
    async def document(
            self, file, title=None, *, description=None, type=None,
            mime_type=None, attributes=None, force_document=False,
            voice_note=False, video_note=False, use_cache=True, id=None,
            text=None, parse_mode=(), link_preview=True,
            geo=None, period=60, contact=None, game=False, buttons=None,
            include_media=True
    ):
        """
        Creates a new inline result of document type.

        `use_cache`, `mime_type`, `attributes`, `force_document`,
        `voice_note` and `video_note` are described in `client.send_file
        <telethon.client.uploads.UploadMethods.send_file>`.

        Args:
            file (`obj`):
                Same as ``file`` for `client.send_file()
                <telethon.client.uploads.UploadMethods.send_file>`.

            title (`str`, optional):
                The title to be shown for this result.

            description (`str`, optional):
                Further explanation of what this result means.

            type (`str`, optional):
                The type of the document. May be one of: article, audio,
                contact, file, geo, gif, photo, sticker, venue, video, voice.
                It will be automatically set if ``mime_type`` is specified,
                and default to ``'file'`` if no matching mime type is found.
                you may need to pass ``attributes`` in order to use ``type``
                effectively.

            attributes (`list`, optional):
                Optional attributes that override the inferred ones, like
                :tl:`DocumentAttributeFilename` and so on.

            include_media (`bool`, optional):
                Whether the document file used to display the result should be
                included in the message itself or not. By default, the document
                is included, and the text parameter alters the caption.

        Example:
            .. code-block:: python

                results = [
                    # Sending just the file when the user selects it.
                    builder.document('/path/to/file.pdf'),

                    # Including a caption with some in-memory file.
                    file_bytesio = ...
                    builder.document(
                        file_bytesio,
                        text='This will be the caption of the sent file',
                    ),

                    # Sending just the message without including the file.
                    builder.document(
                        photo,
                        text='This will be a normal text message',
                        include_media=False,
                    ),
                ]
        """
        if type is None:
            if voice_note:
                type = 'voice'
            elif mime_type:
                for ty, mimes in _TYPE_TO_MIMES.items():
                    for mime in mimes:
                        if mime_type == mime:
                            type = ty
                            break

            if type is None:
                type = 'file'

        try:
            fh = utils.get_input_document(file)
        except TypeError:
            _, media, _ = await self._client._file_to_media(
                file,
                mime_type=mime_type,
                attributes=attributes,
                force_document=force_document,
                voice_note=voice_note,
                video_note=video_note,
                allow_cache=use_cache
            )
            if isinstance(media, types.InputDocument):
                fh = media
            else:
                r = await self._client(functions.messages.UploadMediaRequest(
                    types.InputPeerSelf(), media=media
                ))
                fh = utils.get_input_document(r.document)

        result = types.InputBotInlineResultDocument(
            id=id or '',
            type=type,
            document=fh,
            send_message=await self._message(
                # Empty string for text if there's media but text is None.
                # We may want to display a document but send text; however
                # default to sending the media (without text, i.e. stickers).
                text=text or '',
                parse_mode=parse_mode,
                link_preview=link_preview,
                media=include_media,
                geo=geo,
                period=period,
                contact=contact,
                game=game,
                buttons=buttons
            ),
            title=title,
            description=description
        )
        if id is None:
            result.id = hashlib.sha256(bytes(result)).hexdigest()

        return result

    # noinspection PyIncorrectDocstring
    async def game(
            self, short_name, *, id=None,
            text=None, parse_mode=(), link_preview=True,
            geo=None, period=60, contact=None, game=False, buttons=None
    ):
        """
        Creates a new inline result of game type.

        Args:
            short_name (`str`):
                The short name of the game to use.
        """
        result = types.InputBotInlineResultGame(
            id=id or '',
            short_name=short_name,
            send_message=await self._message(
                text=text, parse_mode=parse_mode, link_preview=link_preview,
                geo=geo, period=period,
                contact=contact,
                game=game,
                buttons=buttons
            )
        )
        if id is None:
            result.id = hashlib.sha256(bytes(result)).hexdigest()

        return result

    async def _message(
            self, *,
            text=None, parse_mode=(), link_preview=True, media=False,
            geo=None, period=60, contact=None, game=False, buttons=None
    ):
        # Empty strings are valid but false-y; if they're empty use dummy '\0'
        args = ('\0' if text == '' else text, geo, contact, game)
        if sum(1 for x in args if x is not None and x is not False) != 1:
            raise ValueError(
                'Must set exactly one of text, geo, contact or game (set {})'
                .format(', '.join(x[0] for x in zip(
                    'text geo contact game'.split(), args) if x[1]) or 'none')
            )

        markup = self._client.build_reply_markup(buttons)
        if text is not None:
            text, msg_entities = await self._client._parse_message_text(
                text, parse_mode
            )
            if media:
                # "MediaAuto" means it will use whatever media the inline
                # result itself has (stickers, photos, or documents), while
                # respecting the user's text (caption) and formatting.
                return types.InputBotInlineMessageMediaAuto(
                    message=text,
                    entities=msg_entities,
                    reply_markup=markup
                )
            else:
                return types.InputBotInlineMessageText(
                    message=text,
                    no_webpage=not link_preview,
                    entities=msg_entities,
                    reply_markup=markup
                )
        elif isinstance(geo, (types.InputGeoPoint, types.GeoPoint)):
            return types.InputBotInlineMessageMediaGeo(
                geo_point=utils.get_input_geo(geo),
                period=period,
                reply_markup=markup
            )
        elif isinstance(geo, (types.InputMediaVenue, types.MessageMediaVenue)):
            if isinstance(geo, types.InputMediaVenue):
                geo_point = geo.geo_point
            else:
                geo_point = geo.geo

            return types.InputBotInlineMessageMediaVenue(
                geo_point=geo_point,
                title=geo.title,
                address=geo.address,
                provider=geo.provider,
                venue_id=geo.venue_id,
                venue_type=geo.venue_type,
                reply_markup=markup
            )
        elif isinstance(contact, (
                types.InputMediaContact, types.MessageMediaContact)):
            return types.InputBotInlineMessageMediaContact(
                phone_number=contact.phone_number,
                first_name=contact.first_name,
                last_name=contact.last_name,
                vcard=contact.vcard,
                reply_markup=markup
            )
        elif game:
            return types.InputBotInlineMessageGame(
                reply_markup=markup
            )
        else:
            raise ValueError('No text, game or valid geo or contact given')