File size: 11,706 Bytes
1d777c4
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
import logging
import os
from dataclasses import dataclass
from http import HTTPStatus

import aiohttp

from .. import types
from ..utils import exceptions, json
from ..utils.helper import Helper, HelperMode, Item

# Main aiogram logger
log = logging.getLogger('aiogram')


@dataclass(frozen=True)
class TelegramAPIServer:
    """
    Base config for API Endpoints
    """

    base: str
    file: str

    def api_url(self, token: str, method: str) -> str:
        """
        Generate URL for API methods

        :param token: Bot token
        :param method: API method name (case insensitive)
        :return: URL
        """
        return self.base.format(token=token, method=method)

    def file_url(self, token: str, path: str) -> str:
        """
        Generate URL for downloading files

        :param token: Bot token
        :param path: file path
        :return: URL
        """
        return self.file.format(token=token, path=path)

    @classmethod
    def from_base(cls, base: str) -> 'TelegramAPIServer':
        base = base.rstrip("/")
        return cls(
            base=f"{base}/bot{{token}}/{{method}}",
            file=f"{base}/file/bot{{token}}/{{path}}",
        )


TELEGRAM_PRODUCTION = TelegramAPIServer.from_base("https://api.telegram.org")


def check_token(token: str) -> bool:
    """
    Validate BOT token

    :param token:
    :return:
    """
    if not isinstance(token, str):
        message = (f"Token is invalid! "
                   f"It must be 'str' type instead of {type(token)} type.")
        raise exceptions.ValidationError(message)

    if any(x.isspace() for x in token):
        message = "Token is invalid! It can't contains spaces."
        raise exceptions.ValidationError(message)

    left, sep, right = token.partition(':')
    if (not sep) or (not left.isdigit()) or (not right):
        raise exceptions.ValidationError('Token is invalid!')

    return True


def check_result(method_name: str, content_type: str, status_code: int, body: str):
    """
    Checks whether `result` is a valid API response.
    A result is considered invalid if:
    - The server returned an HTTP response code other than 200
    - The content of the result is invalid JSON.
    - The method call was unsuccessful (The JSON 'ok' field equals False)

    :param method_name: The name of the method called
    :param status_code: status code
    :param content_type: content type of result
    :param body: result body
    :return: The result parsed to a JSON dictionary
    :raises ApiException: if one of the above listed cases is applicable
    """
    log.debug('Response for %s: [%d] "%r"', method_name, status_code, body)

    if content_type != 'application/json':
        raise exceptions.NetworkError(f"Invalid response with content type {content_type}: \"{body}\"")

    try:
        result_json = json.loads(body)
    except ValueError:
        result_json = {}

    description = result_json.get('description') or body
    parameters = types.ResponseParameters(**result_json.get('parameters', {}) or {})

    if HTTPStatus.OK <= status_code <= HTTPStatus.IM_USED:
        return result_json.get('result')
    elif parameters.retry_after:
        raise exceptions.RetryAfter(parameters.retry_after)
    elif parameters.migrate_to_chat_id:
        raise exceptions.MigrateToChat(parameters.migrate_to_chat_id)
    elif status_code == HTTPStatus.BAD_REQUEST:
        exceptions.BadRequest.detect(description)
    elif status_code == HTTPStatus.NOT_FOUND:
        exceptions.NotFound.detect(description)
    elif status_code == HTTPStatus.CONFLICT:
        exceptions.ConflictError.detect(description)
    elif status_code in (HTTPStatus.UNAUTHORIZED, HTTPStatus.FORBIDDEN):
        exceptions.Unauthorized.detect(description)
    elif status_code == HTTPStatus.REQUEST_ENTITY_TOO_LARGE:
        raise exceptions.NetworkError('File too large for uploading. '
                                      'Check telegram api limits https://core.telegram.org/bots/api#senddocument')
    elif status_code >= HTTPStatus.INTERNAL_SERVER_ERROR:
        if 'restart' in description:
            raise exceptions.RestartingTelegram()
        raise exceptions.TelegramAPIError(description)
    raise exceptions.TelegramAPIError(f"{description} [{status_code}]")


async def make_request(session, server, token, method, data=None, files=None, **kwargs):
    log.debug('Make request: "%s" with data: "%r" and files "%r"', method, data, files)

    url = server.api_url(token=token, method=method)

    req = compose_data(data, files)
    try:
        async with session.post(url, data=req, **kwargs) as response:
            return check_result(method, response.content_type, response.status, await response.text())
    except aiohttp.ClientError as e:
        raise exceptions.NetworkError(f"aiohttp client throws an error: {e.__class__.__name__}: {e}")


