├── .bumpversion.cfg ├── .gitignore ├── CHANGELOG.md ├── INSTALL.md ├── LICENSE ├── Makefile ├── README.md ├── code_generation ├── code_generator.py ├── code_generator_classes.py ├── code_generator_online.py ├── code_generator_settings.py ├── code_generator_template.py ├── code_generator_template_filters.py ├── helper │ └── send_functions_args_compare.py ├── output │ ├── api.html │ ├── api.py │ ├── pytgbot │ │ └── api_types │ │ │ ├── __init__.py │ │ │ ├── __init__.pyi │ │ │ ├── receivable │ │ │ ├── __init__.py │ │ │ ├── __init__.pyi │ │ │ ├── game.py │ │ │ ├── game.pyi │ │ │ ├── inline.py │ │ │ ├── inline.pyi │ │ │ ├── media.py │ │ │ ├── media.pyi │ │ │ ├── passport.py │ │ │ ├── passport.pyi │ │ │ ├── payments.py │ │ │ ├── payments.pyi │ │ │ ├── peer.py │ │ │ ├── peer.pyi │ │ │ ├── responses.py │ │ │ ├── responses.pyi │ │ │ ├── service.py │ │ │ ├── service.pyi │ │ │ ├── stickers.py │ │ │ ├── stickers.pyi │ │ │ ├── updates.py │ │ │ └── updates.pyi │ │ │ └── sendable │ │ │ ├── __init__.py │ │ │ ├── __init__.pyi │ │ │ ├── command.py │ │ │ ├── command.pyi │ │ │ ├── inline.py │ │ │ ├── inline.pyi │ │ │ ├── input_media.py │ │ │ ├── input_media.pyi │ │ │ ├── passport.py │ │ │ ├── passport.pyi │ │ │ ├── payments.py │ │ │ ├── payments.pyi │ │ │ ├── reply_markup.py │ │ │ └── reply_markup.pyi │ ├── teleflask_messages.py │ └── telegram_bot_api_server │ │ └── generated │ │ ├── funcs.py │ │ └── models.py ├── pytgbot-generator-patch.patch ├── pytgbot-generator.diff.sh ├── pytgbot-generator.patch.sh └── templates │ ├── bot.template │ ├── bot_base.template │ ├── class.template │ ├── classfile.template │ ├── func.template │ ├── macros.template │ ├── pony.class.template │ ├── pony.classfile.template │ ├── teleflask_messages_file.template │ ├── telegram_bot_api_server │ ├── classes.template │ ├── fastapi_issue_884_workaround.template │ └── funcs.template │ ├── typehints.template │ └── typehintsfile.template ├── docs └── source │ ├── _templates │ └── autoapi │ │ └── sorted.rst │ ├── conf.py │ ├── index.rst │ └── plantuml.8042.jar ├── examples ├── cli.py ├── inline.py ├── inline_keyboard.py ├── keyboard_text.py ├── mlfw.py ├── native_objects.py ├── ping_bot.py └── post_profile_pictures.py ├── pytgbot ├── __init__.py ├── api_types │ ├── __init__.py │ ├── __init__.pyi │ ├── receivable │ │ ├── __init__.py │ │ ├── __init__.pyi │ │ ├── game.py │ │ ├── game.pyi │ │ ├── inline.py │ │ ├── inline.pyi │ │ ├── media.py │ │ ├── media.pyi │ │ ├── passport.py │ │ ├── passport.pyi │ │ ├── payments.py │ │ ├── payments.pyi │ │ ├── peer.py │ │ ├── peer.pyi │ │ ├── responses.py │ │ ├── responses.pyi │ │ ├── service.py │ │ ├── service.pyi │ │ ├── stickers.py │ │ ├── stickers.pyi │ │ ├── updates.py │ │ └── updates.pyi │ └── sendable │ │ ├── __init__.py │ │ ├── __init__.pyi │ │ ├── command.py │ │ ├── command.pyi │ │ ├── files.py │ │ ├── files.pyi │ │ ├── inline.py │ │ ├── inline.pyi │ │ ├── input_media.py │ │ ├── input_media.pyi │ │ ├── passport.py │ │ ├── passport.pyi │ │ ├── payments.py │ │ ├── payments.pyi │ │ ├── reply_markup.py │ │ └── reply_markup.pyi ├── bot │ ├── __init__.py │ ├── asynchronous.py │ ├── base.py │ └── synchronous.py ├── exceptions.py ├── extra │ ├── __init__.py │ └── bot_response.py ├── other │ └── webhook_simpleserver.py └── webhook.py ├── requirements.txt ├── result_parser.py ├── setup.py └── tests ├── __init__.py ├── api_types ├── __init__.py ├── receivable │ ├── __init__.py │ └── test_update.py ├── sendable │ ├── __init__.py │ ├── test_files.py │ └── test_input_media.py └── test_init.py ├── test_bot.py └── test_bot_parsing.py /.bumpversion.cfg: -------------------------------------------------------------------------------- 1 | [bumpversion] 2 | current_version = 5.7 3 | commit = True 4 | tag = True 5 | parse = (?P\d+)\.(?P\d+)(\.(?P\d+)(\.(?Pdev|post|aaaa)(?P\d+))?)? 6 | serialize = 7 | {major}.{minor}.{patch}.{release}{internal} 8 | {major}.{minor}.{patch}.{internal} 9 | {major}.{minor}.{patch} 10 | {major}.{minor} 11 | 12 | [bumpversion:part:release] 13 | optional_value = aaaa 14 | values = 15 | dev 16 | aaaa 17 | post 18 | 19 | [bumpversion:file:pytgbot/__init__.py] 20 | 21 | [bumpversion:file:setup.py] 22 | 23 | [bumpversion:file:README.md] 24 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # secret files 2 | somewhere.py 3 | secrets.py 4 | 5 | 6 | # Byte-compiled / optimized / DLL files 7 | __pycache__/ 8 | *.py[cod] 9 | *$py.class 10 | 11 | # C extensions 12 | *.so 13 | 14 | # Distribution / packaging 15 | .Python 16 | env/ 17 | build/ 18 | develop-eggs/ 19 | dist/ 20 | downloads/ 21 | eggs/ 22 | .eggs/ 23 | lib/ 24 | lib64/ 25 | parts/ 26 | sdist/ 27 | var/ 28 | *.egg-info/ 29 | .installed.cfg 30 | *.egg 31 | 32 | # PyInstaller 33 | # Usually these files are written by a python script from a template 34 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 35 | *.manifest 36 | *.spec 37 | 38 | # Installer logs 39 | pip-log.txt 40 | pip-delete-this-directory.txt 41 | 42 | # Unit test / coverage reports 43 | htmlcov/ 44 | .tox/ 45 | .coverage 46 | .coverage.* 47 | .cache 48 | nosetests.xml 49 | coverage.xml 50 | *,cover 51 | .hypothesis/ 52 | 53 | # Translations 54 | *.mo 55 | *.pot 56 | 57 | # Django stuff: 58 | *.log 59 | local_settings.py 60 | 61 | # Flask stuff: 62 | instance/ 63 | .webassets-cache 64 | 65 | # Scrapy stuff: 66 | .scrapy 67 | 68 | # Sphinx documentation 69 | docs/_build/ 70 | 71 | # PyBuilder 72 | target/ 73 | 74 | # IPython Notebook 75 | .ipynb_checkpoints 76 | 77 | # pyenv 78 | .python-version 79 | 80 | # celery beat schedule file 81 | celerybeat-schedule 82 | 83 | # dotenv 84 | .env 85 | 86 | # virtualenv 87 | venv/ 88 | ENV/ 89 | *.venv/ 90 | 91 | # Spyder project settings 92 | .spyderproject 93 | 94 | # Rope project settings 95 | .ropeproject -------------------------------------------------------------------------------- /INSTALL.md: -------------------------------------------------------------------------------- 1 | # Normal install 2 | ```sh 3 | pip3 install pytgbot 4 | ``` 5 | 6 | # Manual install: 7 | ```sh 8 | sudo apt-get install python3-pip 9 | sudo apt-get install libffi-dev 10 | sudo apt-get install libbz2-dev 11 | sudo apt-get install libncursesw5-dev 12 | sudo pip3 install virtualenv 13 | 14 | virtualenv -p /usr/bin/python3 virtualenv3.venv 15 | source virtualenv3.venv 16 | 17 | git clone https://github.com/luckydonald/pytgbot.git 18 | cd pytgbot 19 | python setup.py install 20 | ``` -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | - MIT - 2 | 3 | Copyright (c) 2014-2020 luckydonald 4 | 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | MY_VAR := ${shell python -c 'from pytgbot import VERSION as v; print(v)'} 2 | 3 | clean: 4 | rm -rf *.so *.egg-info build *.png *.log *.svg 5 | 6 | upload: clean 7 | python setup.py sdist 8 | @echo UPLOADING VERSION $(MY_VAR) 9 | twine upload dist/pytgbot-${MY_VAR}.tar.gz 10 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # pytgbot - Telegram Bot API [`5`.`7`](https://core.telegram.org/bots/api) 2 | ### Version [5.7 (stable)](https://github.com/luckydonald/pytgbot/blob/master/CHANGELOG.md#changelog) [![Join pytgbot group on telegram](https://img.shields.io/badge/Telegram%20Group-Join-blue.svg)](https://telegram.me/pytg_group) 3 | ###### Python module to access the telegram bot api. 4 | 5 | Native python package with a pure Python interface for the [Telegram Bot API](https://core.telegram.org/bots). 6 | > The code is generated directly from the API documentation, meaning up-to-date code is a matter of minutes. 7 | 8 | # Recent changes 9 | - Updated with official API changes 10 | 11 | [Older changes...](CHANGELOG.md) 12 | 13 | # Are you using pytgbot? 14 | 15 | If you're using this library to build your Telegram Bots, We'd love to know and share the bot with the world. 16 | Tell us about it - **[here](https://github.com/luckydonald/pytgbot/wiki/Who's-using-pytgbot%3F)** 17 | 18 | Check out the [Who's using pytgbot](https://github.com/luckydonald/pytgbot/wiki/Who's-using-pytgbot%3F) wiki page to know more about what people have been building with this library. 19 | 20 | # Installation 21 | ### Releases 22 | Released versions can be found at several locations: 23 | - The [python package index](https://pypi.org/project/pytgbot/#history) (`pip install`), 24 | - on GitHub in the [release section](https://github.com/luckydonald/pytgbot/releases) 25 | - and in the git files as regular tags. 26 | 27 | #### Latest Stable 28 | The [latest version](#releases) seems to be version `4.9.1`. For other releases you must adapt the examples below. 29 | 30 | ##### Python package index (recommended) 31 | ```sh 32 | pip install pytgbot==4.9.1 33 | ``` 34 | 35 | ##### Manually 36 | ```sh 37 | git clone -b v4.9.1 https://github.com/luckydonald/pytgbot.git 38 | cd pytgbot 39 | python setup.py install 40 | ``` 41 | 42 | #### Bleeding edge 43 | To get the most current code manually 44 | ``` 45 | git clone https://github.com/luckydonald/pytgbot.git 46 | cd pytgbot 47 | python setup.py install 48 | ``` 49 | 50 | # Updating 51 | 52 | #### Latest Stable 53 | The [latest version](#releases) seems to be version `4.9.1`. For other releases you must adapt the examples below. 54 | 55 | ##### Python package index (recommended) 56 | ```sh 57 | pip install -U pytgbot==4.9.1 58 | ``` 59 | 60 | ##### Manually 61 | ```sh 62 | cd pytgbot 63 | git fetch 64 | git checkout v4.9.1 65 | python setup.py install 66 | ``` 67 | 68 | #### Bleeding edge 69 | To get the most current code manually 70 | ``` 71 | cd pytgbot 72 | git fetch 73 | git checkout master 74 | git pull 75 | python setup.py install 76 | ``` 77 | 78 | 79 | # Usage 80 | 81 | ```python 82 | from pytgbot import Bot 83 | 84 | API_KEY='123456:ABC-DEF1234ghIkl-zyx57W2v1u123ew11' # change this to the token you get from @BotFather 85 | CHAT='@username' # can be a @username or a id, change this to your own @username or id for example. 86 | 87 | bot = Bot(API_KEY) 88 | 89 | # sending messages: 90 | bot.send_message(CHAT, "Example Text!") 91 | 92 | # getting events: 93 | for x in bot.get_updates(): 94 | print(x) 95 | 96 | ``` 97 | 98 | All the functions can be found in the `Bot` class in the [pytgbot/bot.py](https://github.com/luckydonald/pytgbot/blob/master/pytgbot/bot.py) file. 99 | They are pythonic in small letters with underscores instead of camel case, for example [getUpdates](https://core.telegram.org/bots/api#getupdates) is `bot.get_updates()`. 100 | ## Documentation 101 | You can always inspect the documentation inside the code. 102 | You can also use the python `help()` command in the interactive interpreter: 103 | ```py 104 | >>> from pytgbot import Bot 105 | >>> help(Bot) 106 | >>> # or 107 | >>> help(Bot.get_updates) 108 | ``` 109 | 110 | ## Custom servers 111 | If you want to use a custom Telegram API server, e.g. https://telegram.rest/ it is as simple as setting `base_url` and `download_url` when creating the bot: 112 | ```py 113 | bot = Bot(API_KEY, base_url="https://telegram.rest/bot{api_key}/{command}", download_url="https://telegram.rest/file/bot{api_key}/{file}") 114 | ``` 115 | 116 | ## Examples 117 | Have a look into the [examples](https://github.com/luckydonald/pytgbot/tree/master/examples) folder. 118 | 119 | # In case of errors 120 | First you should set logging to level `DEBUG` to see what's going on. 121 | ```py 122 | # add this to the first lines in your file 123 | import logging 124 | logging.basicConfig(level=logging.DEBUG) 125 | ``` 126 | 127 | If you are about to open a new issue, search the existing ones (open and closed) first. 128 | Sometimes they are already reported or even solved. 129 | 130 | # Note for maintainers of this package: 131 | Best way to apply changes is to create a patch from the commit containing the new generated files `output`. 132 | 133 | ``` 134 | git apply --verbose --reject --no-index -C1 --whitespace=fix --ignore-space-change --ignore-whitespace --directory pytgbot/ NAME.patch 135 | find ./pytgbot/ -type f -name '*.rej' | xargs --replace={} --max-args=1 echo "{} {}" 136 | find ./pytgbot/ -type f -name '*.rej' | xargs --replace={} --max-args=1 bash -c 'file="{}";file="${file%.*}"; echo wiggle --replace ${file} {};' 137 | wiggle --replace pytgbot 138 | ``` 139 | You may need to install wiggle: `brew install wiggle` 140 | _See also https://stackoverflow.com/questions/4770177/git-patch-does-not-apply_ 141 | -------------------------------------------------------------------------------- /code_generation/code_generator_template_filters.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | from typing import Match 4 | import re 5 | 6 | from luckydonaldUtils.logger import logging 7 | 8 | __author__ = 'luckydonald' 9 | 10 | logger = logging.getLogger(__name__) 11 | if __name__ == '__main__': 12 | logging.add_colored_handler(level=logging.DEBUG) 13 | # end if 14 | 15 | 16 | SPLIT_LINE_REGEX = re.compile(r'(?P:|\.|!|\?)(?P[\r\t\f\v ]+)') 17 | 18 | 19 | def split_line_regex_replacer(match: Match) -> str: 20 | return match.group('char') + len(match.group('whitespace')) * "\n" 21 | # end def 22 | 23 | 24 | # noinspection PyShadowingBuiltins 25 | def add_linebreaks(input: str) -> str: 26 | return SPLIT_LINE_REGEX.sub(split_line_regex_replacer, input) 27 | # end def 28 | -------------------------------------------------------------------------------- /code_generation/helper/send_functions_args_compare.py: -------------------------------------------------------------------------------- 1 | from code_generator_online import load_api_definitions 2 | from code_generator_classes import Function 3 | from itertools import permutations 4 | 5 | folder, html_document, results = load_api_definitions() 6 | 7 | sendable = [f for f in results if isinstance(f, Function) and f.api_name.startswith('send')] 8 | 9 | funcs = {s.name: set([v.name for v in s.variables]) for s in sendable} 10 | 11 | print(funcs) 12 | 13 | table = [] 14 | 15 | for colum_name in funcs.keys(): 16 | table.append([colum_name, len(colum_name)]) 17 | # end for 18 | 19 | TITLE_DATA=0 20 | LENGTH_DATA=1 21 | 22 | for i, func_args in enumerate(funcs.values()): 23 | for arg in func_args: 24 | table[i].append(arg) 25 | table[i][LENGTH_DATA] = max(table[i][LENGTH_DATA], len(arg)) 26 | # end if 27 | # end for 28 | 29 | # order table, least arguments first 30 | 31 | print(table) 32 | 33 | table.sort(key=len) 34 | 35 | print(table) 36 | 37 | rows = [] 38 | title_row_string = "" 39 | space_row_string = "" 40 | for cols in table: 41 | title = cols[TITLE_DATA] 42 | length = cols[LENGTH_DATA] 43 | title_row_string += " {t:^{l}} |".format(t=title, l=length) 44 | space_row_string += " "+ ("-"*length) + " |" 45 | # end for 46 | rows.append(title_row_string) 47 | rows.append(space_row_string) 48 | 49 | max_rows = max(max(len(c) for c in table), 0) # lenth of data (first 2 rows are title and border) 50 | 51 | print("{i} rows".format(i=max_rows)) 52 | 53 | for i in range(2, max_rows + 2): # (first 2 rows are title and border) 54 | row_string = "" 55 | for col in table: 56 | length = col[LENGTH_DATA] 57 | if len(col) > i: 58 | value = col[i] 59 | row_string += " {t:^{l}} |".format(t=value, l=length) 60 | else: # we are out of content 61 | # produce empty row, to keep structure 62 | row_string += (" " * (length + 2)) + "|" 63 | # end if 64 | # end for 65 | rows.append(row_string) 66 | # end for 67 | 68 | table_string = "\n".join(rows) 69 | 70 | print("TABLE:\n") 71 | print(table_string) 72 | 73 | 74 | from luckydonaldUtils.interactions import confirm 75 | from luckydonaldUtils.encoding import to_binary as b 76 | 77 | if confirm('save as file & open in browser?'): 78 | import tempfile 79 | import webbrowser 80 | from html import escape 81 | with tempfile.NamedTemporaryFile(suffix='.html', delete=False) as f: 82 | f.write(b("
"))
 83 |         f.write(b(escape(table_string)))
 84 |         f.write(b("
")) 85 | f.flush() 86 | print(f.name) 87 | webbrowser.open('file://'+f.name) 88 | # end with 89 | # end if 90 | 91 | 92 | possibilities = permutations(funcs.keys(), len(funcs)) 93 | #for order in possibilities: 94 | 95 | 96 | 97 | """ 98 | a | b | c | d 99 | - | - | - | - 100 | 1 | 1 | 1 | 1 101 | 2 | 2 | | 2 102 | 3 | | 3 | 3 103 | """ 104 | 105 | """ 106 | s | a | b | c | d | s | 107 | - | - | - | - | - | -- | 108 | | 1 | 1 | 1 | 1 | | 109 | | 2 | 2 | | 2 | | 110 | | 3 | | 3 | 3 | | 111 | - | - | - | - | - | -- | 112 | 0 | 3 | 2 | 2 | 3 | 10 | 113 | """ 114 | """ 115 | s | a | b | c | d | s | 116 | - | - | - | - | - | -- | 117 | 1 | x | x | x | x | | 118 | | 2 | 2 | | 2 | | 119 | | 3 | | 3 | 3 | | 120 | - | - | - | - | - | -- | 121 | 1 | 2 | 1 | 1 | 2 | 7 | 122 | """ 123 | 124 | """ 125 | s | b | c | a | d | s | 126 | - | - | - | - | - | -- | 127 | 1 | x | x | x | x | | 128 | | 2 | | 2 | x | | 129 | | | 3 | 3 | x | | 130 | - | - | - | - | - | -- | 131 | 1 | 1 | 1 | 2 | 0 | 5 | 132 | """ 133 | 134 | """ 135 | s | a | b | c | d | s | 136 | - | - | - | - | - | -- | 137 | | 1 | 1 | 1 | 1 | | 138 | | 2 | 2 | | 2 | | 139 | | 3 | | 3 | 3 | | 140 | - | - | - | - | - | -- | 141 | 0 | 3 | 2 | 2 | 3 | 10 | 142 | """ 143 | 144 | -------------------------------------------------------------------------------- /code_generation/output/pytgbot/api_types/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from luckydonaldUtils.encoding import unicode_type, to_unicode as u 3 | from luckydonaldUtils.exceptions import assert_type_or_raise 4 | __author__ = 'luckydonald' 5 | __all__ = [ 6 | 'TgBotApiObject', 7 | ] 8 | 9 | 10 | class TgBotApiObject(object): 11 | """ 12 | Base class for every api object class. 13 | 14 | Optional keyword parameters: 15 | 16 | :param _raw: Optional. Original data this object was generated from. Could be `None`. 17 | :type _raw: None | dict 18 | """ 19 | 20 | def __init__(self): 21 | self._raw = None 22 | super(TgBotApiObject, self).__init__() 23 | # end def __init__ 24 | 25 | def to_array(self): 26 | array = dict() 27 | return array 28 | # end def to_array 29 | 30 | @staticmethod 31 | def from_array(array): 32 | if not array: 33 | return None 34 | return {} 35 | # end def 36 | 37 | # # # # # # # # # # # # # # 38 | # helper functions below # 39 | # # # # # # # # # # # # # 40 | 41 | @classmethod 42 | def from_array_list(cls, result, list_level): 43 | """ 44 | Tries to parse the `result` as type given in `required_type`, while traversing into lists as often as specified in `list_level`. 45 | 46 | :param cls: Type as what it should be parsed as. Can be any class extending :class:`TgBotApiObject`. 47 | E.g. If you call `Class.from_array_list`, it will automatically be set to `Class`. 48 | :type cls: class 49 | 50 | :param result: The result to parse 51 | 52 | :param list_level: "list of" * list_level 53 | :type list_level: int 54 | 55 | :return: the result as `required_type` type 56 | """ 57 | return from_array_list(cls, result, list_level, is_builtin=False) # the one below, not itself. Yes, same name... 58 | # end def from_array_list 59 | 60 | @staticmethod 61 | def _builtin_from_array_list(required_type, value, list_level): 62 | """ 63 | Helper method to make :func:`from_array_list` available to all classes extending this, 64 | without the need for additional imports. 65 | 66 | :param required_type: Type as what it should be parsed as. Any builtin. 67 | :param value: The result to parse 68 | :param list_level: "list of" * list_level 69 | :return: 70 | """ 71 | return from_array_list(required_type, value, list_level, is_builtin=True) 72 | # end def _builtin_from_array_list 73 | 74 | @staticmethod 75 | def _as_array(obj): 76 | """ 77 | Helper method to make :func:`as_array` available to all classes extending this, 78 | without the need for additional imports. 79 | """ 80 | return as_array(obj) 81 | # end def 82 | 83 | def __setattr__(self, key, value): 84 | """ 85 | Remove `self._raw` if any other value is set. 86 | """ 87 | super(TgBotApiObject, self).__setattr__(key, value) 88 | if not key.startswith('_') and hasattr(self, '_raw') and self._raw is not None: 89 | self._raw = None 90 | # end if 91 | # end def 92 | # end class TgBotApiObject 93 | -------------------------------------------------------------------------------- /code_generation/output/pytgbot/api_types/__init__.pyi: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from luckydonaldUtils.exceptions import assert_type_or_raise 3 | from luckydonaldUtils.encoding import unicode_type, to_unicode as u 4 | from typing import Any, Union, List 5 | __author__ = 'luckydonald' 6 | 7 | 8 | class TgBotApiObject(object): 9 | """ 10 | Base class for every api object class. 11 | 12 | Optional keyword parameters: 13 | 14 | :param _raw: Optional. Original data this object was generated from. Could be `None`. 15 | :type _raw: None | dict 16 | """ 17 | # end class TgBotApiObject 18 | -------------------------------------------------------------------------------- /code_generation/output/pytgbot/api_types/receivable/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from luckydonaldUtils.encoding import unicode_type, to_unicode as u 3 | from luckydonaldUtils.exceptions import assert_type_or_raise 4 | from . import TgBotApiObject 5 | 6 | __author__ = 'luckydonald' 7 | __all__ = [ 8 | 'Receivable', 9 | 'Result', 10 | ] 11 | 12 | 13 | class Receivable(TgBotApiObject): 14 | """ 15 | Base class for all classes for stuff which telegram sends us. 16 | 17 | Optional keyword parameters: 18 | 19 | :param _raw: Optional. Original data this object was generated from. Could be `None`. 20 | :type _raw: None | dict 21 | """ 22 | 23 | pass 24 | # end class Receivable 25 | 26 | 27 | class Result(Receivable): 28 | """ 29 | Base class for all classes for stuff which we throw in requests towards the telegram servers. 30 | 31 | Optional keyword parameters: 32 | 33 | :param _raw: Optional. Original data this object was generated from. Could be `None`. 34 | :type _raw: None | dict 35 | """ 36 | 37 | pass 38 | # end class Result 39 | -------------------------------------------------------------------------------- /code_generation/output/pytgbot/api_types/receivable/__init__.pyi: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from luckydonaldUtils.exceptions import assert_type_or_raise 3 | from luckydonaldUtils.encoding import unicode_type, to_unicode as u 4 | from typing import Any, Union, List 5 | from pytgbot.api_types import TgBotApiObject 6 | from pytgbot.api_types.receivable import Receivable 7 | 8 | __author__ = 'luckydonald' 9 | 10 | 11 | class Receivable(TgBotApiObject): 12 | """ 13 | Base class for all classes for stuff which telegram sends us. 14 | 15 | Optional keyword parameters: 16 | 17 | :param _raw: Optional. Original data this object was generated from. Could be `None`. 18 | :type _raw: None | dict 19 | """ 20 | # end class Receivable 21 | 22 | class Result(Receivable): 23 | """ 24 | Base class for all classes for stuff which we throw in requests towards the telegram servers. 25 | 26 | Optional keyword parameters: 27 | 28 | :param _raw: Optional. Original data this object was generated from. Could be `None`. 29 | :type _raw: None | dict 30 | """ 31 | # end class Result 32 | -------------------------------------------------------------------------------- /code_generation/output/pytgbot/api_types/receivable/game.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from luckydonaldUtils.encoding import unicode_type, to_unicode as u 3 | from luckydonaldUtils.exceptions import assert_type_or_raise 4 | from . import Result 5 | 6 | __author__ = 'luckydonald' 7 | __all__ = [ 8 | 'GameHighScore', 9 | ] 10 | 11 | 12 | class GameHighScore(Result): 13 | """ 14 | This object represents one row of the high scores table for a game. 15 | 16 | https://core.telegram.org/bots/api#gamehighscore 17 | 18 | 19 | Parameters: 20 | 21 | :param position: Position in high score table for the game 22 | :type position: int 23 | 24 | :param user: User 25 | :type user: pytgbot.api_types.receivable.peer.User 26 | 27 | :param score: Score 28 | :type score: int 29 | 30 | 31 | Optional keyword parameters: 32 | 33 | :param _raw: Optional. Original data this object was generated from. Could be `None`. 34 | :type _raw: None | dict 35 | """ 36 | 37 | def __init__(self, position, user, score, _raw=None): 38 | """ 39 | This object represents one row of the high scores table for a game. 40 | 41 | https://core.telegram.org/bots/api#gamehighscore 42 | 43 | 44 | Parameters: 45 | 46 | :param position: Position in high score table for the game 47 | :type position: int 48 | 49 | :param user: User 50 | :type user: pytgbot.api_types.receivable.peer.User 51 | 52 | :param score: Score 53 | :type score: int 54 | 55 | 56 | Optional keyword parameters: 57 | 58 | :param _raw: Optional. Original data this object was generated from. Could be `None`. 59 | :type _raw: None | dict 60 | """ 61 | super(GameHighScore, self).__init__() 62 | from .peer import User 63 | assert_type_or_raise(position, int, parameter_name="position") 64 | self.position = position 65 | assert_type_or_raise(user, User, parameter_name="user") 66 | self.user = user 67 | assert_type_or_raise(score, int, parameter_name="score") 68 | self.score = score 69 | 70 | self._raw = _raw 71 | # end def __init__ 72 | 73 | def to_array(self, prefer_original=False): 74 | """ 75 | Serializes this GameHighScore to a dictionary. 76 | 77 | :param prefer_original: If we should return the data this was constructed with if available. If it's not available, it will be constructed normally from the data of the object. 78 | :type prefer_original: bool 79 | 80 | :return: dictionary representation of this object. 81 | :rtype: dict 82 | """ 83 | if prefer_original and self._raw: 84 | return self._raw 85 | # end if 86 | 87 | from .peer import User 88 | array = super(GameHighScore, self).to_array() 89 | 90 | array['position'] = int(self.position) # type int 91 | array['user'] = self.user.to_array() # type User 92 | array['score'] = int(self.score) # type int 93 | return array 94 | # end def to_array 95 | 96 | @staticmethod 97 | def validate_array(array): 98 | """ 99 | Builds a new array with valid values for the GameHighScore constructor. 100 | 101 | :return: new array with valid values 102 | :rtype: dict 103 | """ 104 | assert_type_or_raise(array, dict, parameter_name="array") 105 | from .peer import User 106 | data = Result.validate_array(array) 107 | data['position'] = int(array.get('position')) 108 | data['user'] = User.from_array(array.get('user')) 109 | data['score'] = int(array.get('score')) 110 | return data 111 | # end def validate_array 112 | 113 | @staticmethod 114 | def from_array(array): 115 | """ 116 | Deserialize a new GameHighScore from a given dictionary. 117 | 118 | :return: new GameHighScore instance. 119 | :rtype: GameHighScore 120 | """ 121 | if not array: # None or {} 122 | return None 123 | # end if 124 | 125 | data = GameHighScore.validate_array(array) 126 | data['_raw'] = array 127 | return GameHighScore(**data) 128 | # end def from_array 129 | 130 | def __str__(self): 131 | """ 132 | Implements `str(gamehighscore_instance)` 133 | """ 134 | return "GameHighScore(position={self.position!r}, user={self.user!r}, score={self.score!r})".format(self=self) 135 | # end def __str__ 136 | 137 | def __repr__(self): 138 | """ 139 | Implements `repr(gamehighscore_instance)` 140 | """ 141 | if self._raw: 142 | return "GameHighScore.from_array({self._raw})".format(self=self) 143 | # end if 144 | return "GameHighScore(position={self.position!r}, user={self.user!r}, score={self.score!r})".format(self=self) 145 | # end def __repr__ 146 | 147 | def __contains__(self, key): 148 | """ 149 | Implements `"key" in gamehighscore_instance` 150 | """ 151 | return ( 152 | key in ["position", "user", "score"] 153 | and hasattr(self, key) 154 | and bool(getattr(self, key, None)) 155 | ) 156 | # end def __contains__ 157 | # end class GameHighScore 158 | -------------------------------------------------------------------------------- /code_generation/output/pytgbot/api_types/receivable/game.pyi: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from luckydonaldUtils.exceptions import assert_type_or_raise 3 | from luckydonaldUtils.encoding import unicode_type, to_unicode as u 4 | from typing import Any, Union, List 5 | from pytgbot.api_types.receivable import Result 6 | 7 | __author__ = 'luckydonald' 8 | 9 | 10 | class GameHighScore(Result): 11 | """ 12 | This object represents one row of the high scores table for a game. 13 | 14 | https://core.telegram.org/bots/api#gamehighscore 15 | 16 | 17 | Parameters: 18 | 19 | :param position: Position in high score table for the game 20 | :type position: int 21 | 22 | :param user: User 23 | :type user: pytgbot.api_types.receivable.peer.User 24 | 25 | :param score: Score 26 | :type score: int 27 | 28 | 29 | Optional keyword parameters: 30 | 31 | :param _raw: Optional. Original data this object was generated from. Could be `None`. 32 | :type _raw: None | dict 33 | """ 34 | position: int 35 | user: User 36 | score: int 37 | # end class GameHighScore 38 | -------------------------------------------------------------------------------- /code_generation/output/pytgbot/api_types/receivable/inline.pyi: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from luckydonaldUtils.exceptions import assert_type_or_raise 3 | from luckydonaldUtils.encoding import unicode_type, to_unicode as u 4 | from typing import Any, Union, List 5 | from pytgbot.api_types.receivable import Result 6 | from pytgbot.api_types.receivable.updates import UpdateType 7 | 8 | __author__ = 'luckydonald' 9 | 10 | 11 | class InlineQuery(Result): 12 | """ 13 | This object represents an incoming inline query. When the user sends an empty query, your bot could return some default or trending results. 14 | 15 | https://core.telegram.org/bots/api#inlinequery 16 | 17 | 18 | Parameters: 19 | 20 | :param id: Unique identifier for this query 21 | :type id: str|unicode 22 | 23 | :param from_peer: Sender 24 | :type from_peer: pytgbot.api_types.receivable.peer.User 25 | 26 | :param query: Text of the query (up to 256 characters) 27 | :type query: str|unicode 28 | 29 | :param offset: Offset of the results to be returned, can be controlled by the bot 30 | :type offset: str|unicode 31 | 32 | 33 | Optional keyword parameters: 34 | 35 | :param chat_type: Optional. Type of the chat, from which the inline query was sent. Can be either "sender" for a private chat with the inline query sender, "private", "group", "supergroup", or "channel". The chat type should be always known for requests sent from official clients and most third-party clients, unless the request was sent from a secret chat 36 | :type chat_type: str|unicode 37 | 38 | :param location: Optional. Sender location, only for bots that request user location 39 | :type location: pytgbot.api_types.receivable.media.Location 40 | 41 | :param _raw: Optional. Original data this object was generated from. Could be `None`. 42 | :type _raw: None | dict 43 | """ 44 | id: str 45 | from_peer: User 46 | query: str 47 | offset: str 48 | chat_type: str 49 | location: Location 50 | # end class InlineQuery 51 | 52 | class ChosenInlineResult(UpdateType): 53 | """ 54 | Represents a result of an inline query that was chosen by the user and sent to their chat partner. 55 | Note: It is necessary to enable inline feedback via @Botfather in order to receive these objects in updates. 56 | 57 | https://core.telegram.org/bots/api#choseninlineresult 58 | 59 | 60 | Parameters: 61 | 62 | :param result_id: The unique identifier for the result that was chosen 63 | :type result_id: str|unicode 64 | 65 | :param from_peer: The user that chose the result 66 | :type from_peer: pytgbot.api_types.receivable.peer.User 67 | 68 | :param query: The query that was used to obtain the result 69 | :type query: str|unicode 70 | 71 | 72 | Optional keyword parameters: 73 | 74 | :param location: Optional. Sender location, only for bots that require user location 75 | :type location: pytgbot.api_types.receivable.media.Location 76 | 77 | :param inline_message_id: Optional. Identifier of the sent inline message. Available only if there is an inline keyboard attached to the message. Will be also received in callback queries and can be used to edit the message. 78 | :type inline_message_id: str|unicode 79 | 80 | :param _raw: Optional. Original data this object was generated from. Could be `None`. 81 | :type _raw: None | dict 82 | """ 83 | result_id: str 84 | from_peer: User 85 | query: str 86 | location: Location 87 | inline_message_id: str 88 | # end class ChosenInlineResult 89 | -------------------------------------------------------------------------------- /code_generation/output/pytgbot/api_types/receivable/passport.pyi: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from luckydonaldUtils.exceptions import assert_type_or_raise 3 | from luckydonaldUtils.encoding import unicode_type, to_unicode as u 4 | from typing import Any, Union, List 5 | from pytgbot.api_types.receivable import Result 6 | 7 | __author__ = 'luckydonald' 8 | 9 | 10 | class PassportData(Result): 11 | """ 12 | Contains information about Telegram Passport data shared with the bot by the user. 13 | 14 | https://core.telegram.org/bots/api#passportdata 15 | 16 | 17 | Parameters: 18 | 19 | :param data: Array with information about documents and other Telegram Passport elements that was shared with the bot 20 | :type data: list of pytgbot.api_types.receivable.passport.EncryptedPassportElement 21 | 22 | :param credentials: Encrypted credentials required to decrypt the data 23 | :type credentials: pytgbot.api_types.receivable.passport.EncryptedCredentials 24 | 25 | 26 | Optional keyword parameters: 27 | 28 | :param _raw: Optional. Original data this object was generated from. Could be `None`. 29 | :type _raw: None | dict 30 | """ 31 | data: List[EncryptedPassportElement] 32 | credentials: EncryptedCredentials 33 | # end class PassportData 34 | 35 | class PassportFile(Result): 36 | """ 37 | This object represents a file uploaded to Telegram Passport. Currently all Telegram Passport files are in JPEG format when decrypted and don't exceed 10MB. 38 | 39 | https://core.telegram.org/bots/api#passportfile 40 | 41 | 42 | Parameters: 43 | 44 | :param file_id: Identifier for this file, which can be used to download or reuse the file 45 | :type file_id: str|unicode 46 | 47 | :param file_unique_id: Unique identifier for this file, which is supposed to be the same over time and for different bots. Can't be used to download or reuse the file. 48 | :type file_unique_id: str|unicode 49 | 50 | :param file_size: File size in bytes 51 | :type file_size: int 52 | 53 | :param file_date: Unix time when the file was uploaded 54 | :type file_date: int 55 | 56 | 57 | Optional keyword parameters: 58 | 59 | :param _raw: Optional. Original data this object was generated from. Could be `None`. 60 | :type _raw: None | dict 61 | """ 62 | file_id: str 63 | file_unique_id: str 64 | file_size: int 65 | file_date: int 66 | # end class PassportFile 67 | 68 | class EncryptedPassportElement(Result): 69 | """ 70 | Contains information about documents or other Telegram Passport elements shared with the bot by the user. 71 | 72 | https://core.telegram.org/bots/api#encryptedpassportelement 73 | 74 | 75 | Parameters: 76 | 77 | :param type: Element type. One of "personal_details", "passport", "driver_license", "identity_card", "internal_passport", "address", "utility_bill", "bank_statement", "rental_agreement", "passport_registration", "temporary_registration", "phone_number", "email". 78 | :type type: str|unicode 79 | 80 | :param hash: Base64-encoded element hash for using in PassportElementErrorUnspecified 81 | :type hash: str|unicode 82 | 83 | 84 | Optional keyword parameters: 85 | 86 | :param data: Optional. Base64-encoded encrypted Telegram Passport element data provided by the user, available for "personal_details", "passport", "driver_license", "identity_card", "internal_passport" and "address" types. Can be decrypted and verified using the accompanying EncryptedCredentials. 87 | :type data: str|unicode 88 | 89 | :param phone_number: Optional. User's verified phone number, available only for "phone_number" type 90 | :type phone_number: str|unicode 91 | 92 | :param email: Optional. User's verified email address, available only for "email" type 93 | :type email: str|unicode 94 | 95 | :param files: Optional. Array of encrypted files with documents provided by the user, available for "utility_bill", "bank_statement", "rental_agreement", "passport_registration" and "temporary_registration" types. Files can be decrypted and verified using the accompanying EncryptedCredentials. 96 | :type files: list of pytgbot.api_types.receivable.passport.PassportFile 97 | 98 | :param front_side: Optional. Encrypted file with the front side of the document, provided by the user. Available for "passport", "driver_license", "identity_card" and "internal_passport". The file can be decrypted and verified using the accompanying EncryptedCredentials. 99 | :type front_side: pytgbot.api_types.receivable.passport.PassportFile 100 | 101 | :param reverse_side: Optional. Encrypted file with the reverse side of the document, provided by the user. Available for "driver_license" and "identity_card". The file can be decrypted and verified using the accompanying EncryptedCredentials. 102 | :type reverse_side: pytgbot.api_types.receivable.passport.PassportFile 103 | 104 | :param selfie: Optional. Encrypted file with the selfie of the user holding a document, provided by the user; available for "passport", "driver_license", "identity_card" and "internal_passport". The file can be decrypted and verified using the accompanying EncryptedCredentials. 105 | :type selfie: pytgbot.api_types.receivable.passport.PassportFile 106 | 107 | :param translation: Optional. Array of encrypted files with translated versions of documents provided by the user. Available if requested for "passport", "driver_license", "identity_card", "internal_passport", "utility_bill", "bank_statement", "rental_agreement", "passport_registration" and "temporary_registration" types. Files can be decrypted and verified using the accompanying EncryptedCredentials. 108 | :type translation: list of pytgbot.api_types.receivable.passport.PassportFile 109 | 110 | :param _raw: Optional. Original data this object was generated from. Could be `None`. 111 | :type _raw: None | dict 112 | """ 113 | type: str 114 | hash: str 115 | data: str 116 | phone_number: str 117 | email: str 118 | files: List[PassportFile] 119 | front_side: PassportFile 120 | reverse_side: PassportFile 121 | selfie: PassportFile 122 | translation: List[PassportFile] 123 | # end class EncryptedPassportElement 124 | 125 | class EncryptedCredentials(Result): 126 | """ 127 | Contains data required for decrypting and authenticating EncryptedPassportElement. See the Telegram Passport Documentation for a complete description of the data decryption and authentication processes. 128 | 129 | https://core.telegram.org/bots/api#encryptedcredentials 130 | 131 | 132 | Parameters: 133 | 134 | :param data: Base64-encoded encrypted JSON-serialized data with unique user's payload, data hashes and secrets required for EncryptedPassportElement decryption and authentication 135 | :type data: str|unicode 136 | 137 | :param hash: Base64-encoded data hash for data authentication 138 | :type hash: str|unicode 139 | 140 | :param secret: Base64-encoded secret, encrypted with the bot's public RSA key, required for data decryption 141 | :type secret: str|unicode 142 | 143 | 144 | Optional keyword parameters: 145 | 146 | :param _raw: Optional. Original data this object was generated from. Could be `None`. 147 | :type _raw: None | dict 148 | """ 149 | data: str 150 | hash: str 151 | secret: str 152 | # end class EncryptedCredentials 153 | -------------------------------------------------------------------------------- /code_generation/output/pytgbot/api_types/receivable/payments.pyi: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from luckydonaldUtils.exceptions import assert_type_or_raise 3 | from luckydonaldUtils.encoding import unicode_type, to_unicode as u 4 | from typing import Any, Union, List 5 | from pytgbot.api_types.receivable import Result 6 | from pytgbot.api_types.receivable.updates import UpdateType 7 | 8 | __author__ = 'luckydonald' 9 | 10 | 11 | class Invoice(Result): 12 | """ 13 | This object contains basic information about an invoice. 14 | 15 | https://core.telegram.org/bots/api#invoice 16 | 17 | 18 | Parameters: 19 | 20 | :param title: Product name 21 | :type title: str|unicode 22 | 23 | :param description: Product description 24 | :type description: str|unicode 25 | 26 | :param start_parameter: Unique bot deep-linking parameter that can be used to generate this invoice 27 | :type start_parameter: str|unicode 28 | 29 | :param currency: Three-letter ISO 4217 currency code 30 | :type currency: str|unicode 31 | 32 | :param total_amount: Total price in the smallest units of the currency (integer, not float/double). For example, for a price of US$ 1.45 pass amount = 145. See the exp parameter in currencies.json, it shows the number of digits past the decimal point for each currency (2 for the majority of currencies). 33 | :type total_amount: int 34 | 35 | 36 | Optional keyword parameters: 37 | 38 | :param _raw: Optional. Original data this object was generated from. Could be `None`. 39 | :type _raw: None | dict 40 | """ 41 | title: str 42 | description: str 43 | start_parameter: str 44 | currency: str 45 | total_amount: int 46 | # end class Invoice 47 | 48 | class ShippingAddress(Result): 49 | """ 50 | This object represents a shipping address. 51 | 52 | https://core.telegram.org/bots/api#shippingaddress 53 | 54 | 55 | Parameters: 56 | 57 | :param country_code: ISO 3166-1 alpha-2 country code 58 | :type country_code: str|unicode 59 | 60 | :param state: State, if applicable 61 | :type state: str|unicode 62 | 63 | :param city: City 64 | :type city: str|unicode 65 | 66 | :param street_line1: First line for the address 67 | :type street_line1: str|unicode 68 | 69 | :param street_line2: Second line for the address 70 | :type street_line2: str|unicode 71 | 72 | :param post_code: Address post code 73 | :type post_code: str|unicode 74 | 75 | 76 | Optional keyword parameters: 77 | 78 | :param _raw: Optional. Original data this object was generated from. Could be `None`. 79 | :type _raw: None | dict 80 | """ 81 | country_code: str 82 | state: str 83 | city: str 84 | street_line1: str 85 | street_line2: str 86 | post_code: str 87 | # end class ShippingAddress 88 | 89 | class OrderInfo(Result): 90 | """ 91 | This object represents information about an order. 92 | 93 | https://core.telegram.org/bots/api#orderinfo 94 | 95 | Optional keyword parameters: 96 | 97 | :param name: Optional. User name 98 | :type name: str|unicode 99 | 100 | :param phone_number: Optional. User's phone number 101 | :type phone_number: str|unicode 102 | 103 | :param email: Optional. User email 104 | :type email: str|unicode 105 | 106 | :param shipping_address: Optional. User shipping address 107 | :type shipping_address: pytgbot.api_types.receivable.payments.ShippingAddress 108 | 109 | :param _raw: Optional. Original data this object was generated from. Could be `None`. 110 | :type _raw: None | dict 111 | """ 112 | name: str 113 | phone_number: str 114 | email: str 115 | shipping_address: ShippingAddress 116 | # end class OrderInfo 117 | 118 | class SuccessfulPayment(Result): 119 | """ 120 | This object contains basic information about a successful payment. 121 | 122 | https://core.telegram.org/bots/api#successfulpayment 123 | 124 | 125 | Parameters: 126 | 127 | :param currency: Three-letter ISO 4217 currency code 128 | :type currency: str|unicode 129 | 130 | :param total_amount: Total price in the smallest units of the currency (integer, not float/double). For example, for a price of US$ 1.45 pass amount = 145. See the exp parameter in currencies.json, it shows the number of digits past the decimal point for each currency (2 for the majority of currencies). 131 | :type total_amount: int 132 | 133 | :param invoice_payload: Bot specified invoice payload 134 | :type invoice_payload: str|unicode 135 | 136 | :param telegram_payment_charge_id: Telegram payment identifier 137 | :type telegram_payment_charge_id: str|unicode 138 | 139 | :param provider_payment_charge_id: Provider payment identifier 140 | :type provider_payment_charge_id: str|unicode 141 | 142 | 143 | Optional keyword parameters: 144 | 145 | :param shipping_option_id: Optional. Identifier of the shipping option chosen by the user 146 | :type shipping_option_id: str|unicode 147 | 148 | :param order_info: Optional. Order info provided by the user 149 | :type order_info: pytgbot.api_types.receivable.payments.OrderInfo 150 | 151 | :param _raw: Optional. Original data this object was generated from. Could be `None`. 152 | :type _raw: None | dict 153 | """ 154 | currency: str 155 | total_amount: int 156 | invoice_payload: str 157 | telegram_payment_charge_id: str 158 | provider_payment_charge_id: str 159 | shipping_option_id: str 160 | order_info: OrderInfo 161 | # end class SuccessfulPayment 162 | 163 | class ShippingQuery(UpdateType): 164 | """ 165 | This object contains information about an incoming shipping query. 166 | 167 | https://core.telegram.org/bots/api#shippingquery 168 | 169 | 170 | Parameters: 171 | 172 | :param id: Unique query identifier 173 | :type id: str|unicode 174 | 175 | :param from_peer: User who sent the query 176 | :type from_peer: pytgbot.api_types.receivable.peer.User 177 | 178 | :param invoice_payload: Bot specified invoice payload 179 | :type invoice_payload: str|unicode 180 | 181 | :param shipping_address: User specified shipping address 182 | :type shipping_address: pytgbot.api_types.receivable.payments.ShippingAddress 183 | 184 | 185 | Optional keyword parameters: 186 | 187 | :param _raw: Optional. Original data this object was generated from. Could be `None`. 188 | :type _raw: None | dict 189 | """ 190 | id: str 191 | from_peer: User 192 | invoice_payload: str 193 | shipping_address: ShippingAddress 194 | # end class ShippingQuery 195 | 196 | class PreCheckoutQuery(UpdateType): 197 | """ 198 | This object contains information about an incoming pre-checkout query. 199 | 200 | https://core.telegram.org/bots/api#precheckoutquery 201 | 202 | 203 | Parameters: 204 | 205 | :param id: Unique query identifier 206 | :type id: str|unicode 207 | 208 | :param from_peer: User who sent the query 209 | :type from_peer: pytgbot.api_types.receivable.peer.User 210 | 211 | :param currency: Three-letter ISO 4217 currency code 212 | :type currency: str|unicode 213 | 214 | :param total_amount: Total price in the smallest units of the currency (integer, not float/double). For example, for a price of US$ 1.45 pass amount = 145. See the exp parameter in currencies.json, it shows the number of digits past the decimal point for each currency (2 for the majority of currencies). 215 | :type total_amount: int 216 | 217 | :param invoice_payload: Bot specified invoice payload 218 | :type invoice_payload: str|unicode 219 | 220 | 221 | Optional keyword parameters: 222 | 223 | :param shipping_option_id: Optional. Identifier of the shipping option chosen by the user 224 | :type shipping_option_id: str|unicode 225 | 226 | :param order_info: Optional. Order info provided by the user 227 | :type order_info: pytgbot.api_types.receivable.payments.OrderInfo 228 | 229 | :param _raw: Optional. Original data this object was generated from. Could be `None`. 230 | :type _raw: None | dict 231 | """ 232 | id: str 233 | from_peer: User 234 | currency: str 235 | total_amount: int 236 | invoice_payload: str 237 | shipping_option_id: str 238 | order_info: OrderInfo 239 | # end class PreCheckoutQuery 240 | -------------------------------------------------------------------------------- /code_generation/output/pytgbot/api_types/receivable/responses.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from luckydonaldUtils.encoding import unicode_type, to_unicode as u 3 | from luckydonaldUtils.exceptions import assert_type_or_raise 4 | from . import Result 5 | 6 | __author__ = 'luckydonald' 7 | __all__ = [ 8 | 'MessageId', 9 | ] 10 | 11 | 12 | class MessageId(Result): 13 | """ 14 | This object represents a unique message identifier. 15 | 16 | https://core.telegram.org/bots/api#messageid 17 | 18 | 19 | Parameters: 20 | 21 | :param message_id: Unique message identifier 22 | :type message_id: int 23 | 24 | 25 | Optional keyword parameters: 26 | 27 | :param _raw: Optional. Original data this object was generated from. Could be `None`. 28 | :type _raw: None | dict 29 | """ 30 | 31 | def __init__(self, message_id, _raw=None): 32 | """ 33 | This object represents a unique message identifier. 34 | 35 | https://core.telegram.org/bots/api#messageid 36 | 37 | 38 | Parameters: 39 | 40 | :param message_id: Unique message identifier 41 | :type message_id: int 42 | 43 | 44 | Optional keyword parameters: 45 | 46 | :param _raw: Optional. Original data this object was generated from. Could be `None`. 47 | :type _raw: None | dict 48 | """ 49 | super(MessageId, self).__init__() 50 | assert_type_or_raise(message_id, int, parameter_name="message_id") 51 | self.message_id = message_id 52 | 53 | self._raw = _raw 54 | # end def __init__ 55 | 56 | def to_array(self, prefer_original=False): 57 | """ 58 | Serializes this MessageId to a dictionary. 59 | 60 | :param prefer_original: If we should return the data this was constructed with if available. If it's not available, it will be constructed normally from the data of the object. 61 | :type prefer_original: bool 62 | 63 | :return: dictionary representation of this object. 64 | :rtype: dict 65 | """ 66 | if prefer_original and self._raw: 67 | return self._raw 68 | # end if 69 | 70 | array = super(MessageId, self).to_array() 71 | 72 | array['message_id'] = int(self.message_id) # type int 73 | return array 74 | # end def to_array 75 | 76 | @staticmethod 77 | def validate_array(array): 78 | """ 79 | Builds a new array with valid values for the MessageId constructor. 80 | 81 | :return: new array with valid values 82 | :rtype: dict 83 | """ 84 | assert_type_or_raise(array, dict, parameter_name="array") 85 | data = Result.validate_array(array) 86 | data['message_id'] = int(array.get('message_id')) 87 | return data 88 | # end def validate_array 89 | 90 | @staticmethod 91 | def from_array(array): 92 | """ 93 | Deserialize a new MessageId from a given dictionary. 94 | 95 | :return: new MessageId instance. 96 | :rtype: MessageId 97 | """ 98 | if not array: # None or {} 99 | return None 100 | # end if 101 | 102 | data = MessageId.validate_array(array) 103 | data['_raw'] = array 104 | return MessageId(**data) 105 | # end def from_array 106 | 107 | def __str__(self): 108 | """ 109 | Implements `str(messageid_instance)` 110 | """ 111 | return "MessageId(message_id={self.message_id!r})".format(self=self) 112 | # end def __str__ 113 | 114 | def __repr__(self): 115 | """ 116 | Implements `repr(messageid_instance)` 117 | """ 118 | if self._raw: 119 | return "MessageId.from_array({self._raw})".format(self=self) 120 | # end if 121 | return "MessageId(message_id={self.message_id!r})".format(self=self) 122 | # end def __repr__ 123 | 124 | def __contains__(self, key): 125 | """ 126 | Implements `"key" in messageid_instance` 127 | """ 128 | return ( 129 | key in ["message_id"] 130 | and hasattr(self, key) 131 | and bool(getattr(self, key, None)) 132 | ) 133 | # end def __contains__ 134 | # end class MessageId 135 | -------------------------------------------------------------------------------- /code_generation/output/pytgbot/api_types/receivable/responses.pyi: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from luckydonaldUtils.exceptions import assert_type_or_raise 3 | from luckydonaldUtils.encoding import unicode_type, to_unicode as u 4 | from typing import Any, Union, List 5 | from pytgbot.api_types.receivable import Result 6 | 7 | __author__ = 'luckydonald' 8 | 9 | 10 | class MessageId(Result): 11 | """ 12 | This object represents a unique message identifier. 13 | 14 | https://core.telegram.org/bots/api#messageid 15 | 16 | 17 | Parameters: 18 | 19 | :param message_id: Unique message identifier 20 | :type message_id: int 21 | 22 | 23 | Optional keyword parameters: 24 | 25 | :param _raw: Optional. Original data this object was generated from. Could be `None`. 26 | :type _raw: None | dict 27 | """ 28 | message_id: int 29 | # end class MessageId 30 | -------------------------------------------------------------------------------- /code_generation/output/pytgbot/api_types/receivable/service.pyi: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from luckydonaldUtils.exceptions import assert_type_or_raise 3 | from luckydonaldUtils.encoding import unicode_type, to_unicode as u 4 | from typing import Any, Union, List 5 | from pytgbot.api_types.receivable import Receivable 6 | from pytgbot.api_types.receivable.service import ServiceMessage 7 | 8 | __author__ = 'luckydonald' 9 | 10 | 11 | class ServiceMessage(Receivable): 12 | """ 13 | parent class for all service messages, those are not directly media related special attributes of the Message object. 14 | 15 | Optional keyword parameters: 16 | 17 | :param _raw: Optional. Original data this object was generated from. Could be `None`. 18 | :type _raw: None | dict 19 | """ 20 | # end class ServiceMessage 21 | 22 | class ProximityAlertTriggered(ServiceMessage): 23 | """ 24 | This object represents the content of a service message, sent whenever a user in the chat triggers a proximity alert set by another user. 25 | 26 | https://core.telegram.org/bots/api#proximityalerttriggered 27 | 28 | 29 | Parameters: 30 | 31 | :param traveler: User that triggered the alert 32 | :type traveler: pytgbot.api_types.receivable.peer.User 33 | 34 | :param watcher: User that set the alert 35 | :type watcher: pytgbot.api_types.receivable.peer.User 36 | 37 | :param distance: The distance between the users 38 | :type distance: int 39 | 40 | 41 | Optional keyword parameters: 42 | 43 | :param _raw: Optional. Original data this object was generated from. Could be `None`. 44 | :type _raw: None | dict 45 | """ 46 | traveler: User 47 | watcher: User 48 | distance: int 49 | # end class ProximityAlertTriggered 50 | 51 | class MessageAutoDeleteTimerChanged(ServiceMessage): 52 | """ 53 | This object represents a service message about a change in auto-delete timer settings. 54 | 55 | https://core.telegram.org/bots/api#messageautodeletetimerchanged 56 | 57 | 58 | Parameters: 59 | 60 | :param message_auto_delete_time: New auto-delete time for messages in the chat; in seconds 61 | :type message_auto_delete_time: int 62 | 63 | 64 | Optional keyword parameters: 65 | 66 | :param _raw: Optional. Original data this object was generated from. Could be `None`. 67 | :type _raw: None | dict 68 | """ 69 | message_auto_delete_time: int 70 | # end class MessageAutoDeleteTimerChanged 71 | 72 | class VoiceChatScheduled(ServiceMessage): 73 | """ 74 | This object represents a service message about a voice chat scheduled in the chat. 75 | 76 | https://core.telegram.org/bots/api#voicechatscheduled 77 | 78 | 79 | Parameters: 80 | 81 | :param start_date: Point in time (Unix timestamp) when the voice chat is supposed to be started by a chat administrator 82 | :type start_date: int 83 | 84 | 85 | Optional keyword parameters: 86 | 87 | :param _raw: Optional. Original data this object was generated from. Could be `None`. 88 | :type _raw: None | dict 89 | """ 90 | start_date: int 91 | # end class VoiceChatScheduled 92 | 93 | class VoiceChatStarted(ServiceMessage): 94 | """ 95 | This object represents a service message about a voice chat started in the chat. Currently holds no information. 96 | 97 | https://core.telegram.org/bots/api#voicechatstarted 98 | 99 | Optional keyword parameters: 100 | 101 | :param _raw: Optional. Original data this object was generated from. Could be `None`. 102 | :type _raw: None | dict 103 | """ 104 | # end class VoiceChatStarted 105 | 106 | class VoiceChatEnded(ServiceMessage): 107 | """ 108 | This object represents a service message about a voice chat ended in the chat. 109 | 110 | https://core.telegram.org/bots/api#voicechatended 111 | 112 | 113 | Parameters: 114 | 115 | :param duration: Voice chat duration in seconds 116 | :type duration: int 117 | 118 | 119 | Optional keyword parameters: 120 | 121 | :param _raw: Optional. Original data this object was generated from. Could be `None`. 122 | :type _raw: None | dict 123 | """ 124 | duration: int 125 | # end class VoiceChatEnded 126 | 127 | class VoiceChatParticipantsInvited(ServiceMessage): 128 | """ 129 | This object represents a service message about new members invited to a voice chat. 130 | 131 | https://core.telegram.org/bots/api#voicechatparticipantsinvited 132 | 133 | Optional keyword parameters: 134 | 135 | :param users: Optional. New members that were invited to the voice chat 136 | :type users: list of pytgbot.api_types.receivable.peer.User 137 | 138 | :param _raw: Optional. Original data this object was generated from. Could be `None`. 139 | :type _raw: None | dict 140 | """ 141 | users: List[User] 142 | # end class VoiceChatParticipantsInvited 143 | -------------------------------------------------------------------------------- /code_generation/output/pytgbot/api_types/receivable/stickers.pyi: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from luckydonaldUtils.exceptions import assert_type_or_raise 3 | from luckydonaldUtils.encoding import unicode_type, to_unicode as u 4 | from typing import Any, Union, List 5 | from pytgbot.api_types.receivable import Result 6 | 7 | __author__ = 'luckydonald' 8 | 9 | 10 | class StickerSet(Result): 11 | """ 12 | This object represents a sticker set. 13 | 14 | https://core.telegram.org/bots/api#stickerset 15 | 16 | 17 | Parameters: 18 | 19 | :param name: Sticker set name 20 | :type name: str|unicode 21 | 22 | :param title: Sticker set title 23 | :type title: str|unicode 24 | 25 | :param is_animated: True, if the sticker set contains animated stickers 26 | :type is_animated: bool 27 | 28 | :param is_video: True, if the sticker set contains video stickers 29 | :type is_video: bool 30 | 31 | :param contains_masks: True, if the sticker set contains masks 32 | :type contains_masks: bool 33 | 34 | :param stickers: List of all set stickers 35 | :type stickers: list of pytgbot.api_types.receivable.media.Sticker 36 | 37 | 38 | Optional keyword parameters: 39 | 40 | :param thumb: Optional. Sticker set thumbnail in the .WEBP, .TGS, or .WEBM format 41 | :type thumb: pytgbot.api_types.receivable.media.PhotoSize 42 | 43 | :param _raw: Optional. Original data this object was generated from. Could be `None`. 44 | :type _raw: None | dict 45 | """ 46 | name: str 47 | title: str 48 | is_animated: bool 49 | is_video: bool 50 | contains_masks: bool 51 | stickers: List[Sticker] 52 | thumb: PhotoSize 53 | # end class StickerSet 54 | 55 | class MaskPosition(Result): 56 | """ 57 | This object describes the position on faces where a mask should be placed by default. 58 | 59 | https://core.telegram.org/bots/api#maskposition 60 | 61 | 62 | Parameters: 63 | 64 | :param point: The part of the face relative to which the mask should be placed. One of "forehead", "eyes", "mouth", or "chin". 65 | :type point: str|unicode 66 | 67 | :param x_shift: Shift by X-axis measured in widths of the mask scaled to the face size, from left to right. For example, choosing -1.0 will place mask just to the left of the default mask position. 68 | :type x_shift: float 69 | 70 | :param y_shift: Shift by Y-axis measured in heights of the mask scaled to the face size, from top to bottom. For example, 1.0 will place the mask just below the default mask position. 71 | :type y_shift: float 72 | 73 | :param scale: Mask scaling coefficient. For example, 2.0 means double size. 74 | :type scale: float 75 | 76 | 77 | Optional keyword parameters: 78 | 79 | :param _raw: Optional. Original data this object was generated from. Could be `None`. 80 | :type _raw: None | dict 81 | """ 82 | point: str 83 | x_shift: float 84 | y_shift: float 85 | scale: float 86 | # end class MaskPosition 87 | -------------------------------------------------------------------------------- /code_generation/output/pytgbot/api_types/sendable/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from luckydonaldUtils.encoding import unicode_type, to_unicode as u 3 | from luckydonaldUtils.exceptions import assert_type_or_raise 4 | from . import TgBotApiObject 5 | 6 | __author__ = 'luckydonald' 7 | __all__ = [ 8 | 'Sendable', 9 | ] 10 | 11 | 12 | class Sendable(TgBotApiObject): 13 | """ 14 | Base class for all classes for stuff which we throw in requests towards the telegram servers. 15 | 16 | Optional keyword parameters: 17 | """ 18 | 19 | pass 20 | # end class Sendable 21 | -------------------------------------------------------------------------------- /code_generation/output/pytgbot/api_types/sendable/__init__.pyi: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from luckydonaldUtils.exceptions import assert_type_or_raise 3 | from luckydonaldUtils.encoding import unicode_type, to_unicode as u 4 | from typing import Any, Union, List 5 | from pytgbot.api_types import TgBotApiObject 6 | 7 | __author__ = 'luckydonald' 8 | 9 | 10 | class Sendable(TgBotApiObject): 11 | """ 12 | Base class for all classes for stuff which we throw in requests towards the telegram servers. 13 | 14 | Optional keyword parameters: 15 | """ 16 | # end class Sendable 17 | -------------------------------------------------------------------------------- /code_generation/output/pytgbot/api_types/sendable/command.pyi: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from luckydonaldUtils.exceptions import assert_type_or_raise 3 | from luckydonaldUtils.encoding import unicode_type, to_unicode as u 4 | from typing import Any, Union, List 5 | from pytgbot.api_types.sendable import Sendable 6 | from pytgbot.api_types.sendable.command import BotCommandScope 7 | 8 | __author__ = 'luckydonald' 9 | 10 | 11 | class BotCommand(Sendable): 12 | """ 13 | This object represents a bot command. 14 | 15 | https://core.telegram.org/bots/api#botcommand 16 | 17 | 18 | Parameters: 19 | 20 | :param command: Text of the command; 1-32 characters. Can contain only lowercase English letters, digits and underscores. 21 | :type command: str|unicode 22 | 23 | :param description: Description of the command; 1-256 characters. 24 | :type description: str|unicode 25 | 26 | 27 | Optional keyword parameters: 28 | """ 29 | command: str 30 | description: str 31 | # end class BotCommand 32 | 33 | class BotCommandScope(Sendable): 34 | """ 35 | This object represents the scope to which bot commands are applied. Currently, the following 7 scopes are supported: 36 | 37 | https://core.telegram.org/bots/api#botcommandscope 38 | 39 | Optional keyword parameters: 40 | """ 41 | # end class BotCommandScope 42 | 43 | class BotCommandScopeDefault(BotCommandScope): 44 | """ 45 | Represents the default scope of bot commands. Default commands are used if no commands with a narrower scope are specified for the user. 46 | 47 | https://core.telegram.org/bots/api#botcommandscopedefault 48 | 49 | 50 | Parameters: 51 | 52 | :param type: Scope type, must be default 53 | :type type: str|unicode 54 | 55 | 56 | Optional keyword parameters: 57 | """ 58 | type: str 59 | # end class BotCommandScopeDefault 60 | 61 | class BotCommandScopeAllPrivateChats(BotCommandScope): 62 | """ 63 | Represents the scope of bot commands, covering all private chats. 64 | 65 | https://core.telegram.org/bots/api#botcommandscopeallprivatechats 66 | 67 | 68 | Parameters: 69 | 70 | :param type: Scope type, must be all_private_chats 71 | :type type: str|unicode 72 | 73 | 74 | Optional keyword parameters: 75 | """ 76 | type: str 77 | # end class BotCommandScopeAllPrivateChats 78 | 79 | class BotCommandScopeAllGroupChats(BotCommandScope): 80 | """ 81 | Represents the scope of bot commands, covering all group and supergroup chats. 82 | 83 | https://core.telegram.org/bots/api#botcommandscopeallgroupchats 84 | 85 | 86 | Parameters: 87 | 88 | :param type: Scope type, must be all_group_chats 89 | :type type: str|unicode 90 | 91 | 92 | Optional keyword parameters: 93 | """ 94 | type: str 95 | # end class BotCommandScopeAllGroupChats 96 | 97 | class BotCommandScopeAllChatAdministrators(BotCommandScope): 98 | """ 99 | Represents the scope of bot commands, covering all group and supergroup chat administrators. 100 | 101 | https://core.telegram.org/bots/api#botcommandscopeallchatadministrators 102 | 103 | 104 | Parameters: 105 | 106 | :param type: Scope type, must be all_chat_administrators 107 | :type type: str|unicode 108 | 109 | 110 | Optional keyword parameters: 111 | """ 112 | type: str 113 | # end class BotCommandScopeAllChatAdministrators 114 | 115 | class BotCommandScopeChat(BotCommandScope): 116 | """ 117 | Represents the scope of bot commands, covering a specific chat. 118 | 119 | https://core.telegram.org/bots/api#botcommandscopechat 120 | 121 | 122 | Parameters: 123 | 124 | :param type: Scope type, must be chat 125 | :type type: str|unicode 126 | 127 | :param chat_id: Unique identifier for the target chat or username of the target supergroup (in the format @supergroupusername) 128 | :type chat_id: int | str|unicode 129 | 130 | 131 | Optional keyword parameters: 132 | """ 133 | type: str 134 | chat_id: Union[int, str] 135 | # end class BotCommandScopeChat 136 | 137 | class BotCommandScopeChatAdministrators(BotCommandScope): 138 | """ 139 | Represents the scope of bot commands, covering all administrators of a specific group or supergroup chat. 140 | 141 | https://core.telegram.org/bots/api#botcommandscopechatadministrators 142 | 143 | 144 | Parameters: 145 | 146 | :param type: Scope type, must be chat_administrators 147 | :type type: str|unicode 148 | 149 | :param chat_id: Unique identifier for the target chat or username of the target supergroup (in the format @supergroupusername) 150 | :type chat_id: int | str|unicode 151 | 152 | 153 | Optional keyword parameters: 154 | """ 155 | type: str 156 | chat_id: Union[int, str] 157 | # end class BotCommandScopeChatAdministrators 158 | 159 | class BotCommandScopeChatMember(BotCommandScope): 160 | """ 161 | Represents the scope of bot commands, covering a specific member of a group or supergroup chat. 162 | 163 | https://core.telegram.org/bots/api#botcommandscopechatmember 164 | 165 | 166 | Parameters: 167 | 168 | :param type: Scope type, must be chat_member 169 | :type type: str|unicode 170 | 171 | :param chat_id: Unique identifier for the target chat or username of the target supergroup (in the format @supergroupusername) 172 | :type chat_id: int | str|unicode 173 | 174 | :param user_id: Unique identifier of the target user 175 | :type user_id: int 176 | 177 | 178 | Optional keyword parameters: 179 | """ 180 | type: str 181 | chat_id: Union[int, str] 182 | user_id: int 183 | # end class BotCommandScopeChatMember 184 | -------------------------------------------------------------------------------- /code_generation/output/pytgbot/api_types/sendable/payments.pyi: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from luckydonaldUtils.exceptions import assert_type_or_raise 3 | from luckydonaldUtils.encoding import unicode_type, to_unicode as u 4 | from typing import Any, Union, List 5 | from pytgbot.api_types.sendable import Sendable 6 | 7 | __author__ = 'luckydonald' 8 | 9 | 10 | class LabeledPrice(Sendable): 11 | """ 12 | This object represents a portion of the price for goods or services. 13 | 14 | https://core.telegram.org/bots/api#labeledprice 15 | 16 | 17 | Parameters: 18 | 19 | :param label: Portion label 20 | :type label: str|unicode 21 | 22 | :param amount: Price of the product in the smallest units of the currency (integer, not float/double). For example, for a price of US$ 1.45 pass amount = 145. See the exp parameter in currencies.json, it shows the number of digits past the decimal point for each currency (2 for the majority of currencies). 23 | :type amount: int 24 | 25 | 26 | Optional keyword parameters: 27 | """ 28 | label: str 29 | amount: int 30 | # end class LabeledPrice 31 | 32 | class ShippingOption(Sendable): 33 | """ 34 | This object represents one shipping option. 35 | 36 | https://core.telegram.org/bots/api#shippingoption 37 | 38 | 39 | Parameters: 40 | 41 | :param id: Shipping option identifier 42 | :type id: str|unicode 43 | 44 | :param title: Option title 45 | :type title: str|unicode 46 | 47 | :param prices: List of price portions 48 | :type prices: list of pytgbot.api_types.sendable.payments.LabeledPrice 49 | 50 | 51 | Optional keyword parameters: 52 | """ 53 | id: str 54 | title: str 55 | prices: List[LabeledPrice] 56 | # end class ShippingOption 57 | -------------------------------------------------------------------------------- /code_generation/pytgbot-generator.diff.sh: -------------------------------------------------------------------------------- 1 | cd / 2 | 3 | 4 | cd /pytgbot/ 5 | 6 | # cp __init__.py code_generation/output/pytgbot/__init__.py 7 | find . -maxdepth 5 \( ! -type d \) -exec sh -c 'echo cp "$@" "../code_generation/output/pytgbot/$@"' _ {} \; 8 | # non-dry-run: 9 | find . -maxdepth 5 \( ! -type d \) -exec sh -c 'cp "$@" "../code_generation/output/pytgbot/$@"' _ {} \; 10 | # cp ./__init__.py code_generation/output/pytgbot/./__init__.py 11 | 12 | cd code_generation/output/pytgbot/ 13 | 14 | git diff . > ../../pytgbot-generator-patch.patch 15 | 16 | -------------------------------------------------------------------------------- /code_generation/pytgbot-generator.patch.sh: -------------------------------------------------------------------------------- 1 | 2 | cd code_generation/output/pytgbot/ 3 | git apply ../../pytgbot-generator-patch.patch 4 | 5 | # cp api_types/__init__.py ../../../pytgbot/api_types/__init__.py 6 | find . -maxdepth 5 \( ! -type d \) -exec sh -c 'echo cp "$@" "../../../pytgbot/$@"' _ {} \; 7 | # non-dry-run: 8 | find . -maxdepth 5 \( ! -type d \) -exec sh -c 'cp "$@" "../../../pytgbot/$@"' _ {} \; 9 | # cp ./api_types/__init__.py ../../../pytgbot/./api_types/__init__.py 10 | 11 | git apply -R ../../pytgbot-generator-patch.patch 12 | -------------------------------------------------------------------------------- /code_generation/templates/classfile.template: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from luckydonaldUtils.encoding import unicode_type, to_unicode as u 3 | from luckydonaldUtils.exceptions import assert_type_or_raise 4 | {% set file_import_path = clazzes[0].import_path -%} 5 | {%- for some_import in imports if some_import.path != file_import_path.path -%} 6 | {{ some_import.import_statement_from_file(file_import_path) }} 7 | {%- if loop.last %} 8 | {% endif %} 9 | {% endfor -%} 10 | 11 | __author__ = 'luckydonald' 12 | __all__ = [{% for clazz in clazzes %} 13 | {{ clazz.clazz.__repr__() }},{# 14 | #}{% endfor %} 15 | ] 16 | 17 | {% for clazz in clazzes %} 18 | {% include "class.template" %} 19 | 20 | {% endfor %} 21 | -------------------------------------------------------------------------------- /code_generation/templates/func.template: -------------------------------------------------------------------------------- 1 | {% from "macros.template" import for_args_none, for_type_list_of_full, types_as_assert_tuple, for_args_set %} 2 | {% if is_asyncio %}async {% endif %}def {{ function.name }}(self, {{ for_args_none(function.variables) }}): 3 | """ 4 | {% block func_docstring -%} 5 | {{ function.description|indent(8) }} 6 | 7 | {{function.link}} 8 | 9 | {% if function.parameters %} 10 | Parameters: 11 | {% for param in function.parameters %} 12 | :param {{ param.name }}: {{ param.description }} 13 | :type {{ param.name }}: {{ for_type_list_of_full(param) }} 14 | {% endfor %} 15 | {% endif %} 16 | {% if function.keywords -%} 17 | Optional keyword parameters: 18 | {% for keyword in function.keywords %} 19 | :param {{ keyword.name }}: {{ keyword.description }} 20 | :type {{ keyword.name }}: {{ for_type_list_of_full(keyword) }} 21 | {% endfor %} 22 | {% endif -%} 23 | 24 | 25 | Returns: 26 | 27 | :return: {{ function.returns.description }} 28 | :rtype: {{ for_type_list_of_full(function.returns) }} 29 | {% endblock -%} 30 | """ 31 | {% block imports_block -%} 32 | {%- for import in function.imports -%} 33 | from {{ import.path }} import {{ import.name }} 34 | {%- if loop.last %} 35 | {% endif %} 36 | {% endfor -%} 37 | {%- endblock %} 38 | 39 | {%- for variable in function.variables -%} 40 | {% if variable.optional -%} 41 | assert_type_or_raise({{ variable.name }}, None, {{ types_as_assert_tuple(variable) }}, parameter_name="{{ variable.name }}") 42 | {% else -%} 43 | assert_type_or_raise({{ variable.name }}, {{ types_as_assert_tuple(variable) }}, parameter_name="{{ variable.name }}") 44 | {% endif -%} 45 | {%- if variable.always_is_value -%} 46 | if {{ variable.name }} is not None and {{ variable.name }} != {{ variable.always_is_value }}: 47 | raise ValueError("The parameter {{ variable.name }} should be the value {expected_value}, but is type {real_type}: {real_value!r}" ) 48 | # end if 49 | {%- endif -%} 50 | {%- if not loop.last %} 51 | {% endif %} 52 | {%- endfor -%} 53 | {# #} 54 | result = {% if is_asyncio %}await {% endif %}self.do("{{ function.api_name }}", {{ for_args_set(function.variables) }}) 55 | if self.return_python_objects: 56 | logger.debug("Trying to parse {data}".format(data=repr(result))) 57 | {%- for import in function.returns.all_imports %} 58 | from {{ import.path }} import {{ import.name }} 59 | {%- endfor -%} 60 | {%- for type in function.returns.types if type.is_builtin == False %} 61 | try: 62 | {%- if type.is_list > 0 %} 63 | return {{ type.string }}.from_array_list(result, list_level={{ type.is_list }}) 64 | {%- else %} 65 | return {{ type.string }}.from_array(result) 66 | {%- endif %} 67 | except TgApiParseException: 68 | logger.debug("Failed parsing as api_type {{ type.string }}", exc_info=True) 69 | # end try 70 | {% endfor -%} 71 | {%- for type in function.returns.types if type.is_builtin == True %} 72 | try: 73 | return from_array_list({{ type.string }}, result, list_level={{ type.is_list }}, is_builtin=True) 74 | except TgApiParseException: 75 | logger.debug("Failed parsing as primitive {{ type.string }}", exc_info=True) 76 | # end try 77 | {% endfor %} # no valid parsing so far 78 | raise TgApiParseException("Could not parse result.") # See debug log for details! 79 | # end if return_python_objects 80 | return result 81 | # end def {{ function.name }} 82 | -------------------------------------------------------------------------------- /code_generation/templates/macros.template: -------------------------------------------------------------------------------- 1 | {%- macro for_type(variable) -%}{% for type in variable.types %}{{ type.string }}{% if not loop.last %}, {% endif -%}{% endfor %}{%- endmacro -%} 2 | {%- macro fix_type_isinstance(type) -%}{% if type.string == 'str' %}unicode_type{% else %}{{ type.string }}{% endif %}{%- endmacro -%} 3 | {%- macro fix_type_docs(type) -%}{% if type.string == 'str' %}str|unicode{% else %}{{ type.as_import.full }}{% endif %}{%- endmacro -%} 4 | {%- macro for_type_or_list(variable) -%}{% for type in variable.types %}{% if type.is_list > 0 %}{{ "list" }}{% else %}{{ fix_type_isinstance(type) }}{% endif %}{% if not loop.last %}, {% endif -%}{% endfor %}{%- endmacro -%} 5 | {%- macro for_type_list_of_full(variable) -%}{% for type in variable.types %}{% if type.is_list > 0 %}{{ "list of " * type.is_list }}{% endif %}{{ fix_type_docs(type) }}{% if not loop.last %} | {% endif -%}{% endfor %}{%- endmacro -%} 6 | {%- macro for_type_set_of_full(variable) -%}{% for type in variable.types %}{% if type.is_list > 0 %}{{ "pony.Set(" * type.is_list }}{% endif %}{% if not type.is_builtin %}"{% endif %}{{ type.as_import.name }}{% if not type.is_builtin %}"{% endif %}{% if type.is_list > 0 %}{{ ")" * type.is_list }}{% endif %}{% endfor %}{%- endmacro -%} 7 | {%- macro for_type_list_of(variable) -%}{% for type in variable.types %}{% if type.is_list > 0 %}{{ "list of " * type.is_list }}{% endif %}{{ type.string }}{% if not loop.last %} | {% endif -%}{% endfor %}{%- endmacro -%} 8 | {%- macro types_as_tuple(variables, variable) -%}{% if variables.type|length > 1 %}({{ for_type_or_list(variable) }}){% else -%}{{ for_type_or_list(variable) }}{% endif -%}{%- endmacro -%} 9 | {%- macro for_args_keys(variables) -%}{%- for variable in variables %}"{{ variable.name }}"{% if not loop.last %}, {% endif -%}{%- endfor %}{% endmacro %} 10 | {%- macro for_args_set(variables) -%}{%- for variable in variables %}{{ variable.name }}={{ variable.name }}{% if not loop.last %}, {% endif -%}{%- endfor %}{% endmacro %} 11 | {%- macro for_args_none(variables) -%}{%- for variable in variables %}{{ variable.name }}{% if variable.optional %}={% if variable.default == None %}None{% else %}{{ variable.default if variable.default is string else fix_type_docs(variable.default) }}{% endif %}{% endif %}{% if not loop.last %}, {% endif -%}{%- endfor %}{% endmacro %} 12 | {%- macro for_args_format_str(variables) -%}{%- for variable in variables %}{{ variable.name }}={{ "{" }}self.{{ variable.name }}{{ "}" }}{% if not loop.last %}, {% endif -%}{%- endfor %}{% endmacro %} 13 | {%- macro for_args_format_repr(variables) -%}{%- for variable in variables %}{{ variable.name }}={{ "{" }}self.{{ variable.name }}{{ "!r}" }}{% if not loop.last %}, {% endif -%}{%- endfor %}{% endmacro %} 14 | {%- macro types_as_assert_tuple(variable) -%}{%- if variable.types|length > 1 %}({{ for_type_or_list(variable) }}){% else -%}{{ for_type_or_list(variable) }}{% endif -%}{%- endmacro -%} 15 | {%- macro for_returns_type(function, do_builtin) -%}{%- for type in function.returns.types if type.is_builtin == False %}{% endfor %}{% endmacro %} 16 | 17 | 18 | {#- CLASS RELATED STUFF -#} 19 | 20 | 21 | {%- macro set_array(variable, helper_clazz='self') -%} 22 | {# 23 | Does the full 24 | 25 | array['xxx'] = u(self.xxx) | self.xxx.to_array() | {helper_clazz}._as_array(self.xxx) 26 | 27 | including having multiple types: 28 | 29 | if isinstance(self.xxx, type1): 30 | array['xxx'] = u(self.xxx) | self.xxx.to_array() | {helper_clazz}._as_array(self.xxx) 31 | elif isinstance(self.xxx, type2): 32 | array['xxx'] = u(self.xxx) | self.xxx.to_array() | {helper_clazz}._as_array(self.xxx) 33 | else: 34 | raise TypeError('Unknown type, must be one of type1, type2.') 35 | # end if 36 | 37 | -#}{# 38 | #}{% if variable.types|length == 1 %}{# 39 | #}{{ set_array_element(variable, variable.types[0], helper_clazz) }}{# 40 | #}{% else %}{# 41 | #}{% for var_type in variable.types %}{# 42 | #}{% if not loop.first %} 43 | el{% endif %}if {% if var_type.string == 'None' %}self.{{ variable.name }} is None{% else %}isinstance(self.{{ variable.name }}, {{ var_type.string }}){% endif %}: 44 | {{ set_array_element(variable, var_type, helper_clazz) }}{# 45 | #}{% endfor %} 46 | else: 47 | raise TypeError('Unknown type, must be one of {{ for_type(variable) }}.') 48 | # end if{# 49 | #}{% endif %}{# 50 | #}{% endmacro %} 51 | 52 | 53 | {%- macro set_array_element(variable, var_type, helper_clazz) %}{# 54 | #}{# 55 | generates those 56 | 57 | array['xxx'] = u(self.xxx) | self.xxx.to_array() | {helper_clazz}._as_array(self.xxx) 58 | 59 | for a single type only 60 | #}{# 61 | #}{% if var_type.is_list > 0 %}{# 62 | #}array['{{ variable.api_name }}'] = {{ helper_clazz }}._as_array(self.{{ variable.name }}) # type {{ for_type_list_of(variable) }}{# 63 | #}{% else %}{# 64 | #}{% if var_type.is_builtin %}{# 65 | #}{% if var_type.string == 'str' %}{# 66 | #}array['{{ variable.api_name }}'] = u(self.{{ variable.name }}) # py2: type unicode, py3: type str{# 67 | #}{% elif var_type.string == 'None' %}{# 68 | #}array['{{ variable.api_name }}'] = None{# 69 | #}{% else %}{# 70 | #}array['{{ variable.api_name }}'] = {{ var_type.string }}(self.{{ variable.name }}) # type {{ var_type.string }}{# 71 | #}{% endif %}{# 72 | #}{% else %}{# 73 | #}array['{{ variable.api_name }}'] = self.{{ variable.name }}.to_array() # type {{ var_type.string }}{# 74 | #}{% endif %}{# 75 | #}{% endif %}{# 76 | #}{% endmacro %} 77 | 78 | 79 | {%- macro set_data_array_element(variable, var_type, clazz_name, optional) -%} 80 | {% if var_type.is_builtin -%} 81 | {% if var_type.string == 'str' -%} 82 | {% if var_type.is_list > 0 -%} 83 | data['{{ variable.name }}'] = {{ clazz_name }}._builtin_from_array_list(required_type=unicode_type, value=array.get('{{ variable.api_name }}'), list_level={{ var_type.is_list }}){%- if optional %} if array.get('{{ variable.api_name }}') is not None else None{%- endif %} 84 | {% else -%} 85 | data['{{ variable.name }}'] = u(array.get('{{ variable.api_name }}')){%- if optional %} if array.get('{{ variable.api_name }}') is not None else None{%- endif %} 86 | {% endif -%} 87 | {% else -%} 88 | {% if var_type.is_list > 0 -%} 89 | data['{{ variable.name }}'] = {{ clazz_name }}._builtin_from_array_list(required_type={{ var_type.string }}, value=array.get('{{ variable.api_name }}'), list_level={{ var_type.is_list }}){%- if optional %} if array.get('{{ variable.api_name }}') is not None else None{%- endif %} 90 | {% elif var_type.always_is_value -%} 91 | data['{{ variable.name }}'] = {{ var_type.always_is_value }}{%- if optional %} if array.get('{{ variable.api_name }}') is not None else None{%- endif %} 92 | {% else -%} 93 | data['{{ variable.name }}'] = {{ var_type.string }}(array.get('{{ variable.api_name }}')){%- if optional %} if array.get('{{ variable.api_name }}') is not None else None{%- endif %} 94 | {% endif -%} 95 | {% endif -%} 96 | {% else -%} 97 | {% if var_type.is_list > 0 -%} 98 | data['{{ variable.name }}'] = {{ var_type.string }}.from_array_list(array.get('{{ variable.api_name }}'), list_level={{ var_type.is_list }}){%- if optional %} if array.get('{{ variable.api_name }}') is not None else None{%- endif %} 99 | {% else -%} 100 | data['{{ variable.name }}'] = {{ var_type.string }}.from_array(array.get('{{ variable.api_name }}')){%- if optional %} if array.get('{{ variable.api_name }}') is not None else None{%- endif %} 101 | {% endif -%} 102 | {% endif -%} 103 | {%- endmacro -%} 104 | -------------------------------------------------------------------------------- /code_generation/templates/pony.class.template: -------------------------------------------------------------------------------- 1 | {% from "macros.template" import for_args_none, for_type_list_of_full, for_type_set_of_full, for_type_list_of, types_as_tuple, for_args_format_str, for_args_keys %} 2 | {% macro set_array(variable, class_name) -%} 3 | {% if variable.types|length == 1 -%} 4 | {% if variable.types[0].is_list > 0 -%} 5 | array['{{ variable.api_name }}'] = self._as_array(self.{{ variable.name }}) # type {{ for_type_list_of(variable) }} 6 | {% else -%} 7 | {% if variable.types[0].is_builtin -%} 8 | array['{{ variable.api_name }}'] = {{ variable.types[0].string }}(self.{{ variable.name }}) # type {{ variable.types[0].string }} 9 | {% else -%} 10 | array['{{ variable.api_name }}'] = self.{{ variable.name }}.to_array() # type {{ variable.types[0].string }} 11 | {% endif -%} 12 | {% endif -%} 13 | {% else -%} 14 | // ERROR: Multiple types ({{ for_type(variable) }}) for 15 | // array['{{ variable.api_name }}'] = self.{{ variable.name }} 16 | {% endif -%} 17 | {%- endmacro %} 18 | {#- macros -#} 19 | 20 | class {{ clazz.clazz -}}(updates.{{ clazz.clazz -}}, db.Entity): 21 | """ 22 | {% block class_docstring -%} 23 | {{ clazz.description.strip()|indent }} 24 | 25 | {{clazz.link}} 26 | {%- endblock %} 27 | """ 28 | {%- for param in clazz.parameters %} 29 | {{ param.name }} = pony.Required({{ for_type_set_of_full(param) }}) 30 | {%- endfor %} 31 | {% for keyword in clazz.keywords -%} 32 | {{ keyword.name }} = pony.Optional({{ for_type_set_of_full(keyword) }}) 33 | {# -#} 34 | {%- endfor -%} 35 | {#- #} 36 | # end class {{ clazz.clazz }} 37 | -------------------------------------------------------------------------------- /code_generation/templates/pony.classfile.template: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from . import updates 3 | from pony import orm as pony 4 | {% for import in imports -%} 5 | from {{ import.path }} import {{ import.name }} 6 | {%- if loop.last %} 7 | {% endif %} 8 | {% endfor -%} 9 | db = pony.Database() 10 | 11 | {% for clazz in clazzes %} 12 | {% include "class.template" %} 13 | 14 | {% endfor %} -------------------------------------------------------------------------------- /code_generation/templates/telegram_bot_api_server/classes.template: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from luckydonaldUtils.exceptions import assert_type_or_raise 3 | from luckydonaldUtils.encoding import unicode_type, to_unicode as u 4 | from pydantic import BaseModel 5 | from typing import Any, Union, List, Optional 6 | 7 | __author__ = 'luckydonald' 8 | 9 | __all__ = [ 10 | {% for clazz in clazzes %} '{{ clazz.clazz -}}Model',{% endfor %} 11 | ] 12 | 13 | {% include 'fastapi_issue_884_workaround.template' %} 14 | 15 | 16 | {% for clazz in clazzes %} 17 | {%- if clazz.clazz.clazz %}{% set custom_clazz = clazz %}{% set clazz = custom_clazz.clazz %}{% else %}{% set custom_clazz = None %}{% endif %} 18 | 19 | class {{ clazz.clazz -}}Model(BaseModel): # {{ clazz.parent_clazz.string }} 20 | {%- if clazz.description or clazz.link -%} 21 | """ 22 | {%- if clazz.description %}{{ clazz.description.strip()|indent }}{% endif %}{%- if clazz.description and clazz.link %} 23 | 24 | {% endif %}{% if clazz.link %}{{ clazz.link }}{% endif %} 25 | """ 26 | {% endif %} 27 | 28 | {%- for variable in clazz.variables %} 29 | {{ variable.name }}: {{ variable.create_typehint_optional_model(json_mode=False, quote_models=True) }} 30 | {%- endfor %} 31 | # end class {{ clazz.clazz }} 32 | {% endfor %} 33 | 34 | # now register all `ForwardRef`s 35 | {% for clazz in clazzes -%} 36 | {{ clazz.clazz }}Model.update_forward_refs() 37 | {% endfor %} 38 | -------------------------------------------------------------------------------- /code_generation/templates/telegram_bot_api_server/fastapi_issue_884_workaround.template: -------------------------------------------------------------------------------- 1 | FAST_API_ISSUE_884_IS_FIXED = False 2 | 3 | 4 | if FAST_API_ISSUE_884_IS_FIXED: 5 | from pydantic import Json 6 | 7 | def parse_obj_as(_, obj, *__, **___): 8 | """ 9 | we don't need any additional parsing as fastapi now does that correctly 10 | """ 11 | return obj 12 | # end def 13 | else: 14 | class __JsonWrapper: 15 | from pydantic import Json 16 | 17 | def __getitem__(self, item): 18 | """ Basically throw away `[Type]` when used like `Json[Type]` """ 19 | return self.Json 20 | # end def 21 | # end def 22 | Json = __JsonWrapper() # so Json[Type] does call Json.__getitem__(self, item=Type) 23 | 24 | from pydantic import parse_obj_as 25 | # end if 26 | -------------------------------------------------------------------------------- /code_generation/templates/telegram_bot_api_server/funcs.template: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | 4 | {% for import in imports -%} 5 | {{- import }} 6 | {% endfor %} 7 | from .....tools.responses import r_success, JSONableResponse 8 | from .....constants import TOKEN_VALIDATION 9 | from ..generated.models import * 10 | 11 | __author__ = 'luckydonald' # but it's automatically generated. 12 | 13 | 14 | logger = logging.getLogger(__name__) 15 | if __name__ == '__main__': 16 | logging.add_colored_handler(level=logging.DEBUG) 17 | # end if 18 | 19 | 20 | routes = APIRouter() # Basically a Blueprint 21 | 22 | 23 | {% include 'fastapi_issue_884_workaround.template' %} 24 | 25 | 26 | {% for function in functions -%} 27 | @routes.api_route('/{token}/{{function.api_name}}', methods=['GET', 'POST'], tags=['official']) 28 | async def {{function.name}}( 29 | token: str = TOKEN_VALIDATION, 30 | {%- for param in function.parameters %} 31 | {{param.name}}: {{param.create_typehint_optional_model(json_mode=True, quote_models=True)}} = Query(..., description={{ param.description.__repr__() }}), 32 | {%- endfor %} 33 | {%- for keyword in function.keywords %} 34 | {{keyword.name}}: {{keyword.create_typehint_optional_model(json_mode=True, quote_models=True)}} = Query({{ keyword.default }}, description={{ keyword.description.__repr__() }}), 35 | {%- endfor %} 36 | ) -> JSONableResponse: 37 | """ 38 | {{ function.description|indent(4*1) }} 39 | 40 | {{function.link}} 41 | """ 42 | {#- 43 | moop: List[TestModel] = parse_obj_as( 44 | type_=List[TestModel], 45 | obj=moop, 46 | ) 47 | #} 48 | {%- for variable in function.variables if variable.typehint_has_model %} 49 | {{variable.name}}: {{variable.create_typehint_optional_model(json_mode=False, quote_models=False)}} = parse_obj_as( 50 | {{variable.create_typehint_optional_model(json_mode=False, quote_models=False)}}, 51 | obj={{variable.name}}, 52 | ) 53 | {%- endfor -%} 54 | {#- #} 55 | 56 | from .....main import _get_bot 57 | bot = await _get_bot(token) 58 | {# #} 59 | {% if "chat_id" in function.variable_names -%} 60 | try: 61 | entity = await get_entity(bot, chat_id) 62 | except BotMethodInvalidError: 63 | assert isinstance(chat_id, int) or (isinstance(chat_id, str) and len(chat_id) > 0 and chat_id[0] == '@') 64 | entity = chat_id 65 | except ValueError: 66 | raise HTTPException(404, detail="chat not found?") 67 | # end try 68 | {%- endif %} 69 | 70 | result = await bot.{{ function.name }}( 71 | {%- for variable in function.variables %}{% if variable.name == 'chat_id' %} 72 | entity=entity, 73 | {%- else %} 74 | {{ variable.name }}={{ variable.name }}, 75 | {%- endif %}{%- endfor %} 76 | ) 77 | data = await to_web_api(result, bot) 78 | return r_success(data.to_array()) 79 | # end def 80 | 81 | 82 | {% endfor -%} 83 | -------------------------------------------------------------------------------- /code_generation/templates/typehints.template: -------------------------------------------------------------------------------- 1 | {%- from "macros.template" import for_args_none, for_type_list_of_full, for_type_list_of, types_as_tuple -%} 2 | {%- from "macros.template" import for_args_format_repr, for_args_keys, for_type -%} 3 | {%- from "macros.template" import set_array, set_data_array_element -%} 4 | 5 | 6 | class {{ clazz.clazz -}}({{ clazz.parent_clazz.string }}): 7 | {% if clazz.description or clazz.link or clazz.parameters or clazz.keywords %}""" 8 | {% block class_docstring -%} 9 | {%- if clazz.description %}{{ clazz.description.strip()|indent }}{% endif %}{%- if clazz.description and clazz.link %} 10 | 11 | {% endif %}{% if clazz.link %}{{ clazz.link }}{% endif %}{%- if (clazz.description or clazz.link) and clazz.parameters %} 12 | {# first line -#} 13 | {% endif %}{%- if clazz.parameters %} 14 | 15 | Parameters: 16 | {% for param in clazz.parameters %} 17 | :param {{ param.name }}: {{ param.description }} 18 | :type {{ param.name }}: {{ for_type_list_of_full(param) }} 19 | {% endfor -%} 20 | {%- endif -%}{# {%- if (clazz.description or clazz.link or clazz.parameters) and clazz.keywords %} #} 21 | {#- {%- if clazz.keywords %} #} 22 | 23 | Optional keyword parameters: 24 | {% for keyword in clazz.keywords %} 25 | :param {{ keyword.name }}: {{ keyword.description }} 26 | :type {{ keyword.name }}: {{ for_type_list_of_full(keyword) }} 27 | {% endfor -%}{%- if not is_sendable %} 28 | :param _raw: Optional. Original data this object was generated from. Could be `None`. 29 | :type _raw: None | dict 30 | {% endif -%}{%- endblock -%} 31 | """{% endif %} 32 | 33 | {%- for variable in clazz.variables %} 34 | {{ variable.name }}: {{ variable.typehint }} 35 | {%- endfor %} 36 | # end class {{ clazz.clazz }} 37 | -------------------------------------------------------------------------------- /code_generation/templates/typehintsfile.template: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from luckydonaldUtils.exceptions import assert_type_or_raise 3 | from luckydonaldUtils.encoding import unicode_type, to_unicode as u 4 | from typing import Any, Union, List 5 | {% for import in imports -%} 6 | from {{ import.path }} import {{ import.name }} 7 | {%- if loop.last %} 8 | {% endif %} 9 | {% endfor -%} 10 | 11 | __author__ = 'luckydonald' 12 | 13 | {% for clazz in clazzes %} 14 | {% include "typehints.template" %} 15 | {% endfor %} 16 | -------------------------------------------------------------------------------- /docs/source/_templates/autoapi/sorted.rst: -------------------------------------------------------------------------------- 1 | =={{ '=' * node.name|length }}== 2 | ``{{ node.name }}`` 3 | =={{ '=' * node.name|length }}== 4 | 5 | .. automodule:: {{ node.name.split(".")[-1] }} 6 | 7 | .. contents:: 8 | :local: 9 | {##} 10 | {%- block modules -%} 11 | {%- if subnodes %} 12 | 13 | Submodules 14 | ========== 15 | 16 | .. toctree:: 17 | {% for item in subnodes %} 18 | {{ item.name }} 19 | {%- endfor %} 20 | {##} 21 | {%- endif -%} 22 | {%- endblock -%} 23 | {##} 24 | .. currentmodule:: {{ node.name }} 25 | {##} 26 | {%- block functions -%} 27 | {%- if node.functions %} 28 | 29 | Functions 30 | ========= 31 | 32 | .. autosummary:: 33 | {% for item in node.functions %} 34 | {{ item }} 35 | {%- endfor %} 36 | 37 | {% for item in node.functions %} 38 | .. autofunction:: {{ item }} 39 | {##} 40 | {%- endfor -%} 41 | {%- endif -%} 42 | {%- endblock -%} 43 | 44 | {%- block classes -%} 45 | {%- if node.classes %} 46 | 47 | Classes 48 | ======= 49 | 50 | {% for item, obj in node.classes.items() -%} 51 | - :py:class:`{{ item }}`: 52 | {{ obj|summary }} 53 | 54 | {% endfor -%} 55 | 56 | {% for item in node.classes %} 57 | .. autoclass:: {{ item }} 58 | :members: 59 | 60 | {##} 61 | {%- endfor -%} 62 | {%- endif -%} 63 | {%- endblock -%} 64 | 65 | {%- block exceptions -%} 66 | {%- if node.exceptions %} 67 | 68 | Exceptions 69 | ========== 70 | 71 | {% for item, obj in node.exceptions.items() -%} 72 | - :py:exc:`{{ item }}`: 73 | {{ obj|summary }} 74 | 75 | {% endfor -%} 76 | 77 | {% for item in node.exceptions %} 78 | .. autoexception:: {{ item }} 79 | 80 | .. rubric:: Inheritance 81 | .. inheritance-diagram:: {{ item }} 82 | :parts: 1 83 | {##} 84 | {%- endfor -%} 85 | {%- endif -%} 86 | {%- endblock -%} 87 | 88 | {%- block variables -%} 89 | {%- if node.variables %} 90 | 91 | Variables 92 | ========= 93 | 94 | {% for item, obj in node.variables.items() -%} 95 | - :py:data:`{{ item }}` 96 | {% endfor -%} 97 | 98 | {% for item, obj in node.variables.items() %} 99 | .. autodata:: {{ item }} 100 | :annotation: 101 | 102 | .. code-block:: python 103 | 104 | {{ obj|pprint|indent(6) }} 105 | {##} 106 | {%- endfor -%} 107 | {%- endif -%} 108 | {%- endblock -%} -------------------------------------------------------------------------------- /docs/source/index.rst: -------------------------------------------------------------------------------- 1 | Welcome to tgbotapi's documentation! 2 | ==================================== 3 | 4 | .. toctree:: 5 | :maxdepth: 5 6 | 7 | pytgbot/pytgbot 8 | 9 | 10 | 11 | 12 | Indices and tables 13 | ================== 14 | 15 | * :ref:`genindex` 16 | * :ref:`modindex` 17 | * :ref:`search` 18 | * :doc:`Internal Documentation Reference. ` 19 | -------------------------------------------------------------------------------- /docs/source/plantuml.8042.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luckydonald/pytgbot/2f84b11253873f7af1bc7539eb7d93197d51c90c/docs/source/plantuml.8042.jar -------------------------------------------------------------------------------- /examples/inline.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | from pytgbot.api_types.sendable.inline import InlineQueryResultArticle, InputTextMessageContent 5 | from pytgbot.api_types.receivable.inline import InlineQuery 6 | from pytgbot.api_types.receivable.updates import Update 7 | from pytgbot.exceptions import TgApiException 8 | 9 | __author__ = 'luckydonald' 10 | 11 | import logging 12 | logger = logging.getLogger(__name__) 13 | 14 | from somewhere import API_KEY # so I don't upload them to github :D 15 | # Just remove the line, and add API_KEY="..." 16 | 17 | from pytgbot import Bot 18 | from luckydonaldUtils.encoding import to_native as n 19 | # get you bot instance. 20 | bot = Bot(API_KEY) 21 | 22 | 23 | my_info=bot.get_me() 24 | print("Information about myself: {info}".format(info=my_info)) 25 | last_update_id = 0 26 | while True: 27 | # loop forever. 28 | for update in bot.get_updates(limit=100, offset=last_update_id+1, error_as_empty=True): 29 | assert isinstance(update, Update) 30 | last_update_id = update.update_id 31 | print(update) 32 | if not update.inline_query: 33 | continue 34 | query_obj = update.inline_query 35 | assert isinstance(query_obj, InlineQuery) 36 | inline_query_id = query_obj.id 37 | query = query_obj.query 38 | print(query) 39 | foo = list() 40 | foo.append(InlineQueryResultArticle( 41 | id=query+"_normal", 42 | title="test 1 (normal)", 43 | input_message_content=InputTextMessageContent(query), 44 | description='Will send {}'.format(repr(n(query))) 45 | )) 46 | foo.append(InlineQueryResultArticle( 47 | id=query+"_markdown", 48 | title="test 2 (markdown)", 49 | input_message_content=InputTextMessageContent(query, parse_mode="Markdown"), 50 | description='Will send {}'.format(repr(n(query))) 51 | )) 52 | foo.append(InlineQueryResultArticle( 53 | id=query+"_html", 54 | title="test 3 (html)", 55 | input_message_content=InputTextMessageContent(query, parse_mode="HTML"), 56 | description='Will send {}'.format(repr(n(query))) 57 | )) 58 | try: 59 | success = bot.answer_inline_query(inline_query_id, foo, cache_time=2) 60 | except TgApiException: 61 | logger.warn("failed.", exc_info=True) 62 | -------------------------------------------------------------------------------- /examples/inline_keyboard.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from pytgbot import Bot 3 | from pytgbot.api_types.receivable.media import PhotoSize 4 | from pytgbot.api_types.receivable.updates import Message 5 | from pytgbot.api_types.sendable.reply_markup import InlineKeyboardButton, InlineKeyboardMarkup 6 | from pytgbot.exceptions import TgApiException 7 | import logging 8 | 9 | __author__ = 'luckydonald' 10 | 11 | logger = logging.getLogger(__name__) 12 | 13 | from somewhere import API_KEY # just set the key manually. 14 | # API_KEY = "1231412:adLsIfTsTsLfEfPdIdPwdwIoSaBgEuSzTwszPdOaNdYs" 15 | 16 | photo_cache = {} # stores the images. 17 | 18 | # get a bot instance 19 | bot = Bot(API_KEY, return_python_objects=True) 20 | 21 | 22 | def main(): 23 | last_update_id = -1 24 | 25 | while True: 26 | # loop forever. 27 | try: 28 | updates = bot.get_updates(limit=100, offset=last_update_id+1, poll_timeout=30, error_as_empty=True) 29 | for update in updates: 30 | last_update_id = update.update_id 31 | if not (update.message and "/start" in update.message.text) and not update.callback_query: 32 | continue 33 | 34 | if update.message: 35 | print("got message: {msg}".format(msg=update.message)) 36 | origin, peer_id = get_sender_infos(update.message) 37 | current_image = 0 38 | photos = cache_peer_images(peer_id, force=True) 39 | result_image, markup = generate_page(current_image, peer_id, photos) 40 | 41 | result = bot.send_message( 42 | origin, 43 | "Profile pic {num}:\n{w}x{h}, {size}".format( 44 | num=current_image, w=result_image.width, h=result_image.height, size=result_image.file_size 45 | ), 46 | disable_web_page_preview=False, 47 | reply_markup=markup 48 | ) 49 | print(result) 50 | elif update.callback_query: 51 | # callback_query.message is the original message the bot sent 52 | peer_id, current_image, do_submit = update.callback_query.data.split(";") 53 | peer_id, current_image = int(peer_id), int(current_image) # str -> int 54 | do_submit = do_submit == "True" # str -> bool 55 | photos = cache_peer_images(peer_id) 56 | result_image, markup = generate_page(current_image, peer_id, photos) 57 | assert isinstance(result_image, PhotoSize) 58 | if do_submit: 59 | bot.answer_callback_query(update.callback_query.id, text="Sending...") 60 | result = bot.send_photo(chat_id=update.callback_query.message.chat.id, photo=result_image.file_id) 61 | else: 62 | bot.answer_callback_query(update.callback_query.id, text=None) 63 | result = bot.edit_message_text( 64 | "Profile pic {num}\n{w}x{h}, {size}B".format( 65 | num=current_image, w=result_image.width, h=result_image.height, size=result_image.file_size 66 | ), 67 | chat_id=update.callback_query.message.chat.id, 68 | message_id=update.callback_query.message.message_id, 69 | disable_web_page_preview=False, 70 | reply_markup=markup 71 | ) 72 | # end if 73 | print(result) 74 | # end if 75 | # end for 76 | except TgApiException: 77 | logger.exception() 78 | # end try 79 | # end while 80 | # end def main 81 | 82 | 83 | def cache_peer_images(peer_id, force=False): 84 | if not force and peer_id in photo_cache: 85 | return photo_cache[peer_id] 86 | photos = bot.get_user_profile_photos(peer_id).photos 87 | photo_cache[peer_id] = [] 88 | for photo in photos: 89 | photo_cache[peer_id].append( 90 | max(photo, key=lambda p: p.file_size) # get the biggest image. 91 | ) 92 | return photos 93 | # end def 94 | 95 | 96 | def get_sender_infos(message): 97 | assert isinstance(message, Message) 98 | peer_id = message.from_peer.id 99 | origin = message.chat.id if message.chat else message.from_peer.id 100 | return origin, peer_id 101 | 102 | 103 | # noinspection PyTypeChecker 104 | def generate_page(current_image, peer_id, photos): 105 | buttons = [[], []] # 2 rows 106 | # first button row 107 | if current_image > 0: 108 | buttons[0].append(InlineKeyboardButton( 109 | "<<", callback_data="{peer_id};{next_pos};False".format(peer_id=peer_id, next_pos=current_image-1) 110 | )) 111 | # end if 112 | if current_image < len(photos)-1: 113 | buttons[0].append(InlineKeyboardButton( 114 | ">>", callback_data="{peer_id};{next_pos};False".format(peer_id=peer_id, next_pos=current_image + 1) 115 | )) 116 | # end if 117 | # second button row 118 | buttons[1].append(InlineKeyboardButton( 119 | "send", callback_data="{peer_id};{curr_pos};True".format(peer_id=peer_id, curr_pos=current_image) 120 | )) 121 | markup = InlineKeyboardMarkup(buttons) 122 | result_image = photo_cache[peer_id][current_image] 123 | return result_image, markup 124 | 125 | if __name__ == '__main__': 126 | main() 127 | # end if -------------------------------------------------------------------------------- /examples/keyboard_text.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | import json 3 | 4 | from pytgbot import Bot 5 | from pytgbot.api_types import as_array 6 | from pytgbot.api_types.sendable.reply_markup import ForceReply, ReplyKeyboardHide, ReplyKeyboardMarkup, KeyboardButton 7 | 8 | from somewhere import API_KEY, TEST_CHAT # so I don't upload them to github :D 9 | # Just remove the line, and add API_KEY="..." and TEST_CHAT = 12345 10 | 11 | __author__ = 'luckydonald' 12 | 13 | 14 | # get you bot instance. 15 | bot = Bot(API_KEY) 16 | 17 | def main(): 18 | my_info=bot.get_me() 19 | print("Information about myself: {info}".format(info=my_info)) 20 | last_update_id = -1 21 | while True: 22 | for update in bot.get_updates(limit=1, offset=last_update_id+1): 23 | last_update_id = update.update_id 24 | print(update) 25 | if not update.message or not update.message.entities: 26 | continue 27 | for entity in update.message.entities: 28 | # MessageEntity 29 | if entity.type == "bot_command": 30 | command = update.message.text[entity.offset:entity.offset+entity.length] 31 | if command == "/key": 32 | do_keyboard(update.message.chat.id) 33 | elif command == "/unkey": 34 | hide_keyboard(update.message.chat.id) 35 | # end if 36 | 37 | # end for 38 | # end for update 39 | # end while forever 40 | # end def main 41 | 42 | 43 | def do_keyboard(chat_id): 44 | buttons = [ 45 | ["YES", "NO"], 46 | ["Maybe", "lol"], 47 | [KeyboardButton("Contact?", request_contact=True), KeyboardButton("Location?", request_location=True)], 48 | ] 49 | markup = ReplyKeyboardMarkup(buttons, resize_keyboard=True, one_time_keyboard=True) 50 | print(bot.send_msg(chat_id, "test!", reply_markup=markup)) 51 | 52 | 53 | def hide_keyboard(chat_id): 54 | print(bot.send_msg(chat_id, "okey, keyboard hidden.", reply_markup=ReplyKeyboardHide())) 55 | main() 56 | 57 | -------------------------------------------------------------------------------- /examples/mlfw.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | from json import dumps 5 | 6 | from pytgbot import Bot 7 | from pytgbot.exceptions import TgApiException, TgApiServerException, TgApiParseException 8 | from pytgbot.api_types.sendable.inline import InlineQueryResultArticle, InlineQueryResultGif, InlineQueryResultPhoto 9 | from pytgbot.api_types.sendable.inline import InputTextMessageContent 10 | 11 | from luckydonaldUtils.logger import logging 12 | from luckydonaldUtils.download import get_json 13 | 14 | 15 | __author__ = 'luckydonald' 16 | VERSION = "v0.3.1" 17 | 18 | try: 19 | from urllib import quote # python 2 20 | except ImportError: 21 | from urllib.parse import quote # python 3 22 | # end try 23 | logger = logging.getLogger(__name__) 24 | 25 | from somewhere import API_KEY # so I don't upload them to github :D 26 | # Just remove the line, and add API_KEY="..." 27 | 28 | 29 | def main(): 30 | # get you bot instance. 31 | bot = Bot(API_KEY, return_python_objects=False) 32 | # Set `return_python_objects=False` 33 | # because we need to be really fast to answer inline queries in time, 34 | # and my computer is crap, 35 | # so any nanosecond this adds is too much, 36 | # resulting in the queries timing out. 37 | 38 | logging.add_colored_handler(logger_name=__name__, level=logging.DEBUG) 39 | 40 | my_info = bot.get_me() 41 | logger.info("Information about myself: {info}".format(info=my_info)) 42 | last_update_id = 0 43 | mlfw = MLFW(bot) 44 | while True: 45 | # loop forever. 46 | for update in bot.get_updates(limit=100, offset=last_update_id+1, error_as_empty=True).result: 47 | last_update_id = update.update_id 48 | logger.debug(update) 49 | if "inline_query" not in update or not update.inline_query: 50 | continue 51 | inline_query_id = update.inline_query.id 52 | query = update.inline_query.query 53 | query_offset = update.inline_query.offset 54 | mlfw.search(query, inline_query_id, offset=query_offset) 55 | # end for 56 | # end while 57 | # end def main 58 | 59 | 60 | class MLFW(object): 61 | root = "http://mylittlefacewhen.com/" 62 | tag_search = "http://mylittlefacewhen.com/api/v2/tag/" 63 | tag_info = "http://mylittlefacewhen.com/api/v2/face/" 64 | error_image = "http://www.iconsdb.com/icons/preview/red/warning-xxl.png" 65 | 66 | def __init__(self, bot): 67 | super(MLFW, self).__init__() 68 | self.bot = bot 69 | 70 | def search(self, string, inline_query_id, offset): 71 | if not string: # nothing entered. 72 | string = "littlepip" 73 | results = [] 74 | next_offset=None 75 | if offset is None or len(str(offset).strip()) < 1: 76 | offset = 0 77 | else: 78 | offset = int(offset) 79 | valid_tag_names = [] 80 | for string_part in string.split(","): 81 | string_part = string_part.strip() 82 | valid_tag_obj = get_json(self.tag_search, params=dict(format="json", name__startswith=string_part, limit=1)) 83 | if "error" in valid_tag_obj: 84 | error_message = InlineQueryResultArticle( 85 | id="404e:"+string, 86 | title=u"\"{tag}\" not found.".format(tag=string), 87 | input_message_content=InputTextMessageContent(string), 88 | description=valid_tag_obj.error, thumb_url=self.error_image 89 | ) 90 | try: 91 | logger.debug("Sending result: {}".format((inline_query_id, [error_message]))) 92 | result = self.check_result(self.bot.answer_inline_query(inline_query_id, [error_message])) 93 | logger.success(result) 94 | except TgApiException: 95 | logger.exception("Answering query failed.") 96 | return 97 | for tag_obj in valid_tag_obj.objects: 98 | valid_tag_names.append(tag_obj.name) 99 | if len(valid_tag_names) == 0: 100 | result = InlineQueryResultArticle( 101 | id="404t:"+string, 102 | title=u"\"{tag}\" not found.".format(tag=string), 103 | input_message_content=InputTextMessageContent(string), 104 | description="No similar tag found.", 105 | thumb_url=self.error_image 106 | ) 107 | try: 108 | logger.debug("Sending result: {}".format((inline_query_id, result))) 109 | result = self.check_result(self.bot.answer_inline_query(inline_query_id, result)) 110 | logger.success(result) 111 | except TgApiException as e: 112 | logger.exception("Answering query failed: {e}".format(e=e)) 113 | return 114 | logger.info("tags: {}".format(valid_tag_names)) 115 | logger.debug("offset: {}".format(offset)) 116 | images_of_tag = get_json(self.tag_info, params=dict(search=dumps(valid_tag_names), format="json", limit=10, offset=offset)) 117 | logger.debug(images_of_tag) 118 | if images_of_tag.meta.total_count < 1 or len(images_of_tag.objects) < 1: 119 | error_message = InlineQueryResultArticle( 120 | id="404i:"+string, 121 | title=u"\"{tag}\" not found.".format(tag=string), 122 | input_message_content=InputTextMessageContent(string), 123 | description="Search results no images.", 124 | thumb_url=self.error_image 125 | ) 126 | try: 127 | logger.debug("Sending result: {}".format((inline_query_id, [error_message]))) 128 | result = self.check_result(self.bot.answer_inline_query(inline_query_id, [error_message])) 129 | logger.success(result) 130 | except TgApiException as e: 131 | logger.exception("Answering query failed: {e}".format(e=e)) 132 | return 133 | if images_of_tag.meta.next: 134 | next_offset = offset+10 135 | for img in images_of_tag.objects: 136 | # image = self.root + tag.objects[0].resizes.small 137 | image_full = self.root + img.image 138 | image_small = image_full 139 | if "resizes" in img and "small" in img.resizes: 140 | image_small = self.root + img.resizes.small 141 | if "thumbnails" in img: 142 | if "png" in img.thumbnails: 143 | image_small = self.root + img.thumbnails.png 144 | elif "jpg" in img.thumbnails: 145 | image_small = self.root + img.thumbnails.jpg 146 | image_gif = self.root + img.thumbnails.gif if "gif" in img.thumbnails else None 147 | tag_total_count = images_of_tag.meta.total_count 148 | id = "mlfw-{id}".format(id=img.id) 149 | if not id: 150 | logger.error("NO ID: {}".format(img)) 151 | continue 152 | logger.debug("id: {id}".format(id=id)) 153 | # results.append(InlineQueryResultArticle(id=id, thumb_url=image_small, title=u"{tag}".format(tag=img.title), message_text=image_full, description=img.description)) 154 | if image_gif: 155 | results.append(InlineQueryResultGif(id=id, title=img.title, gif_url=image_full, thumb_url=image_small, caption=self.str_to_caption(string))) 156 | else: 157 | results.append(InlineQueryResultPhoto(id=id, title=img.title, photo_url=image_full, thumb_url=image_small, caption=self.str_to_caption(string))) 158 | for res in results: 159 | logger.debug(res.to_array()) 160 | logger.debug("next_offset=" + str(next_offset)) 161 | try: 162 | logger.debug("Sending result: {}, cache_time=300, next_offset={next_offset}".format((inline_query_id, results), next_offset=next_offset)) 163 | result = self.check_result(self.bot.answer_inline_query(inline_query_id, results, cache_time=300, next_offset=next_offset)) 164 | logger.success(result) 165 | except TgApiException as e: 166 | logger.exception("Answering query failed: {e}".format(e=e)) 167 | # end try 168 | # end def 169 | 170 | @staticmethod 171 | def str_to_caption(search_string): 172 | search_string = search_string.strip() 173 | if search_string.lower() == "littlepip": 174 | return "#littlepip #best_pony" 175 | # end def 176 | return "#{search}".format(search=search_string.strip().lower().replace(" ", "_")) 177 | # end def str_to_caption 178 | 179 | def check_result(self, res): 180 | if res.ok != True: 181 | raise TgApiServerException( 182 | error_code=res.error_code if "error_code" in res else None, 183 | response=res.response if "response" in res else None, 184 | description=res.description if "description" in res else None, 185 | ) 186 | # end if not ok 187 | if "result" not in res: 188 | raise TgApiParseException('Key "result" is missing.') 189 | # end if no result 190 | return res.result 191 | # end class 192 | 193 | if __name__ == '__main__': 194 | main() -------------------------------------------------------------------------------- /examples/native_objects.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from luckydonaldUtils.logger import logging 3 | from pytgbot import Bot 4 | from pytgbot.api_types.receivable import Result 5 | from pytgbot.api_types.receivable.updates import Update 6 | 7 | 8 | from somewhere import API_KEY # so I don't upload them to github :D 9 | # Just remove the line, and add API_KEY="..." 10 | 11 | __author__ = 'luckydonald' 12 | logger = logging.getLogger(__name__) 13 | 14 | 15 | def main(): 16 | logging.add_colored_handler(level=logging.DEBUG) 17 | # get you bot instance. 18 | bot = Bot(API_KEY, return_python_objects=True) 19 | print(bot.get_me()) 20 | 21 | # do the update loop. 22 | last_update_id = 0 23 | while True: # loop forever. 24 | updates = bot.get_updates(limit=100, offset=last_update_id + 1) 25 | for update in updates: # for every new update 26 | last_update_id = update.update_id 27 | print(update) 28 | result = update.to_array() 29 | assert isinstance(result, dict) 30 | print(result) 31 | # end for 32 | # end while 33 | # end def 34 | 35 | 36 | if __name__ == '__main__': 37 | main() 38 | # end if -------------------------------------------------------------------------------- /examples/ping_bot.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | __author__ = 'luckydonald' 4 | 5 | import logging 6 | logger = logging.getLogger(__name__) 7 | 8 | from pytgbot import Bot 9 | 10 | from somewhere import API_KEY # so I don't upload them to github :D 11 | # Just remove the line, and add API_KEY="..." and TEST_CHAT = 12345 12 | 13 | # get you bot instance. 14 | bot = Bot(API_KEY) 15 | 16 | 17 | my_info=bot.get_me() 18 | print("Information about myself: {info}".format(info=my_info)) 19 | last_update_id = 0 20 | 21 | while True: 22 | # loop forever. 23 | for update in bot.get_updates(limit=100, offset=last_update_id+1): 24 | last_update_id = update.update_id 25 | print(update) 26 | if update.message and update.message.text: # we have a text message. 27 | if update.message.chat: # is a group chat 28 | sender = update.message.chat.id 29 | else: # user chat 30 | sender = update.message.from_peer.id 31 | # end if 32 | 33 | if update.message.text == "ping": 34 | print(bot.send_msg(sender, "pong!", reply_to_message_id=update.message.message_id)) 35 | 36 | 37 | -------------------------------------------------------------------------------- /examples/post_profile_pictures.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | __author__ = 'luckydonald' 3 | 4 | import logging 5 | logger = logging.getLogger(__name__) 6 | 7 | from pytgbot import Bot 8 | 9 | from somewhere import API_KEY # just set the key manually. 10 | # API_KEY = "1231412:adLsIfTsTsLfEfPdIdPwdwIoSaBgEuSzTwszPdOaNdYs" 11 | 12 | 13 | # get a bot instance 14 | bot = Bot(API_KEY) 15 | 16 | last_update_id = -1 17 | while True: 18 | # loop forever. 19 | for update in bot.get_updates(limit=100, offset=last_update_id+1, poll_timeout=30): 20 | last_update_id = update.update_id 21 | print("got message: {msg}".format(msg=update)) 22 | if not "message" in update: 23 | continue 24 | message = update.message 25 | if not "text" in message: 26 | continue 27 | logger.debug("got text: {msg}".format(msg=message.text.encode("utf-8"))) 28 | if not message.text == "/pics": 29 | continue 30 | origin = message.chat.id if "chat" in message else message.from_peer.id 31 | if "reply_to_message" in message: 32 | name = message.reply_to_message.from_peer.first_name 33 | peer = message.reply_to_message.from_peer.id 34 | photos = bot.get_user_profile_photos(peer).photos 35 | if len(photos) == 0: 36 | bot.send_message(origin, "No images.") 37 | continue 38 | for x in reversed(photos): 39 | biggest_image = max(x, key=lambda p: p.file_size) # get the biggest image. 40 | bot.send_photo(origin, biggest_image.file_id, caption="{name}".format(name=name, 41 | w=biggest_image.width, h=biggest_image.height) ) 42 | else: 43 | bot.send_message(origin, "Please reply to some message to select a user.") 44 | # end if 45 | # end for 46 | #end while -------------------------------------------------------------------------------- /pytgbot/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | import logging 3 | from .bot import Bot 4 | 5 | __author__ = 'luckydonald' 6 | __version__ = "5.7" 7 | __all__ = ["api_types", "bot", "Bot"] 8 | VERSION = __version__ 9 | 10 | API_VERSION = "5" + "." + "7" # so the bumpversion script doesn't break this accidentally. 11 | API_DATE = "January 31, 2022" 12 | 13 | logger = logging.getLogger(__name__) 14 | logger.debug('pytgbot version {pytgbot} (API {api}, {api_date})'.format( 15 | pytgbot=__version__, api=API_VERSION, api_date=API_DATE 16 | )) 17 | -------------------------------------------------------------------------------- /pytgbot/api_types/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | import logging 3 | from json import dumps as _json_dumps 4 | from luckydonaldUtils.encoding import unicode_type, to_unicode as u 5 | # NOTE: `from . import receivable` import at the bottom of this file 6 | 7 | __author__ = 'luckydonald' 8 | __all__ = [ 9 | 'TgBotApiObject', 10 | ] 11 | __all__ += ["from_array_list", "as_array", "receivable"] 12 | logger = logging.getLogger(__name__) 13 | 14 | 15 | class TgBotApiObject(object): 16 | """ 17 | Base class for every api object class. 18 | 19 | Optional keyword parameters: 20 | 21 | :param _raw: Optional. Original data this object was generated from. Could be `None`. 22 | :type _raw: None | dict 23 | """ 24 | 25 | def __init__(self): 26 | self._raw = None 27 | super(TgBotApiObject, self).__init__() 28 | # end def __init__ 29 | 30 | def to_array(self): 31 | array = dict() 32 | return array 33 | # end def to_array 34 | 35 | @staticmethod 36 | def validate_array(array): 37 | """ 38 | Builds a new array with valid values for the {{ clazz.clazz }} constructor. 39 | 40 | :return: new array with valid values 41 | :rtype: dict 42 | """ 43 | return {} 44 | # end def 45 | 46 | @staticmethod 47 | def from_array(array): 48 | if not array: 49 | return None 50 | return TgBotApiObject() 51 | # end def from_array 52 | 53 | # # # # # # # # # # # # # # 54 | # helper functions below # 55 | # # # # # # # # # # # # # 56 | 57 | @classmethod 58 | def from_array_list(cls, result, list_level): 59 | """ 60 | Tries to parse the `result` as type given in `required_type`, while traversing into lists as often as specified in `list_level`. 61 | 62 | :param cls: Type as what it should be parsed as. Can be any class extending :class:`TgBotApiObject`. 63 | E.g. If you call `Class.from_array_list`, it will automatically be set to `Class`. 64 | :type cls: class 65 | 66 | :param result: The result to parse 67 | 68 | :param list_level: "list of" * list_level 69 | :type list_level: int 70 | 71 | :return: the result as `required_type` type 72 | """ 73 | return from_array_list(cls, result, list_level, is_builtin=False) # the one below, not itself. Yes, same name... 74 | # end def from_array_list 75 | 76 | @staticmethod 77 | def _builtin_from_array_list(required_type, value, list_level): 78 | """ 79 | Helper method to make :func:`from_array_list` available to all classes extending this, 80 | without the need for additional imports. 81 | 82 | :param required_type: Type as what it should be parsed as. Any builtin. 83 | :param value: The result to parse 84 | :param list_level: "list of" * list_level 85 | :return: 86 | """ 87 | return from_array_list(required_type, value, list_level, is_builtin=True) 88 | # end def _builtin_from_array_list 89 | 90 | @staticmethod 91 | def _as_array(obj): 92 | """ 93 | Helper method to make :func:`as_array` available to all classes extending this, 94 | without the need for additional imports. 95 | """ 96 | return as_array(obj) 97 | # end def 98 | 99 | def __setattr__(self, key, value): 100 | """ 101 | Remove `self._raw` if any other value is set. 102 | """ 103 | super(TgBotApiObject, self).__setattr__(key, value) 104 | if not key.startswith('_') and hasattr(self, '_raw') and self._raw is not None: 105 | self._raw = None 106 | # end if 107 | # end def 108 | # end class 109 | 110 | 111 | # # # # # # # 112 | # Functions # 113 | # # # # # # # 114 | 115 | 116 | def from_array_list(required_type, result, list_level, is_builtin): 117 | """ 118 | Tries to parse the `result` as type given in `required_type`, while traversing into lists as often as specified in `list_level`. 119 | 120 | :param required_type: What it should be parsed as 121 | :type required_type: class 122 | 123 | :param result: The result to parse 124 | 125 | :param list_level: "list of" * list_level 126 | :type list_level: int 127 | 128 | :param is_builtin: if it is a builtin python type like :class:`int`, :class:`bool`, etc. 129 | :type is_builtin: bool 130 | 131 | :return: the result as `required_type` type 132 | """ 133 | logger.debug("Trying parsing as {type}, list_level={list_level}, is_builtin={is_builtin}".format( 134 | type=required_type.__name__, list_level=list_level, is_builtin=is_builtin 135 | )) 136 | if list_level > 0: 137 | assert isinstance(result, (list, tuple)) 138 | return [from_array_list(required_type, obj, list_level-1, is_builtin) for obj in result] 139 | # end if 140 | if is_builtin: 141 | if isinstance(result, required_type): 142 | logger.debug("Already is correct type.") 143 | return required_type(result) 144 | elif isinstance(required_type, unicode_type): # handle str, so emojis work for py2. 145 | return u(result) 146 | else: 147 | logger.warning("Trying parsing with ast.literal_eval()...") 148 | import ast 149 | return ast.literal_eval(str(result)) # raises ValueError if it could not parse 150 | # end if 151 | else: 152 | return required_type.from_array(result) 153 | # end if 154 | # end def _parse_builtin_type 155 | 156 | 157 | def as_array(obj): 158 | """ 159 | Creates an json-like representation of a variable, supporting types with a `.to_array()` function. 160 | 161 | :rtype: dict|list|str|int|float|bool|None 162 | """ 163 | if hasattr(obj, "to_array"): 164 | return obj.to_array() 165 | elif isinstance(obj, (list, tuple)): 166 | return [as_array(x) for x in obj] 167 | elif isinstance(obj, dict): 168 | return {key: as_array(obj[key]) for key in obj.keys()} 169 | else: 170 | _json_dumps(obj) # raises error if is wrong json 171 | return obj 172 | # end if 173 | # end def 174 | 175 | 176 | from . import receivable # bottom of file so TgBotApiObject is already defined. 177 | -------------------------------------------------------------------------------- /pytgbot/api_types/__init__.pyi: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from luckydonaldUtils.exceptions import assert_type_or_raise 3 | from luckydonaldUtils.encoding import unicode_type, to_unicode as u 4 | from luckydonaldUtils.typing import JSONType 5 | from typing import TypeVar, Type, Union 6 | 7 | __author__ = 'luckydonald' 8 | 9 | 10 | class TgBotApiObject(object): 11 | """ 12 | Base class for every api object class. 13 | 14 | Optional keyword parameters: 15 | 16 | :param _raw: Optional. Original data this object was generated from. Could be `None`. 17 | :type _raw: None | dict 18 | """ 19 | # end class TgBotApiObject 20 | 21 | 22 | REQUIRED_TYPE = TypeVar('REQUIRED_TYPE') 23 | def from_array_list(required_type: Type[REQUIRED_TYPE], result: JSONType, list_level: int, is_builtin: bool) -> REQUIRED_TYPE: pass 24 | 25 | def as_array(obj: Union[TgBotApiObject, list, tuple, dict, JSONType]) -> JSONType: pass 26 | -------------------------------------------------------------------------------- /pytgbot/api_types/receivable/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from luckydonaldUtils.encoding import unicode_type, to_unicode as u 3 | from luckydonaldUtils.exceptions import assert_type_or_raise 4 | from .. import TgBotApiObject 5 | 6 | __author__ = 'luckydonald' 7 | __all__ = [ 8 | 'Receivable', 9 | 'Result', 10 | ] 11 | __all__ += ["game", "inline", "media", "payments", "peer", "stickers", "updates", "Receivable", "Result", "WebhookInfo"] 12 | 13 | 14 | class Receivable(TgBotApiObject): 15 | """ 16 | Base class for all classes for stuff which telegram sends us. 17 | 18 | Optional keyword parameters: 19 | 20 | :param _raw: Optional. Original data this object was generated from. Could be `None`. 21 | :type _raw: None | dict 22 | """ 23 | 24 | pass 25 | # end class Receivable 26 | 27 | 28 | class Result(Receivable): 29 | """ 30 | Base class for all classes for stuff which we throw in requests towards the telegram servers. 31 | 32 | Optional keyword parameters: 33 | 34 | :param _raw: Optional. Original data this object was generated from. Could be `None`. 35 | :type _raw: None | dict 36 | """ 37 | 38 | pass 39 | # end class Result 40 | 41 | from .updates import WebhookInfo 42 | -------------------------------------------------------------------------------- /pytgbot/api_types/receivable/__init__.pyi: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from luckydonaldUtils.exceptions import assert_type_or_raise 3 | from luckydonaldUtils.encoding import unicode_type, to_unicode as u 4 | from typing import Any, Union, List 5 | from pytgbot.api_types import TgBotApiObject 6 | from pytgbot.api_types.receivable import Receivable 7 | 8 | __author__ = 'luckydonald' 9 | 10 | 11 | class Receivable(TgBotApiObject): 12 | """ 13 | Base class for all classes for stuff which telegram sends us. 14 | 15 | Optional keyword parameters: 16 | 17 | :param _raw: Optional. Original data this object was generated from. Could be `None`. 18 | :type _raw: None | dict 19 | """ 20 | # end class Receivable 21 | 22 | class Result(Receivable): 23 | """ 24 | Base class for all classes for stuff which we throw in requests towards the telegram servers. 25 | 26 | Optional keyword parameters: 27 | 28 | :param _raw: Optional. Original data this object was generated from. Could be `None`. 29 | :type _raw: None | dict 30 | """ 31 | # end class Result 32 | -------------------------------------------------------------------------------- /pytgbot/api_types/receivable/game.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from luckydonaldUtils.encoding import unicode_type, to_unicode as u 3 | from luckydonaldUtils.exceptions import assert_type_or_raise 4 | from . import Result 5 | 6 | __author__ = 'luckydonald' 7 | __all__ = [ 8 | 'GameHighScore', 9 | ] 10 | 11 | 12 | class GameHighScore(Result): 13 | """ 14 | This object represents one row of the high scores table for a game. 15 | 16 | https://core.telegram.org/bots/api#gamehighscore 17 | 18 | 19 | Parameters: 20 | 21 | :param position: Position in high score table for the game 22 | :type position: int 23 | 24 | :param user: User 25 | :type user: pytgbot.api_types.receivable.peer.User 26 | 27 | :param score: Score 28 | :type score: int 29 | 30 | 31 | Optional keyword parameters: 32 | 33 | :param _raw: Optional. Original data this object was generated from. Could be `None`. 34 | :type _raw: None | dict 35 | """ 36 | 37 | def __init__(self, position, user, score, _raw=None): 38 | """ 39 | This object represents one row of the high scores table for a game. 40 | 41 | https://core.telegram.org/bots/api#gamehighscore 42 | 43 | 44 | Parameters: 45 | 46 | :param position: Position in high score table for the game 47 | :type position: int 48 | 49 | :param user: User 50 | :type user: pytgbot.api_types.receivable.peer.User 51 | 52 | :param score: Score 53 | :type score: int 54 | 55 | 56 | Optional keyword parameters: 57 | 58 | :param _raw: Optional. Original data this object was generated from. Could be `None`. 59 | :type _raw: None | dict 60 | """ 61 | super(GameHighScore, self).__init__() 62 | from .peer import User 63 | assert_type_or_raise(position, int, parameter_name="position") 64 | self.position = position 65 | assert_type_or_raise(user, User, parameter_name="user") 66 | self.user = user 67 | assert_type_or_raise(score, int, parameter_name="score") 68 | self.score = score 69 | 70 | self._raw = _raw 71 | # end def __init__ 72 | 73 | def to_array(self, prefer_original=False): 74 | """ 75 | Serializes this GameHighScore to a dictionary. 76 | 77 | :param prefer_original: If we should return the data this was constructed with if available. If it's not available, it will be constructed normally from the data of the object. 78 | :type prefer_original: bool 79 | 80 | :return: dictionary representation of this object. 81 | :rtype: dict 82 | """ 83 | if prefer_original and self._raw: 84 | return self._raw 85 | # end if 86 | 87 | from .peer import User 88 | array = super(GameHighScore, self).to_array() 89 | 90 | array['position'] = int(self.position) # type int 91 | array['user'] = self.user.to_array() # type User 92 | array['score'] = int(self.score) # type int 93 | return array 94 | # end def to_array 95 | 96 | @staticmethod 97 | def validate_array(array): 98 | """ 99 | Builds a new array with valid values for the GameHighScore constructor. 100 | 101 | :return: new array with valid values 102 | :rtype: dict 103 | """ 104 | assert_type_or_raise(array, dict, parameter_name="array") 105 | from .peer import User 106 | data = Result.validate_array(array) 107 | data['position'] = int(array.get('position')) 108 | data['user'] = User.from_array(array.get('user')) 109 | data['score'] = int(array.get('score')) 110 | return data 111 | # end def validate_array 112 | 113 | @staticmethod 114 | def from_array(array): 115 | """ 116 | Deserialize a new GameHighScore from a given dictionary. 117 | 118 | :return: new GameHighScore instance. 119 | :rtype: GameHighScore 120 | """ 121 | if not array: # None or {} 122 | return None 123 | # end if 124 | 125 | data = GameHighScore.validate_array(array) 126 | data['_raw'] = array 127 | return GameHighScore(**data) 128 | # end def from_array 129 | 130 | def __str__(self): 131 | """ 132 | Implements `str(gamehighscore_instance)` 133 | """ 134 | return "GameHighScore(position={self.position!r}, user={self.user!r}, score={self.score!r})".format(self=self) 135 | # end def __str__ 136 | 137 | def __repr__(self): 138 | """ 139 | Implements `repr(gamehighscore_instance)` 140 | """ 141 | if self._raw: 142 | return "GameHighScore.from_array({self._raw})".format(self=self) 143 | # end if 144 | return "GameHighScore(position={self.position!r}, user={self.user!r}, score={self.score!r})".format(self=self) 145 | # end def __repr__ 146 | 147 | def __contains__(self, key): 148 | """ 149 | Implements `"key" in gamehighscore_instance` 150 | """ 151 | return ( 152 | key in ["position", "user", "score"] 153 | and hasattr(self, key) 154 | and bool(getattr(self, key, None)) 155 | ) 156 | # end def __contains__ 157 | # end class GameHighScore 158 | -------------------------------------------------------------------------------- /pytgbot/api_types/receivable/game.pyi: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from luckydonaldUtils.exceptions import assert_type_or_raise 3 | from luckydonaldUtils.encoding import unicode_type, to_unicode as u 4 | from typing import Any, Union, List 5 | from pytgbot.api_types.receivable import Result 6 | 7 | __author__ = 'luckydonald' 8 | 9 | 10 | class GameHighScore(Result): 11 | """ 12 | This object represents one row of the high scores table for a game. 13 | 14 | https://core.telegram.org/bots/api#gamehighscore 15 | 16 | 17 | Parameters: 18 | 19 | :param position: Position in high score table for the game 20 | :type position: int 21 | 22 | :param user: User 23 | :type user: pytgbot.api_types.receivable.peer.User 24 | 25 | :param score: Score 26 | :type score: int 27 | 28 | 29 | Optional keyword parameters: 30 | 31 | :param _raw: Optional. Original data this object was generated from. Could be `None`. 32 | :type _raw: None | dict 33 | """ 34 | position: int 35 | user: User 36 | score: int 37 | # end class GameHighScore 38 | -------------------------------------------------------------------------------- /pytgbot/api_types/receivable/inline.pyi: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from luckydonaldUtils.exceptions import assert_type_or_raise 3 | from luckydonaldUtils.encoding import unicode_type, to_unicode as u 4 | from typing import Any, Union, List 5 | from pytgbot.api_types.receivable import Result 6 | from pytgbot.api_types.receivable.updates import UpdateType 7 | 8 | __author__ = 'luckydonald' 9 | 10 | 11 | class InlineQuery(Result): 12 | """ 13 | This object represents an incoming inline query. When the user sends an empty query, your bot could return some default or trending results. 14 | 15 | https://core.telegram.org/bots/api#inlinequery 16 | 17 | 18 | Parameters: 19 | 20 | :param id: Unique identifier for this query 21 | :type id: str|unicode 22 | 23 | :param from_peer: Sender 24 | :type from_peer: pytgbot.api_types.receivable.peer.User 25 | 26 | :param query: Text of the query (up to 256 characters) 27 | :type query: str|unicode 28 | 29 | :param offset: Offset of the results to be returned, can be controlled by the bot 30 | :type offset: str|unicode 31 | 32 | 33 | Optional keyword parameters: 34 | 35 | :param chat_type: Optional. Type of the chat, from which the inline query was sent. Can be either "sender" for a private chat with the inline query sender, "private", "group", "supergroup", or "channel". The chat type should be always known for requests sent from official clients and most third-party clients, unless the request was sent from a secret chat 36 | :type chat_type: str|unicode 37 | 38 | :param location: Optional. Sender location, only for bots that request user location 39 | :type location: pytgbot.api_types.receivable.media.Location 40 | 41 | :param _raw: Optional. Original data this object was generated from. Could be `None`. 42 | :type _raw: None | dict 43 | """ 44 | id: str 45 | from_peer: User 46 | query: str 47 | offset: str 48 | chat_type: str 49 | location: Location 50 | # end class InlineQuery 51 | 52 | class ChosenInlineResult(UpdateType): 53 | """ 54 | Represents a result of an inline query that was chosen by the user and sent to their chat partner. 55 | Note: It is necessary to enable inline feedback via @Botfather in order to receive these objects in updates. 56 | 57 | https://core.telegram.org/bots/api#choseninlineresult 58 | 59 | 60 | Parameters: 61 | 62 | :param result_id: The unique identifier for the result that was chosen 63 | :type result_id: str|unicode 64 | 65 | :param from_peer: The user that chose the result 66 | :type from_peer: pytgbot.api_types.receivable.peer.User 67 | 68 | :param query: The query that was used to obtain the result 69 | :type query: str|unicode 70 | 71 | 72 | Optional keyword parameters: 73 | 74 | :param location: Optional. Sender location, only for bots that require user location 75 | :type location: pytgbot.api_types.receivable.media.Location 76 | 77 | :param inline_message_id: Optional. Identifier of the sent inline message. Available only if there is an inline keyboard attached to the message. Will be also received in callback queries and can be used to edit the message. 78 | :type inline_message_id: str|unicode 79 | 80 | :param _raw: Optional. Original data this object was generated from. Could be `None`. 81 | :type _raw: None | dict 82 | """ 83 | result_id: str 84 | from_peer: User 85 | query: str 86 | location: Location 87 | inline_message_id: str 88 | # end class ChosenInlineResult 89 | -------------------------------------------------------------------------------- /pytgbot/api_types/receivable/passport.pyi: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from luckydonaldUtils.exceptions import assert_type_or_raise 3 | from luckydonaldUtils.encoding import unicode_type, to_unicode as u 4 | from typing import Any, Union, List 5 | from pytgbot.api_types.receivable import Result 6 | 7 | __author__ = 'luckydonald' 8 | 9 | 10 | class PassportData(Result): 11 | """ 12 | Contains information about Telegram Passport data shared with the bot by the user. 13 | 14 | https://core.telegram.org/bots/api#passportdata 15 | 16 | 17 | Parameters: 18 | 19 | :param data: Array with information about documents and other Telegram Passport elements that was shared with the bot 20 | :type data: list of pytgbot.api_types.receivable.passport.EncryptedPassportElement 21 | 22 | :param credentials: Encrypted credentials required to decrypt the data 23 | :type credentials: pytgbot.api_types.receivable.passport.EncryptedCredentials 24 | 25 | 26 | Optional keyword parameters: 27 | 28 | :param _raw: Optional. Original data this object was generated from. Could be `None`. 29 | :type _raw: None | dict 30 | """ 31 | data: List[EncryptedPassportElement] 32 | credentials: EncryptedCredentials 33 | # end class PassportData 34 | 35 | class PassportFile(Result): 36 | """ 37 | This object represents a file uploaded to Telegram Passport. Currently all Telegram Passport files are in JPEG format when decrypted and don't exceed 10MB. 38 | 39 | https://core.telegram.org/bots/api#passportfile 40 | 41 | 42 | Parameters: 43 | 44 | :param file_id: Identifier for this file, which can be used to download or reuse the file 45 | :type file_id: str|unicode 46 | 47 | :param file_unique_id: Unique identifier for this file, which is supposed to be the same over time and for different bots. Can't be used to download or reuse the file. 48 | :type file_unique_id: str|unicode 49 | 50 | :param file_size: File size in bytes 51 | :type file_size: int 52 | 53 | :param file_date: Unix time when the file was uploaded 54 | :type file_date: int 55 | 56 | 57 | Optional keyword parameters: 58 | 59 | :param _raw: Optional. Original data this object was generated from. Could be `None`. 60 | :type _raw: None | dict 61 | """ 62 | file_id: str 63 | file_unique_id: str 64 | file_size: int 65 | file_date: int 66 | # end class PassportFile 67 | 68 | class EncryptedPassportElement(Result): 69 | """ 70 | Contains information about documents or other Telegram Passport elements shared with the bot by the user. 71 | 72 | https://core.telegram.org/bots/api#encryptedpassportelement 73 | 74 | 75 | Parameters: 76 | 77 | :param type: Element type. One of "personal_details", "passport", "driver_license", "identity_card", "internal_passport", "address", "utility_bill", "bank_statement", "rental_agreement", "passport_registration", "temporary_registration", "phone_number", "email". 78 | :type type: str|unicode 79 | 80 | :param hash: Base64-encoded element hash for using in PassportElementErrorUnspecified 81 | :type hash: str|unicode 82 | 83 | 84 | Optional keyword parameters: 85 | 86 | :param data: Optional. Base64-encoded encrypted Telegram Passport element data provided by the user, available for "personal_details", "passport", "driver_license", "identity_card", "internal_passport" and "address" types. Can be decrypted and verified using the accompanying EncryptedCredentials. 87 | :type data: str|unicode 88 | 89 | :param phone_number: Optional. User's verified phone number, available only for "phone_number" type 90 | :type phone_number: str|unicode 91 | 92 | :param email: Optional. User's verified email address, available only for "email" type 93 | :type email: str|unicode 94 | 95 | :param files: Optional. Array of encrypted files with documents provided by the user, available for "utility_bill", "bank_statement", "rental_agreement", "passport_registration" and "temporary_registration" types. Files can be decrypted and verified using the accompanying EncryptedCredentials. 96 | :type files: list of pytgbot.api_types.receivable.passport.PassportFile 97 | 98 | :param front_side: Optional. Encrypted file with the front side of the document, provided by the user. Available for "passport", "driver_license", "identity_card" and "internal_passport". The file can be decrypted and verified using the accompanying EncryptedCredentials. 99 | :type front_side: pytgbot.api_types.receivable.passport.PassportFile 100 | 101 | :param reverse_side: Optional. Encrypted file with the reverse side of the document, provided by the user. Available for "driver_license" and "identity_card". The file can be decrypted and verified using the accompanying EncryptedCredentials. 102 | :type reverse_side: pytgbot.api_types.receivable.passport.PassportFile 103 | 104 | :param selfie: Optional. Encrypted file with the selfie of the user holding a document, provided by the user; available for "passport", "driver_license", "identity_card" and "internal_passport". The file can be decrypted and verified using the accompanying EncryptedCredentials. 105 | :type selfie: pytgbot.api_types.receivable.passport.PassportFile 106 | 107 | :param translation: Optional. Array of encrypted files with translated versions of documents provided by the user. Available if requested for "passport", "driver_license", "identity_card", "internal_passport", "utility_bill", "bank_statement", "rental_agreement", "passport_registration" and "temporary_registration" types. Files can be decrypted and verified using the accompanying EncryptedCredentials. 108 | :type translation: list of pytgbot.api_types.receivable.passport.PassportFile 109 | 110 | :param _raw: Optional. Original data this object was generated from. Could be `None`. 111 | :type _raw: None | dict 112 | """ 113 | type: str 114 | hash: str 115 | data: str 116 | phone_number: str 117 | email: str 118 | files: List[PassportFile] 119 | front_side: PassportFile 120 | reverse_side: PassportFile 121 | selfie: PassportFile 122 | translation: List[PassportFile] 123 | # end class EncryptedPassportElement 124 | 125 | class EncryptedCredentials(Result): 126 | """ 127 | Contains data required for decrypting and authenticating EncryptedPassportElement. See the Telegram Passport Documentation for a complete description of the data decryption and authentication processes. 128 | 129 | https://core.telegram.org/bots/api#encryptedcredentials 130 | 131 | 132 | Parameters: 133 | 134 | :param data: Base64-encoded encrypted JSON-serialized data with unique user's payload, data hashes and secrets required for EncryptedPassportElement decryption and authentication 135 | :type data: str|unicode 136 | 137 | :param hash: Base64-encoded data hash for data authentication 138 | :type hash: str|unicode 139 | 140 | :param secret: Base64-encoded secret, encrypted with the bot's public RSA key, required for data decryption 141 | :type secret: str|unicode 142 | 143 | 144 | Optional keyword parameters: 145 | 146 | :param _raw: Optional. Original data this object was generated from. Could be `None`. 147 | :type _raw: None | dict 148 | """ 149 | data: str 150 | hash: str 151 | secret: str 152 | # end class EncryptedCredentials 153 | -------------------------------------------------------------------------------- /pytgbot/api_types/receivable/payments.pyi: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from luckydonaldUtils.exceptions import assert_type_or_raise 3 | from luckydonaldUtils.encoding import unicode_type, to_unicode as u 4 | from typing import Any, Union, List 5 | from pytgbot.api_types.receivable import Result 6 | from pytgbot.api_types.receivable.updates import UpdateType 7 | 8 | __author__ = 'luckydonald' 9 | 10 | 11 | class Invoice(Result): 12 | """ 13 | This object contains basic information about an invoice. 14 | 15 | https://core.telegram.org/bots/api#invoice 16 | 17 | 18 | Parameters: 19 | 20 | :param title: Product name 21 | :type title: str|unicode 22 | 23 | :param description: Product description 24 | :type description: str|unicode 25 | 26 | :param start_parameter: Unique bot deep-linking parameter that can be used to generate this invoice 27 | :type start_parameter: str|unicode 28 | 29 | :param currency: Three-letter ISO 4217 currency code 30 | :type currency: str|unicode 31 | 32 | :param total_amount: Total price in the smallest units of the currency (integer, not float/double). For example, for a price of US$ 1.45 pass amount = 145. See the exp parameter in currencies.json, it shows the number of digits past the decimal point for each currency (2 for the majority of currencies). 33 | :type total_amount: int 34 | 35 | 36 | Optional keyword parameters: 37 | 38 | :param _raw: Optional. Original data this object was generated from. Could be `None`. 39 | :type _raw: None | dict 40 | """ 41 | title: str 42 | description: str 43 | start_parameter: str 44 | currency: str 45 | total_amount: int 46 | # end class Invoice 47 | 48 | class ShippingAddress(Result): 49 | """ 50 | This object represents a shipping address. 51 | 52 | https://core.telegram.org/bots/api#shippingaddress 53 | 54 | 55 | Parameters: 56 | 57 | :param country_code: ISO 3166-1 alpha-2 country code 58 | :type country_code: str|unicode 59 | 60 | :param state: State, if applicable 61 | :type state: str|unicode 62 | 63 | :param city: City 64 | :type city: str|unicode 65 | 66 | :param street_line1: First line for the address 67 | :type street_line1: str|unicode 68 | 69 | :param street_line2: Second line for the address 70 | :type street_line2: str|unicode 71 | 72 | :param post_code: Address post code 73 | :type post_code: str|unicode 74 | 75 | 76 | Optional keyword parameters: 77 | 78 | :param _raw: Optional. Original data this object was generated from. Could be `None`. 79 | :type _raw: None | dict 80 | """ 81 | country_code: str 82 | state: str 83 | city: str 84 | street_line1: str 85 | street_line2: str 86 | post_code: str 87 | # end class ShippingAddress 88 | 89 | class OrderInfo(Result): 90 | """ 91 | This object represents information about an order. 92 | 93 | https://core.telegram.org/bots/api#orderinfo 94 | 95 | Optional keyword parameters: 96 | 97 | :param name: Optional. User name 98 | :type name: str|unicode 99 | 100 | :param phone_number: Optional. User's phone number 101 | :type phone_number: str|unicode 102 | 103 | :param email: Optional. User email 104 | :type email: str|unicode 105 | 106 | :param shipping_address: Optional. User shipping address 107 | :type shipping_address: pytgbot.api_types.receivable.payments.ShippingAddress 108 | 109 | :param _raw: Optional. Original data this object was generated from. Could be `None`. 110 | :type _raw: None | dict 111 | """ 112 | name: str 113 | phone_number: str 114 | email: str 115 | shipping_address: ShippingAddress 116 | # end class OrderInfo 117 | 118 | class SuccessfulPayment(Result): 119 | """ 120 | This object contains basic information about a successful payment. 121 | 122 | https://core.telegram.org/bots/api#successfulpayment 123 | 124 | 125 | Parameters: 126 | 127 | :param currency: Three-letter ISO 4217 currency code 128 | :type currency: str|unicode 129 | 130 | :param total_amount: Total price in the smallest units of the currency (integer, not float/double). For example, for a price of US$ 1.45 pass amount = 145. See the exp parameter in currencies.json, it shows the number of digits past the decimal point for each currency (2 for the majority of currencies). 131 | :type total_amount: int 132 | 133 | :param invoice_payload: Bot specified invoice payload 134 | :type invoice_payload: str|unicode 135 | 136 | :param telegram_payment_charge_id: Telegram payment identifier 137 | :type telegram_payment_charge_id: str|unicode 138 | 139 | :param provider_payment_charge_id: Provider payment identifier 140 | :type provider_payment_charge_id: str|unicode 141 | 142 | 143 | Optional keyword parameters: 144 | 145 | :param shipping_option_id: Optional. Identifier of the shipping option chosen by the user 146 | :type shipping_option_id: str|unicode 147 | 148 | :param order_info: Optional. Order info provided by the user 149 | :type order_info: pytgbot.api_types.receivable.payments.OrderInfo 150 | 151 | :param _raw: Optional. Original data this object was generated from. Could be `None`. 152 | :type _raw: None | dict 153 | """ 154 | currency: str 155 | total_amount: int 156 | invoice_payload: str 157 | telegram_payment_charge_id: str 158 | provider_payment_charge_id: str 159 | shipping_option_id: str 160 | order_info: OrderInfo 161 | # end class SuccessfulPayment 162 | 163 | class ShippingQuery(UpdateType): 164 | """ 165 | This object contains information about an incoming shipping query. 166 | 167 | https://core.telegram.org/bots/api#shippingquery 168 | 169 | 170 | Parameters: 171 | 172 | :param id: Unique query identifier 173 | :type id: str|unicode 174 | 175 | :param from_peer: User who sent the query 176 | :type from_peer: pytgbot.api_types.receivable.peer.User 177 | 178 | :param invoice_payload: Bot specified invoice payload 179 | :type invoice_payload: str|unicode 180 | 181 | :param shipping_address: User specified shipping address 182 | :type shipping_address: pytgbot.api_types.receivable.payments.ShippingAddress 183 | 184 | 185 | Optional keyword parameters: 186 | 187 | :param _raw: Optional. Original data this object was generated from. Could be `None`. 188 | :type _raw: None | dict 189 | """ 190 | id: str 191 | from_peer: User 192 | invoice_payload: str 193 | shipping_address: ShippingAddress 194 | # end class ShippingQuery 195 | 196 | class PreCheckoutQuery(UpdateType): 197 | """ 198 | This object contains information about an incoming pre-checkout query. 199 | 200 | https://core.telegram.org/bots/api#precheckoutquery 201 | 202 | 203 | Parameters: 204 | 205 | :param id: Unique query identifier 206 | :type id: str|unicode 207 | 208 | :param from_peer: User who sent the query 209 | :type from_peer: pytgbot.api_types.receivable.peer.User 210 | 211 | :param currency: Three-letter ISO 4217 currency code 212 | :type currency: str|unicode 213 | 214 | :param total_amount: Total price in the smallest units of the currency (integer, not float/double). For example, for a price of US$ 1.45 pass amount = 145. See the exp parameter in currencies.json, it shows the number of digits past the decimal point for each currency (2 for the majority of currencies). 215 | :type total_amount: int 216 | 217 | :param invoice_payload: Bot specified invoice payload 218 | :type invoice_payload: str|unicode 219 | 220 | 221 | Optional keyword parameters: 222 | 223 | :param shipping_option_id: Optional. Identifier of the shipping option chosen by the user 224 | :type shipping_option_id: str|unicode 225 | 226 | :param order_info: Optional. Order info provided by the user 227 | :type order_info: pytgbot.api_types.receivable.payments.OrderInfo 228 | 229 | :param _raw: Optional. Original data this object was generated from. Could be `None`. 230 | :type _raw: None | dict 231 | """ 232 | id: str 233 | from_peer: User 234 | currency: str 235 | total_amount: int 236 | invoice_payload: str 237 | shipping_option_id: str 238 | order_info: OrderInfo 239 | # end class PreCheckoutQuery 240 | -------------------------------------------------------------------------------- /pytgbot/api_types/receivable/responses.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from luckydonaldUtils.encoding import unicode_type, to_unicode as u 3 | from luckydonaldUtils.exceptions import assert_type_or_raise 4 | from . import Result 5 | 6 | __author__ = 'luckydonald' 7 | __all__ = [ 8 | 'MessageId', 9 | ] 10 | 11 | 12 | class MessageId(Result): 13 | """ 14 | This object represents a unique message identifier. 15 | 16 | https://core.telegram.org/bots/api#messageid 17 | 18 | 19 | Parameters: 20 | 21 | :param message_id: Unique message identifier 22 | :type message_id: int 23 | 24 | 25 | Optional keyword parameters: 26 | 27 | :param _raw: Optional. Original data this object was generated from. Could be `None`. 28 | :type _raw: None | dict 29 | """ 30 | 31 | def __init__(self, message_id, _raw=None): 32 | """ 33 | This object represents a unique message identifier. 34 | 35 | https://core.telegram.org/bots/api#messageid 36 | 37 | 38 | Parameters: 39 | 40 | :param message_id: Unique message identifier 41 | :type message_id: int 42 | 43 | 44 | Optional keyword parameters: 45 | 46 | :param _raw: Optional. Original data this object was generated from. Could be `None`. 47 | :type _raw: None | dict 48 | """ 49 | super(MessageId, self).__init__() 50 | assert_type_or_raise(message_id, int, parameter_name="message_id") 51 | self.message_id = message_id 52 | 53 | self._raw = _raw 54 | # end def __init__ 55 | 56 | def to_array(self, prefer_original=False): 57 | """ 58 | Serializes this MessageId to a dictionary. 59 | 60 | :param prefer_original: If we should return the data this was constructed with if available. If it's not available, it will be constructed normally from the data of the object. 61 | :type prefer_original: bool 62 | 63 | :return: dictionary representation of this object. 64 | :rtype: dict 65 | """ 66 | if prefer_original and self._raw: 67 | return self._raw 68 | # end if 69 | 70 | array = super(MessageId, self).to_array() 71 | 72 | array['message_id'] = int(self.message_id) # type int 73 | return array 74 | # end def to_array 75 | 76 | @staticmethod 77 | def validate_array(array): 78 | """ 79 | Builds a new array with valid values for the MessageId constructor. 80 | 81 | :return: new array with valid values 82 | :rtype: dict 83 | """ 84 | assert_type_or_raise(array, dict, parameter_name="array") 85 | data = Result.validate_array(array) 86 | data['message_id'] = int(array.get('message_id')) 87 | return data 88 | # end def validate_array 89 | 90 | @staticmethod 91 | def from_array(array): 92 | """ 93 | Deserialize a new MessageId from a given dictionary. 94 | 95 | :return: new MessageId instance. 96 | :rtype: MessageId 97 | """ 98 | if not array: # None or {} 99 | return None 100 | # end if 101 | 102 | data = MessageId.validate_array(array) 103 | data['_raw'] = array 104 | return MessageId(**data) 105 | # end def from_array 106 | 107 | def __str__(self): 108 | """ 109 | Implements `str(messageid_instance)` 110 | """ 111 | return "MessageId(message_id={self.message_id!r})".format(self=self) 112 | # end def __str__ 113 | 114 | def __repr__(self): 115 | """ 116 | Implements `repr(messageid_instance)` 117 | """ 118 | if self._raw: 119 | return "MessageId.from_array({self._raw})".format(self=self) 120 | # end if 121 | return "MessageId(message_id={self.message_id!r})".format(self=self) 122 | # end def __repr__ 123 | 124 | def __contains__(self, key): 125 | """ 126 | Implements `"key" in messageid_instance` 127 | """ 128 | return ( 129 | key in ["message_id"] 130 | and hasattr(self, key) 131 | and bool(getattr(self, key, None)) 132 | ) 133 | # end def __contains__ 134 | # end class MessageId 135 | -------------------------------------------------------------------------------- /pytgbot/api_types/receivable/responses.pyi: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from luckydonaldUtils.exceptions import assert_type_or_raise 3 | from luckydonaldUtils.encoding import unicode_type, to_unicode as u 4 | from typing import Any, Union, List 5 | from pytgbot.api_types.receivable import Result 6 | 7 | __author__ = 'luckydonald' 8 | 9 | 10 | class MessageId(Result): 11 | """ 12 | This object represents a unique message identifier. 13 | 14 | https://core.telegram.org/bots/api#messageid 15 | 16 | 17 | Parameters: 18 | 19 | :param message_id: Unique message identifier 20 | :type message_id: int 21 | 22 | 23 | Optional keyword parameters: 24 | 25 | :param _raw: Optional. Original data this object was generated from. Could be `None`. 26 | :type _raw: None | dict 27 | """ 28 | message_id: int 29 | # end class MessageId 30 | -------------------------------------------------------------------------------- /pytgbot/api_types/receivable/service.pyi: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from luckydonaldUtils.exceptions import assert_type_or_raise 3 | from luckydonaldUtils.encoding import unicode_type, to_unicode as u 4 | from typing import Any, Union, List 5 | from pytgbot.api_types.receivable import Receivable 6 | from pytgbot.api_types.receivable.service import ServiceMessage 7 | 8 | __author__ = 'luckydonald' 9 | 10 | 11 | class ServiceMessage(Receivable): 12 | """ 13 | parent class for all service messages, those are not directly media related special attributes of the Message object. 14 | 15 | Optional keyword parameters: 16 | 17 | :param _raw: Optional. Original data this object was generated from. Could be `None`. 18 | :type _raw: None | dict 19 | """ 20 | # end class ServiceMessage 21 | 22 | class ProximityAlertTriggered(ServiceMessage): 23 | """ 24 | This object represents the content of a service message, sent whenever a user in the chat triggers a proximity alert set by another user. 25 | 26 | https://core.telegram.org/bots/api#proximityalerttriggered 27 | 28 | 29 | Parameters: 30 | 31 | :param traveler: User that triggered the alert 32 | :type traveler: pytgbot.api_types.receivable.peer.User 33 | 34 | :param watcher: User that set the alert 35 | :type watcher: pytgbot.api_types.receivable.peer.User 36 | 37 | :param distance: The distance between the users 38 | :type distance: int 39 | 40 | 41 | Optional keyword parameters: 42 | 43 | :param _raw: Optional. Original data this object was generated from. Could be `None`. 44 | :type _raw: None | dict 45 | """ 46 | traveler: User 47 | watcher: User 48 | distance: int 49 | # end class ProximityAlertTriggered 50 | 51 | class MessageAutoDeleteTimerChanged(ServiceMessage): 52 | """ 53 | This object represents a service message about a change in auto-delete timer settings. 54 | 55 | https://core.telegram.org/bots/api#messageautodeletetimerchanged 56 | 57 | 58 | Parameters: 59 | 60 | :param message_auto_delete_time: New auto-delete time for messages in the chat; in seconds 61 | :type message_auto_delete_time: int 62 | 63 | 64 | Optional keyword parameters: 65 | 66 | :param _raw: Optional. Original data this object was generated from. Could be `None`. 67 | :type _raw: None | dict 68 | """ 69 | message_auto_delete_time: int 70 | # end class MessageAutoDeleteTimerChanged 71 | 72 | class VoiceChatScheduled(ServiceMessage): 73 | """ 74 | This object represents a service message about a voice chat scheduled in the chat. 75 | 76 | https://core.telegram.org/bots/api#voicechatscheduled 77 | 78 | 79 | Parameters: 80 | 81 | :param start_date: Point in time (Unix timestamp) when the voice chat is supposed to be started by a chat administrator 82 | :type start_date: int 83 | 84 | 85 | Optional keyword parameters: 86 | 87 | :param _raw: Optional. Original data this object was generated from. Could be `None`. 88 | :type _raw: None | dict 89 | """ 90 | start_date: int 91 | # end class VoiceChatScheduled 92 | 93 | class VoiceChatStarted(ServiceMessage): 94 | """ 95 | This object represents a service message about a voice chat started in the chat. Currently holds no information. 96 | 97 | https://core.telegram.org/bots/api#voicechatstarted 98 | 99 | Optional keyword parameters: 100 | 101 | :param _raw: Optional. Original data this object was generated from. Could be `None`. 102 | :type _raw: None | dict 103 | """ 104 | # end class VoiceChatStarted 105 | 106 | class VoiceChatEnded(ServiceMessage): 107 | """ 108 | This object represents a service message about a voice chat ended in the chat. 109 | 110 | https://core.telegram.org/bots/api#voicechatended 111 | 112 | 113 | Parameters: 114 | 115 | :param duration: Voice chat duration in seconds 116 | :type duration: int 117 | 118 | 119 | Optional keyword parameters: 120 | 121 | :param _raw: Optional. Original data this object was generated from. Could be `None`. 122 | :type _raw: None | dict 123 | """ 124 | duration: int 125 | # end class VoiceChatEnded 126 | 127 | class VoiceChatParticipantsInvited(ServiceMessage): 128 | """ 129 | This object represents a service message about new members invited to a voice chat. 130 | 131 | https://core.telegram.org/bots/api#voicechatparticipantsinvited 132 | 133 | Optional keyword parameters: 134 | 135 | :param users: Optional. New members that were invited to the voice chat 136 | :type users: list of pytgbot.api_types.receivable.peer.User 137 | 138 | :param _raw: Optional. Original data this object was generated from. Could be `None`. 139 | :type _raw: None | dict 140 | """ 141 | users: List[User] 142 | # end class VoiceChatParticipantsInvited 143 | -------------------------------------------------------------------------------- /pytgbot/api_types/receivable/stickers.pyi: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from luckydonaldUtils.exceptions import assert_type_or_raise 3 | from luckydonaldUtils.encoding import unicode_type, to_unicode as u 4 | from typing import Any, Union, List 5 | from pytgbot.api_types.receivable import Result 6 | 7 | __author__ = 'luckydonald' 8 | 9 | 10 | class StickerSet(Result): 11 | """ 12 | This object represents a sticker set. 13 | 14 | https://core.telegram.org/bots/api#stickerset 15 | 16 | 17 | Parameters: 18 | 19 | :param name: Sticker set name 20 | :type name: str|unicode 21 | 22 | :param title: Sticker set title 23 | :type title: str|unicode 24 | 25 | :param is_animated: True, if the sticker set contains animated stickers 26 | :type is_animated: bool 27 | 28 | :param is_video: True, if the sticker set contains video stickers 29 | :type is_video: bool 30 | 31 | :param contains_masks: True, if the sticker set contains masks 32 | :type contains_masks: bool 33 | 34 | :param stickers: List of all set stickers 35 | :type stickers: list of pytgbot.api_types.receivable.media.Sticker 36 | 37 | 38 | Optional keyword parameters: 39 | 40 | :param thumb: Optional. Sticker set thumbnail in the .WEBP, .TGS, or .WEBM format 41 | :type thumb: pytgbot.api_types.receivable.media.PhotoSize 42 | 43 | :param _raw: Optional. Original data this object was generated from. Could be `None`. 44 | :type _raw: None | dict 45 | """ 46 | name: str 47 | title: str 48 | is_animated: bool 49 | is_video: bool 50 | contains_masks: bool 51 | stickers: List[Sticker] 52 | thumb: PhotoSize 53 | # end class StickerSet 54 | 55 | class MaskPosition(Result): 56 | """ 57 | This object describes the position on faces where a mask should be placed by default. 58 | 59 | https://core.telegram.org/bots/api#maskposition 60 | 61 | 62 | Parameters: 63 | 64 | :param point: The part of the face relative to which the mask should be placed. One of "forehead", "eyes", "mouth", or "chin". 65 | :type point: str|unicode 66 | 67 | :param x_shift: Shift by X-axis measured in widths of the mask scaled to the face size, from left to right. For example, choosing -1.0 will place mask just to the left of the default mask position. 68 | :type x_shift: float 69 | 70 | :param y_shift: Shift by Y-axis measured in heights of the mask scaled to the face size, from top to bottom. For example, 1.0 will place the mask just below the default mask position. 71 | :type y_shift: float 72 | 73 | :param scale: Mask scaling coefficient. For example, 2.0 means double size. 74 | :type scale: float 75 | 76 | 77 | Optional keyword parameters: 78 | 79 | :param _raw: Optional. Original data this object was generated from. Could be `None`. 80 | :type _raw: None | dict 81 | """ 82 | point: str 83 | x_shift: float 84 | y_shift: float 85 | scale: float 86 | # end class MaskPosition 87 | -------------------------------------------------------------------------------- /pytgbot/api_types/sendable/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from luckydonaldUtils.encoding import unicode_type, to_unicode as u 3 | from luckydonaldUtils.exceptions import assert_type_or_raise 4 | from .. import TgBotApiObject 5 | 6 | __author__ = 'luckydonald' 7 | __all__ = [ 8 | 'Sendable', 9 | ] 10 | __all__ += ["files", "inline", "payments", "reply_markup"] 11 | 12 | # UPCOMING CHANGE IN v2.2.0: 13 | from . import files # backwards compatibility, before v2.2.0 14 | 15 | 16 | class Sendable(TgBotApiObject): 17 | """ 18 | Base class for all classes for stuff which we throw in requests towards the telegram servers. 19 | 20 | Optional keyword parameters: 21 | """ 22 | 23 | pass 24 | # end class Sendable 25 | -------------------------------------------------------------------------------- /pytgbot/api_types/sendable/__init__.pyi: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from luckydonaldUtils.exceptions import assert_type_or_raise 3 | from luckydonaldUtils.encoding import unicode_type, to_unicode as u 4 | from typing import Any, Union, List 5 | from pytgbot.api_types import TgBotApiObject 6 | 7 | __author__ = 'luckydonald' 8 | 9 | 10 | class Sendable(TgBotApiObject): 11 | """ 12 | Base class for all classes for stuff which we throw in requests towards the telegram servers. 13 | 14 | Optional keyword parameters: 15 | """ 16 | # end class Sendable 17 | -------------------------------------------------------------------------------- /pytgbot/api_types/sendable/command.pyi: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from luckydonaldUtils.exceptions import assert_type_or_raise 3 | from luckydonaldUtils.encoding import unicode_type, to_unicode as u 4 | from typing import Any, Union, List 5 | from pytgbot.api_types.sendable import Sendable 6 | from pytgbot.api_types.sendable.command import BotCommandScope 7 | 8 | __author__ = 'luckydonald' 9 | 10 | 11 | class BotCommand(Sendable): 12 | """ 13 | This object represents a bot command. 14 | 15 | https://core.telegram.org/bots/api#botcommand 16 | 17 | 18 | Parameters: 19 | 20 | :param command: Text of the command; 1-32 characters. Can contain only lowercase English letters, digits and underscores. 21 | :type command: str|unicode 22 | 23 | :param description: Description of the command; 1-256 characters. 24 | :type description: str|unicode 25 | 26 | 27 | Optional keyword parameters: 28 | """ 29 | command: str 30 | description: str 31 | # end class BotCommand 32 | 33 | class BotCommandScope(Sendable): 34 | """ 35 | This object represents the scope to which bot commands are applied. Currently, the following 7 scopes are supported: 36 | 37 | https://core.telegram.org/bots/api#botcommandscope 38 | 39 | Optional keyword parameters: 40 | """ 41 | # end class BotCommandScope 42 | 43 | class BotCommandScopeDefault(BotCommandScope): 44 | """ 45 | Represents the default scope of bot commands. Default commands are used if no commands with a narrower scope are specified for the user. 46 | 47 | https://core.telegram.org/bots/api#botcommandscopedefault 48 | 49 | 50 | Parameters: 51 | 52 | :param type: Scope type, must be default 53 | :type type: str|unicode 54 | 55 | 56 | Optional keyword parameters: 57 | """ 58 | type: str 59 | # end class BotCommandScopeDefault 60 | 61 | class BotCommandScopeAllPrivateChats(BotCommandScope): 62 | """ 63 | Represents the scope of bot commands, covering all private chats. 64 | 65 | https://core.telegram.org/bots/api#botcommandscopeallprivatechats 66 | 67 | 68 | Parameters: 69 | 70 | :param type: Scope type, must be all_private_chats 71 | :type type: str|unicode 72 | 73 | 74 | Optional keyword parameters: 75 | """ 76 | type: str 77 | # end class BotCommandScopeAllPrivateChats 78 | 79 | class BotCommandScopeAllGroupChats(BotCommandScope): 80 | """ 81 | Represents the scope of bot commands, covering all group and supergroup chats. 82 | 83 | https://core.telegram.org/bots/api#botcommandscopeallgroupchats 84 | 85 | 86 | Parameters: 87 | 88 | :param type: Scope type, must be all_group_chats 89 | :type type: str|unicode 90 | 91 | 92 | Optional keyword parameters: 93 | """ 94 | type: str 95 | # end class BotCommandScopeAllGroupChats 96 | 97 | class BotCommandScopeAllChatAdministrators(BotCommandScope): 98 | """ 99 | Represents the scope of bot commands, covering all group and supergroup chat administrators. 100 | 101 | https://core.telegram.org/bots/api#botcommandscopeallchatadministrators 102 | 103 | 104 | Parameters: 105 | 106 | :param type: Scope type, must be all_chat_administrators 107 | :type type: str|unicode 108 | 109 | 110 | Optional keyword parameters: 111 | """ 112 | type: str 113 | # end class BotCommandScopeAllChatAdministrators 114 | 115 | class BotCommandScopeChat(BotCommandScope): 116 | """ 117 | Represents the scope of bot commands, covering a specific chat. 118 | 119 | https://core.telegram.org/bots/api#botcommandscopechat 120 | 121 | 122 | Parameters: 123 | 124 | :param type: Scope type, must be chat 125 | :type type: str|unicode 126 | 127 | :param chat_id: Unique identifier for the target chat or username of the target supergroup (in the format @supergroupusername) 128 | :type chat_id: int | str|unicode 129 | 130 | 131 | Optional keyword parameters: 132 | """ 133 | type: str 134 | chat_id: Union[int, str] 135 | # end class BotCommandScopeChat 136 | 137 | class BotCommandScopeChatAdministrators(BotCommandScope): 138 | """ 139 | Represents the scope of bot commands, covering all administrators of a specific group or supergroup chat. 140 | 141 | https://core.telegram.org/bots/api#botcommandscopechatadministrators 142 | 143 | 144 | Parameters: 145 | 146 | :param type: Scope type, must be chat_administrators 147 | :type type: str|unicode 148 | 149 | :param chat_id: Unique identifier for the target chat or username of the target supergroup (in the format @supergroupusername) 150 | :type chat_id: int | str|unicode 151 | 152 | 153 | Optional keyword parameters: 154 | """ 155 | type: str 156 | chat_id: Union[int, str] 157 | # end class BotCommandScopeChatAdministrators 158 | 159 | class BotCommandScopeChatMember(BotCommandScope): 160 | """ 161 | Represents the scope of bot commands, covering a specific member of a group or supergroup chat. 162 | 163 | https://core.telegram.org/bots/api#botcommandscopechatmember 164 | 165 | 166 | Parameters: 167 | 168 | :param type: Scope type, must be chat_member 169 | :type type: str|unicode 170 | 171 | :param chat_id: Unique identifier for the target chat or username of the target supergroup (in the format @supergroupusername) 172 | :type chat_id: int | str|unicode 173 | 174 | :param user_id: Unique identifier of the target user 175 | :type user_id: int 176 | 177 | 178 | Optional keyword parameters: 179 | """ 180 | type: str 181 | chat_id: Union[int, str] 182 | user_id: int 183 | # end class BotCommandScopeChatMember 184 | -------------------------------------------------------------------------------- /pytgbot/api_types/sendable/files.pyi: -------------------------------------------------------------------------------- 1 | from typing import Union, Tuple, Type, Dict, Optional, MutableMapping, Text, IO, BinaryIO 2 | from luckydonaldUtils.encoding import binary_type 3 | 4 | 5 | class InputFile(...): 6 | ... 7 | def __new__( 8 | cls 9 | ) -> Union[ 10 | 'InputFileFromBlob', 'InputFileFromDisk', 'InputFileFromURL', 11 | str, 12 | ]: ... 13 | # end def 14 | 15 | 16 | def __init__( 17 | self, # : InputFile, 18 | file_id: Optional[str] = ..., 19 | path: Optional[str] = ..., 20 | url: Optional[str] = ..., 21 | blob: Optional[binary_type] = ..., 22 | mime: Optional[str] = ..., 23 | prefer_local_download: bool = ..., 24 | prefer_str: bool = ..., 25 | create_instance: bool = ..., 26 | ): 27 | super(InputFile, self).__init__() 28 | ... 29 | # end def 30 | 31 | 32 | def get_request_files( 33 | self, # : InputFile, 34 | var_name: str 35 | ) -> Dict[str, Optional[Tuple[str, IO, str]]]: ... 36 | # end def 37 | 38 | def get_input_media_referenced_files( 39 | self, # : InputFile, 40 | var_name: str 41 | # `('attach://{var_name}', {var_name: ('foo.png', open('foo.png', 'rb'), 'image/png')})` 42 | ) -> Tuple[str, Optional[Dict[str, Optional[Tuple[str, IO, str]]]]]: ... 43 | # end def 44 | 45 | def _calculate_size(self) -> int: ... # self: InputFile 46 | # end def 47 | 48 | @property 49 | def size(self) -> int: # self: InputFile 50 | return self._size 51 | # end def 52 | 53 | @classmethod 54 | def factory( 55 | cls: Type[InputFile], 56 | file_id: Optional[str] = ..., 57 | path: Optional[str] = ..., 58 | url: Optional[str] = ..., 59 | blob: Optional[binary_type] = ..., 60 | mime: Optional[str] = ..., 61 | prefer_local_download: bool = ..., 62 | prefer_str: bool = ..., 63 | create_instance: bool = ..., 64 | ) -> \ 65 | Union[ 66 | InputFile, 'InputFileFromBlob', 'InputFileFromDisk', 'InputFileFromURL', str, 67 | Tuple[ 68 | Union[Type['InputFileFromBlob'], Type['InputFileFromDisk'], Type['InputFileFromURL'], Type[str]], 69 | Tuple, 70 | Dict, 71 | ] 72 | ]: 73 | ... 74 | # end def 75 | # end class 76 | 77 | 78 | 79 | class BaseInputFileUse(InputFile): 80 | def get_request_files( 81 | self, # : BaseInputFileUse, 82 | var_name: str 83 | ) -> Dict: 84 | ... 85 | # end def 86 | # end def 87 | 88 | 89 | class InputFileUseFileId(BaseInputFileUse): 90 | def __init__( 91 | self, # : InputFileUseFileID, 92 | file_id: str 93 | ) -> None: 94 | super().__init__() 95 | ... 96 | 97 | def get_input_media_referenced_files( 98 | self, # : InputFileUseFileID, 99 | var_name: str 100 | ) -> Tuple[str, None]: ... 101 | # end def 102 | # end class 103 | 104 | 105 | class InputFileUseUrl(BaseInputFileUse): 106 | def __init__( 107 | self, 108 | url: str 109 | ): 110 | super(InputFileUseUrl, self).__init__() 111 | ... 112 | # end def 113 | 114 | def get_input_media_referenced_files( 115 | self, # : InputFileUseUrl, 116 | var_name: str 117 | ) -> Tuple[str, None]: ... 118 | # end def 119 | # end class 120 | 121 | 122 | class InputFileFromBlob(InputFile): 123 | def __init__( 124 | self: Type[InputFileFromBlob], # 125 | blob: binary_type, 126 | name: str = "file.unknown", 127 | mime: Optional[str] = None 128 | ) -> None: 129 | super(InputFileFromBlob, self).__init__(name=name, mime=mime) 130 | ... 131 | # end def 132 | 133 | @staticmethod 134 | def mime_from_blob( 135 | blob: binary_type 136 | ) -> str: ... 137 | # end def 138 | 139 | def get_request_files( 140 | self, 141 | var_name: str 142 | ) -> Dict[str, Tuple[str, binary_type, str]]: ... 143 | # end def 144 | 145 | def _calculate_size(self) -> int: ... 146 | # end def 147 | # end class 148 | 149 | 150 | class InputFileFromDisk(InputFile): 151 | def __init__( 152 | self, 153 | path: str, 154 | name: Optional[str] = None, 155 | mime: Optional[str] = None, 156 | **kwargs, 157 | ) -> None: 158 | super(InputFileFromDisk, self).__init__(name=name, mime=mime) 159 | ... 160 | # end def __init__ 161 | 162 | @staticmethod 163 | def mime_from_file( 164 | path: str 165 | ) -> str: ... 166 | # end def 167 | 168 | def get_request_files( 169 | self, 170 | var_name: str 171 | ) -> Dict[str, Tuple[str, BinaryIO, str]]: ... 172 | # end def 173 | 174 | def _calculate_size(self) -> int: ... 175 | # end class 176 | 177 | 178 | class InputFileFromURL(InputFile): 179 | def __init__( 180 | self, 181 | url: str, 182 | name: Optional[str] = None, 183 | mime: Optional[str] = None, 184 | ) -> None: 185 | super(InputFileFromURL, self).__init__(name=name, mime=mime) 186 | ... 187 | # end def 188 | 189 | def __str__(self) -> str: ... 190 | # end def __str__ 191 | 192 | @staticmethod 193 | def name_from_url( 194 | url: str 195 | ) -> str: ... 196 | # end def 197 | 198 | def get_request_files( 199 | self, 200 | var_name: str 201 | ) -> Dict[str, Tuple[str, binary_type, str]]: ... 202 | # end def 203 | 204 | def _calculate_size(self) -> int: ... 205 | # end def 206 | # end class InputFileFromURL 207 | 208 | -------------------------------------------------------------------------------- /pytgbot/api_types/sendable/payments.pyi: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from luckydonaldUtils.exceptions import assert_type_or_raise 3 | from luckydonaldUtils.encoding import unicode_type, to_unicode as u 4 | from typing import Any, Union, List 5 | from pytgbot.api_types.sendable import Sendable 6 | 7 | __author__ = 'luckydonald' 8 | 9 | 10 | class LabeledPrice(Sendable): 11 | """ 12 | This object represents a portion of the price for goods or services. 13 | 14 | https://core.telegram.org/bots/api#labeledprice 15 | 16 | 17 | Parameters: 18 | 19 | :param label: Portion label 20 | :type label: str|unicode 21 | 22 | :param amount: Price of the product in the smallest units of the currency (integer, not float/double). For example, for a price of US$ 1.45 pass amount = 145. See the exp parameter in currencies.json, it shows the number of digits past the decimal point for each currency (2 for the majority of currencies). 23 | :type amount: int 24 | 25 | 26 | Optional keyword parameters: 27 | """ 28 | label: str 29 | amount: int 30 | # end class LabeledPrice 31 | 32 | class ShippingOption(Sendable): 33 | """ 34 | This object represents one shipping option. 35 | 36 | https://core.telegram.org/bots/api#shippingoption 37 | 38 | 39 | Parameters: 40 | 41 | :param id: Shipping option identifier 42 | :type id: str|unicode 43 | 44 | :param title: Option title 45 | :type title: str|unicode 46 | 47 | :param prices: List of price portions 48 | :type prices: list of pytgbot.api_types.sendable.payments.LabeledPrice 49 | 50 | 51 | Optional keyword parameters: 52 | """ 53 | id: str 54 | title: str 55 | prices: List[LabeledPrice] 56 | # end class ShippingOption 57 | -------------------------------------------------------------------------------- /pytgbot/bot/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | from luckydonaldUtils.logger import logging 4 | from .synchronous import SyncBot as Bot 5 | 6 | __author__ = 'luckydonald' 7 | __all__ = ["Bot", "asynchronous", "synchronous"] 8 | 9 | logger = logging.getLogger(__name__) 10 | if __name__ == '__main__': 11 | logging.add_colored_handler(level=logging.DEBUG) 12 | # end if 13 | 14 | 15 | -------------------------------------------------------------------------------- /pytgbot/exceptions.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from luckydonaldUtils.logger import logging 3 | 4 | __author__ = 'luckydonald' 5 | logger = logging.getLogger(__name__) 6 | 7 | 8 | class TgApiException(Exception): 9 | """ 10 | Base Class for all exceptions. 11 | """ 12 | pass 13 | # end class TgApiException 14 | 15 | 16 | class TgApiResponseException(TgApiException): 17 | """ 18 | Server responded something, but that can't be parsed as json. 19 | Contains the `response`, and also direct access to the `status_code` of that response. 20 | """ 21 | def __init__(self, message, response=None, exception=None): 22 | """ 23 | :param message: The message of the exception 24 | :type message: str 25 | 26 | :param response: The failed response 27 | :type response: requests.Response 28 | 29 | :param exception: The exception which occured when parsing the json. 30 | :type exception: 31 | """ 32 | super(TgApiResponseException, self).__init__(message) 33 | self.response = response 34 | self.exception = exception 35 | # end def 36 | 37 | @property 38 | def status_code(self): 39 | return self.response.status_code if self.response is not None else None 40 | # end def 41 | 42 | def __str__(self): 43 | return "{msg} [{code}] {e_type}({e_text!r})".format( 44 | code=self.status_code, e_type=self.exception.__class__.__name__, 45 | e_text=self.exception.args[0] if len(self.exception.args) == 1 else self.exception.args, 46 | msg=self.args[0] if len(self.args) == 1 else self.args, 47 | ) 48 | # end def 49 | 50 | def __repr__(self): 51 | return "{cls}({msg!r}, response={r!r}, exception={e!r})".format( 52 | cls=self.__class__.__name__, r=self.response, e=self.exception, 53 | msg=self.args[0] if len(self.args) == 1 else self.args, 54 | ) 55 | # end def 56 | # end class 57 | 58 | 59 | class TgApiServerException(TgApiException): 60 | """ 61 | Raised if the api returns "ok" == false 62 | """ 63 | def __init__(self, error_code=None, response=None, description=None, request=None): 64 | super(TgApiServerException, self).__init__(description) 65 | self.error_code = error_code 66 | self.response = response 67 | self.description = description 68 | self.request=request 69 | # end def __init__ 70 | 71 | def __str__(self, *args, **kwargs): 72 | return "TgApiServerException(error_code={self.error_code!r}, response={self.response!r}, " \ 73 | "description={self.description!r}, request={self.request!r})".format(self=self) 74 | # end def __str__ 75 | # end class TgApiException 76 | 77 | 78 | class TgApiParseException(TgApiException): 79 | """ 80 | Raised if something did go wrong with parsing. 81 | E.g. a missing key, an unexpected value, etc. 82 | """ 83 | pass 84 | 85 | 86 | class TgApiTypeError(TgApiException, TypeError): 87 | """ Raised where a TypeError is needed""" 88 | pass 89 | -------------------------------------------------------------------------------- /pytgbot/extra/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from luckydonaldUtils.logger import logging 3 | 4 | __author__ = 'luckydonald' 5 | logger = logging.getLogger(__name__) 6 | -------------------------------------------------------------------------------- /pytgbot/extra/bot_response.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from luckydonaldUtils.logger import logging 3 | from luckydonaldUtils.encoding import to_native as n 4 | 5 | from pytgbot import Bot 6 | 7 | __author__ = 'luckydonald' 8 | logger = logging.getLogger(__name__) 9 | 10 | 11 | class ResponseBot(Bot): 12 | """ 13 | A :class:`Bot` subclass, not instantly sending responses, but instead returning them. 14 | Useful for replying responses to an open Webhook connection. 15 | """ 16 | def __init__(self, api_key): 17 | super(ResponseBot, self).__init__(api_key, return_python_objects=True) 18 | # end def 19 | 20 | def do(self, command, files=None, use_long_polling=False, request_timeout=None, **query): 21 | """ 22 | Return the request params we would send to the api. 23 | """ 24 | url, params = self._prepare_request(command, query) 25 | return { 26 | "url": url, "params": params, "files": files, "stream": use_long_polling, 27 | "verify": True, # No self signed certificates. Telegram should be trustworthy anyway... 28 | "timeout": request_timeout 29 | } 30 | # end def 31 | # end class 32 | -------------------------------------------------------------------------------- /pytgbot/other/webhook_simpleserver.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | __author__ = 'luckydonald' 3 | 4 | import logging 5 | import time 6 | import json 7 | 8 | from pytgbot import VERSION 9 | 10 | try: 11 | from BaseHTTPServer import BaseHTTPRequestHandler, HTTPServer 12 | import urlparse 13 | except ImportError: 14 | from http.server import BaseHTTPRequestHandler, HTTPServer 15 | import urllib.parse as urlparse 16 | # end try 17 | 18 | # import ssl 19 | 20 | logger = logging.getLogger(__name__) 21 | 22 | 23 | HOST_NAME = "" 24 | PORT_NUMBER = 22113 25 | CERT_FILE = '/path/to/localhost.pem' 26 | 27 | logging.basicConfig() 28 | 29 | 30 | def handle_hook(payload): 31 | logger.info("payload: " + payload) 32 | 33 | 34 | class HookHandler(BaseHTTPRequestHandler): 35 | server_version = "pytgbot/%s" % VERSION 36 | 37 | def do_GET(self): 38 | self.do_STUFF("get") 39 | 40 | def do_POST(self): 41 | # Check that the IP is within the GH ranges 42 | # if not any(s.client_address[0].startswith(IP) 43 | # for IP in ('192.30.252', '192.30.253', '192.30.254', '192.30.255')): 44 | # s.send_error(403) 45 | self.do_STUFF("post") 46 | 47 | def do_STUFF(self, how): 48 | # logger.warn("asdasd!") 49 | length = int(self.headers['Content-Length']) 50 | print(self.headers) 51 | post_data = urlparse.parse_qs(self.rfile.read().decode('utf-8')) 52 | logger.warn("data: " + post_data) 53 | payload = json.loads(post_data['payload'][0]) 54 | self.send_response(200) 55 | self.handle_hook(payload) 56 | 57 | def handle_hook(self, payload): 58 | # TODO process 59 | self._callback(payload) 60 | 61 | @property 62 | def callback(self): 63 | return self._callback 64 | 65 | @callback.setter 66 | def callback(self, value): 67 | self._callback = value 68 | 69 | class HookHTTPServer(HTTPServer): 70 | def handle_request(self,request): 71 | pass 72 | 73 | 74 | if __name__ == '__main__': 75 | httpd = HTTPServer((HOST_NAME, PORT_NUMBER), HookHandler) 76 | def handle_request(request,): 77 | #httpd.socket = ssl.wrap_socket(httpd.socket, certfile=CERT_FILE) 78 | pass 79 | print("{time} Server Starts - {host}:{port} (using certificate (.pem) from {cert})".format(time=time.asctime(), 80 | host=HOST_NAME, 81 | port=PORT_NUMBER, 82 | cert=CERT_FILE)) 83 | try: 84 | httpd.serve_forever() 85 | except KeyboardInterrupt: 86 | pass 87 | httpd.server_close() 88 | print("{time} Server Stops - {host}:{port} (using certificate (.pem) from {cert})".format(time=time.asctime(), 89 | host=HOST_NAME, 90 | port=PORT_NUMBER, 91 | cert=CERT_FILE)) 92 | -------------------------------------------------------------------------------- /pytgbot/webhook.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from luckydonaldUtils.logger import logging 3 | 4 | from pytgbot.bot import Bot 5 | from pytgbot.exceptions import TgApiServerException, TgApiParseException 6 | 7 | __author__ = 'luckydonald' 8 | logger = logging.getLogger(__name__) 9 | 10 | 11 | class Webhook(Bot): 12 | """ 13 | Subclass of Bot, will be returned of a sucessful webhook setting. 14 | Differs with the normal Bot class, as the sending function stores the result to send, 15 | so you can actually get that and return the data on your incomming message. 16 | """ 17 | 18 | stored_request = None 19 | 20 | def _prepare_request(self, command, query): 21 | """ 22 | :param command: The Url command parameter 23 | :type command: str 24 | 25 | :param query: will get json encoded. 26 | :type query: dict 27 | 28 | :return: 29 | """ 30 | from luckydonaldUtils.encoding import to_native as n 31 | from pytgbot.api_types.sendable import Sendable 32 | from pytgbot.api_types import as_array 33 | from DictObject import DictObject 34 | import json 35 | 36 | params = {} 37 | for key in query.keys(): 38 | element = query[key] 39 | if element is not None: 40 | if isinstance(element, Sendable): 41 | params[key] = json.dumps(as_array(element)) 42 | else: 43 | params[key] = element 44 | url = self._base_url.format(api_key=n(self.api_key), command=n(command)) 45 | return DictObject(url=url, params=params) 46 | # end def 47 | 48 | def _do_request(self, url, params=None, files=None, use_long_polling=None, request_timeout=None): 49 | """ 50 | 51 | :param url: The complete url to send to 52 | :type url: str 53 | 54 | :keyword params: Parameter for that connection 55 | 56 | :keyword files: Optional files parameters 57 | 58 | :keyword use_long_polling: if it should use long polling. 59 | (see http://docs.python-requests.org/en/latest/api/#requests.Response.iter_content) 60 | :type use_long_polling: bool 61 | 62 | :keyword request_timeout: When the request should time out. 63 | :type request_timeout: int 64 | 65 | :return: json data received 66 | :rtype: DictObject.DictObject 67 | """ 68 | import requests 69 | r = requests.post(url, params=params, files=files, stream=use_long_polling, 70 | verify=True, timeout=request_timeout) 71 | # No self signed certificates. Telegram should be trustworthy anyway... 72 | from DictObject import DictObject 73 | try: 74 | logger.debug("Response: {}".format(r.json())) 75 | json_data = DictObject.objectify(r.json()) 76 | except Exception: 77 | logger.exception("Parsing answer failed.\nRequest: {r!s}\nContent: {r.content}".format(r=r)) 78 | raise 79 | # end if 80 | json_data["response"] = r # TODO: does this failes on json lists? Does TG does that? 81 | return json_data 82 | # end def 83 | 84 | def _process_response(self, json_data): 85 | # TG should always return an dict, with at least a status or something. 86 | if self.return_python_objects: 87 | if json_data.ok != True: 88 | raise TgApiServerException( 89 | error_code=json_data.error_code if "error_code" in json_data else None, 90 | response=json_data.response if "response" in json_data else None, 91 | description=json_data.description if "description" in json_data else None, 92 | request=r.request 93 | ) 94 | # end if not ok 95 | if "result" not in json_data: 96 | raise TgApiParseException('Key "result" is missing.') 97 | # end if no result 98 | return json_data.result 99 | # end if return_python_objects 100 | return json_data 101 | # end def 102 | 103 | def do(self, command, files=None, use_long_polling=False, request_timeout=None, **query): 104 | """ 105 | Send a request to the api. 106 | 107 | If the bot is set to return the json objects, it will look like this: 108 | 109 | ```json 110 | { 111 | "ok": bool, 112 | "result": {...}, 113 | # optionally present: 114 | "description": "human-readable description of the result", 115 | "error_code": int 116 | } 117 | ``` 118 | 119 | :param command: The Url command parameter 120 | :type command: str 121 | 122 | :keyword request_timeout: When the request should time out. 123 | :type request_timeout: int 124 | 125 | :keyword files: if it needs to send files. 126 | 127 | :keyword use_long_polling: if it should use long polling. 128 | (see http://docs.python-requests.org/en/latest/api/#requests.Response.iter_content) 129 | :type use_long_polling: bool 130 | 131 | :param query: will get json encoded. 132 | 133 | :return: The json response from the server, or, if `self.return_python_objects` is `True`, a parsed return type. 134 | :rtype: DictObject.DictObject | pytgbot.api_types.receivable.Receivable 135 | """ 136 | params = self._prepare_request(command, query) 137 | r = self._do_request( 138 | params.url, params=params.params, 139 | files=files, stream=use_long_polling, timeout=request_timeout 140 | ) 141 | return self._process_response(r) 142 | 143 | 144 | 145 | 146 | 147 | 148 | # end def do 149 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | # online docs 2 | bs4 3 | jinja2 4 | luckydonaldUtils>=0.77 5 | 6 | # linter 7 | yapf 8 | black 9 | 10 | # publish 11 | twine 12 | -------------------------------------------------------------------------------- /result_parser.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | __author__ = 'luckydonald' 3 | 4 | import logging 5 | logger = logging.getLogger(__name__) 6 | 7 | #import sys 8 | #sys.path.append("/path/to/pytg/") 9 | 10 | import pytg 11 | pytg 12 | pass -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from setuptools import setup, find_packages # Always prefer setuptools over distutils 3 | from os import path 4 | __author__ = 'luckydonald' 5 | 6 | here = path.abspath(path.dirname(__file__)) 7 | 8 | long_description = """A Python module that connects to the Telegram bot api, allowing to interact with Telegram users or groups.""" 9 | 10 | sync_requirements = ["requests", "requests[security]"] 11 | setup( 12 | name='pytgbot', version="5.7", 13 | description='Connect to the Telegram Bot API, receive and send Telegram messages.', 14 | long_description=long_description, 15 | # The project's main homepage. 16 | url='https://github.com/luckydonald/pytgbot', 17 | # Author details 18 | author='luckydonald', 19 | author_email='code@luckydonald.de', 20 | # Choose your license 21 | license='MIT', 22 | # See https://pypi.python.org/pypi?%3Aaction=list_classifiers 23 | classifiers=[ 24 | 'Development Status :: 4 - Beta', # 2 - Pre-Alpha, 3 - Alpha, 4 - Beta, 5 - Production/Stable 25 | # Indicate who your project is intended for 26 | 'Intended Audience :: Developers', 27 | 'Topic :: Communications', 28 | 'Topic :: Communications :: Chat', 29 | 'Topic :: Multimedia', 30 | 'Topic :: Software Development :: Libraries', 31 | 'Topic :: Software Development :: Libraries :: Python Modules', 32 | # Pick your license as you wish (should match "license" above) 33 | 'License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+)', 34 | # Specify the Python versions you support here. In particular, ensure 35 | # that you indicate whether you support Python 2, Python 3 or both. 36 | # 'Programming Language :: Python :: 2', 37 | # 'Programming Language :: Python :: 2.6', 38 | 'Programming Language :: Python :: 2.7', 39 | 'Programming Language :: Python :: 3', 40 | # 'Programming Language :: Python :: 3.2', 41 | # 'Programming Language :: Python :: 3.3', 42 | 'Programming Language :: Python :: 3.4', 43 | 'Operating System :: MacOS :: MacOS X', 44 | 'Operating System :: Unix', 45 | ], 46 | # What does your project relate to? 47 | keywords='telegram bot api python message send receive python secure fast answer reply image voice picture location contacts typing multi messanger inline quick reply gif image video mp4 mpeg4 sticker file_id markdown markdownV2', 48 | # You can just specify the packages manually here if your project is 49 | # simple. Or you can use find_packages(). 50 | packages=['pytgbot', 'pytgbot.api_types', 'pytgbot.api_types.receivable', 'pytgbot.api_types.sendable', 'pytgbot.bot'], 51 | # find_packages(exclude=['contrib', 'docs', 'tests*']), 52 | # List run-time dependencies here. These will be installed by pip when your 53 | # project is installed. For an analysis of "install_requires" vs pip's 54 | # requirements files see: 55 | # https://packaging.python.org/en/latest/requirements.html 56 | install_requires=["DictObject", "python-magic", "libmagic"] + sync_requirements, 57 | # List additional groups of dependencies here (e.g. development dependencies). 58 | # You can install these using the following syntax, for example: 59 | # $ pip install -e .[dev,test] 60 | extras_require={ 61 | 'sync': sync_requirements, 62 | 'async': ["httpx", "async-property"], 63 | 'dev': ["luckydonaldUtils>=0.77"], 64 | }, 65 | # If there are data files included in your packages that need to be 66 | # installed, specify them here. If using Python 2.6 or less, then these 67 | # have to be included in MANIFEST.in as well. 68 | # package_data={ 69 | # 'sample': ['package_data.dat'], 70 | # }, 71 | # Although 'package_data' is the preferred approach, in some case you may 72 | # need to place data files outside of your packages. 73 | # see http://docs.python.org/3.4/distutils/setupscript.html#installing-additional-files 74 | # In this case, 'data_file' will be installed into '/my_data' 75 | # data_files=[('my_data', ['data/data_file'])], 76 | # To provide executable scripts, use entry points in preference to the 77 | # "scripts" keyword. Entry points provide cross-platform support and allow 78 | # pip to create the appropriate form of executable for the target platform. 79 | ) 80 | -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luckydonald/pytgbot/2f84b11253873f7af1bc7539eb7d93197d51c90c/tests/__init__.py -------------------------------------------------------------------------------- /tests/api_types/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luckydonald/pytgbot/2f84b11253873f7af1bc7539eb7d93197d51c90c/tests/api_types/__init__.py -------------------------------------------------------------------------------- /tests/api_types/receivable/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luckydonald/pytgbot/2f84b11253873f7af1bc7539eb7d93197d51c90c/tests/api_types/receivable/__init__.py -------------------------------------------------------------------------------- /tests/api_types/receivable/test_update.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | from unittest import TestCase 4 | 5 | from luckydonaldUtils.logger import logging 6 | 7 | from pytgbot.api_types.receivable.updates import Update, Message 8 | 9 | __author__ = 'luckydonald' 10 | 11 | logger = logging.getLogger(__name__) 12 | if __name__ == '__main__': 13 | logging.add_colored_handler(level=logging.DEBUG) 14 | 15 | 16 | # end if 17 | 18 | class TestUpdate(TestCase): 19 | def test_1(self): 20 | data = { 21 | "update_id": 1234, 22 | } 23 | update = Update.from_array(data) 24 | self.assertEqual(1234, update.update_id) 25 | 26 | self.assertIsNone(update.message, "element should be not set") 27 | self.assertIsNone(update.callback_query, "element should be not set") 28 | self.assertIsNone(update.inline_query, "element should be not set") 29 | self.assertIsNone(update.channel_post, "element should be not set") 30 | self.assertIsNone(update.chosen_inline_result, "element should be not set") 31 | self.assertIsNone(update.edited_channel_post, "element should be not set") 32 | self.assertIsNone(update.poll, "element should be not set") 33 | self.assertIsNone(update.pre_checkout_query, "element should be not set") 34 | self.assertIsNone(update.shipping_query, "element should be not set") 35 | 36 | self.assertNotIn("message", update, "__contains__ should be false as well") 37 | self.assertNotIn("callback_query", update, "__contains__ should be false as well") 38 | self.assertNotIn("inline_query", update, "__contains__ should be false as well") 39 | self.assertNotIn("channel_post", update, "__contains__ should be false as well") 40 | self.assertNotIn("chosen_inline_result", update, "__contains__ should be false as well") 41 | self.assertNotIn("edited_channel_post", update, "__contains__ should be false as well") 42 | self.assertNotIn("poll", update, "__contains__ should be false as well") 43 | self.assertNotIn("pre_checkout_query", update, "__contains__ should be false as well") 44 | self.assertNotIn("shipping_query", update, "__contains__ should be false as well") 45 | 46 | new = Update(update_id=1234) 47 | self.assertEqual(data, new.to_array(), 'to_array()') 48 | # self.assertEqual(data, update, 'compare') 49 | # end def 50 | 51 | 52 | class TestMessage(TestCase): 53 | def test_1(self): 54 | data = { 55 | "date": 1441645532, 56 | "chat": { 57 | "id": 1111101, 58 | "type": "private", 59 | "first_name": "Alfred", 60 | "last_name": "Alfons", 61 | }, 62 | "message_id": 1365, 63 | "from": { 64 | "id": 1111101, 65 | "first_name": "Alfred", 66 | "last_name": "Alfons", 67 | "is_bot": False, 68 | }, 69 | "text": "/start" 70 | } 71 | msg = Message.from_array(data) 72 | self.assertEqual(1441645532, msg.date) 73 | 74 | from pytgbot.api_types.receivable.peer import Chat, User 75 | new = Message( 76 | message_id=1365, date=1441645532, 77 | chat=Chat(id=1111101, type="private", first_name="Alfred", last_name="Alfons"), 78 | from_peer=User(id=1111101, first_name="Alfred", last_name="Alfons", is_bot=False), 79 | text="/start" 80 | ) 81 | self.assertEqual(data, new.to_array(), 'to_array()') 82 | # self.assertEqual(data, msg, 'compare') 83 | # end def 84 | 85 | 86 | # end class 87 | 88 | 89 | class TEstUpdate(TestCase): 90 | def test_foo_test(self): 91 | x = { 92 | 'message': { 93 | 'chat': { 94 | 'first_name': 'Test User', 95 | 'id': 204306969, 96 | 'type': 'private', 97 | 'username': 'username' 98 | }, 99 | 'date': 1582795588, 100 | 'entities': [ 101 | {'length': 6, 'offset': 0, 'type': 'bot_command'} 102 | ], 103 | 'from': { 104 | 'first_name': 'Test User', 105 | 'id': 204306969, 106 | 'is_bot': False, 107 | 'language_code': 'en', 108 | 'username': 'username'}, 109 | 'message_id': 10816, 110 | 'text': '/start' 111 | }, 112 | 'update_id': 711222445 113 | } 114 | update = Update.from_array(x) 115 | # end def 116 | # enc class 117 | -------------------------------------------------------------------------------- /tests/api_types/sendable/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luckydonald/pytgbot/2f84b11253873f7af1bc7539eb7d93197d51c90c/tests/api_types/sendable/__init__.py -------------------------------------------------------------------------------- /tests/api_types/sendable/test_files.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | from base64 import b64decode 3 | from pytgbot.api_types.sendable.files import InputFile 4 | from pytgbot.api_types.sendable.files import InputFileFromURL, InputFileUseUrl 5 | from pytgbot.api_types.sendable.files import InputFileUseFileID 6 | from pytgbot.api_types.sendable.files import InputFileFromDisk 7 | from pytgbot.api_types.sendable.files import InputFileFromBlob 8 | 9 | IMG = "https://derpicdn.net/img/view/2017/5/16/1438309.png" 10 | IMG_FILENAME = '1438309.png' 11 | IMG_MIME = 'image/png' 12 | FILE_ID = 'AwADBAADbXXXXXXXXXXXGBdhD2l6XX' 13 | FORM_VALUE = 'pony' 14 | OS_FILE = __file__ 15 | OS_FILE_NAME = 'test_files.py' 16 | OS_FILE_MIME = 'text/x-python' 17 | 18 | BLOB = b64decode( 19 | b'iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAMAAACdt4HsAAABAlBMVEUAAADlcXHnb2/ZRUXkcnLmcnLkcnLfXl7TLy/l' 20 | b'cnLeW1vlcnLjcnLmc3PbTk7idHTmc3PwkV/vkWHvlWrwmm3ynm/2qW78umr9wmrriHb2rnv9v2L+y3//wGL/uFH+yn78' 21 | b'wn7ofHT/uE3/xG3pgnX/ulTyoHnmd3T/pyXvnSLtnCXzpjP/t03/vl/3sXzxnnnvlXjunSTvnyT/qiT9piT+piX+piXr' 22 | b'miTmlyPvozL+piX+piX/pib9piTwnST/uE//vFr/pyb+pyX+pib+piX2oSX9pSb+pyb/pyf/pSX/pib9pyT/pSb+pyb/' 23 | b'qSfzpTL9pyT2pzL/pib4qTP6qTH4rj920d5MAAAAVnRSTlMATYjWffD+///U/+XCUv9YvmaQpK+2rJyT//+A/31h////' 24 | b'Wv//////S6vD4v//////uJQ5jNr+////87pymv///2vQ4MP/esg0UjyTNqpc/4X/X//N9yBRhkgAAAHYSURBVHgBYqA1' 25 | b'GAWMTMzMTIxka2dhZWMHcD4XBxAEMAgAc8a6u2v/LZ57Xuz8E8A0DZyOsokFwDZNG4AlGzj4PIAjNBcAHhNuXCF5ePB9' 26 | b'PATCCfGHrQBFKBGUSBgxlFgYCZREGCmUVBgZlEwYOZRcGAWUQhhlhT9VyT2o8afmHjTtX4Wq7YTRD+OED0zj0AtjXtpx' 27 | b'xds6tsvMPbhQUlcJCkMxFIazkMEdUpyDu7vtfy24hYbOvd/7+euN/f0F4on7PBEP/P3FLALJVDrDzt9FNpfP57KX+Z/D' 28 | b'mUKxRAbKFaBaqzM7l92Lw1yvVdFotshbE8AjwLHIIxGIxPgeuGi0vY6Ou84twNzt9Ryn1+sy3wId3DT69MMAMiC9AkCT' 29 | b'VEOYBjAixQgvYz0wBjzOYQKbANxPo2EXaNCXFj7pAcDjFKa2gSlJsA2AhJl9YCYCSQhVLVCFkBSBuX1gLgILCB0t0IGw' 30 | b'IME+QFLFNjDUvuSXsRYY49OSvtgGVvRlbRdokovoawHlGQgTm0CZFAPzQIVUDePAglRJ08CGfujjacsuWzzt6KeJSWBJ' 31 | b'HiZeAX2v/xv37LLXzl8xb3gGDvSv2RDAkV2OAIYlMtFer88LCcUAIb6+tgw0AaMAAGw2K1yqbj5cAAAAAElFTkSuQmCC' 32 | ) 33 | BLOB_MIME = 'image/png' 34 | BLOB_NAME_UNKNOWN = 'file.unknown' 35 | BLOB_NAME_PNG = 'file.png' 36 | BLOB_NAME1 = 'example.png' 37 | BLOB_NAME2 = 'example.blob' 38 | 39 | 40 | class FilesTest(unittest.TestCase): 41 | def test_InputFileFromURL(self): 42 | f = InputFileFromURL(IMG) 43 | self.assertIsInstance(f, InputFile) 44 | self.assertIsInstance(f, InputFileFromURL) 45 | self.assertEqual(f.url, IMG) 46 | self.assertEqual(f.name, IMG_FILENAME) 47 | self.assertEqual(f.mime, IMG_MIME) 48 | self.assertEqual(f.get_request_files(FORM_VALUE), {FORM_VALUE: (IMG_FILENAME, f.blob, IMG_MIME)}) 49 | # end def 50 | 51 | 52 | class FilesFactoryTest(unittest.TestCase): 53 | def test_url(self): 54 | f = InputFile(url=IMG) 55 | self.assertIsInstance(f, InputFile) 56 | self.assertIsInstance(f, InputFileFromURL) 57 | self.assertEqual(f.url, IMG) 58 | self.assertEqual(f.name, IMG_FILENAME) 59 | self.assertEqual(f.mime, IMG_MIME) 60 | self.assertEqual(f.get_request_files(FORM_VALUE), {FORM_VALUE: (IMG_FILENAME, f.blob, IMG_MIME)}) 61 | # end def 62 | 63 | def test_url_str(self): 64 | f = InputFile(url=IMG, prefer_str=True) 65 | self.assertIsInstance(f, InputFile) 66 | self.assertIsInstance(f, InputFileFromURL) 67 | self.assertEqual(f.url, IMG) 68 | self.assertEqual(f.name, IMG_FILENAME) 69 | self.assertEqual(f.mime, IMG_MIME) 70 | self.assertEqual(f.get_request_files(FORM_VALUE), {FORM_VALUE: (IMG_FILENAME, f.blob, IMG_MIME)}) 71 | # end def 72 | 73 | def test_url_tg(self): 74 | f = InputFile(url=IMG, prefer_local_download=False) 75 | self.assertIsInstance(f, InputFile) 76 | self.assertIsInstance(f, InputFileUseUrl) 77 | self.assertEqual(f.url, IMG) 78 | self.assertEqual(f.get_request_files(FORM_VALUE), {}) 79 | # end def 80 | 81 | def test_url_tg_str(self): 82 | f = InputFile(url=IMG, prefer_local_download=False, prefer_str=True) 83 | self.assertIsInstance(f, str) 84 | self.assertEqual(f, IMG) 85 | # end def 86 | 87 | def test_file_id(self): 88 | f = InputFile(file_id=FILE_ID) 89 | self.assertIsInstance(f, InputFile) 90 | self.assertIsInstance(f, InputFileUseFileID) 91 | self.assertEqual(f.file_id, FILE_ID) 92 | self.assertEqual(f.get_request_files(FORM_VALUE), {}) 93 | # end def 94 | 95 | def test_file_id_str(self): 96 | f = InputFile(file_id=FILE_ID, prefer_str=True) 97 | self.assertIsInstance(f, str) 98 | self.assertEqual(f, FILE_ID) 99 | # end def 100 | 101 | def test_file_disk(self): 102 | f = InputFile(path=OS_FILE) 103 | self.assertIsInstance(f, InputFile) 104 | self.assertIsInstance(f, InputFileFromDisk) 105 | self.assertEqual(f.name, OS_FILE_NAME) 106 | self.assertEqual(f.mime, OS_FILE_MIME) 107 | request_files = f.get_request_files(FORM_VALUE) 108 | # tuple(FORM_VALUE, {FORM_VALUE: (OS_FILE_NAME, os.open(...), OS_FILE_MIME)}) 109 | # we can't compare the os.open result with anything, 110 | # so we need to manually compare the whole array. 111 | self.assertIn(FORM_VALUE, request_files, msg='key exist') 112 | self.assertListEqual(list(request_files.keys()), [FORM_VALUE], msg='key(s) correct') 113 | self.assertIsInstance(request_files[FORM_VALUE], tuple, msg='dict contains tuple') 114 | self.assertEqual(len(request_files[FORM_VALUE]), 3, msg='tuple has 3 elements') 115 | self.assertEqual(request_files[FORM_VALUE][0], OS_FILE_NAME, msg='first tuple element is correct filename') 116 | self.assertEqual(request_files[FORM_VALUE][1].name, OS_FILE, msg='second tuple element is probably opened file. We confirm only correct name.') 117 | self.assertEqual(request_files[FORM_VALUE][2], OS_FILE_MIME, msg='third tuple element is correct mime') 118 | # end def 119 | 120 | def test_file_blob(self): 121 | f = InputFile(blob=BLOB) 122 | self.assertIsInstance(f, InputFile) 123 | self.assertIsInstance(f, InputFileFromBlob) 124 | self.assertEqual(f.name, BLOB_NAME_UNKNOWN) 125 | self.assertEqual(f.mime, BLOB_MIME) 126 | self.assertEqual(f.blob, BLOB) 127 | self.assertEqual(f.get_request_files(FORM_VALUE), {FORM_VALUE: (BLOB_NAME_UNKNOWN, BLOB, BLOB_MIME)}) 128 | # end def 129 | 130 | def test_file_blob(self): 131 | f = InputFile(blob=BLOB) 132 | self.assertIsInstance(f, InputFile) 133 | self.assertIsInstance(f, InputFileFromBlob) 134 | self.assertEqual(f.name, BLOB_NAME_UNKNOWN) 135 | self.assertEqual(f.mime, BLOB_MIME) 136 | self.assertEqual(f.blob, BLOB) 137 | self.assertEqual(f.get_request_files(FORM_VALUE), {FORM_VALUE: (BLOB_NAME_UNKNOWN, BLOB, BLOB_MIME)}) 138 | # end def 139 | 140 | 141 | 142 | 143 | 144 | if __name__ == '__main__': 145 | unittest.main() 146 | -------------------------------------------------------------------------------- /tests/api_types/sendable/test_input_media.py: -------------------------------------------------------------------------------- 1 | from pytgbot.api_types.sendable.files import InputFileFromURL 2 | from pytgbot.api_types.sendable.input_media import InputMediaVideo 3 | import unittest 4 | 5 | 6 | class MyTestCase(unittest.TestCase): 7 | def test_InputMediaVideo_thumb_files(self): 8 | vid1 = 'https://derpicdn.net/img/view/2016/12/21/1322277.mp4' 9 | pic1 = 'https://derpicdn.net/img/2017/7/21/1491832/thumb.jpeg' 10 | i = InputMediaVideo(InputFileFromURL(vid1), InputFileFromURL(pic1)) 11 | d = i.get_request_data('lel', full_data=True) 12 | self.assertEqual(d[0], {'type': 'video', 'thumb': 'attach://lel_thumb', 'media': 'attach://lel_media'}) 13 | self.assertListEqual(list(d[1].keys()), ['lel_media', 'lel_thumb']) 14 | self.assertEqual(d[1]['lel_media'][0], '1322277.mp4', msg='Filename video') 15 | self.assertEqual(d[1]['lel_thumb'][0], 'thumb.jpeg', msg='Filename thumb') 16 | # end def 17 | # end class 18 | 19 | 20 | if __name__ == '__main__': 21 | unittest.main() 22 | # end if 23 | -------------------------------------------------------------------------------- /tests/api_types/test_init.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | from pytgbot.api_types import TgBotApiObject 3 | 4 | 5 | class TestableTgBotApiObjectRawViaConstructor(TgBotApiObject): 6 | def __init__(self, foobar, _raw=None): 7 | super(TestableTgBotApiObjectRawViaConstructor, self).__init__() 8 | self.foobar = foobar 9 | self._raw = _raw 10 | # end def 11 | 12 | @staticmethod 13 | def from_array(array): 14 | data = {} 15 | data['foobar'] = array['foobar'] 16 | data['_raw'] = array 17 | return TestableTgBotApiObjectRawViaConstructor(**data) 18 | # end def 19 | # end class 20 | 21 | class TestableTgBotApiObjectRawViaAssignLater(TgBotApiObject): 22 | def __init__(self, foobar): 23 | super(TestableTgBotApiObjectRawViaAssignLater, self).__init__() 24 | self.foobar = foobar 25 | # end def 26 | 27 | @staticmethod 28 | def from_array(array): 29 | data = {} 30 | data['foobar'] = array['foobar'] 31 | new = TestableTgBotApiObjectRawViaAssignLater(**data) 32 | new._raw = array 33 | return new 34 | # end def 35 | # end class 36 | 37 | 38 | class TgBotApiObjectTestCase(unittest.TestCase): 39 | def test_deletion_of__raw_via_constructor(self): 40 | # Like class Update 41 | data = {'foobar': 123} 42 | foo = TestableTgBotApiObjectRawViaConstructor.from_array(data) 43 | self.assertEqual(foo.foobar, 123, 'constructor. Fail = broken test') 44 | self.assertIsNotNone(foo._raw, '_raw is the used data.') 45 | self.assertDictEqual(foo._raw, data, '_raw is the used data.') 46 | foo.foobar = 456 47 | self.assertEqual(foo.foobar, 456, 'we just set that. Fail = broken test') 48 | self.assertEqual(foo._raw, None, '_raw was reset.') 49 | # end def 50 | 51 | def test_deletion_of__raw_via_assign_later(self): 52 | # Like class InputMedia 53 | data = {'foobar': 123} 54 | foo = TestableTgBotApiObjectRawViaAssignLater.from_array(data) 55 | self.assertEqual(foo.foobar, 123, 'constructor. Fail = broken test') 56 | self.assertIsNotNone(foo._raw, '_raw is the used data.') 57 | self.assertDictEqual(foo._raw, data, '_raw is the used data.') 58 | foo.foobar = 456 59 | self.assertEqual(foo.foobar, 456, 'we just set that. Fail = broken test') 60 | self.assertEqual(foo._raw, None, '_raw was reset.') 61 | # end def 62 | # end class 63 | -------------------------------------------------------------------------------- /tests/test_bot.py: -------------------------------------------------------------------------------- 1 | from pytgbot.exceptions import TgApiServerException 2 | 3 | from somewhere import API_KEY, TEST_CHAT 4 | import unittest 5 | from luckydonaldUtils.logger import logging 6 | from pytgbot.bot import Bot 7 | from pytgbot.api_types.receivable.media import PhotoSize 8 | from pytgbot.api_types.receivable.updates import Message 9 | from pytgbot.api_types.sendable.files import InputFileFromURL 10 | from pytgbot.api_types.sendable.input_media import InputMediaPhoto, InputMediaVideo 11 | logging.add_colored_handler(level=logging.DEBUG) 12 | logger = logging.getLogger(__name__) 13 | 14 | 15 | class BotTest(unittest.TestCase): 16 | def setUp(self): 17 | self.bot = Bot(API_KEY) 18 | self.messages = [] 19 | # end def 20 | 21 | def test_edit_message_media(self): 22 | # upload by url 23 | url1 = 'https://derpicdn.net/img/view/2012/1/22/1382.jpg' 24 | url2 = 'https://derpicdn.net/img/view/2016/2/3/1079240.png' 25 | 26 | msg = self.bot.send_photo(TEST_CHAT, url1, caption="unittest", disable_notification=True) 27 | self.messages.append(msg) 28 | print("msg 1: {!r}".format(msg)) 29 | self.assertIsInstance(msg, Message) 30 | self.assertEqual(msg.caption, 'unittest') 31 | self.assertIn('photo', msg) 32 | self.assertIsInstance(msg.photo, list) 33 | 34 | msg_id = msg.message_id 35 | file_id = self._get_biggest_photo_fileid(msg) 36 | 37 | # edit by url 38 | msg2 = self.bot.edit_message_media(InputMediaPhoto(url2), TEST_CHAT, message_id=msg_id) 39 | self.messages.append(msg2) 40 | print("msg 2: {!r}".format(msg2)) 41 | self.assertIsInstance(msg2, Message) 42 | self.assertIn('photo', msg2) 43 | self.assertIsInstance(msg2.photo, list) 44 | self.assertEqual(msg2.caption, None) 45 | file_id2 = self._get_biggest_photo_fileid(msg2) 46 | 47 | # edit by file_id 48 | msg3 = self.bot.edit_message_media(InputMediaPhoto(file_id), TEST_CHAT, message_id=msg_id) 49 | self.messages.append(msg3) 50 | print("msg 3: {!r}".format(msg3)) 51 | self.assertIsInstance(msg3, Message) 52 | self.assertIn('photo', msg3) 53 | self.assertIsInstance(msg3.photo, list) 54 | file_id3 = self._get_biggest_photo_fileid(msg3) 55 | self.assertEqual(msg2.caption, None) 56 | self.assertEqual(file_id3, file_id) 57 | 58 | # edit by upload (url) 59 | msg4 = self.bot.edit_message_media(InputMediaPhoto(InputFileFromURL(url2)), TEST_CHAT, message_id=msg_id) 60 | self.messages.append(msg4) 61 | print("msg 4: {!r}".format(msg4)) 62 | self.assertIsInstance(msg4, Message) 63 | self.assertIn('photo', msg4) 64 | self.assertIsInstance(msg4.photo, list) 65 | self.assertEqual(msg4.caption, None) 66 | file_id4 = self._get_biggest_photo_fileid(msg4) 67 | 68 | self.messages.append(self.bot.send_message(TEST_CHAT, 'done.')) 69 | # end def 70 | 71 | def test_send_media_group(self): 72 | url1 = 'https://derpicdn.net/img/view/2012/1/22/1382.jpg' 73 | url2 = 'https://derpicdn.net/img/view/2016/2/3/1079240.png' 74 | vid1 = 'https://derpicdn.net/img/view/2016/12/21/1322277.mp4' 75 | pic1 = 'https://derpicdn.net/img/2017/7/21/1491832/thumb.jpeg' 76 | 77 | stuff = [ 78 | InputMediaPhoto(url1, caption='1'), 79 | InputMediaPhoto(InputFileFromURL(url1), caption='2'), 80 | InputMediaVideo(vid1, caption='3'), 81 | InputMediaVideo(InputFileFromURL(vid1), thumb=pic1, caption='4'), 82 | InputMediaVideo(InputFileFromURL(vid1), thumb=InputFileFromURL(pic1), caption='5'), 83 | ] 84 | msgs = self.bot.send_media_group(TEST_CHAT, stuff, disable_notification=True, ) 85 | self.messages.extend(msgs) 86 | 87 | self.messages.append(self.bot.send_message(TEST_CHAT, 'done.')) 88 | # end def 89 | 90 | # 91 | # utils: 92 | # 93 | 94 | def _get_biggest_photo_fileid(self, msg): 95 | biggest = msg.photo[0] 96 | for photo in msg.photo: 97 | self.assertIsInstance(photo, PhotoSize) 98 | if photo.file_size > biggest.file_size: 99 | biggest = photo 100 | # end if 101 | # end for 102 | return biggest.file_id 103 | # end def 104 | 105 | def tearDown(self): 106 | if self.bot and self.messages: 107 | for msg in reversed(self.messages): 108 | try: 109 | self.bot.delete_message(TEST_CHAT, msg.message_id) 110 | except TgApiServerException as e: 111 | if e.error_code == 400 and e.description == 'Bad Request: message to delete not found': 112 | logger.info('delete message fail, not found.') 113 | continue 114 | # end if 115 | logger.debug('delete message fail.', exc_info=True) 116 | # end try 117 | # end for 118 | # end if 119 | self.messages = [] 120 | # end def 121 | # end class 122 | 123 | 124 | if __name__ == '__main__': 125 | unittest.main() 126 | # end def 127 | -------------------------------------------------------------------------------- /tests/test_bot_parsing.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | from pytgbot.api_types.receivable.peer import ChatMember, ChatMemberOwner 4 | from pytgbot.bot.synchronous import Bot 5 | 6 | 7 | class MyTestCase(unittest.TestCase): 8 | def test_s(self): 9 | bot = Bot('not-a-real-api-key') 10 | result = bot._get_chat_member__process_result( 11 | { 12 | 'user': { 13 | 'id': 10717954, 'is_bot': False, 'first_name': 'Lucky', 14 | 'last_name': 'Donald :\uf803', 'username': 'luckydonald', 15 | 'language_code': 'en' 16 | }, 'status': 'creator', 'is_anonymous': False 17 | } 18 | ) 19 | self.assertIsInstance(result, ChatMember) 20 | self.assertIsInstance(result, ChatMemberOwner) 21 | # end def 22 | # end class 23 | 24 | if __name__ == '__main__': 25 | unittest.main() 26 | --------------------------------------------------------------------------------