def guess_filename(obj):
    """
    Get file name from object

    :param obj:
    :return:
    """
    name = getattr(obj, 'name', None)
    if name and isinstance(name, str) and name[0] != '<' and name[-1] != '>':
        return os.path.basename(name)


def compose_data(params=None, files=None):
    """
    Prepare request data

    :param params:
    :param files:
    :return:
    """
    data = aiohttp.formdata.FormData(quote_fields=False)

    if params:
        for key, value in params.items():
            data.add_field(key, str(value))

    if files:
        for key, f in files.items():
            if isinstance(f, tuple):
                if len(f) == 2:
                    filename, fileobj = f
                else:
                    raise ValueError('Tuple must have exactly 2 elements: filename, fileobj')
            elif isinstance(f, types.InputFile):
                filename, fileobj = f.filename, f.file
            else:
                filename, fileobj = guess_filename(f) or key, f

            data.add_field(key, fileobj, filename=filename)

    return data


class Methods(Helper):
    """
    Helper for Telegram API Methods listed on https://core.telegram.org/bots/api
    """
    mode = HelperMode.lowerCamelCase

    # Getting Updates
    GET_UPDATES = Item()  # getUpdates
    SET_WEBHOOK = Item()  # setWebhook
    DELETE_WEBHOOK = Item()  # deleteWebhook
    GET_WEBHOOK_INFO = Item()  # getWebhookInfo

    # Available methods
    GET_ME = Item()  # getMe
    LOG_OUT = Item()  # logOut
    CLOSE = Item()  # close
    SEND_MESSAGE = Item()  # sendMessage
    FORWARD_MESSAGE = Item()  # forwardMessage
    COPY_MESSAGE = Item()  # copyMessage
    SEND_PHOTO = Item()  # sendPhoto
    SEND_AUDIO = Item()  # sendAudio
    SEND_DOCUMENT = Item()  # sendDocument
    SEND_VIDEO = Item()  # sendVideo
    SEND_ANIMATION = Item()  # sendAnimation
    SEND_VOICE = Item()  # sendVoice
    SEND_VIDEO_NOTE = Item()  # sendVideoNote
    SEND_MEDIA_GROUP = Item()  # sendMediaGroup
    SEND_LOCATION = Item()  # sendLocation
    EDIT_MESSAGE_LIVE_LOCATION = Item()  # editMessageLiveLocation
    STOP_MESSAGE_LIVE_LOCATION = Item()  # stopMessageLiveLocation
    SEND_VENUE = Item()  # sendVenue
    SEND_CONTACT = Item()  # sendContact
    SEND_POLL = Item()  # sendPoll
    SEND_DICE = Item()  # sendDice
    SEND_CHAT_ACTION = Item()  # sendChatAction
    GET_USER_PROFILE_PHOTOS = Item()  # getUserProfilePhotos
    GET_FILE = Item()  # getFile
    KICK_CHAT_MEMBER = Item()  # kickChatMember
    BAN_CHAT_MEMBER = Item()  # banChatMember
    UNBAN_CHAT_MEMBER = Item()  # unbanChatMember
    RESTRICT_CHAT_MEMBER = Item()  # restrictChatMember
    PROMOTE_CHAT_MEMBER = Item()  # promoteChatMember
    SET_CHAT_ADMINISTRATOR_CUSTOM_TITLE = Item()  # setChatAdministratorCustomTitle
    BAN_CHAT_SENDER_CHAT = Item()  # banChatSenderChat
    UNBAN_CHAT_SENDER_CHAT = Item()  # unbanChatSenderChat
    SET_CHAT_PERMISSIONS = Item()  # setChatPermissions
    EXPORT_CHAT_INVITE_LINK = Item()  # exportChatInviteLink
    CREATE_CHAT_INVITE_LINK = Item()  # createChatInviteLink
    EDIT_CHAT_INVITE_LINK = Item()  # editChatInviteLink
    REVOKE_CHAT_INVITE_LINK = Item()  # revokeChatInviteLink
    APPROVE_CHAT_JOIN_REQUEST = Item()  # approveChatJoinRequest
    DECLINE_CHAT_JOIN_REQUEST = Item()  # declineChatJoinRequest
    SET_CHAT_PHOTO = Item()  # setChatPhoto
    DELETE_CHAT_PHOTO = Item()  # deleteChatPhoto
    SET_CHAT_TITLE = Item()  # setChatTitle
    SET_CHAT_DESCRIPTION = Item()  # setChatDescription
    PIN_CHAT_MESSAGE = Item()  # pinChatMessage
    UNPIN_CHAT_MESSAGE = Item()  # unpinChatMessage
    UNPIN_ALL_CHAT_MESSAGES = Item()  # unpinAllChatMessages
    LEAVE_CHAT = Item()  # leaveChat
    GET_CHAT = Item()  # getChat
    GET_CHAT_ADMINISTRATORS = Item()  # getChatAdministrators
    GET_CHAT_MEMBER_COUNT = Item()  # getChatMemberCount
    GET_CHAT_MEMBERS_COUNT = Item()  # getChatMembersCount (renamed to getChatMemberCount)
    GET_CHAT_MEMBER = Item()  # getChatMember
    SET_CHAT_STICKER_SET = Item()  # setChatStickerSet
    DELETE_CHAT_STICKER_SET = Item()  # deleteChatStickerSet
    GET_FORUM_TOPIC_ICON_STICKERS = Item()  # getForumTopicIconStickers
    CREATE_FORUM_TOPIC = Item()  # createForumTopic
    EDIT_FORUM_TOPIC = Item()  # editForumTopic
    CLOSE_FORUM_TOPIC = Item()  # closeForumTopic
    REOPEN_FORUM_TOPIC = Item()  # reopenForumTopic
    DELETE_FORUM_TOPIC = Item()  # deleteForumTopic
    UNPIN_ALL_FORUM_TOPIC_MESSAGES = Item()  # unpinAllForumTopicMessages
    EDIT_GENERAL_FORUM_TOPIC = Item()  # editGeneralForumTopic
    CLOSE_GENERAL_FORUM_TOPIC = Item()  # closeGeneralForumTopic
    REOPEN_GENERAL_FORUM_TOPIC = Item()  # reopenGeneralForumTopic
    HIDE_GENERAL_FORUM_TOPIC = Item()  # hideGeneralForumTopic
    UNHIDE_GENERAL_FORUM_TOPIC = Item()  # unhideGeneralForumTopic
    ANSWER_CALLBACK_QUERY = Item()  # answerCallbackQuery
    SET_MY_COMMANDS = Item()  # setMyCommands
    DELETE_MY_COMMANDS = Item()  # deleteMyCommands
    GET_MY_COMMANDS = Item()  # getMyCommands

    # Updating messages
    EDIT_MESSAGE_TEXT = Item()  # editMessageText
    EDIT_MESSAGE_CAPTION = Item()  # editMessageCaption
    EDIT_MESSAGE_MEDIA = Item()  # editMessageMedia
    EDIT_MESSAGE_REPLY_MARKUP = Item()  # editMessageReplyMarkup
    STOP_POLL = Item()  # stopPoll
    DELETE_MESSAGE = Item()  # deleteMessage

    # Stickers
    SEND_STICKER = Item()  # sendSticker
    GET_STICKER_SET = Item()  # getStickerSet
    UPLOAD_STICKER_FILE = Item()  # uploadStickerFile
    GET_CUSTOM_EMOJI_STICKERS = Item()  # getCustomEmojiStickers
    CREATE_NEW_STICKER_SET = Item()  # createNewStickerSet
    ADD_STICKER_TO_SET = Item()  # addStickerToSet
    SET_STICKER_POSITION_IN_SET = Item()  # setStickerPositionInSet
    DELETE_STICKER_FROM_SET = Item()  # deleteStickerFromSet
    SET_STICKER_SET_THUMB = Item()  # setStickerSetThumb

    # Inline mode
    ANSWER_INLINE_QUERY = Item()  # answerInlineQuery

    ANSWER_WEB_APP_QUERY = Item()  # answerWebAppQuery
    SET_CHAT_MENU_BUTTON = Item()  # setChatMenuButton
    GET_CHAT_MENU_BUTTON = Item()  # getChatMenuButton

    SET_MY_DEFAULT_ADMINISTRATOR_RIGHTS = Item()  # setMyDefaultAdministratorRights
    GET_MY_DEFAULT_ADMINISTRATOR_RIGHTS = Item()  # getMyDefaultAdministratorRights

    # Payments
    SEND_INVOICE = Item()  # sendInvoice
    CREATE_INVOICE_LINK = Item()  # createInvoiceLink
    ANSWER_SHIPPING_QUERY = Item()  # answerShippingQuery
    ANSWER_PRE_CHECKOUT_QUERY = Item()  # answerPreCheckoutQuery

    # Telegram Passport
    SET_PASSPORT_DATA_ERRORS = Item()  # setPassportDataErrors

    # Games
    SEND_GAME = Item()  # sendGame
    SET_GAME_SCORE = Item()  # setGameScore
    GET_GAME_HIGH_SCORES = Item()  # getGameHighScores