diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml deleted file mode 100644 index 13e0071eb0..0000000000 --- a/.github/FUNDING.yml +++ /dev/null @@ -1,2 +0,0 @@ -github: delivrance -custom: https://docs.pyrogram.org/support diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md deleted file mode 100644 index 7efd4ca160..0000000000 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ /dev/null @@ -1,28 +0,0 @@ ---- -name: Bug Report -about: Create a bug report affecting the library or the documentation ---- - - - -## Checklist -- [ ] I am sure the error is coming from Pyrogram's code and not elsewhere. -- [ ] I have searched in the issue tracker for similar bug reports, including closed ones. -- [ ] I ran `pip3 install -U https://github.com/pyrogram/pyrogram/archive/master.zip` and reproduced the issue using the latest development version. - -## Description -A **clear** and **concise** description of the problem. Code snippets must be -[minimal, reproducible](https://stackoverflow.com/help/minimal-reproducible-example) and properly formatted. - -``` python -from pyrogram import Client -... -``` - -## Traceback -The full traceback (if applicable). - -``` -Traceback (most recent call last): - File "main.py", line 1, in -``` \ No newline at end of file diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml new file mode 100644 index 0000000000..3b0ff4ee2d --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -0,0 +1,52 @@ +name: Bug report +description: Report issues affecting the framework or the documentation +body: + - type: checkboxes + attributes: + label: Checklist + description: Invalid, incomplete or inadequate issue reports may not be taken into consideration + options: + - label: I am sure the error is coming from Pyrogram's code and not elsewhere + required: true + - label: I have searched in the issue tracker for similar bug reports, including closed ones + required: true + - label: I ran `pip3 install -U https://github.com/pyrogram/pyrogram/archive/master.zip` and reproduced the issue using the latest development version + required: true + + - type: textarea + attributes: + label: Description + description: Provide a clear and concise description of the issue + placeholder: Description... + validations: + required: true + + - type: textarea + attributes: + label: Steps to reproduce + description: Explain precisely how to reproduce the issue + placeholder: | + 1. + 2. + 3. + validations: + required: true + + - type: textarea + attributes: + label: Code example + description: Provide a [minimal, complete, consistently reproducible](https://stackoverflow.com/help/minimal-reproducible-example) and properly formatted example involving normal usages (if applicable) + placeholder: | + from pyrogram import Client + ... + render: python + + - type: textarea + attributes: + label: Logs + description: Provide the complete traceback (if applicable) + placeholder: | + Traceback (most recent call last): + File "main.py", line 1, in + ... + render: shell \ No newline at end of file diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml index 37e0cd33be..80d40a188d 100644 --- a/.github/ISSUE_TEMPLATE/config.yml +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -2,7 +2,7 @@ blank_issues_enabled: false contact_links: - name: Ask Pyrogram related questions url: https://stackoverflow.com/questions/tagged/pyrogram - about: This place is for issues about Pyrogram. If you'd like to ask a question, please do so at StackOverflow. - - name: Join the Telegram community + about: This place is only for reporting issues about Pyrogram. You can ask questions on StackOverflow. + - name: Join the Telegram channel url: https://t.me/pyrogram - about: Join the official channel to stay tuned for news and updates. \ No newline at end of file + about: Join the official channel and stay tuned for news, updates and announcements. \ No newline at end of file diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md deleted file mode 100644 index 854db44d9c..0000000000 --- a/.github/ISSUE_TEMPLATE/feature_request.md +++ /dev/null @@ -1,14 +0,0 @@ ---- -name: Feature Request -about: Suggest ideas, new features or enhancements -labels: "enhancement" ---- - - - -## Checklist -- [ ] I believe the idea is awesome and would benefit the library. -- [ ] I have searched in the issue tracker for similar requests, including closed ones. - -## Description -A detailed description of the request. \ No newline at end of file diff --git a/.github/ISSUE_TEMPLATE/feature_request.yml b/.github/ISSUE_TEMPLATE/feature_request.yml new file mode 100644 index 0000000000..59202d14a9 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.yml @@ -0,0 +1,20 @@ +name: Feature request +description: Suggest ideas, new features or enhancements +labels: [enhancement] +body: + - type: checkboxes + attributes: + label: Checklist + options: + - label: I believe the idea is awesome and would benefit the framework + required: true + - label: I have searched in the issue tracker for similar requests, including closed ones + required: true + + - type: textarea + attributes: + label: Description + description: Provide a detailed description of the request + placeholder: Description... + validations: + required: true \ No newline at end of file diff --git a/.github/workflows/python.yml b/.github/workflows/python.yml index 89ed8532ce..e12233fb20 100644 --- a/.github/workflows/python.yml +++ b/.github/workflows/python.yml @@ -8,11 +8,11 @@ jobs: runs-on: ${{ matrix.os }} strategy: matrix: - os: [ubuntu-latest, macos-latest, windows-latest] - python-version: [3.6, 3.7, 3.8, 3.9, "3.10"] + os: [ubuntu-latest, macos-latest] + python-version: ["3.7", "3.8", "3.9", "3.10", "3.11"] steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - name: Set up Python ${{ matrix.python-version }} uses: actions/setup-python@v2 @@ -26,8 +26,9 @@ jobs: - name: Generate API run: | - python setup.py generate --api + make venv + make api - name: Run tests run: | - tox + tox \ No newline at end of file diff --git a/.gitignore b/.gitignore index ff9ee02406..7a99a5db85 100644 --- a/.gitignore +++ b/.gitignore @@ -1,8 +1,10 @@ # Development +docs *.session config.ini main.py unknown_errors.txt +.DS_Store # Pyrogram generated code pyrogram/errors/exceptions/ diff --git a/MANIFEST.in b/MANIFEST.in index 4a750253e2..395ca54ee9 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,10 +1,13 @@ -## Include +# Include files include README.md COPYING COPYING.lesser NOTICE requirements.txt recursive-include compiler *.py *.tl *.tsv *.txt +recursive-include tests *.py -## Exclude +# Exclude files +exclude pyrogram/raw/all.py + +# Prune directories prune pyrogram/errors/exceptions prune pyrogram/raw/functions prune pyrogram/raw/types prune pyrogram/raw/base -exclude pyrogram/raw/all.py \ No newline at end of file diff --git a/Makefile b/Makefile new file mode 100644 index 0000000000..32f30a8520 --- /dev/null +++ b/Makefile @@ -0,0 +1,42 @@ +VENV := venv +PYTHON := $(VENV)/bin/python +HOST = $(shell ifconfig | grep "inet " | tail -1 | cut -d\ -f2) +TAG = v$(shell grep -E '__version__ = ".*"' pyrogram/__init__.py | cut -d\" -f2) + +RM := rm -rf + +.PHONY: venv clean-build clean-api clean api build tag dtag + +venv: + $(RM) $(VENV) + python3 -m venv $(VENV) + $(PYTHON) -m pip install -U pip wheel setuptools + $(PYTHON) -m pip install -U -r requirements.txt -r dev-requirements.txt + @echo "Created venv with $$($(PYTHON) --version)" + +clean-build: + $(RM) *.egg-info build dist + +clean-api: + $(RM) pyrogram/errors/exceptions pyrogram/raw/all.py pyrogram/raw/base pyrogram/raw/functions pyrogram/raw/types + +clean: + make clean-build + make clean-api + +api: + cd compiler/api && ../../$(PYTHON) compiler.py + cd compiler/errors && ../../$(PYTHON) compiler.py + +build: + make clean + $(PYTHON) setup.py sdist + $(PYTHON) setup.py bdist_wheel + +tag: + git tag $(TAG) + git push origin $(TAG) + +dtag: + git tag -d $(TAG) + git push origin -d $(TAG) \ No newline at end of file diff --git a/NOTICE b/NOTICE index 58defc6402..7a9aaab647 100644 --- a/NOTICE +++ b/NOTICE @@ -1,5 +1,5 @@ Pyrogram - Telegram MTProto API Client Library for Python -Copyright (C) 2017-2021 Dan +Copyright (C) 2017-present Dan This file is part of Pyrogram. diff --git a/README.md b/README.md index f8c4432e5f..e250b40668 100644 --- a/README.md +++ b/README.md @@ -1,25 +1,34 @@

- Pyrogram + Pyrogram
Telegram MTProto API Framework for Python
+ + Homepage + + • Documentation • - + Releases • - - Community + + News

## Pyrogram +> [!NOTE] +> The project is no longer maintained or supported. Thanks for appreciating it. + +> Elegant, modern and asynchronous Telegram MTProto API framework in Python for users and bots + ``` python from pyrogram import Client, filters @@ -28,34 +37,25 @@ app = Client("my_account") @app.on_message(filters.private) async def hello(client, message): - await message.reply_text(f"Hello {message.from_user.mention}") + await message.reply("Hello from Pyrogram!") app.run() ``` -**Pyrogram** is a modern, elegant and easy-to-use [Telegram](https://telegram.org/) client library framework written -from the ground up in Python and C. It enables you to easily create custom Telegram client applications for both user -and bot identities (bot API alternative) via the [MTProto API](https://docs.pyrogram.org/topics/mtproto-vs-botapi). +**Pyrogram** is a modern, elegant and asynchronous [MTProto API](https://docs.pyrogram.org/topics/mtproto-vs-botapi) +framework. It enables you to easily interact with the main Telegram API through a user account (custom client) or a bot +identity (bot API alternative) using Python. -### Features +### Key Features -- **Easy**: You can install Pyrogram with pip and start building your applications right away. -- **Elegant**: Low-level details are abstracted and re-presented in a much nicer and easier way. -- **Fast**: Crypto parts are boosted up by [TgCrypto](https://github.com/pyrogram/tgcrypto), a high-performance library - written in pure C. -- **Asynchronous**: Allows both synchronous and asynchronous models to fit all usage needs. -- **Documented**: API methods, types and public interfaces are all [well documented](https://docs.pyrogram.org). +- **Ready**: Install Pyrogram with pip and start building your applications right away. +- **Easy**: Makes the Telegram API simple and intuitive, while still allowing advanced usages. +- **Elegant**: Low-level details are abstracted and re-presented in a more convenient way. +- **Fast**: Boosted up by [TgCrypto](https://github.com/pyrogram/tgcrypto), a high-performance cryptography library written in C. - **Type-hinted**: Types and methods are all type-hinted, enabling excellent editor support. -- **Updated**, to make use of the latest Telegram API version and features. -- **Bot API-like**: Similar to the Bot API in its simplicity, but much more powerful and detailed. -- **Pluggable**: The Smart Plugin system allows to write components with minimal boilerplate code. -- **Comprehensive**: Execute any advanced action an official client is able to do, and even more. - -### Requirements - -- Python 3.6 or higher. -- A [Telegram API key](https://docs.pyrogram.org/intro/setup#api-keys). +- **Async**: Fully asynchronous (also usable synchronously if wanted, for convenience). +- **Powerful**: Full access to Telegram's API to execute any official client action and more. ### Installing @@ -65,11 +65,6 @@ pip3 install pyrogram ### Resources -- The docs contain lots of resources to help you get started with Pyrogram: https://docs.pyrogram.org. -- Seeking extra help? Come join and ask our community: https://t.me/pyrogram. -- For other kind of inquiries, you can send a [message](https://t.me/haskell) or an [e-mail](mailto:dan@pyrogram.org). - -### Copyright & License - -- Copyright (C) 2017-2021 Dan <> -- Licensed under the terms of the [GNU Lesser General Public License v3 or later (LGPLv3+)](COPYING.lesser) +- Check out the docs at https://docs.pyrogram.org to learn more about Pyrogram, get started right +away and discover more in-depth material for building your client applications. +- Join the official channel at https://t.me/pyrogram and stay tuned for news, updates and announcements. diff --git a/compiler/__init__.py b/compiler/__init__.py index 4ad4f32b47..46887cb7a5 100644 --- a/compiler/__init__.py +++ b/compiler/__init__.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2021 Dan +# Copyright (C) 2017-present Dan # # This file is part of Pyrogram. # diff --git a/compiler/api/__init__.py b/compiler/api/__init__.py index 4ad4f32b47..46887cb7a5 100644 --- a/compiler/api/__init__.py +++ b/compiler/api/__init__.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2021 Dan +# Copyright (C) 2017-present Dan # # This file is part of Pyrogram. # diff --git a/compiler/api/compiler.py b/compiler/api/compiler.py index d79fc80160..c29b7ed87f 100644 --- a/compiler/api/compiler.py +++ b/compiler/api/compiler.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2021 Dan +# Copyright (C) 2017-present Dan # # This file is part of Pyrogram. # @@ -16,6 +16,7 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . +import json import os import re import shutil @@ -34,9 +35,9 @@ LAYER_RE = re.compile(r"//\sLAYER\s(\d+)") COMBINATOR_RE = re.compile(r"^([\w.]+)#([0-9a-f]+)\s(?:.*)=\s([\w<>.]+);$", re.MULTILINE) ARGS_RE = re.compile(r"[^{](\w+):([\w?!.<>#]+)") -FLAGS_RE = re.compile(r"flags\.(\d+)\?") -FLAGS_RE_2 = re.compile(r"flags\.(\d+)\?([\w<>.]+)") -FLAGS_RE_3 = re.compile(r"flags:#") +FLAGS_RE = re.compile(r"flags(\d?)\.(\d+)\?") +FLAGS_RE_2 = re.compile(r"flags(\d?)\.(\d+)\?([\w<>.]+)") +FLAGS_RE_3 = re.compile(r"flags(\d?):#") INT_RE = re.compile(r"int(\d+)") CORE_TYPES = ["int", "long", "int128", "int256", "double", "bytes", "string", "Bool", "true"] @@ -59,6 +60,16 @@ namespaces_to_constructors = {} namespaces_to_functions = {} +try: + with open("docs.json") as f: + docs = json.load(f) +except FileNotFoundError: + docs = { + "type": {}, + "constructor": {}, + "method": {} + } + class Combinator(NamedTuple): section: str @@ -115,7 +126,7 @@ def get_type_hint(type: str) -> str: type = f"List[{get_type_hint(sub_type)}]" if is_core: - return f"Union[None, {type}] = None" if is_flag else type + return f"Optional[{type}] = None" if is_flag else type else: ns, name = type.split(".") if "." in type else ("", type) type = f'"raw.base.' + ".".join([ns, name]).strip(".") + '"' @@ -131,10 +142,9 @@ def sort_args(args): for i in flags: args.remove(i) - try: - args.remove(("flags", "#")) - except ValueError: - pass + for i in args[:]: + if re.match(r"flags\d?", i[0]) and i[1] == "#": + args.remove(i) return args + flags @@ -150,7 +160,7 @@ def remove_whitespaces(source: str) -> str: return "\n".join(lines) -def get_docstring_arg_type(t: str, is_list: bool = False, is_pyrogram_type: bool = False): +def get_docstring_arg_type(t: str): if t in CORE_TYPES: if t == "long": return "``int`` ``64-bit``" @@ -168,9 +178,9 @@ def get_docstring_arg_type(t: str, is_list: bool = False, is_pyrogram_type: bool elif t == "TLObject" or t == "X": return "Any object from :obj:`~pyrogram.raw.types`" elif t == "!X": - return "Any method from :obj:`~pyrogram.raw.functions`" + return "Any function from :obj:`~pyrogram.raw.functions`" elif t.lower().startswith("vector"): - return "List of " + get_docstring_arg_type(t.split("<", 1)[1][:-1], True) + return "List of " + get_docstring_arg_type(t.split("<", 1)[1][:-1]) else: return f":obj:`{t} `" @@ -184,10 +194,7 @@ def get_references(t: str, kind: str): raise ValueError("Invalid kind") if t: - return "\n ".join( - f"- :obj:`{i} `" - for i in t - ), len(t) + return "\n ".join(t), len(t) return None, 0 @@ -316,17 +323,33 @@ def start(format: bool = False): constructors = sorted(types_to_constructors[qualtype]) constr_count = len(constructors) - items = "\n ".join([f"- :obj:`{c} `" for c in constructors]) + items = "\n ".join([f"{c}" for c in constructors]) + + type_docs = docs["type"].get(qualtype, None) + + if type_docs: + type_docs = type_docs["desc"] + else: + type_docs = "Telegram API base type." + + docstring = type_docs - docstring = f"This base type has {constr_count} constructor{'s' if constr_count > 1 else ''} available.\n\n" - docstring += f" Constructors:\n .. hlist::\n :columns: 2\n\n {items}" + docstring += f"\n\n Constructors:\n" \ + f" This base type has {constr_count} constructor{'s' if constr_count > 1 else ''} available.\n\n" \ + f" .. currentmodule:: pyrogram.raw.types\n\n" \ + f" .. autosummary::\n" \ + f" :nosignatures:\n\n" \ + f" {items}" references, ref_count = get_references(qualtype, "types") if references: - docstring += f"\n\n See Also:\n This object can be returned by " \ - f"{ref_count} method{'s' if ref_count > 1 else ''}:" \ - f"\n\n .. hlist::\n :columns: 2\n\n " + references + docstring += f"\n\n Functions:\n This object can be returned by " \ + f"{ref_count} function{'s' if ref_count > 1 else ''}.\n\n" \ + f" .. currentmodule:: pyrogram.raw.functions\n\n" \ + f" .. autosummary::\n" \ + f" :nosignatures:\n\n" \ + f" " + references with open(dir_path / f"{snake(module)}.py", "w") as f: f.write( @@ -360,83 +383,113 @@ def start(format: bool = False): docstring = "" docstring_args = [] + if c.section == "functions": + combinator_docs = docs["method"] + else: + combinator_docs = docs["constructor"] + for i, arg in enumerate(sorted_args): arg_name, arg_type = arg is_optional = FLAGS_RE.match(arg_type) flag_number = is_optional.group(1) if is_optional else -1 arg_type = arg_type.split("?")[-1] + arg_docs = combinator_docs.get(c.qualname, None) + + if arg_docs: + arg_docs = arg_docs["params"].get(arg_name, "N/A") + else: + arg_docs = "N/A" + docstring_args.append( - "{}{}: {}".format( + "{} ({}{}):\n {}\n".format( arg_name, - " (optional)".format(flag_number) if is_optional else "", - get_docstring_arg_type(arg_type, is_pyrogram_type=c.namespace == "pyrogram") + get_docstring_arg_type(arg_type), + ", *optional*".format(flag_number) if is_optional else "", + arg_docs ) ) if c.section == "types": - docstring += f"This object is a constructor of the base type :obj:`~pyrogram.raw.base.{c.qualtype}`.\n\n" - else: - docstring += f"Telegram API method.\n\n" + constructor_docs = docs["constructor"].get(c.qualname, None) - docstring += f" Details:\n - Layer: ``{layer}``\n - ID: ``{c.id}``\n\n" + if constructor_docs: + constructor_docs = constructor_docs["desc"] + else: + constructor_docs = "Telegram API type." - if docstring_args: - docstring += " Parameters:\n " + "\n ".join(docstring_args) + docstring += constructor_docs + "\n" + docstring += f"\n Constructor of :obj:`~pyrogram.raw.base.{c.qualtype}`." else: - docstring += " **No parameters required.**" + function_docs = docs["method"].get(c.qualname, None) + + if function_docs: + docstring += function_docs["desc"] + "\n" + else: + docstring += f"Telegram API function." + + docstring += f"\n\n Details:\n - Layer: ``{layer}``\n - ID: ``{c.id[2:].upper()}``\n\n" + docstring += f" Parameters:\n " + \ + (f"\n ".join(docstring_args) if docstring_args else "No parameters required.\n") if c.section == "functions": - docstring += "\n\n Returns:\n " + get_docstring_arg_type(c.qualtype) + docstring += "\n Returns:\n " + get_docstring_arg_type(c.qualtype) else: references, count = get_references(c.qualname, "constructors") if references: - docstring += f"\n\n See Also:\n This object can be returned by " \ - f"{count} method{'s' if count > 1 else ''}:" \ - f"\n\n .. hlist::\n :columns: 2\n\n " + references + docstring += f"\n Functions:\n This object can be returned by " \ + f"{count} function{'s' if count > 1 else ''}.\n\n" \ + f" .. currentmodule:: pyrogram.raw.functions\n\n" \ + f" .. autosummary::\n" \ + f" :nosignatures:\n\n" \ + f" " + references write_types = read_types = "" if c.has_flags else "# No flags\n " for arg_name, arg_type in c.args: flag = FLAGS_RE_2.match(arg_type) - if arg_name == "flags" and arg_type == "#": + if re.match(r"flags\d?", arg_name) and arg_type == "#": write_flags = [] for i in c.args: flag = FLAGS_RE_2.match(i[1]) if flag: - if flag.group(2) == "true": - write_flags.append(f"flags |= (1 << {flag.group(1)}) if self.{i[0]} else 0") + if arg_name != f"flags{flag.group(1)}": + continue + + if flag.group(3) == "true" or flag.group(3).startswith("Vector"): + write_flags.append(f"{arg_name} |= (1 << {flag.group(2)}) if self.{i[0]} else 0") else: - write_flags.append(f"flags |= (1 << {flag.group(1)}) if self.{i[0]} is not None else 0") + write_flags.append( + f"{arg_name} |= (1 << {flag.group(2)}) if self.{i[0]} is not None else 0") write_flags = "\n ".join([ - "flags = 0", + f"{arg_name} = 0", "\n ".join(write_flags), - "b.write(Int(flags))\n " + f"b.write(Int({arg_name}))\n " ]) write_types += write_flags - read_types += "flags = Int.read(b)\n " + read_types += f"\n {arg_name} = Int.read(b)\n " continue if flag: - index, flag_type = flag.groups() + number, index, flag_type = flag.groups() if flag_type == "true": read_types += "\n " - read_types += f"{arg_name} = True if flags & (1 << {index}) else False" + read_types += f"{arg_name} = True if flags{number} & (1 << {index}) else False" elif flag_type in CORE_TYPES: write_types += "\n " write_types += f"if self.{arg_name} is not None:\n " write_types += f"b.write({flag_type.title()}(self.{arg_name}))\n " read_types += "\n " - read_types += f"{arg_name} = {flag_type.title()}.read(b) if flags & (1 << {index}) else None" + read_types += f"{arg_name} = {flag_type.title()}.read(b) if flags{number} & (1 << {index}) else None" elif "vector" in flag_type.lower(): sub_type = arg_type.split("<")[1][:-1] @@ -447,8 +500,8 @@ def start(format: bool = False): ) read_types += "\n " - read_types += "{} = TLObject.read(b{}) if flags & (1 << {}) else []\n ".format( - arg_name, f", {sub_type.title()}" if sub_type in CORE_TYPES else "", index + read_types += "{} = TLObject.read(b{}) if flags{} & (1 << {}) else []\n ".format( + arg_name, f", {sub_type.title()}" if sub_type in CORE_TYPES else "", number, index ) else: write_types += "\n " @@ -456,7 +509,7 @@ def start(format: bool = False): write_types += f"b.write(self.{arg_name}.write())\n " read_types += "\n " - read_types += f"{arg_name} = TLObject.read(b) if flags & (1 << {index}) else None\n " + read_types += f"{arg_name} = TLObject.read(b) if flags{number} & (1 << {index}) else None\n " else: if arg_type in CORE_TYPES: write_types += "\n " diff --git a/compiler/api/source/main_api.tl b/compiler/api/source/main_api.tl index bc5b1bf365..7fd74edb3d 100644 --- a/compiler/api/source/main_api.tl +++ b/compiler/api/source/main_api.tl @@ -36,23 +36,23 @@ inputFile#f52ff27f id:long parts:int name:string md5_checksum:string = InputFile inputFileBig#fa4f0bb5 id:long parts:int name:string = InputFile; inputMediaEmpty#9664f57f = InputMedia; -inputMediaUploadedPhoto#1e287d04 flags:# file:InputFile stickers:flags.0?Vector ttl_seconds:flags.1?int = InputMedia; -inputMediaPhoto#b3ba0635 flags:# id:InputPhoto ttl_seconds:flags.0?int = InputMedia; +inputMediaUploadedPhoto#1e287d04 flags:# spoiler:flags.2?true file:InputFile stickers:flags.0?Vector ttl_seconds:flags.1?int = InputMedia; +inputMediaPhoto#b3ba0635 flags:# spoiler:flags.1?true id:InputPhoto ttl_seconds:flags.0?int = InputMedia; inputMediaGeoPoint#f9c44144 geo_point:InputGeoPoint = InputMedia; inputMediaContact#f8ab7dfb phone_number:string first_name:string last_name:string vcard:string = InputMedia; -inputMediaUploadedDocument#5b38c6c1 flags:# nosound_video:flags.3?true force_file:flags.4?true file:InputFile thumb:flags.2?InputFile mime_type:string attributes:Vector stickers:flags.0?Vector ttl_seconds:flags.1?int = InputMedia; -inputMediaDocument#33473058 flags:# id:InputDocument ttl_seconds:flags.0?int query:flags.1?string = InputMedia; +inputMediaUploadedDocument#5b38c6c1 flags:# nosound_video:flags.3?true force_file:flags.4?true spoiler:flags.5?true file:InputFile thumb:flags.2?InputFile mime_type:string attributes:Vector stickers:flags.0?Vector ttl_seconds:flags.1?int = InputMedia; +inputMediaDocument#33473058 flags:# spoiler:flags.2?true id:InputDocument ttl_seconds:flags.0?int query:flags.1?string = InputMedia; inputMediaVenue#c13d1c11 geo_point:InputGeoPoint title:string address:string provider:string venue_id:string venue_type:string = InputMedia; -inputMediaPhotoExternal#e5bbfe1a flags:# url:string ttl_seconds:flags.0?int = InputMedia; -inputMediaDocumentExternal#fb52dc99 flags:# url:string ttl_seconds:flags.0?int = InputMedia; +inputMediaPhotoExternal#e5bbfe1a flags:# spoiler:flags.1?true url:string ttl_seconds:flags.0?int = InputMedia; +inputMediaDocumentExternal#fb52dc99 flags:# spoiler:flags.1?true url:string ttl_seconds:flags.0?int = InputMedia; inputMediaGame#d33f43f3 id:InputGame = InputMedia; -inputMediaInvoice#d9799874 flags:# title:string description:string photo:flags.0?InputWebDocument invoice:Invoice payload:bytes provider:string provider_data:DataJSON start_param:flags.1?string = InputMedia; +inputMediaInvoice#8eb5a6d5 flags:# title:string description:string photo:flags.0?InputWebDocument invoice:Invoice payload:bytes provider:string provider_data:DataJSON start_param:flags.1?string extended_media:flags.2?InputMedia = InputMedia; inputMediaGeoLive#971fa843 flags:# stopped:flags.0?true geo_point:InputGeoPoint heading:flags.2?int period:flags.1?int proximity_notification_radius:flags.3?int = InputMedia; inputMediaPoll#f94e5f1 flags:# poll:Poll correct_answers:flags.0?Vector solution:flags.1?string solution_entities:flags.1?Vector = InputMedia; inputMediaDice#e66fbf7b emoticon:string = InputMedia; inputChatPhotoEmpty#1ca48f57 = InputChatPhoto; -inputChatUploadedPhoto#c642724e flags:# file:flags.0?InputFile video:flags.1?InputFile video_start_ts:flags.2?double = InputChatPhoto; +inputChatUploadedPhoto#bdcdaec0 flags:# file:flags.0?InputFile video:flags.1?InputFile video_start_ts:flags.2?double video_emoji_markup:flags.3?VideoSize = InputChatPhoto; inputChatPhoto#8953ad37 id:InputPhoto = InputChatPhoto; inputGeoPointEmpty#e4c123d6 = InputGeoPoint; @@ -88,10 +88,10 @@ storage.fileMp4#b3cea0e4 = storage.FileType; storage.fileWebp#1081464c = storage.FileType; userEmpty#d3bc4b7a id:long = User; -user#3ff6ecb0 flags:# self:flags.10?true contact:flags.11?true mutual_contact:flags.12?true deleted:flags.13?true bot:flags.14?true bot_chat_history:flags.15?true bot_nochats:flags.16?true verified:flags.17?true restricted:flags.18?true min:flags.20?true bot_inline_geo:flags.21?true support:flags.23?true scam:flags.24?true apply_min_photo:flags.25?true fake:flags.26?true id:long access_hash:flags.0?long first_name:flags.1?string last_name:flags.2?string username:flags.3?string phone:flags.4?string photo:flags.5?UserProfilePhoto status:flags.6?UserStatus bot_info_version:flags.14?int restriction_reason:flags.18?Vector bot_inline_placeholder:flags.19?string lang_code:flags.22?string = User; +user#8f97c628 flags:# self:flags.10?true contact:flags.11?true mutual_contact:flags.12?true deleted:flags.13?true bot:flags.14?true bot_chat_history:flags.15?true bot_nochats:flags.16?true verified:flags.17?true restricted:flags.18?true min:flags.20?true bot_inline_geo:flags.21?true support:flags.23?true scam:flags.24?true apply_min_photo:flags.25?true fake:flags.26?true bot_attach_menu:flags.27?true premium:flags.28?true attach_menu_enabled:flags.29?true flags2:# bot_can_edit:flags2.1?true id:long access_hash:flags.0?long first_name:flags.1?string last_name:flags.2?string username:flags.3?string phone:flags.4?string photo:flags.5?UserProfilePhoto status:flags.6?UserStatus bot_info_version:flags.14?int restriction_reason:flags.18?Vector bot_inline_placeholder:flags.19?string lang_code:flags.22?string emoji_status:flags.30?EmojiStatus usernames:flags2.0?Vector = User; userProfilePhotoEmpty#4f11bae1 = UserProfilePhoto; -userProfilePhoto#82d1f706 flags:# has_video:flags.0?true photo_id:long stripped_thumb:flags.1?bytes dc_id:int = UserProfilePhoto; +userProfilePhoto#82d1f706 flags:# has_video:flags.0?true personal:flags.2?true photo_id:long stripped_thumb:flags.1?bytes dc_id:int = UserProfilePhoto; userStatusEmpty#9d05049 = UserStatus; userStatusOnline#edb93949 expires:int = UserStatus; @@ -101,13 +101,13 @@ userStatusLastWeek#7bf09fc = UserStatus; userStatusLastMonth#77ebc742 = UserStatus; chatEmpty#29562865 id:long = Chat; -chat#41cbf256 flags:# creator:flags.0?true kicked:flags.1?true left:flags.2?true deactivated:flags.5?true call_active:flags.23?true call_not_empty:flags.24?true noforwards:flags.25?true id:long title:string photo:ChatPhoto participants_count:int date:int version:int migrated_to:flags.6?InputChannel admin_rights:flags.14?ChatAdminRights default_banned_rights:flags.18?ChatBannedRights = Chat; +chat#41cbf256 flags:# creator:flags.0?true left:flags.2?true deactivated:flags.5?true call_active:flags.23?true call_not_empty:flags.24?true noforwards:flags.25?true id:long title:string photo:ChatPhoto participants_count:int date:int version:int migrated_to:flags.6?InputChannel admin_rights:flags.14?ChatAdminRights default_banned_rights:flags.18?ChatBannedRights = Chat; chatForbidden#6592a1a7 id:long title:string = Chat; -channel#8261ac61 flags:# creator:flags.0?true left:flags.2?true broadcast:flags.5?true verified:flags.7?true megagroup:flags.8?true restricted:flags.9?true signatures:flags.11?true min:flags.12?true scam:flags.19?true has_link:flags.20?true has_geo:flags.21?true slowmode_enabled:flags.22?true call_active:flags.23?true call_not_empty:flags.24?true fake:flags.25?true gigagroup:flags.26?true noforwards:flags.27?true id:long access_hash:flags.13?long title:string username:flags.6?string photo:ChatPhoto date:int restriction_reason:flags.9?Vector admin_rights:flags.14?ChatAdminRights banned_rights:flags.15?ChatBannedRights default_banned_rights:flags.18?ChatBannedRights participants_count:flags.17?int = Chat; +channel#83259464 flags:# creator:flags.0?true left:flags.2?true broadcast:flags.5?true verified:flags.7?true megagroup:flags.8?true restricted:flags.9?true signatures:flags.11?true min:flags.12?true scam:flags.19?true has_link:flags.20?true has_geo:flags.21?true slowmode_enabled:flags.22?true call_active:flags.23?true call_not_empty:flags.24?true fake:flags.25?true gigagroup:flags.26?true noforwards:flags.27?true join_to_send:flags.28?true join_request:flags.29?true forum:flags.30?true flags2:# id:long access_hash:flags.13?long title:string username:flags.6?string photo:ChatPhoto date:int restriction_reason:flags.9?Vector admin_rights:flags.14?ChatAdminRights banned_rights:flags.15?ChatBannedRights default_banned_rights:flags.18?ChatBannedRights participants_count:flags.17?int usernames:flags2.0?Vector = Chat; channelForbidden#17d493d5 flags:# broadcast:flags.5?true megagroup:flags.8?true id:long access_hash:long title:string until_date:flags.16?int = Chat; -chatFull#46a6ffb4 flags:# can_set_username:flags.7?true has_scheduled:flags.8?true id:long about:string participants:ChatParticipants chat_photo:flags.2?Photo notify_settings:PeerNotifySettings exported_invite:flags.13?ExportedChatInvite bot_info:flags.3?Vector pinned_msg_id:flags.6?int folder_id:flags.11?int call:flags.12?InputGroupCall ttl_period:flags.14?int groupcall_default_join_as:flags.15?Peer theme_emoticon:flags.16?string requests_pending:flags.17?int recent_requesters:flags.17?Vector = ChatFull; -channelFull#56662e2e flags:# can_view_participants:flags.3?true can_set_username:flags.6?true can_set_stickers:flags.7?true hidden_prehistory:flags.10?true can_set_location:flags.16?true has_scheduled:flags.19?true can_view_stats:flags.20?true blocked:flags.22?true id:long about:string participants_count:flags.0?int admins_count:flags.1?int kicked_count:flags.2?int banned_count:flags.2?int online_count:flags.13?int read_inbox_max_id:int read_outbox_max_id:int unread_count:int chat_photo:Photo notify_settings:PeerNotifySettings exported_invite:flags.23?ExportedChatInvite bot_info:Vector migrated_from_chat_id:flags.4?long migrated_from_max_id:flags.4?int pinned_msg_id:flags.5?int stickerset:flags.8?StickerSet available_min_id:flags.9?int folder_id:flags.11?int linked_chat_id:flags.14?long location:flags.15?ChannelLocation slowmode_seconds:flags.17?int slowmode_next_send_date:flags.18?int stats_dc:flags.12?int pts:int call:flags.21?InputGroupCall ttl_period:flags.24?int pending_suggestions:flags.25?Vector groupcall_default_join_as:flags.26?Peer theme_emoticon:flags.27?string requests_pending:flags.28?int recent_requesters:flags.28?Vector default_send_as:flags.29?Peer = ChatFull; +chatFull#c9d31138 flags:# can_set_username:flags.7?true has_scheduled:flags.8?true translations_disabled:flags.19?true id:long about:string participants:ChatParticipants chat_photo:flags.2?Photo notify_settings:PeerNotifySettings exported_invite:flags.13?ExportedChatInvite bot_info:flags.3?Vector pinned_msg_id:flags.6?int folder_id:flags.11?int call:flags.12?InputGroupCall ttl_period:flags.14?int groupcall_default_join_as:flags.15?Peer theme_emoticon:flags.16?string requests_pending:flags.17?int recent_requesters:flags.17?Vector available_reactions:flags.18?ChatReactions = ChatFull; +channelFull#f2355507 flags:# can_view_participants:flags.3?true can_set_username:flags.6?true can_set_stickers:flags.7?true hidden_prehistory:flags.10?true can_set_location:flags.16?true has_scheduled:flags.19?true can_view_stats:flags.20?true blocked:flags.22?true flags2:# can_delete_channel:flags2.0?true antispam:flags2.1?true participants_hidden:flags2.2?true translations_disabled:flags2.3?true id:long about:string participants_count:flags.0?int admins_count:flags.1?int kicked_count:flags.2?int banned_count:flags.2?int online_count:flags.13?int read_inbox_max_id:int read_outbox_max_id:int unread_count:int chat_photo:Photo notify_settings:PeerNotifySettings exported_invite:flags.23?ExportedChatInvite bot_info:Vector migrated_from_chat_id:flags.4?long migrated_from_max_id:flags.4?int pinned_msg_id:flags.5?int stickerset:flags.8?StickerSet available_min_id:flags.9?int folder_id:flags.11?int linked_chat_id:flags.14?long location:flags.15?ChannelLocation slowmode_seconds:flags.17?int slowmode_next_send_date:flags.18?int stats_dc:flags.12?int pts:int call:flags.21?InputGroupCall ttl_period:flags.24?int pending_suggestions:flags.25?Vector groupcall_default_join_as:flags.26?Peer theme_emoticon:flags.27?string requests_pending:flags.28?int recent_requesters:flags.28?Vector default_send_as:flags.29?Peer available_reactions:flags.30?ChatReactions = ChatFull; chatParticipant#c02d4007 user_id:long inviter_id:long date:int = ChatParticipant; chatParticipantCreator#e46bcee4 user_id:long = ChatParticipant; @@ -120,19 +120,19 @@ chatPhotoEmpty#37c1011c = ChatPhoto; chatPhoto#1c6e1c11 flags:# has_video:flags.0?true photo_id:long stripped_thumb:flags.1?bytes dc_id:int = ChatPhoto; messageEmpty#90a6ca84 flags:# id:int peer_id:flags.0?Peer = Message; -message#85d6cbe2 flags:# out:flags.1?true mentioned:flags.4?true media_unread:flags.5?true silent:flags.13?true post:flags.14?true from_scheduled:flags.18?true legacy:flags.19?true edit_hide:flags.21?true pinned:flags.24?true noforwards:flags.26?true id:int from_id:flags.8?Peer peer_id:Peer fwd_from:flags.2?MessageFwdHeader via_bot_id:flags.11?long reply_to:flags.3?MessageReplyHeader date:int message:string media:flags.9?MessageMedia reply_markup:flags.6?ReplyMarkup entities:flags.7?Vector views:flags.10?int forwards:flags.10?int replies:flags.23?MessageReplies edit_date:flags.15?int post_author:flags.16?string grouped_id:flags.17?long restriction_reason:flags.22?Vector ttl_period:flags.25?int = Message; +message#38116ee0 flags:# out:flags.1?true mentioned:flags.4?true media_unread:flags.5?true silent:flags.13?true post:flags.14?true from_scheduled:flags.18?true legacy:flags.19?true edit_hide:flags.21?true pinned:flags.24?true noforwards:flags.26?true id:int from_id:flags.8?Peer peer_id:Peer fwd_from:flags.2?MessageFwdHeader via_bot_id:flags.11?long reply_to:flags.3?MessageReplyHeader date:int message:string media:flags.9?MessageMedia reply_markup:flags.6?ReplyMarkup entities:flags.7?Vector views:flags.10?int forwards:flags.10?int replies:flags.23?MessageReplies edit_date:flags.15?int post_author:flags.16?string grouped_id:flags.17?long reactions:flags.20?MessageReactions restriction_reason:flags.22?Vector ttl_period:flags.25?int = Message; messageService#2b085862 flags:# out:flags.1?true mentioned:flags.4?true media_unread:flags.5?true silent:flags.13?true post:flags.14?true legacy:flags.19?true id:int from_id:flags.8?Peer peer_id:Peer reply_to:flags.3?MessageReplyHeader date:int action:MessageAction ttl_period:flags.25?int = Message; messageMediaEmpty#3ded6320 = MessageMedia; -messageMediaPhoto#695150d7 flags:# photo:flags.0?Photo ttl_seconds:flags.2?int = MessageMedia; +messageMediaPhoto#695150d7 flags:# spoiler:flags.3?true photo:flags.0?Photo ttl_seconds:flags.2?int = MessageMedia; messageMediaGeo#56e0d474 geo:GeoPoint = MessageMedia; messageMediaContact#70322949 phone_number:string first_name:string last_name:string vcard:string user_id:long = MessageMedia; messageMediaUnsupported#9f84f49e = MessageMedia; -messageMediaDocument#9cb070d7 flags:# document:flags.0?Document ttl_seconds:flags.2?int = MessageMedia; +messageMediaDocument#9cb070d7 flags:# nopremium:flags.3?true spoiler:flags.4?true document:flags.0?Document ttl_seconds:flags.2?int = MessageMedia; messageMediaWebPage#a32dd600 webpage:WebPage = MessageMedia; messageMediaVenue#2ec0533f geo:GeoPoint title:string address:string provider:string venue_id:string venue_type:string = MessageMedia; messageMediaGame#fdb19008 game:Game = MessageMedia; -messageMediaInvoice#84551347 flags:# shipping_address_requested:flags.1?true test:flags.3?true title:string description:string photo:flags.0?WebDocument receipt_msg_id:flags.2?int currency:string total_amount:long start_param:string = MessageMedia; +messageMediaInvoice#f6a548d3 flags:# shipping_address_requested:flags.1?true test:flags.3?true title:string description:string photo:flags.0?WebDocument receipt_msg_id:flags.2?int currency:string total_amount:long start_param:string extended_media:flags.4?MessageExtendedMedia = MessageMedia; messageMediaGeoLive#b940c666 flags:# geo:GeoPoint heading:flags.0?int period:int proximity_notification_radius:flags.1?int = MessageMedia; messageMediaPoll#4bd6e798 poll:Poll results:PollResults = MessageMedia; messageMediaDice#3f7ee58b value:int emoticon:string = MessageMedia; @@ -151,24 +151,33 @@ messageActionChannelMigrateFrom#ea3948e9 title:string chat_id:long = MessageActi messageActionPinMessage#94bd38ed = MessageAction; messageActionHistoryClear#9fbab604 = MessageAction; messageActionGameScore#92a72876 game_id:long score:int = MessageAction; -messageActionPaymentSentMe#8f31b327 flags:# currency:string total_amount:long payload:bytes info:flags.0?PaymentRequestedInfo shipping_option_id:flags.1?string charge:PaymentCharge = MessageAction; -messageActionPaymentSent#40699cd0 currency:string total_amount:long = MessageAction; +messageActionPaymentSentMe#8f31b327 flags:# recurring_init:flags.2?true recurring_used:flags.3?true currency:string total_amount:long payload:bytes info:flags.0?PaymentRequestedInfo shipping_option_id:flags.1?string charge:PaymentCharge = MessageAction; +messageActionPaymentSent#96163f56 flags:# recurring_init:flags.2?true recurring_used:flags.3?true currency:string total_amount:long invoice_slug:flags.0?string = MessageAction; messageActionPhoneCall#80e11a7f flags:# video:flags.2?true call_id:long reason:flags.0?PhoneCallDiscardReason duration:flags.1?int = MessageAction; messageActionScreenshotTaken#4792929b = MessageAction; messageActionCustomAction#fae69f56 message:string = MessageAction; -messageActionBotAllowed#abe9affe domain:string = MessageAction; +messageActionBotAllowed#c516d679 flags:# attach_menu:flags.1?true domain:flags.0?string app:flags.2?BotApp = MessageAction; messageActionSecureValuesSentMe#1b287353 values:Vector credentials:SecureCredentialsEncrypted = MessageAction; messageActionSecureValuesSent#d95c6154 types:Vector = MessageAction; messageActionContactSignUp#f3f25f76 = MessageAction; messageActionGeoProximityReached#98e0d697 from_id:Peer to_id:Peer distance:int = MessageAction; messageActionGroupCall#7a0d7f42 flags:# call:InputGroupCall duration:flags.0?int = MessageAction; messageActionInviteToGroupCall#502f92f7 call:InputGroupCall users:Vector = MessageAction; -messageActionSetMessagesTTL#aa1afbfd period:int = MessageAction; +messageActionSetMessagesTTL#3c134d7b flags:# period:int auto_setting_from:flags.0?long = MessageAction; messageActionGroupCallScheduled#b3a07661 call:InputGroupCall schedule_date:int = MessageAction; messageActionSetChatTheme#aa786345 emoticon:string = MessageAction; messageActionChatJoinedByRequest#ebbca3cb = MessageAction; - -dialog#2c171f72 flags:# pinned:flags.2?true unread_mark:flags.3?true peer:Peer top_message:int read_inbox_max_id:int read_outbox_max_id:int unread_count:int unread_mentions_count:int notify_settings:PeerNotifySettings pts:flags.0?int draft:flags.1?DraftMessage folder_id:flags.4?int = Dialog; +messageActionWebViewDataSentMe#47dd8079 text:string data:string = MessageAction; +messageActionWebViewDataSent#b4c38cb5 text:string = MessageAction; +messageActionGiftPremium#c83d6aec flags:# currency:string amount:long months:int crypto_currency:flags.0?string crypto_amount:flags.0?long = MessageAction; +messageActionTopicCreate#d999256 flags:# title:string icon_color:int icon_emoji_id:flags.0?long = MessageAction; +messageActionTopicEdit#c0944820 flags:# title:flags.0?string icon_emoji_id:flags.1?long closed:flags.2?Bool hidden:flags.3?Bool = MessageAction; +messageActionSuggestProfilePhoto#57de635e photo:Photo = MessageAction; +messageActionRequestedPeer#fe77345d button_id:int peer:Peer = MessageAction; +messageActionSetChatWallPaper#bc44a927 wallpaper:WallPaper = MessageAction; +messageActionSetSameChatWallPaper#c0787d6d wallpaper:WallPaper = MessageAction; + +dialog#d58a08c6 flags:# pinned:flags.2?true unread_mark:flags.3?true peer:Peer top_message:int read_inbox_max_id:int read_outbox_max_id:int unread_count:int unread_mentions_count:int unread_reactions_count:int notify_settings:PeerNotifySettings pts:flags.0?int draft:flags.1?DraftMessage folder_id:flags.4?int ttl_period:flags.5?int = Dialog; dialogFolder#71bd134c flags:# pinned:flags.2?true folder:Folder peer:Peer top_message:int unread_muted_peers_count:int unread_unmuted_peers_count:int unread_muted_messages_count:int unread_unmuted_messages_count:int = Dialog; photoEmpty#2331b22d id:long = Photo; @@ -185,8 +194,9 @@ geoPointEmpty#1117dd5f = GeoPoint; geoPoint#b2a2f663 flags:# long:double lat:double access_hash:long accuracy_radius:flags.0?int = GeoPoint; auth.sentCode#5e002502 flags:# type:auth.SentCodeType phone_code_hash:string next_type:flags.1?auth.CodeType timeout:flags.2?int = auth.SentCode; +auth.sentCodeSuccess#2390fe44 authorization:auth.Authorization = auth.SentCode; -auth.authorization#33fb7bb8 flags:# setup_password_required:flags.1?true otherwise_relogin_days:flags.1?int tmp_sessions:flags.0?int user:User = auth.Authorization; +auth.authorization#2ea2c0d4 flags:# setup_password_required:flags.1?true otherwise_relogin_days:flags.1?int tmp_sessions:flags.0?int future_auth_token:flags.2?bytes user:User = auth.Authorization; auth.authorizationSignUpRequired#44747e9a flags:# terms_of_service:flags.0?help.TermsOfService = auth.Authorization; auth.exportedAuthorization#b434e2b8 id:long bytes:bytes = auth.ExportedAuthorization; @@ -195,10 +205,11 @@ inputNotifyPeer#b8bc5b0c peer:InputPeer = InputNotifyPeer; inputNotifyUsers#193b4417 = InputNotifyPeer; inputNotifyChats#4a95e84e = InputNotifyPeer; inputNotifyBroadcasts#b1db7c7e = InputNotifyPeer; +inputNotifyForumTopic#5c467992 peer:InputPeer top_msg_id:int = InputNotifyPeer; -inputPeerNotifySettings#9c3d198e flags:# show_previews:flags.0?Bool silent:flags.1?Bool mute_until:flags.2?int sound:flags.3?string = InputPeerNotifySettings; +inputPeerNotifySettings#df1f002b flags:# show_previews:flags.0?Bool silent:flags.1?Bool mute_until:flags.2?int sound:flags.3?NotificationSound = InputPeerNotifySettings; -peerNotifySettings#af509d20 flags:# show_previews:flags.0?Bool silent:flags.1?Bool mute_until:flags.2?int sound:flags.3?string = PeerNotifySettings; +peerNotifySettings#a83b0426 flags:# show_previews:flags.0?Bool silent:flags.1?Bool mute_until:flags.2?int ios_sound:flags.3?NotificationSound android_sound:flags.4?NotificationSound other_sound:flags.5?NotificationSound = PeerNotifySettings; peerSettings#a518110d flags:# report_spam:flags.0?true add_contact:flags.1?true block_contact:flags.2?true share_contact:flags.3?true need_contacts_exception:flags.4?true report_geo:flags.5?true autoarchived:flags.7?true invite_members:flags.8?true request_chat_broadcast:flags.10?true geo_distance:flags.6?int request_chat_title:flags.9?string request_chat_date:flags.9?int = PeerSettings; @@ -213,8 +224,10 @@ inputReportReasonOther#c1e4a2b1 = ReportReason; inputReportReasonCopyright#9b89f93a = ReportReason; inputReportReasonGeoIrrelevant#dbd4feed = ReportReason; inputReportReasonFake#f5ddd6e7 = ReportReason; +inputReportReasonIllegalDrugs#a8eb2be = ReportReason; +inputReportReasonPersonalDetails#9ec7863d = ReportReason; -userFull#cf366521 flags:# blocked:flags.0?true phone_calls_available:flags.4?true phone_calls_private:flags.5?true can_pin_message:flags.7?true has_scheduled:flags.12?true video_calls_available:flags.13?true id:long about:flags.1?string settings:PeerSettings profile_photo:flags.2?Photo notify_settings:PeerNotifySettings bot_info:flags.3?BotInfo pinned_msg_id:flags.6?int common_chats_count:int folder_id:flags.11?int ttl_period:flags.14?int theme_emoticon:flags.15?string private_forward_name:flags.16?string = UserFull; +userFull#93eadb53 flags:# blocked:flags.0?true phone_calls_available:flags.4?true phone_calls_private:flags.5?true can_pin_message:flags.7?true has_scheduled:flags.12?true video_calls_available:flags.13?true voice_messages_forbidden:flags.20?true translations_disabled:flags.23?true id:long about:flags.1?string settings:PeerSettings personal_photo:flags.21?Photo profile_photo:flags.2?Photo fallback_photo:flags.22?Photo notify_settings:PeerNotifySettings bot_info:flags.3?BotInfo pinned_msg_id:flags.6?int common_chats_count:int folder_id:flags.11?int ttl_period:flags.14?int theme_emoticon:flags.15?string private_forward_name:flags.16?string bot_group_admin_rights:flags.17?ChatAdminRights bot_broadcast_admin_rights:flags.18?ChatAdminRights premium_gifts:flags.19?Vector wallpaper:flags.24?WallPaper = UserFull; contact#145ade0b user_id:long mutual:Bool = Contact; @@ -236,7 +249,7 @@ messages.dialogsNotModified#f0e3e596 count:int = messages.Dialogs; messages.messages#8c718e87 messages:Vector chats:Vector users:Vector = messages.Messages; messages.messagesSlice#3a54685e flags:# inexact:flags.1?true count:int next_rate:flags.0?int offset_id_offset:flags.2?int messages:Vector chats:Vector users:Vector = messages.Messages; -messages.channelMessages#64479808 flags:# inexact:flags.1?true pts:int count:int offset_id_offset:flags.2?int messages:Vector chats:Vector users:Vector = messages.Messages; +messages.channelMessages#c776ba4e flags:# inexact:flags.1?true pts:int count:int offset_id_offset:flags.2?int messages:Vector topics:Vector chats:Vector users:Vector = messages.Messages; messages.messagesNotModified#74535f21 count:int = messages.Messages; messages.chats#64ff9fd5 chats:Vector = messages.Chats; @@ -271,8 +284,7 @@ updateUserTyping#c01e857f user_id:long action:SendMessageAction = Update; updateChatUserTyping#83487af0 chat_id:long from_id:Peer action:SendMessageAction = Update; updateChatParticipants#7761198 participants:ChatParticipants = Update; updateUserStatus#e5bdf8de user_id:long status:UserStatus = Update; -updateUserName#c3f202e0 user_id:long first_name:string last_name:string username:string = Update; -updateUserPhoto#f227868c user_id:long date:int photo:UserProfilePhoto previous:Bool = Update; +updateUserName#a7848924 user_id:long first_name:string last_name:string usernames:Vector = Update; updateNewEncryptedMessage#12bcbd9a message:EncryptedMessage qts:int = Update; updateEncryptedChatTyping#1710f156 chat_id:int = Update; updateEncryption#b4a2e88d chat:EncryptedChat date:int = Update; @@ -296,8 +308,8 @@ updateDeleteChannelMessages#c32d5b12 channel_id:long messages:Vector pts:in updateChannelMessageViews#f226ac08 channel_id:long id:int views:int = Update; updateChatParticipantAdmin#d7ca61a2 chat_id:long user_id:long is_admin:Bool version:int = Update; updateNewStickerSet#688a30aa stickerset:messages.StickerSet = Update; -updateStickerSetsOrder#bb2d201 flags:# masks:flags.0?true order:Vector = Update; -updateStickerSets#43ae3dec = Update; +updateStickerSetsOrder#bb2d201 flags:# masks:flags.0?true emojis:flags.1?true order:Vector = Update; +updateStickerSets#31c24808 flags:# masks:flags.0?true emojis:flags.1?true = Update; updateSavedGifs#9375341e = Update; updateBotInlineQuery#496f379c flags:# query_id:long user_id:long query:string geo:flags.0?GeoPoint peer_type:flags.1?InlineQueryPeerType offset:string = Update; updateBotInlineSend#12f12a07 flags:# user_id:long query:string geo:flags.0?GeoPoint id:string msg_id:flags.1?InputBotInlineMessageID = Update; @@ -306,7 +318,7 @@ updateBotCallbackQuery#b9cfc48d flags:# query_id:long user_id:long peer:Peer msg updateEditMessage#e40370a3 message:Message pts:int pts_count:int = Update; updateInlineBotCallbackQuery#691e9052 flags:# query_id:long user_id:long msg_id:InputBotInlineMessageID chat_instance:long data:flags.0?bytes game_short_name:flags.1?string = Update; updateReadChannelOutbox#b75f99a9 channel_id:long max_id:int = Update; -updateDraftMessage#ee2bb969 peer:Peer draft:DraftMessage = Update; +updateDraftMessage#1b49ec6d flags:# peer:Peer top_msg_id:flags.0?int draft:DraftMessage = Update; updateReadFeaturedStickers#571d2742 = Update; updateRecentStickers#9a422c20 = Update; updateConfig#a229dd06 = Update; @@ -322,7 +334,7 @@ updatePhoneCall#ab0f6b1e phone_call:PhoneCall = Update; updateLangPackTooLong#46560264 lang_code:string = Update; updateLangPack#56022f4d difference:LangPackDifference = Update; updateFavedStickers#e511996d = Update; -updateChannelReadMessagesContents#44bdd535 channel_id:long messages:Vector = Update; +updateChannelReadMessagesContents#ea29055d flags:# channel_id:long top_msg_id:flags.0?int messages:Vector = Update; updateContactsReset#7084a7be = Update; updateChannelAvailableMessages#b23fc698 channel_id:long available_min_id:int = Update; updateDialogUnreadMark#e16459c3 flags:# unread:flags.0?true peer:DialogPeer = Update; @@ -353,12 +365,29 @@ updateGroupCallParticipants#f2ebdb4e call:InputGroupCall participants:Vector = Update; updatePendingJoinRequests#7063c3db peer:Peer requests_pending:int recent_requesters:Vector = Update; updateBotChatInviteRequester#11dfa986 peer:Peer date:int user_id:long about:string invite:ExportedChatInvite qts:int = Update; +updateMessageReactions#5e1b3cb8 flags:# peer:Peer msg_id:int top_msg_id:flags.0?int reactions:MessageReactions = Update; +updateAttachMenuBots#17b7a20b = Update; +updateWebViewResultSent#1592b79d query_id:long = Update; +updateBotMenuButton#14b85813 bot_id:long button:BotMenuButton = Update; +updateSavedRingtones#74d8be99 = Update; +updateTranscribedAudio#84cd5a flags:# pending:flags.0?true peer:Peer msg_id:int transcription_id:long text:string = Update; +updateReadFeaturedEmojiStickers#fb4c496c = Update; +updateUserEmojiStatus#28373599 user_id:long emoji_status:EmojiStatus = Update; +updateRecentEmojiStatuses#30f443db = Update; +updateRecentReactions#6f7863f4 = Update; +updateMoveStickerSetToTop#86fccf85 flags:# masks:flags.0?true emojis:flags.1?true stickerset:long = Update; +updateMessageExtendedMedia#5a73a98c peer:Peer msg_id:int extended_media:MessageExtendedMedia = Update; +updateChannelPinnedTopic#192efbe3 flags:# pinned:flags.0?true channel_id:long topic_id:int = Update; +updateChannelPinnedTopics#fe198602 flags:# channel_id:long order:flags.0?Vector = Update; +updateUser#20529438 user_id:long = Update; +updateAutoSaveSettings#ec05b097 = Update; +updateGroupInvitePrivacyForbidden#ccf08ad6 user_id:long = Update; updates.state#a56c2a3e pts:int qts:int date:int seq:int unread_count:int = updates.State; @@ -383,9 +412,9 @@ photos.photo#20212ca8 photo:Photo users:Vector = photos.Photo; upload.file#96a18d5 type:storage.FileType mtime:int bytes:bytes = upload.File; upload.fileCdnRedirect#f18cda44 dc_id:int file_token:bytes encryption_key:bytes encryption_iv:bytes file_hashes:Vector = upload.File; -dcOption#18b7a10d flags:# ipv6:flags.0?true media_only:flags.1?true tcpo_only:flags.2?true cdn:flags.3?true static:flags.4?true id:int ip_address:string port:int secret:flags.10?bytes = DcOption; +dcOption#18b7a10d flags:# ipv6:flags.0?true media_only:flags.1?true tcpo_only:flags.2?true cdn:flags.3?true static:flags.4?true this_port_only:flags.5?true id:int ip_address:string port:int secret:flags.10?bytes = DcOption; -config#330b4067 flags:# phonecalls_enabled:flags.1?true default_p2p_contacts:flags.3?true preload_featured_stickers:flags.4?true ignore_phone_entities:flags.5?true revoke_pm_inbox:flags.6?true blocked_mode:flags.8?true pfs_enabled:flags.13?true date:int expires:int test_mode:Bool this_dc:int dc_options:Vector dc_txt_domain_name:string chat_size_max:int megagroup_size_max:int forwarded_count_max:int online_update_period_ms:int offline_blur_timeout_ms:int offline_idle_timeout_ms:int online_cloud_timeout_ms:int notify_cloud_delay_ms:int notify_default_delay_ms:int push_chat_period_ms:int push_chat_limit:int saved_gifs_limit:int edit_time_limit:int revoke_time_limit:int revoke_pm_time_limit:int rating_e_decay:int stickers_recent_limit:int stickers_faved_limit:int channels_read_media_period:int tmp_sessions:flags.0?int pinned_dialogs_count_max:int pinned_infolder_count_max:int call_receive_timeout_ms:int call_ring_timeout_ms:int call_connect_timeout_ms:int call_packet_timeout_ms:int me_url_prefix:string autoupdate_url_prefix:flags.7?string gif_search_username:flags.9?string venue_search_username:flags.10?string img_search_username:flags.11?string static_maps_provider:flags.12?string caption_length_max:int message_length_max:int webfile_dc_id:int suggested_lang_code:flags.2?string lang_pack_version:flags.2?int base_lang_pack_version:flags.2?int = Config; +config#cc1a241e flags:# default_p2p_contacts:flags.3?true preload_featured_stickers:flags.4?true revoke_pm_inbox:flags.6?true blocked_mode:flags.8?true force_try_ipv6:flags.14?true date:int expires:int test_mode:Bool this_dc:int dc_options:Vector dc_txt_domain_name:string chat_size_max:int megagroup_size_max:int forwarded_count_max:int online_update_period_ms:int offline_blur_timeout_ms:int offline_idle_timeout_ms:int online_cloud_timeout_ms:int notify_cloud_delay_ms:int notify_default_delay_ms:int push_chat_period_ms:int push_chat_limit:int edit_time_limit:int revoke_time_limit:int revoke_pm_time_limit:int rating_e_decay:int stickers_recent_limit:int channels_read_media_period:int tmp_sessions:flags.0?int call_receive_timeout_ms:int call_ring_timeout_ms:int call_connect_timeout_ms:int call_packet_timeout_ms:int me_url_prefix:string autoupdate_url_prefix:flags.7?string gif_search_username:flags.9?string venue_search_username:flags.10?string img_search_username:flags.11?string static_maps_provider:flags.12?string caption_length_max:int message_length_max:int webfile_dc_id:int suggested_lang_code:flags.2?string lang_pack_version:flags.2?int base_lang_pack_version:flags.2?int reactions_default:flags.15?Reaction autologin_token:flags.16?string = Config; nearestDc#8e1a1775 country:string this_dc:int nearest_dc:int = NearestDc; @@ -403,7 +432,7 @@ encryptedChatDiscarded#1e1c7c45 flags:# history_deleted:flags.0?true id:int = En inputEncryptedChat#f141b5e1 chat_id:int access_hash:long = InputEncryptedChat; encryptedFileEmpty#c21f497e = EncryptedFile; -encryptedFile#4a70994c id:long access_hash:long size:int dc_id:int key_fingerprint:int = EncryptedFile; +encryptedFile#a8008cd8 id:long access_hash:long size:long dc_id:int key_fingerprint:int = EncryptedFile; inputEncryptedFileEmpty#1837c364 = InputEncryptedFile; inputEncryptedFileUploaded#64bd0306 id:long parts:int md5_checksum:string key_fingerprint:int = InputEncryptedFile; @@ -423,7 +452,7 @@ inputDocumentEmpty#72f0eaae = InputDocument; inputDocument#1abfb575 id:long access_hash:long file_reference:bytes = InputDocument; documentEmpty#36f8c871 id:long = Document; -document#1e87342b flags:# id:long access_hash:long file_reference:bytes date:int mime_type:string size:int thumbs:flags.0?Vector video_thumbs:flags.1?Vector dc_id:int attributes:Vector = Document; +document#8fd4c4d8 flags:# id:long access_hash:long file_reference:bytes date:int mime_type:string size:long thumbs:flags.0?Vector video_thumbs:flags.1?Vector dc_id:int attributes:Vector = Document; help.support#17c6b5f6 phone_number:string user:User = help.Support; @@ -431,6 +460,7 @@ notifyPeer#9fd40bd8 peer:Peer = NotifyPeer; notifyUsers#b4c83b4c = NotifyPeer; notifyChats#c007cec3 = NotifyPeer; notifyBroadcasts#d612e8ef = NotifyPeer; +notifyForumTopic#226e6308 peer:Peer top_msg_id:int = NotifyPeer; sendMessageTypingAction#16bf744e = SendMessageAction; sendMessageCancelAction#fd5ec8f5 = SendMessageAction; @@ -461,6 +491,7 @@ inputPrivacyKeyForwards#a4dd4c08 = InputPrivacyKey; inputPrivacyKeyProfilePhoto#5719bacc = InputPrivacyKey; inputPrivacyKeyPhoneNumber#352dafa = InputPrivacyKey; inputPrivacyKeyAddedByPhone#d1219bdd = InputPrivacyKey; +inputPrivacyKeyVoiceMessages#aee69d68 = InputPrivacyKey; privacyKeyStatusTimestamp#bc2eab30 = PrivacyKey; privacyKeyChatInvite#500e6dfa = PrivacyKey; @@ -470,6 +501,7 @@ privacyKeyForwards#69ec56a3 = PrivacyKey; privacyKeyProfilePhoto#96151fed = PrivacyKey; privacyKeyPhoneNumber#d19ae46d = PrivacyKey; privacyKeyAddedByPhone#42ffd42b = PrivacyKey; +privacyKeyVoiceMessages#697f414 = PrivacyKey; inputPrivacyValueAllowContacts#d09e07b = InputPrivacyRule; inputPrivacyValueAllowAll#184b35ce = InputPrivacyRule; @@ -500,6 +532,7 @@ documentAttributeVideo#ef02ce6 flags:# round_message:flags.0?true supports_strea documentAttributeAudio#9852f9c6 flags:# voice:flags.10?true duration:int title:flags.0?string performer:flags.1?string waveform:flags.2?bytes = DocumentAttribute; documentAttributeFilename#15590068 file_name:string = DocumentAttribute; documentAttributeHasStickers#9801d2f7 = DocumentAttribute; +documentAttributeCustomEmoji#fd149899 flags:# free:flags.0?true text_color:flags.1?true alt:string stickerset:InputStickerSet = DocumentAttribute; messages.stickersNotModified#f1749a22 = messages.Stickers; messages.stickers#30a6ec7e hash:long stickers:Vector = messages.Stickers; @@ -520,7 +553,7 @@ authorization#ad01d61d flags:# current:flags.0?true official_app:flags.1?true pa account.authorizations#4bff8ea0 authorization_ttl_days:int authorizations:Vector = account.Authorizations; -account.password#185b184f flags:# has_recovery:flags.0?true has_secure_values:flags.1?true has_password:flags.2?true current_algo:flags.2?PasswordKdfAlgo srp_B:flags.2?bytes srp_id:flags.2?long hint:flags.3?string email_unconfirmed_pattern:flags.4?string new_algo:PasswordKdfAlgo new_secure_algo:SecurePasswordKdfAlgo secure_random:bytes pending_reset_date:flags.5?int = account.Password; +account.password#957b50fb flags:# has_recovery:flags.0?true has_secure_values:flags.1?true has_password:flags.2?true current_algo:flags.2?PasswordKdfAlgo srp_B:flags.2?bytes srp_id:flags.2?long hint:flags.3?string email_unconfirmed_pattern:flags.4?string new_algo:PasswordKdfAlgo new_secure_algo:SecurePasswordKdfAlgo secure_random:bytes pending_reset_date:flags.5?int login_email_pattern:flags.6?string = account.Password; account.passwordSettings#9a5c33e5 flags:# email:flags.0?string secure_settings:flags.1?SecureSecretSettings = account.PasswordSettings; @@ -531,6 +564,7 @@ auth.passwordRecovery#137948a5 email_pattern:string = auth.PasswordRecovery; receivedNotifyMessage#a384b779 id:int flags:int = ReceivedNotifyMessage; chatInviteExported#ab4a819 flags:# revoked:flags.0?true permanent:flags.5?true request_needed:flags.6?true link:string admin_id:long date:int start_date:flags.4?int expire_date:flags.1?int usage_limit:flags.2?int usage:flags.3?int requested:flags.7?int title:flags.8?string = ExportedChatInvite; +chatInvitePublicJoinRequests#ed107ab7 = ExportedChatInvite; chatInviteAlready#5a686d7c chat:Chat = ChatInvite; chatInvite#300c44c1 flags:# channel:flags.0?true broadcast:flags.1?true public:flags.2?true megagroup:flags.3?true request_needed:flags.6?true title:string about:flags.5?string photo:Photo participants_count:int participants:flags.4?Vector = ChatInvite; @@ -542,22 +576,26 @@ inputStickerSetShortName#861cc8a0 short_name:string = InputStickerSet; inputStickerSetAnimatedEmoji#28703c8 = InputStickerSet; inputStickerSetDice#e67f520e emoticon:string = InputStickerSet; inputStickerSetAnimatedEmojiAnimations#cde3739 = InputStickerSet; +inputStickerSetPremiumGifts#c88b3b02 = InputStickerSet; +inputStickerSetEmojiGenericAnimations#4c4d4ce = InputStickerSet; +inputStickerSetEmojiDefaultStatuses#29d0f5ee = InputStickerSet; +inputStickerSetEmojiDefaultTopicIcons#44c1f8e9 = InputStickerSet; -stickerSet#d7df217a flags:# archived:flags.1?true official:flags.2?true masks:flags.3?true animated:flags.5?true installed_date:flags.0?int id:long access_hash:long title:string short_name:string thumbs:flags.4?Vector thumb_dc_id:flags.4?int thumb_version:flags.4?int count:int hash:int = StickerSet; +stickerSet#2dd14edc flags:# archived:flags.1?true official:flags.2?true masks:flags.3?true animated:flags.5?true videos:flags.6?true emojis:flags.7?true installed_date:flags.0?int id:long access_hash:long title:string short_name:string thumbs:flags.4?Vector thumb_dc_id:flags.4?int thumb_version:flags.4?int thumb_document_id:flags.8?long count:int hash:int = StickerSet; -messages.stickerSet#b60a24a6 set:StickerSet packs:Vector documents:Vector = messages.StickerSet; +messages.stickerSet#6e153f16 set:StickerSet packs:Vector keywords:Vector documents:Vector = messages.StickerSet; messages.stickerSetNotModified#d3f924eb = messages.StickerSet; botCommand#c27ac8c7 command:string description:string = BotCommand; -botInfo#1b74b335 user_id:long description:string commands:Vector = BotInfo; +botInfo#8f300b57 flags:# user_id:flags.0?long description:flags.1?string description_photo:flags.4?Photo description_document:flags.5?Document commands:flags.2?Vector menu_button:flags.3?BotMenuButton = BotInfo; keyboardButton#a2fa4880 text:string = KeyboardButton; keyboardButtonUrl#258aff05 text:string url:string = KeyboardButton; keyboardButtonCallback#35bbdb6b flags:# requires_password:flags.0?true text:string data:bytes = KeyboardButton; keyboardButtonRequestPhone#b16a6c29 text:string = KeyboardButton; keyboardButtonRequestGeoLocation#fc796b3f text:string = KeyboardButton; -keyboardButtonSwitchInline#568a748 flags:# same_peer:flags.0?true text:string query:string = KeyboardButton; +keyboardButtonSwitchInline#93b9fbb5 flags:# same_peer:flags.0?true text:string query:string peer_types:flags.1?Vector = KeyboardButton; keyboardButtonGame#50f41ccf text:string = KeyboardButton; keyboardButtonBuy#afd93fbb text:string = KeyboardButton; keyboardButtonUrlAuth#10b78d29 flags:# text:string fwd_text:flags.0?string url:string button_id:int = KeyboardButton; @@ -565,12 +603,15 @@ inputKeyboardButtonUrlAuth#d02e7fd4 flags:# request_write_access:flags.0?true te keyboardButtonRequestPoll#bbc7515d flags:# quiz:flags.0?Bool text:string = KeyboardButton; inputKeyboardButtonUserProfile#e988037b text:string user_id:InputUser = KeyboardButton; keyboardButtonUserProfile#308660c1 text:string user_id:long = KeyboardButton; +keyboardButtonWebView#13767230 text:string url:string = KeyboardButton; +keyboardButtonSimpleWebView#a0c0505c text:string url:string = KeyboardButton; +keyboardButtonRequestPeer#d0b468c text:string button_id:int peer_type:RequestPeerType = KeyboardButton; keyboardButtonRow#77608b83 buttons:Vector = KeyboardButtonRow; replyKeyboardHide#a03e5b85 flags:# selective:flags.2?true = ReplyMarkup; replyKeyboardForceReply#86b40b08 flags:# single_use:flags.1?true selective:flags.2?true placeholder:flags.3?string = ReplyMarkup; -replyKeyboardMarkup#85dd99d1 flags:# resize:flags.0?true single_use:flags.1?true selective:flags.2?true rows:Vector placeholder:flags.3?string = ReplyMarkup; +replyKeyboardMarkup#85dd99d1 flags:# resize:flags.0?true single_use:flags.1?true selective:flags.2?true persistent:flags.4?true rows:Vector placeholder:flags.3?string = ReplyMarkup; replyInlineMarkup#48a30254 rows:Vector = ReplyMarkup; messageEntityUnknown#bb92ba95 offset:int length:int = MessageEntity; @@ -592,6 +633,8 @@ messageEntityUnderline#9c4e7e8b offset:int length:int = MessageEntity; messageEntityStrike#bf0693d4 offset:int length:int = MessageEntity; messageEntityBlockquote#20df5d0 offset:int length:int = MessageEntity; messageEntityBankCard#761e6af4 offset:int length:int = MessageEntity; +messageEntitySpoiler#32ca960f offset:int length:int = MessageEntity; +messageEntityCustomEmoji#c8cf05f8 offset:int length:int document_id:long = MessageEntity; inputChannelEmpty#ee8c1e86 = InputChannel; inputChannel#f35aec28 channel_id:long access_hash:long = InputChannel; @@ -657,7 +700,7 @@ botInlineMessageMediaInvoice#354a9b09 flags:# shipping_address_requested:flags.1 botInlineResult#11965f3a flags:# id:string type:string title:flags.1?string description:flags.2?string url:flags.3?string thumb:flags.4?WebDocument content:flags.5?WebDocument send_message:BotInlineMessage = BotInlineResult; botInlineMediaResult#17db940b flags:# id:string type:string photo:flags.0?Photo document:flags.1?Document title:flags.2?string description:flags.3?string send_message:BotInlineMessage = BotInlineResult; -messages.botResults#947ca848 flags:# gallery:flags.0?true query_id:long next_offset:flags.1?string switch_pm:flags.2?InlineBotSwitchPM results:Vector cache_time:int users:Vector = messages.BotResults; +messages.botResults#e021f2f6 flags:# gallery:flags.0?true query_id:long next_offset:flags.1?string switch_pm:flags.2?InlineBotSwitchPM switch_webview:flags.3?InlineBotWebView results:Vector cache_time:int users:Vector = messages.BotResults; exportedMessageLink#5dab1af4 link:string html:string = ExportedMessageLink; @@ -667,12 +710,17 @@ auth.codeTypeSms#72a3158c = auth.CodeType; auth.codeTypeCall#741cd3e3 = auth.CodeType; auth.codeTypeFlashCall#226ccefb = auth.CodeType; auth.codeTypeMissedCall#d61ad6ee = auth.CodeType; +auth.codeTypeFragmentSms#6ed998c = auth.CodeType; auth.sentCodeTypeApp#3dbb5986 length:int = auth.SentCodeType; auth.sentCodeTypeSms#c000bba2 length:int = auth.SentCodeType; auth.sentCodeTypeCall#5353e5a7 length:int = auth.SentCodeType; auth.sentCodeTypeFlashCall#ab03c6d9 pattern:string = auth.SentCodeType; auth.sentCodeTypeMissedCall#82006484 prefix:string length:int = auth.SentCodeType; +auth.sentCodeTypeEmailCode#f450f59b flags:# apple_signin_allowed:flags.0?true google_signin_allowed:flags.1?true email_pattern:string length:int reset_available_period:flags.3?int reset_pending_date:flags.4?int = auth.SentCodeType; +auth.sentCodeTypeSetUpEmailRequired#a5491dea flags:# apple_signin_allowed:flags.0?true google_signin_allowed:flags.1?true = auth.SentCodeType; +auth.sentCodeTypeFragmentSms#d9565c39 url:string length:int = auth.SentCodeType; +auth.sentCodeTypeFirebaseSms#e57b1432 flags:# nonce:flags.0?bytes receipt:flags.1?string push_timeout:flags.1?int length:int = auth.SentCodeType; messages.botCallbackAnswer#36585ea4 flags:# alert:flags.1?true has_url:flags.3?true native_ui:flags.4?true message:flags.0?string url:flags.2?string cache_time:int = messages.BotCallbackAnswer; @@ -706,7 +754,7 @@ draftMessageEmpty#1b0c841a flags:# date:flags.0?int = DraftMessage; draftMessage#fd8e711f flags:# no_webpage:flags.1?true reply_to_msg_id:flags.0?int message:string entities:flags.3?Vector date:int = DraftMessage; messages.featuredStickersNotModified#c6dc0c66 count:int = messages.FeaturedStickers; -messages.featuredStickers#84c02310 hash:long count:int sets:Vector unread:Vector = messages.FeaturedStickers; +messages.featuredStickers#be382906 flags:# premium:flags.0?true hash:long count:int sets:Vector unread:Vector = messages.FeaturedStickers; messages.recentStickersNotModified#b17f890 = messages.RecentStickers; messages.recentStickers#88d37c56 hash:long packs:Vector stickers:Vector dates:Vector = messages.RecentStickers; @@ -718,6 +766,8 @@ messages.stickerSetInstallResultArchive#35e410a8 sets:Vector stickerSetCovered#6410a5d2 set:StickerSet cover:Document = StickerSetCovered; stickerSetMultiCovered#3407e51b set:StickerSet covers:Vector = StickerSetCovered; +stickerSetFullCovered#40d13c0e set:StickerSet packs:Vector keywords:Vector documents:Vector = StickerSetCovered; +stickerSetNoCovered#77b15d1c set:StickerSet = StickerSetCovered; maskCoords#aed6dbb2 n:int x:double y:double zoom:double = MaskCoords; @@ -789,7 +839,7 @@ dataJSON#7d748d04 data:string = DataJSON; labeledPrice#cb296bf8 label:string amount:long = LabeledPrice; -invoice#cd886e0 flags:# test:flags.0?true name_requested:flags.1?true phone_requested:flags.2?true email_requested:flags.3?true shipping_address_requested:flags.4?true flexible:flags.5?true phone_to_provider:flags.6?true email_to_provider:flags.7?true currency:string prices:Vector max_tip_amount:flags.8?long suggested_tip_amounts:flags.8?Vector = Invoice; +invoice#3e85a91b flags:# test:flags.0?true name_requested:flags.1?true phone_requested:flags.2?true email_requested:flags.3?true shipping_address_requested:flags.4?true flexible:flags.5?true phone_to_provider:flags.6?true email_to_provider:flags.7?true recurring:flags.9?true currency:string prices:Vector max_tip_amount:flags.8?long suggested_tip_amounts:flags.8?Vector recurring_terms_url:flags.9?string = Invoice; paymentCharge#ea02c27e id:string provider_charge_id:string = PaymentCharge; @@ -806,10 +856,11 @@ inputWebDocument#9bed434d url:string size:int mime_type:string attributes:Vector inputWebFileLocation#c239d686 url:string access_hash:long = InputWebFileLocation; inputWebFileGeoPointLocation#9f2221c9 geo_point:InputGeoPoint access_hash:long w:int h:int zoom:int scale:int = InputWebFileLocation; +inputWebFileAudioAlbumThumbLocation#f46fe924 flags:# small:flags.2?true document:flags.0?InputDocument title:flags.1?string performer:flags.1?string = InputWebFileLocation; upload.webFile#21e753bc size:int mime_type:string file_type:storage.FileType mtime:int bytes:bytes = upload.WebFile; -payments.paymentForm#1694761b flags:# can_save_credentials:flags.2?true password_missing:flags.3?true form_id:long bot_id:long invoice:Invoice provider_id:long url:string native_provider:flags.4?string native_params:flags.4?DataJSON saved_info:flags.0?PaymentRequestedInfo saved_credentials:flags.1?PaymentSavedCredentials users:Vector = payments.PaymentForm; +payments.paymentForm#a0058751 flags:# can_save_credentials:flags.2?true password_missing:flags.3?true form_id:long bot_id:long title:string description:string photo:flags.5?WebDocument invoice:Invoice provider_id:long url:string native_provider:flags.4?string native_params:flags.4?DataJSON additional_methods:flags.6?Vector saved_info:flags.0?PaymentRequestedInfo saved_credentials:flags.1?Vector users:Vector = payments.PaymentForm; payments.validatedRequestedInfo#d1451883 flags:# id:flags.0?string shipping_options:flags.1?Vector = payments.ValidatedRequestedInfo; @@ -829,7 +880,7 @@ account.tmpPassword#db64fd34 tmp_password:bytes valid_until:int = account.TmpPas shippingOption#b6213cdf id:string title:string prices:Vector = ShippingOption; -inputStickerSetItem#ffa0a496 flags:# document:InputDocument emoji:string mask_coords:flags.0?MaskCoords = InputStickerSetItem; +inputStickerSetItem#32da9e9c flags:# document:InputDocument emoji:string mask_coords:flags.0?MaskCoords keywords:flags.1?string = InputStickerSetItem; inputPhoneCall#1e36fded id:long access_hash:long = InputPhoneCall; @@ -840,7 +891,7 @@ phoneCallAccepted#3660c311 flags:# video:flags.6?true id:long access_hash:long d phoneCall#967f7c67 flags:# p2p_allowed:flags.5?true video:flags.6?true id:long access_hash:long date:int admin_id:long participant_id:long g_a_or_b:bytes key_fingerprint:long protocol:PhoneCallProtocol connections:Vector start_date:int = PhoneCall; phoneCallDiscarded#50ca4de1 flags:# need_rating:flags.2?true need_debug:flags.3?true video:flags.6?true id:long reason:flags.0?PhoneCallDiscardReason duration:flags.1?int = PhoneCall; -phoneConnection#9d4c17c0 id:long ip:string ipv6:string port:int peer_tag:bytes = PhoneConnection; +phoneConnection#9cc123c7 flags:# tcp:flags.0?true id:long ip:string ipv6:string port:int peer_tag:bytes = PhoneConnection; phoneConnectionWebrtc#635fe375 flags:# turn:flags.0?true stun:flags.1?true id:long ip:string ipv6:string port:int username:string password:string = PhoneConnection; phoneCallProtocol#fc878fc8 flags:# udp_p2p:flags.0?true udp_reflector:flags.1?true min_layer:int max_layer:int library_versions:Vector = PhoneCallProtocol; @@ -888,7 +939,7 @@ channelAdminLogEventActionDiscardGroupCall#db9f9140 call:InputGroupCall = Channe channelAdminLogEventActionParticipantMute#f92424d2 participant:GroupCallParticipant = ChannelAdminLogEventAction; channelAdminLogEventActionParticipantUnmute#e64429c0 participant:GroupCallParticipant = ChannelAdminLogEventAction; channelAdminLogEventActionToggleGroupCallSetting#56d6a247 join_muted:Bool = ChannelAdminLogEventAction; -channelAdminLogEventActionParticipantJoinByInvite#5cdada77 invite:ExportedChatInvite = ChannelAdminLogEventAction; +channelAdminLogEventActionParticipantJoinByInvite#fe9fc158 flags:# via_chatlist:flags.0?true invite:ExportedChatInvite = ChannelAdminLogEventAction; channelAdminLogEventActionExportedInviteDelete#5a50fca4 invite:ExportedChatInvite = ChannelAdminLogEventAction; channelAdminLogEventActionExportedInviteRevoke#410a134e invite:ExportedChatInvite = ChannelAdminLogEventAction; channelAdminLogEventActionExportedInviteEdit#e90ebb59 prev_invite:ExportedChatInvite new_invite:ExportedChatInvite = ChannelAdminLogEventAction; @@ -897,12 +948,20 @@ channelAdminLogEventActionChangeHistoryTTL#6e941a38 prev_value:int new_value:int channelAdminLogEventActionParticipantJoinByRequest#afb6144a invite:ExportedChatInvite approved_by:long = ChannelAdminLogEventAction; channelAdminLogEventActionToggleNoForwards#cb2ac766 new_value:Bool = ChannelAdminLogEventAction; channelAdminLogEventActionSendMessage#278f2868 message:Message = ChannelAdminLogEventAction; +channelAdminLogEventActionChangeAvailableReactions#be4e0ef8 prev_value:ChatReactions new_value:ChatReactions = ChannelAdminLogEventAction; +channelAdminLogEventActionChangeUsernames#f04fb3a9 prev_value:Vector new_value:Vector = ChannelAdminLogEventAction; +channelAdminLogEventActionToggleForum#2cc6383 new_value:Bool = ChannelAdminLogEventAction; +channelAdminLogEventActionCreateTopic#58707d28 topic:ForumTopic = ChannelAdminLogEventAction; +channelAdminLogEventActionEditTopic#f06fe208 prev_topic:ForumTopic new_topic:ForumTopic = ChannelAdminLogEventAction; +channelAdminLogEventActionDeleteTopic#ae168909 topic:ForumTopic = ChannelAdminLogEventAction; +channelAdminLogEventActionPinTopic#5d8d353b flags:# prev_topic:flags.0?ForumTopic new_topic:flags.1?ForumTopic = ChannelAdminLogEventAction; +channelAdminLogEventActionToggleAntiSpam#64f36dfc new_value:Bool = ChannelAdminLogEventAction; channelAdminLogEvent#1fad68cd id:long date:int user_id:long action:ChannelAdminLogEventAction = ChannelAdminLogEvent; channels.adminLogResults#ed8af74d events:Vector chats:Vector users:Vector = channels.AdminLogResults; -channelAdminLogEventsFilter#ea107ae4 flags:# join:flags.0?true leave:flags.1?true invite:flags.2?true ban:flags.3?true unban:flags.4?true kick:flags.5?true unkick:flags.6?true promote:flags.7?true demote:flags.8?true info:flags.9?true settings:flags.10?true pinned:flags.11?true edit:flags.12?true delete:flags.13?true group_call:flags.14?true invites:flags.15?true send:flags.16?true = ChannelAdminLogEventsFilter; +channelAdminLogEventsFilter#ea107ae4 flags:# join:flags.0?true leave:flags.1?true invite:flags.2?true ban:flags.3?true unban:flags.4?true kick:flags.5?true unkick:flags.6?true promote:flags.7?true demote:flags.8?true info:flags.9?true settings:flags.10?true pinned:flags.11?true edit:flags.12?true delete:flags.13?true group_call:flags.14?true invites:flags.15?true send:flags.16?true forums:flags.17?true = ChannelAdminLogEventsFilter; popularContact#5ce14175 client_id:long importers:int = PopularContact; @@ -937,7 +996,7 @@ dialogPeerFolder#514519e2 folder_id:int = DialogPeer; messages.foundStickerSetsNotModified#d54b65d = messages.FoundStickerSets; messages.foundStickerSets#8af09dd2 hash:long sets:Vector = messages.FoundStickerSets; -fileHash#6242c773 offset:int limit:int hash:bytes = FileHash; +fileHash#f39b035c offset:long limit:int hash:bytes = FileHash; inputClientProxy#75588b3f address:string port:int = InputClientProxy; @@ -948,7 +1007,7 @@ inputSecureFileUploaded#3334b0f0 id:long parts:int md5_checksum:string file_hash inputSecureFile#5367e5be id:long access_hash:long = InputSecureFile; secureFileEmpty#64199744 = SecureFile; -secureFile#e0277a62 id:long access_hash:long size:int dc_id:int date:int file_hash:bytes secret:bytes = SecureFile; +secureFile#7d09c27e id:long access_hash:long size:long dc_id:int date:int file_hash:bytes secret:bytes = SecureFile; secureData#8aeabec3 data:bytes data_hash:bytes secret:bytes = SecureData; @@ -1060,9 +1119,9 @@ chatOnlines#f041e250 onlines:int = ChatOnlines; statsURL#47a971e0 url:string = StatsURL; -chatAdminRights#5fb224d5 flags:# change_info:flags.0?true post_messages:flags.1?true edit_messages:flags.2?true delete_messages:flags.3?true ban_users:flags.4?true invite_users:flags.5?true pin_messages:flags.7?true add_admins:flags.9?true anonymous:flags.10?true manage_call:flags.11?true other:flags.12?true = ChatAdminRights; +chatAdminRights#5fb224d5 flags:# change_info:flags.0?true post_messages:flags.1?true edit_messages:flags.2?true delete_messages:flags.3?true ban_users:flags.4?true invite_users:flags.5?true pin_messages:flags.7?true add_admins:flags.9?true anonymous:flags.10?true manage_call:flags.11?true other:flags.12?true manage_topics:flags.13?true = ChatAdminRights; -chatBannedRights#9f120418 flags:# view_messages:flags.0?true send_messages:flags.1?true send_media:flags.2?true send_stickers:flags.3?true send_gifs:flags.4?true send_games:flags.5?true send_inline:flags.6?true embed_links:flags.7?true send_polls:flags.8?true change_info:flags.10?true invite_users:flags.15?true pin_messages:flags.17?true until_date:int = ChatBannedRights; +chatBannedRights#9f120418 flags:# view_messages:flags.0?true send_messages:flags.1?true send_media:flags.2?true send_stickers:flags.3?true send_gifs:flags.4?true send_games:flags.5?true send_inline:flags.6?true embed_links:flags.7?true send_polls:flags.8?true change_info:flags.10?true invite_users:flags.15?true pin_messages:flags.17?true manage_topics:flags.18?true send_photos:flags.19?true send_videos:flags.20?true send_roundvideos:flags.21?true send_audios:flags.22?true send_voices:flags.23?true send_docs:flags.24?true send_plain:flags.25?true until_date:int = ChatBannedRights; inputWallPaper#e630b979 id:long access_hash:long = InputWallPaper; inputWallPaperSlug#72091c80 slug:string = InputWallPaper; @@ -1071,11 +1130,11 @@ inputWallPaperNoFile#967a462e id:long = InputWallPaper; account.wallPapersNotModified#1c199183 = account.WallPapers; account.wallPapers#cdc3858c hash:long wallpapers:Vector = account.WallPapers; -codeSettings#8a6469c2 flags:# allow_flashcall:flags.0?true current_number:flags.1?true allow_app_hash:flags.4?true allow_missed_call:flags.5?true logout_tokens:flags.6?Vector = CodeSettings; +codeSettings#ad253d78 flags:# allow_flashcall:flags.0?true current_number:flags.1?true allow_app_hash:flags.4?true allow_missed_call:flags.5?true allow_firebase:flags.7?true logout_tokens:flags.6?Vector token:flags.8?string app_sandbox:flags.8?Bool = CodeSettings; wallPaperSettings#1dc1bca4 flags:# blur:flags.1?true motion:flags.2?true background_color:flags.0?int second_background_color:flags.4?int third_background_color:flags.5?int fourth_background_color:flags.6?int intensity:flags.3?int rotation:flags.4?int = WallPaperSettings; -autoDownloadSettings#e04232f3 flags:# disabled:flags.0?true video_preload_large:flags.1?true audio_preload_next:flags.2?true phonecalls_less_data:flags.3?true photo_size_max:int video_size_max:int file_size_max:int video_upload_maxbitrate:int = AutoDownloadSettings; +autoDownloadSettings#8efab953 flags:# disabled:flags.0?true video_preload_large:flags.1?true audio_preload_next:flags.2?true phonecalls_less_data:flags.3?true photo_size_max:int video_size_max:long file_size_max:long video_upload_maxbitrate:int = AutoDownloadSettings; account.autoDownloadSettings#63cacf26 low:AutoDownloadSettings medium:AutoDownloadSettings high:AutoDownloadSettings = account.AutoDownloadSettings; @@ -1147,6 +1206,8 @@ bankCardOpenUrl#f568028a url:string name:string = BankCardOpenUrl; payments.bankCardData#3e24e573 title:string open_urls:Vector = payments.BankCardData; dialogFilter#7438f7e8 flags:# contacts:flags.0?true non_contacts:flags.1?true groups:flags.2?true broadcasts:flags.3?true bots:flags.4?true exclude_muted:flags.11?true exclude_read:flags.12?true exclude_archived:flags.13?true id:int title:string emoticon:flags.25?string pinned_peers:Vector include_peers:Vector exclude_peers:Vector = DialogFilter; +dialogFilterDefault#363293ae = DialogFilter; +dialogFilterChatlist#d64a04a8 flags:# has_my_invites:flags.26?true id:int title:string emoticon:flags.25?string pinned_peers:Vector include_peers:Vector = DialogFilter; dialogFilterSuggested#77744d4a filter:DialogFilter description:string = DialogFilterSuggested; @@ -1168,6 +1229,8 @@ help.promoDataEmpty#98f6ac75 expires:int = help.PromoData; help.promoData#8c39793f flags:# proxy:flags.0?true expires:int peer:Peer chats:Vector users:Vector psa_type:flags.1?string psa_message:flags.2?string = help.PromoData; videoSize#de33b094 flags:# type:string w:int h:int size:int video_start_ts:flags.0?double = VideoSize; +videoSizeEmojiMarkup#f85c413c emoji_id:long background_colors:Vector = VideoSize; +videoSizeStickerMarkup#da082fe stickerset:InputStickerSet sticker_id:long background_colors:Vector = VideoSize; statsGroupTopPoster#9d04af9b user_id:long messages:int avg_chars:int = StatsGroupTopPoster; @@ -1192,7 +1255,7 @@ messages.messageViews#b6c4f543 views:Vector chats:Vector use messages.discussionMessage#a6341782 flags:# messages:Vector max_id:flags.0?int read_inbox_max_id:flags.1?int read_outbox_max_id:flags.2?int unread_count:int chats:Vector users:Vector = messages.DiscussionMessage; -messageReplyHeader#a6d57763 flags:# reply_to_msg_id:int reply_to_peer_id:flags.0?Peer reply_to_top_id:flags.1?int = MessageReplyHeader; +messageReplyHeader#a6d57763 flags:# reply_to_scheduled:flags.2?true forum_topic:flags.3?true reply_to_msg_id:int reply_to_peer_id:flags.0?Peer reply_to_top_id:flags.1?int = MessageReplyHeader; messageReplies#83d60fc2 flags:# comments:flags.0?true replies:int replies_pts:int recent_repliers:flags.1?Vector channel_id:flags.0?long max_id:flags.2?int read_max_id:flags.3?int = MessageReplies; @@ -1201,7 +1264,7 @@ peerBlocked#e8fd8014 peer_id:Peer date:int = PeerBlocked; stats.messageStats#8999f295 views_graph:StatsGraph = stats.MessageStats; groupCallDiscarded#7780bcb4 id:long access_hash:long duration:int = GroupCall; -groupCall#d597650c flags:# join_muted:flags.1?true can_change_join_muted:flags.2?true join_date_asc:flags.6?true schedule_start_subscribed:flags.8?true can_start_video:flags.9?true record_video_active:flags.11?true id:long access_hash:long participants_count:int title:flags.3?string stream_dc_id:flags.4?int record_start_date:flags.5?int schedule_date:flags.7?int unmuted_video_count:flags.10?int unmuted_video_limit:int version:int = GroupCall; +groupCall#d597650c flags:# join_muted:flags.1?true can_change_join_muted:flags.2?true join_date_asc:flags.6?true schedule_start_subscribed:flags.8?true can_start_video:flags.9?true record_video_active:flags.11?true rtmp_stream:flags.12?true listeners_hidden:flags.13?true id:long access_hash:long participants_count:int title:flags.3?string stream_dc_id:flags.4?int record_start_date:flags.5?int schedule_date:flags.7?int unmuted_video_count:flags.10?int unmuted_video_limit:int version:int = GroupCall; inputGroupCall#d8aa840f id:long access_hash:long = InputGroupCall; @@ -1216,6 +1279,7 @@ inlineQueryPeerTypePM#833c0fac = InlineQueryPeerType; inlineQueryPeerTypeChat#d766c50a = InlineQueryPeerType; inlineQueryPeerTypeMegagroup#5ec4be43 = InlineQueryPeerType; inlineQueryPeerTypeBroadcast#6334ee9a = InlineQueryPeerType; +inlineQueryPeerTypeBotPM#e3b2d0c = InlineQueryPeerType; messages.historyImport#1662af0b id:long = messages.HistoryImport; @@ -1223,7 +1287,7 @@ messages.historyImportParsed#5e0fb7b9 flags:# pm:flags.0?true group:flags.1?true messages.affectedFoundMessages#ef8d3e6c pts:int pts_count:int offset:int messages:Vector = messages.AffectedFoundMessages; -chatInviteImporter#8c5adfd9 flags:# requested:flags.0?true user_id:long date:int about:flags.2?string approved_by:flags.1?long = ChatInviteImporter; +chatInviteImporter#8c5adfd9 flags:# requested:flags.0?true via_chatlist:flags.3?true user_id:long date:int about:flags.2?string approved_by:flags.1?long = ChatInviteImporter; messages.exportedChatInvites#bdc62dcc count:int invites:Vector users:Vector = messages.ExportedChatInvites; @@ -1260,9 +1324,10 @@ account.resetPasswordFailedWait#e3779861 retry_date:int = account.ResetPasswordR account.resetPasswordRequestedWait#e9effc7d until_date:int = account.ResetPasswordResult; account.resetPasswordOk#e926d63e = account.ResetPasswordResult; -sponsoredMessage#d151e19a flags:# random_id:bytes from_id:Peer channel_post:flags.2?int start_param:flags.0?string message:string entities:flags.1?Vector = SponsoredMessage; +sponsoredMessage#fc25b828 flags:# recommended:flags.5?true show_peer_photo:flags.6?true random_id:bytes from_id:flags.3?Peer chat_invite:flags.4?ChatInvite chat_invite_hash:flags.4?string channel_post:flags.2?int start_param:flags.0?string message:string entities:flags.1?Vector sponsor_info:flags.7?string additional_info:flags.8?string = SponsoredMessage; -messages.sponsoredMessages#65a4c7d5 messages:Vector chats:Vector users:Vector = messages.SponsoredMessages; +messages.sponsoredMessages#c9ee1d87 flags:# posts_between:flags.0?int messages:Vector chats:Vector users:Vector = messages.SponsoredMessages; +messages.sponsoredMessagesEmpty#1839490f = messages.SponsoredMessages; searchResultsCalendarPeriod#c9b0539f date:int min_msg_id:int max_msg_id:int count:int = SearchResultsCalendarPeriod; @@ -1272,7 +1337,7 @@ searchResultPosition#7f648b67 msg_id:int date:int offset:int = SearchResultsPosi messages.searchResultsPositions#53b22baf count:int positions:Vector = messages.SearchResultsPositions; -channels.sendAsPeers#8356cda9 peers:Vector chats:Vector users:Vector = channels.SendAsPeers; +channels.sendAsPeers#f496b0c6 peers:Vector chats:Vector users:Vector = channels.SendAsPeers; users.userFull#3b6d152e full_user:UserFull chats:Vector users:Vector = users.UserFull; @@ -1280,6 +1345,182 @@ messages.peerSettings#6880b94d settings:PeerSettings chats:Vector users:Ve auth.loggedOut#c3a2835f flags:# future_auth_token:flags.0?bytes = auth.LoggedOut; +reactionCount#a3d1cb80 flags:# chosen_order:flags.0?int reaction:Reaction count:int = ReactionCount; + +messageReactions#4f2b9479 flags:# min:flags.0?true can_see_list:flags.2?true results:Vector recent_reactions:flags.1?Vector = MessageReactions; + +messages.messageReactionsList#31bd492d flags:# count:int reactions:Vector chats:Vector users:Vector next_offset:flags.0?string = messages.MessageReactionsList; + +availableReaction#c077ec01 flags:# inactive:flags.0?true premium:flags.2?true reaction:string title:string static_icon:Document appear_animation:Document select_animation:Document activate_animation:Document effect_animation:Document around_animation:flags.1?Document center_icon:flags.1?Document = AvailableReaction; + +messages.availableReactionsNotModified#9f071957 = messages.AvailableReactions; +messages.availableReactions#768e3aad hash:int reactions:Vector = messages.AvailableReactions; + +messagePeerReaction#8c79b63c flags:# big:flags.0?true unread:flags.1?true peer_id:Peer date:int reaction:Reaction = MessagePeerReaction; + +groupCallStreamChannel#80eb48af channel:int scale:int last_timestamp_ms:long = GroupCallStreamChannel; + +phone.groupCallStreamChannels#d0e482b2 channels:Vector = phone.GroupCallStreamChannels; + +phone.groupCallStreamRtmpUrl#2dbf3432 url:string key:string = phone.GroupCallStreamRtmpUrl; + +attachMenuBotIconColor#4576f3f0 name:string color:int = AttachMenuBotIconColor; + +attachMenuBotIcon#b2a7386b flags:# name:string icon:Document colors:flags.0?Vector = AttachMenuBotIcon; + +attachMenuBot#c8aa2cd2 flags:# inactive:flags.0?true has_settings:flags.1?true request_write_access:flags.2?true bot_id:long short_name:string peer_types:Vector icons:Vector = AttachMenuBot; + +attachMenuBotsNotModified#f1d88a5c = AttachMenuBots; +attachMenuBots#3c4301c0 hash:long bots:Vector users:Vector = AttachMenuBots; + +attachMenuBotsBot#93bf667f bot:AttachMenuBot users:Vector = AttachMenuBotsBot; + +webViewResultUrl#c14557c query_id:long url:string = WebViewResult; + +simpleWebViewResultUrl#882f76bb url:string = SimpleWebViewResult; + +webViewMessageSent#c94511c flags:# msg_id:flags.0?InputBotInlineMessageID = WebViewMessageSent; + +botMenuButtonDefault#7533a588 = BotMenuButton; +botMenuButtonCommands#4258c205 = BotMenuButton; +botMenuButton#c7b57ce6 text:string url:string = BotMenuButton; + +account.savedRingtonesNotModified#fbf6e8b1 = account.SavedRingtones; +account.savedRingtones#c1e92cc5 hash:long ringtones:Vector = account.SavedRingtones; + +notificationSoundDefault#97e8bebe = NotificationSound; +notificationSoundNone#6f0c34df = NotificationSound; +notificationSoundLocal#830b9ae4 title:string data:string = NotificationSound; +notificationSoundRingtone#ff6c8049 id:long = NotificationSound; + +account.savedRingtone#b7263f6d = account.SavedRingtone; +account.savedRingtoneConverted#1f307eb7 document:Document = account.SavedRingtone; + +attachMenuPeerTypeSameBotPM#7d6be90e = AttachMenuPeerType; +attachMenuPeerTypeBotPM#c32bfa1a = AttachMenuPeerType; +attachMenuPeerTypePM#f146d31f = AttachMenuPeerType; +attachMenuPeerTypeChat#509113f = AttachMenuPeerType; +attachMenuPeerTypeBroadcast#7bfbdefc = AttachMenuPeerType; + +inputInvoiceMessage#c5b56859 peer:InputPeer msg_id:int = InputInvoice; +inputInvoiceSlug#c326caef slug:string = InputInvoice; + +payments.exportedInvoice#aed0cbd9 url:string = payments.ExportedInvoice; + +messages.transcribedAudio#93752c52 flags:# pending:flags.0?true transcription_id:long text:string = messages.TranscribedAudio; + +help.premiumPromo#5334759c status_text:string status_entities:Vector video_sections:Vector videos:Vector period_options:Vector users:Vector = help.PremiumPromo; + +inputStorePaymentPremiumSubscription#a6751e66 flags:# restore:flags.0?true upgrade:flags.1?true = InputStorePaymentPurpose; +inputStorePaymentGiftPremium#616f7fe8 user_id:InputUser currency:string amount:long = InputStorePaymentPurpose; + +premiumGiftOption#74c34319 flags:# months:int currency:string amount:long bot_url:string store_product:flags.0?string = PremiumGiftOption; + +paymentFormMethod#88f8f21b url:string title:string = PaymentFormMethod; + +emojiStatusEmpty#2de11aae = EmojiStatus; +emojiStatus#929b619d document_id:long = EmojiStatus; +emojiStatusUntil#fa30a8c7 document_id:long until:int = EmojiStatus; + +account.emojiStatusesNotModified#d08ce645 = account.EmojiStatuses; +account.emojiStatuses#90c467d1 hash:long statuses:Vector = account.EmojiStatuses; + +reactionEmpty#79f5d419 = Reaction; +reactionEmoji#1b2286b8 emoticon:string = Reaction; +reactionCustomEmoji#8935fc73 document_id:long = Reaction; + +chatReactionsNone#eafc32bc = ChatReactions; +chatReactionsAll#52928bca flags:# allow_custom:flags.0?true = ChatReactions; +chatReactionsSome#661d4037 reactions:Vector = ChatReactions; + +messages.reactionsNotModified#b06fdbdf = messages.Reactions; +messages.reactions#eafdf716 hash:long reactions:Vector = messages.Reactions; + +emailVerifyPurposeLoginSetup#4345be73 phone_number:string phone_code_hash:string = EmailVerifyPurpose; +emailVerifyPurposeLoginChange#527d22eb = EmailVerifyPurpose; +emailVerifyPurposePassport#bbf51685 = EmailVerifyPurpose; + +emailVerificationCode#922e55a9 code:string = EmailVerification; +emailVerificationGoogle#db909ec2 token:string = EmailVerification; +emailVerificationApple#96d074fd token:string = EmailVerification; + +account.emailVerified#2b96cd1b email:string = account.EmailVerified; +account.emailVerifiedLogin#e1bb0d61 email:string sent_code:auth.SentCode = account.EmailVerified; + +premiumSubscriptionOption#5f2d1df2 flags:# current:flags.1?true can_purchase_upgrade:flags.2?true transaction:flags.3?string months:int currency:string amount:long bot_url:string store_product:flags.0?string = PremiumSubscriptionOption; + +sendAsPeer#b81c7034 flags:# premium_required:flags.0?true peer:Peer = SendAsPeer; + +messageExtendedMediaPreview#ad628cc8 flags:# w:flags.0?int h:flags.0?int thumb:flags.1?PhotoSize video_duration:flags.2?int = MessageExtendedMedia; +messageExtendedMedia#ee479c64 media:MessageMedia = MessageExtendedMedia; + +stickerKeyword#fcfeb29c document_id:long keyword:Vector = StickerKeyword; + +username#b4073647 flags:# editable:flags.0?true active:flags.1?true username:string = Username; + +forumTopicDeleted#23f109b id:int = ForumTopic; +forumTopic#71701da9 flags:# my:flags.1?true closed:flags.2?true pinned:flags.3?true short:flags.5?true hidden:flags.6?true id:int date:int title:string icon_color:int icon_emoji_id:flags.0?long top_message:int read_inbox_max_id:int read_outbox_max_id:int unread_count:int unread_mentions_count:int unread_reactions_count:int from_id:Peer notify_settings:PeerNotifySettings draft:flags.4?DraftMessage = ForumTopic; + +messages.forumTopics#367617d3 flags:# order_by_create_date:flags.0?true count:int topics:Vector messages:Vector chats:Vector users:Vector pts:int = messages.ForumTopics; + +defaultHistoryTTL#43b46b20 period:int = DefaultHistoryTTL; + +exportedContactToken#41bf109b url:string expires:int = ExportedContactToken; + +requestPeerTypeUser#5f3b8a00 flags:# bot:flags.0?Bool premium:flags.1?Bool = RequestPeerType; +requestPeerTypeChat#c9f06e1b flags:# creator:flags.0?true bot_participant:flags.5?true has_username:flags.3?Bool forum:flags.4?Bool user_admin_rights:flags.1?ChatAdminRights bot_admin_rights:flags.2?ChatAdminRights = RequestPeerType; +requestPeerTypeBroadcast#339bef6c flags:# creator:flags.0?true has_username:flags.3?Bool user_admin_rights:flags.1?ChatAdminRights bot_admin_rights:flags.2?ChatAdminRights = RequestPeerType; + +emojiListNotModified#481eadfa = EmojiList; +emojiList#7a1e11d1 hash:long document_id:Vector = EmojiList; + +emojiGroup#7a9abda9 title:string icon_emoji_id:long emoticons:Vector = EmojiGroup; + +messages.emojiGroupsNotModified#6fb4ad87 = messages.EmojiGroups; +messages.emojiGroups#881fb94b hash:int groups:Vector = messages.EmojiGroups; + +textWithEntities#751f3146 text:string entities:Vector = TextWithEntities; + +messages.translateResult#33db32f8 result:Vector = messages.TranslatedText; + +autoSaveSettings#c84834ce flags:# photos:flags.0?true videos:flags.1?true video_max_size:flags.2?long = AutoSaveSettings; + +autoSaveException#81602d47 peer:Peer settings:AutoSaveSettings = AutoSaveException; + +account.autoSaveSettings#4c3e069d users_settings:AutoSaveSettings chats_settings:AutoSaveSettings broadcasts_settings:AutoSaveSettings exceptions:Vector chats:Vector users:Vector = account.AutoSaveSettings; + +help.appConfigNotModified#7cde641d = help.AppConfig; +help.appConfig#dd18782e hash:int config:JSONValue = help.AppConfig; + +inputBotAppID#a920bd7a id:long access_hash:long = InputBotApp; +inputBotAppShortName#908c0407 bot_id:InputUser short_name:string = InputBotApp; + +botAppNotModified#5da674b7 = BotApp; +botApp#95fcd1d6 flags:# id:long access_hash:long short_name:string title:string description:string photo:Photo document:flags.0?Document hash:long = BotApp; + +messages.botApp#eb50adf5 flags:# inactive:flags.0?true request_write_access:flags.1?true app:BotApp = messages.BotApp; + +appWebViewResultUrl#3c1b4f0d url:string = AppWebViewResult; + +inlineBotWebView#b57295d5 text:string url:string = InlineBotWebView; + +readParticipantDate#4a4ff172 user_id:long date:int = ReadParticipantDate; + +inputChatlistDialogFilter#f3e0da33 filter_id:int = InputChatlist; + +exportedChatlistInvite#c5181ac flags:# title:string url:string peers:Vector = ExportedChatlistInvite; + +chatlists.exportedChatlistInvite#10e6e3a6 filter:DialogFilter invite:ExportedChatlistInvite = chatlists.ExportedChatlistInvite; + +chatlists.exportedInvites#10ab6dc7 invites:Vector chats:Vector users:Vector = chatlists.ExportedInvites; + +chatlists.chatlistInviteAlready#fa87f659 filter_id:int missing_peers:Vector already_peers:Vector chats:Vector users:Vector = chatlists.ChatlistInvite; +chatlists.chatlistInvite#1dcd839d flags:# title:string emoticon:flags.0?string peers:Vector chats:Vector users:Vector = chatlists.ChatlistInvite; + +chatlists.chatlistUpdates#93bd878d missing_peers:Vector chats:Vector users:Vector = chatlists.ChatlistUpdates; + +bots.botInfo#e8a775b0 name:string about:string description:string = bots.BotInfo; + ---functions--- invokeAfterMsg#cb9f372d {X:Type} msg_id:long query:!X = X; @@ -1292,7 +1533,7 @@ invokeWithTakeout#aca9fd2e {X:Type} takeout_id:long query:!X = X; auth.sendCode#a677244f phone_number:string api_id:int api_hash:string settings:CodeSettings = auth.SentCode; auth.signUp#80eee427 phone_number:string phone_code_hash:string first_name:string last_name:string = auth.Authorization; -auth.signIn#bcd51581 phone_number:string phone_code_hash:string phone_code:string = auth.Authorization; +auth.signIn#8d52a951 flags:# phone_number:string phone_code_hash:string phone_code:flags.0?string email_verification:flags.1?EmailVerification = auth.Authorization; auth.logOut#3e72ba19 = auth.LoggedOut; auth.resetAuthorizations#9fab0d1a = Bool; auth.exportAuthorization#e5bfffcd dc_id:int = auth.ExportedAuthorization; @@ -1309,6 +1550,9 @@ auth.exportLoginToken#b7e085fe api_id:int api_hash:string except_ids:Vector = Bool; account.unregisterDevice#6a0d3206 token_type:int token:string other_uids:Vector = Bool; @@ -1323,7 +1567,7 @@ account.checkUsername#2714d86c username:string = Bool; account.updateUsername#3e0bdd7c username:string = User; account.getPrivacy#dadbc950 key:InputPrivacyKey = account.PrivacyRules; account.setPrivacy#c9f81ce8 key:InputPrivacyKey rules:Vector = account.PrivacyRules; -account.deleteAccount#418d4e0b reason:string = Bool; +account.deleteAccount#a2c0cf74 flags:# reason:string password:flags.0?InputCheckPasswordSRP = Bool; account.getAccountTTL#8fc711d = AccountDaysTTL; account.setAccountTTL#2442485e ttl:AccountDaysTTL = Bool; account.sendChangePhoneCode#82574ae5 phone_number:string settings:CodeSettings = auth.SentCode; @@ -1348,9 +1592,9 @@ account.getAuthorizationForm#a929597a bot_id:long scope:string public_key:string account.acceptAuthorization#f3ed4c73 bot_id:long scope:string public_key:string value_hashes:Vector credentials:SecureCredentialsEncrypted = Bool; account.sendVerifyPhoneCode#a5a356f9 phone_number:string settings:CodeSettings = auth.SentCode; account.verifyPhone#4dd3a7f6 phone_number:string phone_code_hash:string phone_code:string = Bool; -account.sendVerifyEmailCode#7011509f email:string = account.SentEmailCode; -account.verifyEmail#ecba39db email:string code:string = Bool; -account.initTakeoutSession#f05b4804 flags:# contacts:flags.0?true message_users:flags.1?true message_chats:flags.2?true message_megagroups:flags.3?true message_channels:flags.4?true files:flags.5?true file_max_size:flags.5?int = account.Takeout; +account.sendVerifyEmailCode#98e037bb purpose:EmailVerifyPurpose email:string = account.SentEmailCode; +account.verifyEmail#32da4cf purpose:EmailVerifyPurpose verification:EmailVerification = account.EmailVerified; +account.initTakeoutSession#8ef3eab0 flags:# contacts:flags.0?true message_users:flags.1?true message_chats:flags.2?true message_megagroups:flags.3?true message_channels:flags.4?true files:flags.5?true file_max_size:flags.5?long = account.Takeout; account.finishTakeoutSession#1d2652ee flags:# success:flags.0?true = Bool; account.confirmPasswordEmail#8fdf1920 code:string = Bool; account.resendPasswordEmail#7a7f2a15 = Bool; @@ -1359,7 +1603,7 @@ account.getContactSignUpNotification#9f07c728 = Bool; account.setContactSignUpNotification#cff43f61 silent:Bool = Bool; account.getNotifyExceptions#53577479 flags:# compare_sound:flags.1?true peer:flags.0?InputNotifyPeer = Updates; account.getWallPaper#fc8ddbea wallpaper:InputWallPaper = WallPaper; -account.uploadWallPaper#dd853661 file:InputFile mime_type:string settings:WallPaperSettings = WallPaper; +account.uploadWallPaper#e39a8f03 flags:# for_chat:flags.0?true file:InputFile mime_type:string settings:WallPaperSettings = WallPaper; account.saveWallPaper#6c5a5b37 wallpaper:InputWallPaper unsave:Bool settings:WallPaperSettings = Bool; account.installWallPaper#feed5769 wallpaper:InputWallPaper settings:WallPaperSettings = Bool; account.resetWallPapers#bb3b9804 = Bool; @@ -1370,7 +1614,7 @@ account.createTheme#652e4400 flags:# slug:string title:string document:flags.2?I account.updateTheme#2bf40ccc flags:# format:string theme:InputTheme slug:flags.0?string title:flags.1?string document:flags.2?InputDocument settings:flags.3?Vector = Theme; account.saveTheme#f257106c theme:InputTheme unsave:Bool = Bool; account.installTheme#c727bb3b flags:# dark:flags.0?true theme:flags.1?InputTheme format:flags.2?string base_theme:flags.3?BaseTheme = Bool; -account.getTheme#8d9d742b format:string theme:InputTheme document_id:long = Theme; +account.getTheme#3a5869ec format:string theme:InputTheme = Theme; account.getThemes#7206e458 format:string hash:long = account.Themes; account.setContentSettings#b574b16b flags:# sensitive_enabled:flags.0?true = Bool; account.getContentSettings#8b9b4dae = account.ContentSettings; @@ -1383,6 +1627,20 @@ account.declinePasswordReset#4c9409f6 = Bool; account.getChatThemes#d638de89 hash:long = account.Themes; account.setAuthorizationTTL#bf899aa0 authorization_ttl_days:int = Bool; account.changeAuthorizationSettings#40f48462 flags:# hash:long encrypted_requests_disabled:flags.0?Bool call_requests_disabled:flags.1?Bool = Bool; +account.getSavedRingtones#e1902288 hash:long = account.SavedRingtones; +account.saveRingtone#3dea5b03 id:InputDocument unsave:Bool = account.SavedRingtone; +account.uploadRingtone#831a83a2 file:InputFile file_name:string mime_type:string = Document; +account.updateEmojiStatus#fbd3de6b emoji_status:EmojiStatus = Bool; +account.getDefaultEmojiStatuses#d6753386 hash:long = account.EmojiStatuses; +account.getRecentEmojiStatuses#f578105 hash:long = account.EmojiStatuses; +account.clearRecentEmojiStatuses#18201aae = Bool; +account.reorderUsernames#ef500eab order:Vector = Bool; +account.toggleUsername#58d6b376 username:string active:Bool = Bool; +account.getDefaultProfilePhotoEmojis#e2750328 hash:long = EmojiList; +account.getDefaultGroupPhotoEmojis#915860ae hash:long = EmojiList; +account.getAutoSaveSettings#adcbbcda = account.AutoSaveSettings; +account.saveAutoSaveSettings#d69b8361 flags:# users:flags.0?true chats:flags.1?true broadcasts:flags.2?true peer:flags.3?InputPeer settings:AutoSaveSettings = Bool; +account.deleteAutoSaveExceptions#53bc0020 = Bool; users.getUsers#d91a548 id:Vector = Vector; users.getFullUser#b60f5918 id:InputUser = users.UserFull; @@ -1408,6 +1666,9 @@ contacts.addContact#e8f463d0 flags:# add_phone_privacy_exception:flags.0?true id contacts.acceptContact#f831a20f id:InputUser = Updates; contacts.getLocated#d348bc44 flags:# background:flags.1?true geo_point:InputGeoPoint self_expires:flags.0?int = Updates; contacts.blockFromReplies#29a8962c flags:# delete_message:flags.0?true delete_history:flags.1?true report_spam:flags.2?true msg_id:int = Updates; +contacts.resolvePhone#8af94344 phone:string = contacts.ResolvedPeer; +contacts.exportContactToken#f8654027 = ExportedContactToken; +contacts.importContactToken#13005788 token:string = User; messages.getMessages#63c66506 id:Vector = messages.Messages; messages.getDialogs#a0f4cb4f flags:# exclude_pinned:flags.0?true folder_id:flags.1?int offset_date:int offset_id:int offset_peer:InputPeer limit:int hash:long = messages.Dialogs; @@ -1418,9 +1679,9 @@ messages.deleteHistory#b08f922a flags:# just_clear:flags.0?true revoke:flags.1?t messages.deleteMessages#e58e95d2 flags:# revoke:flags.0?true id:Vector = messages.AffectedMessages; messages.receivedMessages#5a954c0 max_id:int = Vector; messages.setTyping#58943ee2 flags:# peer:InputPeer top_msg_id:flags.0?int action:SendMessageAction = Bool; -messages.sendMessage#d9d75a4 flags:# no_webpage:flags.1?true silent:flags.5?true background:flags.6?true clear_draft:flags.7?true peer:InputPeer reply_to_msg_id:flags.0?int message:string random_id:long reply_markup:flags.2?ReplyMarkup entities:flags.3?Vector schedule_date:flags.10?int send_as:flags.13?InputPeer = Updates; -messages.sendMedia#e25ff8e0 flags:# silent:flags.5?true background:flags.6?true clear_draft:flags.7?true peer:InputPeer reply_to_msg_id:flags.0?int media:InputMedia message:string random_id:long reply_markup:flags.2?ReplyMarkup entities:flags.3?Vector schedule_date:flags.10?int send_as:flags.13?InputPeer = Updates; -messages.forwardMessages#cc30290b flags:# silent:flags.5?true background:flags.6?true with_my_score:flags.8?true drop_author:flags.11?true drop_media_captions:flags.12?true from_peer:InputPeer id:Vector random_id:Vector to_peer:InputPeer schedule_date:flags.10?int send_as:flags.13?InputPeer = Updates; +messages.sendMessage#1cc20387 flags:# no_webpage:flags.1?true silent:flags.5?true background:flags.6?true clear_draft:flags.7?true noforwards:flags.14?true update_stickersets_order:flags.15?true peer:InputPeer reply_to_msg_id:flags.0?int top_msg_id:flags.9?int message:string random_id:long reply_markup:flags.2?ReplyMarkup entities:flags.3?Vector schedule_date:flags.10?int send_as:flags.13?InputPeer = Updates; +messages.sendMedia#7547c966 flags:# silent:flags.5?true background:flags.6?true clear_draft:flags.7?true noforwards:flags.14?true update_stickersets_order:flags.15?true peer:InputPeer reply_to_msg_id:flags.0?int top_msg_id:flags.9?int media:InputMedia message:string random_id:long reply_markup:flags.2?ReplyMarkup entities:flags.3?Vector schedule_date:flags.10?int send_as:flags.13?InputPeer = Updates; +messages.forwardMessages#c661bbc4 flags:# silent:flags.5?true background:flags.6?true with_my_score:flags.8?true drop_author:flags.11?true drop_media_captions:flags.12?true noforwards:flags.14?true from_peer:InputPeer id:Vector random_id:Vector to_peer:InputPeer top_msg_id:flags.9?int schedule_date:flags.10?int send_as:flags.13?InputPeer = Updates; messages.reportSpam#cf1592db peer:InputPeer = Bool; messages.getPeerSettings#efd9a6a2 peer:InputPeer = messages.PeerSettings; messages.report#8953ab4e peer:InputPeer id:Vector reason:ReportReason message:string = Bool; @@ -1430,7 +1691,7 @@ messages.editChatTitle#73783ffd chat_id:long title:string = Updates; messages.editChatPhoto#35ddd674 chat_id:long photo:InputChatPhoto = Updates; messages.addChatUser#f24753e3 chat_id:long user_id:InputUser fwd_limit:int = Updates; messages.deleteChatUser#a2185cab flags:# revoke_history:flags.0?true chat_id:long user_id:InputUser = Updates; -messages.createChat#9cb126e users:Vector title:string = Updates; +messages.createChat#34a818 flags:# users:Vector title:string ttl_period:flags.0?int = Updates; messages.getDhConfig#26cf8950 version:int random_length:int = messages.DhConfig; messages.requestEncryption#f64daf43 user_id:InputUser random_id:int g_a:bytes = EncryptedChat; messages.acceptEncryption#3dbc0415 peer:InputEncryptedChat g_b:bytes key_fingerprint:long = EncryptedChat; @@ -1457,27 +1718,27 @@ messages.getMessagesViews#5784d3e1 peer:InputPeer id:Vector increment:Bool messages.editChatAdmin#a85bd1c2 chat_id:long user_id:InputUser is_admin:Bool = Bool; messages.migrateChat#a2875319 chat_id:long = Updates; messages.searchGlobal#4bc6589a flags:# folder_id:flags.0?int q:string filter:MessagesFilter min_date:int max_date:int offset_rate:int offset_peer:InputPeer offset_id:int limit:int = messages.Messages; -messages.reorderStickerSets#78337739 flags:# masks:flags.0?true order:Vector = Bool; -messages.getDocumentByHash#338e2464 sha256:bytes size:int mime_type:string = Document; +messages.reorderStickerSets#78337739 flags:# masks:flags.0?true emojis:flags.1?true order:Vector = Bool; +messages.getDocumentByHash#b1f2061f sha256:bytes size:long mime_type:string = Document; messages.getSavedGifs#5cf09635 hash:long = messages.SavedGifs; messages.saveGif#327a30cb id:InputDocument unsave:Bool = Bool; messages.getInlineBotResults#514e999d flags:# bot:InputUser peer:InputPeer geo_point:flags.0?InputGeoPoint query:string offset:string = messages.BotResults; -messages.setInlineBotResults#eb5ea206 flags:# gallery:flags.0?true private:flags.1?true query_id:long results:Vector cache_time:int next_offset:flags.2?string switch_pm:flags.3?InlineBotSwitchPM = Bool; -messages.sendInlineBotResult#7aa11297 flags:# silent:flags.5?true background:flags.6?true clear_draft:flags.7?true hide_via:flags.11?true peer:InputPeer reply_to_msg_id:flags.0?int random_id:long query_id:long id:string schedule_date:flags.10?int send_as:flags.13?InputPeer = Updates; +messages.setInlineBotResults#bb12a419 flags:# gallery:flags.0?true private:flags.1?true query_id:long results:Vector cache_time:int next_offset:flags.2?string switch_pm:flags.3?InlineBotSwitchPM switch_webview:flags.4?InlineBotWebView = Bool; +messages.sendInlineBotResult#d3fbdccb flags:# silent:flags.5?true background:flags.6?true clear_draft:flags.7?true hide_via:flags.11?true peer:InputPeer reply_to_msg_id:flags.0?int top_msg_id:flags.9?int random_id:long query_id:long id:string schedule_date:flags.10?int send_as:flags.13?InputPeer = Updates; messages.getMessageEditData#fda68d36 peer:InputPeer id:int = messages.MessageEditData; messages.editMessage#48f71778 flags:# no_webpage:flags.1?true peer:InputPeer id:int message:flags.11?string media:flags.14?InputMedia reply_markup:flags.2?ReplyMarkup entities:flags.3?Vector schedule_date:flags.15?int = Updates; messages.editInlineBotMessage#83557dba flags:# no_webpage:flags.1?true id:InputBotInlineMessageID message:flags.11?string media:flags.14?InputMedia reply_markup:flags.2?ReplyMarkup entities:flags.3?Vector = Bool; messages.getBotCallbackAnswer#9342ca07 flags:# game:flags.1?true peer:InputPeer msg_id:int data:flags.0?bytes password:flags.2?InputCheckPasswordSRP = messages.BotCallbackAnswer; messages.setBotCallbackAnswer#d58f130a flags:# alert:flags.1?true query_id:long message:flags.0?string url:flags.2?string cache_time:int = Bool; messages.getPeerDialogs#e470bcfd peers:Vector = messages.PeerDialogs; -messages.saveDraft#bc39e14b flags:# no_webpage:flags.1?true reply_to_msg_id:flags.0?int peer:InputPeer message:string entities:flags.3?Vector = Bool; +messages.saveDraft#b4331e3f flags:# no_webpage:flags.1?true reply_to_msg_id:flags.0?int top_msg_id:flags.2?int peer:InputPeer message:string entities:flags.3?Vector = Bool; messages.getAllDrafts#6a3f8d65 = Updates; messages.getFeaturedStickers#64780b14 hash:long = messages.FeaturedStickers; messages.readFeaturedStickers#5b118126 id:Vector = Bool; messages.getRecentStickers#9da9403b flags:# attached:flags.0?true hash:long = messages.RecentStickers; messages.saveRecentSticker#392718f8 flags:# attached:flags.0?true id:InputDocument unsave:Bool = Bool; messages.clearRecentStickers#8999602d flags:# attached:flags.0?true = Bool; -messages.getArchivedStickers#57f17692 flags:# masks:flags.0?true offset_id:long limit:int = messages.ArchivedStickers; +messages.getArchivedStickers#57f17692 flags:# masks:flags.0?true emojis:flags.1?true offset_id:long limit:int = messages.ArchivedStickers; messages.getMaskStickers#640f82b8 hash:long = messages.AllStickers; messages.getAttachedStickers#cc5b67cc media:InputStickeredMedia = Vector; messages.setGameScore#8ef8ecc0 flags:# edit_message:flags.0?true force:flags.1?true peer:InputPeer id:int user_id:InputUser score:int = Updates; @@ -1496,10 +1757,10 @@ messages.uploadMedia#519bc2b1 peer:InputPeer media:InputMedia = MessageMedia; messages.sendScreenshotNotification#c97df020 peer:InputPeer reply_to_msg_id:int random_id:long = Updates; messages.getFavedStickers#4f1aaa9 hash:long = messages.FavedStickers; messages.faveSticker#b9ffc55b id:InputDocument unfave:Bool = Bool; -messages.getUnreadMentions#46578472 peer:InputPeer offset_id:int add_offset:int limit:int max_id:int min_id:int = messages.Messages; -messages.readMentions#f0189d3 peer:InputPeer = messages.AffectedHistory; +messages.getUnreadMentions#f107e790 flags:# peer:InputPeer top_msg_id:flags.0?int offset_id:int add_offset:int limit:int max_id:int min_id:int = messages.Messages; +messages.readMentions#36e5bf4d flags:# peer:InputPeer top_msg_id:flags.0?int = messages.AffectedHistory; messages.getRecentLocations#702a40e0 peer:InputPeer limit:int hash:long = messages.Messages; -messages.sendMultiMedia#f803138f flags:# silent:flags.5?true background:flags.6?true clear_draft:flags.7?true peer:InputPeer reply_to_msg_id:flags.0?int multi_media:Vector schedule_date:flags.10?int send_as:flags.13?InputPeer = Updates; +messages.sendMultiMedia#b6f11a1c flags:# silent:flags.5?true background:flags.6?true clear_draft:flags.7?true noforwards:flags.14?true update_stickersets_order:flags.15?true peer:InputPeer reply_to_msg_id:flags.0?int top_msg_id:flags.9?int multi_media:Vector schedule_date:flags.10?int send_as:flags.13?InputPeer = Updates; messages.uploadEncryptedFile#5057c497 peer:InputEncryptedChat file:InputEncryptedFile = EncryptedFile; messages.searchStickerSets#35705b8a flags:# exclude_featured:flags.0?true q:string hash:long = messages.FoundStickerSets; messages.getSplitRanges#1cff7e08 = Vector; @@ -1516,7 +1777,7 @@ messages.getEmojiKeywords#35a0e062 lang_code:string = EmojiKeywordsDifference; messages.getEmojiKeywordsDifference#1508b6af lang_code:string from_version:int = EmojiKeywordsDifference; messages.getEmojiKeywordsLanguages#4e9963b2 lang_codes:Vector = Vector; messages.getEmojiURL#d5b10c26 lang_code:string = EmojiURL; -messages.getSearchCounters#732eef00 peer:InputPeer filters:Vector = Vector; +messages.getSearchCounters#ae7cc1 flags:# peer:InputPeer top_msg_id:flags.0?int filters:Vector = Vector; messages.requestUrlAuth#198fb446 flags:# peer:flags.1?InputPeer msg_id:flags.1?int button_id:flags.1?int url:flags.2?string = UrlAuthResult; messages.acceptUrlAuth#b12c7125 flags:# write_allowed:flags.0?true peer:flags.1?InputPeer msg_id:flags.1?int button_id:flags.1?int url:flags.2?string = UrlAuthResult; messages.hidePeerSettingsBar#4facb138 peer:InputPeer = Bool; @@ -1534,7 +1795,7 @@ messages.getOldFeaturedStickers#7ed094a1 offset:int limit:int hash:long = messag messages.getReplies#22ddd30c peer:InputPeer msg_id:int offset_id:int offset_date:int add_offset:int limit:int max_id:int min_id:int hash:long = messages.Messages; messages.getDiscussionMessage#446972fd peer:InputPeer msg_id:int = messages.DiscussionMessage; messages.readDiscussion#f731a9f4 peer:InputPeer msg_id:int read_max_id:int = Bool; -messages.unpinAllMessages#f025bc8b peer:InputPeer = messages.AffectedHistory; +messages.unpinAllMessages#ee22b9a8 flags:# peer:InputPeer top_msg_id:flags.0?int = messages.AffectedHistory; messages.deleteChat#5bd0ee50 chat_id:long = Bool; messages.deletePhoneCallHistory#f9cbe409 flags:# revoke:flags.0?true = messages.AffectedFoundMessages; messages.checkHistoryImport#43fe19f3 import_head:string = messages.HistoryImportParsed; @@ -1551,31 +1812,71 @@ messages.getChatInviteImporters#df04dd4e flags:# requested:flags.0?true peer:Inp messages.setHistoryTTL#b80e5fe4 peer:InputPeer period:int = Updates; messages.checkHistoryImportPeer#5dc60f03 peer:InputPeer = messages.CheckedHistoryImportPeer; messages.setChatTheme#e63be13f peer:InputPeer emoticon:string = Updates; -messages.getMessageReadParticipants#2c6f97b7 peer:InputPeer msg_id:int = Vector; +messages.getMessageReadParticipants#31c1c44f peer:InputPeer msg_id:int = Vector; messages.getSearchResultsCalendar#49f0bde9 peer:InputPeer filter:MessagesFilter offset_id:int offset_date:int = messages.SearchResultsCalendar; messages.getSearchResultsPositions#6e9583a3 peer:InputPeer filter:MessagesFilter offset_id:int limit:int = messages.SearchResultsPositions; messages.hideChatJoinRequest#7fe7e815 flags:# approved:flags.0?true peer:InputPeer user_id:InputUser = Updates; messages.hideAllChatJoinRequests#e085f4ea flags:# approved:flags.0?true peer:InputPeer link:flags.1?string = Updates; messages.toggleNoForwards#b11eafa2 peer:InputPeer enabled:Bool = Updates; messages.saveDefaultSendAs#ccfddf96 peer:InputPeer send_as:InputPeer = Bool; +messages.sendReaction#d30d78d4 flags:# big:flags.1?true add_to_recent:flags.2?true peer:InputPeer msg_id:int reaction:flags.0?Vector = Updates; +messages.getMessagesReactions#8bba90e6 peer:InputPeer id:Vector = Updates; +messages.getMessageReactionsList#461b3f48 flags:# peer:InputPeer id:int reaction:flags.0?Reaction offset:flags.1?string limit:int = messages.MessageReactionsList; +messages.setChatAvailableReactions#feb16771 peer:InputPeer available_reactions:ChatReactions = Updates; +messages.getAvailableReactions#18dea0ac hash:int = messages.AvailableReactions; +messages.setDefaultReaction#4f47a016 reaction:Reaction = Bool; +messages.translateText#63183030 flags:# peer:flags.0?InputPeer id:flags.0?Vector text:flags.1?Vector to_lang:string = messages.TranslatedText; +messages.getUnreadReactions#3223495b flags:# peer:InputPeer top_msg_id:flags.0?int offset_id:int add_offset:int limit:int max_id:int min_id:int = messages.Messages; +messages.readReactions#54aa7f8e flags:# peer:InputPeer top_msg_id:flags.0?int = messages.AffectedHistory; +messages.searchSentMedia#107e31a0 q:string filter:MessagesFilter limit:int = messages.Messages; +messages.getAttachMenuBots#16fcc2cb hash:long = AttachMenuBots; +messages.getAttachMenuBot#77216192 bot:InputUser = AttachMenuBotsBot; +messages.toggleBotInAttachMenu#69f59d69 flags:# write_allowed:flags.0?true bot:InputUser enabled:Bool = Bool; +messages.requestWebView#178b480b flags:# from_bot_menu:flags.4?true silent:flags.5?true peer:InputPeer bot:InputUser url:flags.1?string start_param:flags.3?string theme_params:flags.2?DataJSON platform:string reply_to_msg_id:flags.0?int top_msg_id:flags.9?int send_as:flags.13?InputPeer = WebViewResult; +messages.prolongWebView#7ff34309 flags:# silent:flags.5?true peer:InputPeer bot:InputUser query_id:long reply_to_msg_id:flags.0?int top_msg_id:flags.9?int send_as:flags.13?InputPeer = Bool; +messages.requestSimpleWebView#299bec8e flags:# from_switch_webview:flags.1?true bot:InputUser url:string theme_params:flags.0?DataJSON platform:string = SimpleWebViewResult; +messages.sendWebViewResultMessage#a4314f5 bot_query_id:string result:InputBotInlineResult = WebViewMessageSent; +messages.sendWebViewData#dc0242c8 bot:InputUser random_id:long button_text:string data:string = Updates; +messages.transcribeAudio#269e9a49 peer:InputPeer msg_id:int = messages.TranscribedAudio; +messages.rateTranscribedAudio#7f1d072f peer:InputPeer msg_id:int transcription_id:long good:Bool = Bool; +messages.getCustomEmojiDocuments#d9ab0f54 document_id:Vector = Vector; +messages.getEmojiStickers#fbfca18f hash:long = messages.AllStickers; +messages.getFeaturedEmojiStickers#ecf6736 hash:long = messages.FeaturedStickers; +messages.reportReaction#3f64c076 peer:InputPeer id:int reaction_peer:InputPeer = Bool; +messages.getTopReactions#bb8125ba limit:int hash:long = messages.Reactions; +messages.getRecentReactions#39461db2 limit:int hash:long = messages.Reactions; +messages.clearRecentReactions#9dfeefb4 = Bool; +messages.getExtendedMedia#84f80814 peer:InputPeer id:Vector = Updates; +messages.setDefaultHistoryTTL#9eb51445 period:int = Bool; +messages.getDefaultHistoryTTL#658b7188 = DefaultHistoryTTL; +messages.sendBotRequestedPeer#fe38d01b peer:InputPeer msg_id:int button_id:int requested_peer:InputPeer = Updates; +messages.getEmojiGroups#7488ce5b hash:int = messages.EmojiGroups; +messages.getEmojiStatusGroups#2ecd56cd hash:int = messages.EmojiGroups; +messages.getEmojiProfilePhotoGroups#21a548f3 hash:int = messages.EmojiGroups; +messages.searchCustomEmoji#2c11c0d7 emoticon:string hash:long = EmojiList; +messages.togglePeerTranslations#e47cb579 flags:# disabled:flags.0?true peer:InputPeer = Bool; +messages.getBotApp#34fdc5c3 app:InputBotApp hash:long = messages.BotApp; +messages.requestAppWebView#8c5a3b3c flags:# write_allowed:flags.0?true peer:InputPeer app:InputBotApp start_param:flags.1?string theme_params:flags.2?DataJSON platform:string = AppWebViewResult; +messages.setChatWallPaper#8ffacae1 flags:# peer:InputPeer wallpaper:flags.0?InputWallPaper settings:flags.2?WallPaperSettings id:flags.1?int = Updates; updates.getState#edd4882a = updates.State; updates.getDifference#25939651 flags:# pts:int pts_total_limit:flags.0?int date:int qts:int = updates.Difference; updates.getChannelDifference#3173d78 flags:# force:flags.0?true channel:InputChannel filter:ChannelMessagesFilter pts:int limit:int = updates.ChannelDifference; -photos.updateProfilePhoto#72d4742c id:InputPhoto = photos.Photo; -photos.uploadProfilePhoto#89f30f69 flags:# file:flags.0?InputFile video:flags.1?InputFile video_start_ts:flags.2?double = photos.Photo; +photos.updateProfilePhoto#9e82039 flags:# fallback:flags.0?true bot:flags.1?InputUser id:InputPhoto = photos.Photo; +photos.uploadProfilePhoto#388a3b5 flags:# fallback:flags.3?true bot:flags.5?InputUser file:flags.0?InputFile video:flags.1?InputFile video_start_ts:flags.2?double video_emoji_markup:flags.4?VideoSize = photos.Photo; photos.deletePhotos#87cf7f2f id:Vector = Vector; photos.getUserPhotos#91cd32a8 user_id:InputUser offset:int max_id:long limit:int = photos.Photos; +photos.uploadContactProfilePhoto#e14c4a71 flags:# suggest:flags.3?true save:flags.4?true user_id:InputUser file:flags.0?InputFile video:flags.1?InputFile video_start_ts:flags.2?double video_emoji_markup:flags.5?VideoSize = photos.Photo; upload.saveFilePart#b304a621 file_id:long file_part:int bytes:bytes = Bool; -upload.getFile#b15a9afc flags:# precise:flags.0?true cdn_supported:flags.1?true location:InputFileLocation offset:int limit:int = upload.File; +upload.getFile#be5335be flags:# precise:flags.0?true cdn_supported:flags.1?true location:InputFileLocation offset:long limit:int = upload.File; upload.saveBigFilePart#de7b673d file_id:long file_part:int file_total_parts:int bytes:bytes = Bool; upload.getWebFile#24e6818d location:InputWebFileLocation offset:int limit:int = upload.WebFile; -upload.getCdnFile#2000bcc3 file_token:bytes offset:int limit:int = upload.CdnFile; +upload.getCdnFile#395f69da file_token:bytes offset:long limit:int = upload.CdnFile; upload.reuploadCdnFile#9b2754a8 file_token:bytes request_token:bytes = Vector; -upload.getCdnFileHashes#4da54231 file_token:bytes offset:int = Vector; -upload.getFileHashes#c7025931 location:InputFileLocation offset:int = Vector; +upload.getCdnFileHashes#91dc3f31 file_token:bytes offset:long = Vector; +upload.getFileHashes#9156982a location:InputFileLocation offset:long = Vector; help.getConfig#c4f9186b = Config; help.getNearestDc#1fb33026 = NearestDc; @@ -1589,7 +1890,7 @@ help.getRecentMeUrls#3dc0f114 referer:string = help.RecentMeUrls; help.getTermsOfServiceUpdate#2ca51fd1 = help.TermsOfServiceUpdate; help.acceptTermsOfService#ee72f79a id:DataJSON = Bool; help.getDeepLinkInfo#3fedc75f path:string = help.DeepLinkInfo; -help.getAppConfig#98914110 = JSONValue; +help.getAppConfig#61e3f854 hash:int = help.AppConfig; help.saveAppLog#6f02f748 events:Vector = Bool; help.getPassportConfig#c661ad08 hash:int = help.PassportConfig; help.getSupportName#d360e72c = help.SupportName; @@ -1599,6 +1900,7 @@ help.getPromoData#c0977421 = help.PromoData; help.hidePromoData#1e251c95 peer:InputPeer = Bool; help.dismissSuggestion#f50dbaa1 peer:InputPeer suggestion:string = Bool; help.getCountriesList#735787a8 lang_code:string hash:int = help.CountriesList; +help.getPremiumPromo#b81b93d4 = help.PremiumPromo; channels.readHistory#cc104937 channel:InputChannel max_id:int = Bool; channels.deleteMessages#84c1fd4e channel:InputChannel id:Vector = messages.AffectedMessages; @@ -1608,7 +1910,7 @@ channels.getParticipants#77ced9d0 channel:InputChannel filter:ChannelParticipant channels.getParticipant#a0ab6cc6 channel:InputChannel participant:InputPeer = channels.ChannelParticipant; channels.getChannels#a7f6bbb id:Vector = messages.Chats; channels.getFullChannel#8736a09 channel:InputChannel = messages.ChatFull; -channels.createChannel#3d5fb10f flags:# broadcast:flags.0?true megagroup:flags.1?true for_import:flags.3?true title:string about:string geo_point:flags.2?InputGeoPoint address:flags.2?string = Updates; +channels.createChannel#91006707 flags:# broadcast:flags.0?true megagroup:flags.1?true for_import:flags.3?true forum:flags.5?true title:string about:string geo_point:flags.2?InputGeoPoint address:flags.2?string ttl_period:flags.4?int = Updates; channels.editAdmin#d33c8902 channel:InputChannel user_id:InputUser admin_rights:ChatAdminRights rank:string = Updates; channels.editTitle#566decd0 channel:InputChannel title:string = Updates; channels.editPhoto#f12e57c9 channel:InputChannel photo:InputChatPhoto = Updates; @@ -1625,7 +1927,7 @@ channels.editBanned#96e6cd81 channel:InputChannel participant:InputPeer banned_r channels.getAdminLog#33ddf480 flags:# channel:InputChannel q:string events_filter:flags.0?ChannelAdminLogEventsFilter admins:flags.1?Vector max_id:long min_id:long limit:int = channels.AdminLogResults; channels.setStickers#ea8ca4f9 channel:InputChannel stickerset:InputStickerSet = Bool; channels.readMessageContents#eab5dc38 channel:InputChannel id:Vector = Bool; -channels.deleteHistory#af369d42 channel:InputChannel max_id:int = Bool; +channels.deleteHistory#9baa9647 flags:# for_everyone:flags.0?true channel:InputChannel max_id:int = Updates; channels.togglePreHistoryHidden#eabbb94c channel:InputChannel enabled:Bool = Updates; channels.getLeftChannels#8341ecc0 offset:int = messages.Chats; channels.getGroupsForDiscussion#f5dad378 = messages.Chats; @@ -1639,28 +1941,59 @@ channels.viewSponsoredMessage#beaedb94 channel:InputChannel random_id:bytes = Bo channels.getSponsoredMessages#ec210fbf channel:InputChannel = messages.SponsoredMessages; channels.getSendAs#dc770ee peer:InputPeer = channels.SendAsPeers; channels.deleteParticipantHistory#367544db channel:InputChannel participant:InputPeer = messages.AffectedHistory; +channels.toggleJoinToSend#e4cb9580 channel:InputChannel enabled:Bool = Updates; +channels.toggleJoinRequest#4c2985b6 channel:InputChannel enabled:Bool = Updates; +channels.reorderUsernames#b45ced1d channel:InputChannel order:Vector = Bool; +channels.toggleUsername#50f24105 channel:InputChannel username:string active:Bool = Bool; +channels.deactivateAllUsernames#a245dd3 channel:InputChannel = Bool; +channels.toggleForum#a4298b29 channel:InputChannel enabled:Bool = Updates; +channels.createForumTopic#f40c0224 flags:# channel:InputChannel title:string icon_color:flags.0?int icon_emoji_id:flags.3?long random_id:long send_as:flags.2?InputPeer = Updates; +channels.getForumTopics#de560d1 flags:# channel:InputChannel q:flags.0?string offset_date:int offset_id:int offset_topic:int limit:int = messages.ForumTopics; +channels.getForumTopicsByID#b0831eb9 channel:InputChannel topics:Vector = messages.ForumTopics; +channels.editForumTopic#f4dfa185 flags:# channel:InputChannel topic_id:int title:flags.0?string icon_emoji_id:flags.1?long closed:flags.2?Bool hidden:flags.3?Bool = Updates; +channels.updatePinnedForumTopic#6c2d9026 channel:InputChannel topic_id:int pinned:Bool = Updates; +channels.deleteTopicHistory#34435f2d channel:InputChannel top_msg_id:int = messages.AffectedHistory; +channels.reorderPinnedForumTopics#2950a18f flags:# force:flags.0?true channel:InputChannel order:Vector = Updates; +channels.toggleAntiSpam#68f3e4eb channel:InputChannel enabled:Bool = Updates; +channels.reportAntiSpamFalsePositive#a850a693 channel:InputChannel msg_id:int = Bool; +channels.toggleParticipantsHidden#6a6e7854 channel:InputChannel enabled:Bool = Updates; bots.sendCustomRequest#aa2769ed custom_method:string params:DataJSON = DataJSON; bots.answerWebhookJSONQuery#e6213f4d query_id:long data:DataJSON = Bool; bots.setBotCommands#517165a scope:BotCommandScope lang_code:string commands:Vector = Bool; bots.resetBotCommands#3d8de0f9 scope:BotCommandScope lang_code:string = Bool; bots.getBotCommands#e34c0dd6 scope:BotCommandScope lang_code:string = Vector; - -payments.getPaymentForm#8a333c8d flags:# peer:InputPeer msg_id:int theme_params:flags.0?DataJSON = payments.PaymentForm; +bots.setBotMenuButton#4504d54f user_id:InputUser button:BotMenuButton = Bool; +bots.getBotMenuButton#9c60eb28 user_id:InputUser = BotMenuButton; +bots.setBotBroadcastDefaultAdminRights#788464e1 admin_rights:ChatAdminRights = Bool; +bots.setBotGroupDefaultAdminRights#925ec9ea admin_rights:ChatAdminRights = Bool; +bots.setBotInfo#10cf3123 flags:# bot:flags.2?InputUser lang_code:string name:flags.3?string about:flags.0?string description:flags.1?string = Bool; +bots.getBotInfo#dcd914fd flags:# bot:flags.0?InputUser lang_code:string = bots.BotInfo; +bots.reorderUsernames#9709b1c2 bot:InputUser order:Vector = Bool; +bots.toggleUsername#53ca973 bot:InputUser username:string active:Bool = Bool; + +payments.getPaymentForm#37148dbb flags:# invoice:InputInvoice theme_params:flags.0?DataJSON = payments.PaymentForm; payments.getPaymentReceipt#2478d1cc peer:InputPeer msg_id:int = payments.PaymentReceipt; -payments.validateRequestedInfo#db103170 flags:# save:flags.0?true peer:InputPeer msg_id:int info:PaymentRequestedInfo = payments.ValidatedRequestedInfo; -payments.sendPaymentForm#30c3bc9d flags:# form_id:long peer:InputPeer msg_id:int requested_info_id:flags.0?string shipping_option_id:flags.1?string credentials:InputPaymentCredentials tip_amount:flags.2?long = payments.PaymentResult; +payments.validateRequestedInfo#b6c8f12b flags:# save:flags.0?true invoice:InputInvoice info:PaymentRequestedInfo = payments.ValidatedRequestedInfo; +payments.sendPaymentForm#2d03522f flags:# form_id:long invoice:InputInvoice requested_info_id:flags.0?string shipping_option_id:flags.1?string credentials:InputPaymentCredentials tip_amount:flags.2?long = payments.PaymentResult; payments.getSavedInfo#227d824b = payments.SavedInfo; payments.clearSavedInfo#d83d70c1 flags:# credentials:flags.0?true info:flags.1?true = Bool; payments.getBankCardData#2e79d779 number:string = payments.BankCardData; +payments.exportInvoice#f91b065 invoice_media:InputMedia = payments.ExportedInvoice; +payments.assignAppStoreTransaction#80ed747d receipt:bytes purpose:InputStorePaymentPurpose = Updates; +payments.assignPlayMarketTransaction#dffd50d3 receipt:DataJSON purpose:InputStorePaymentPurpose = Updates; +payments.canPurchasePremium#9fc19eb6 purpose:InputStorePaymentPurpose = Bool; -stickers.createStickerSet#9021ab67 flags:# masks:flags.0?true animated:flags.1?true user_id:InputUser title:string short_name:string thumb:flags.2?InputDocument stickers:Vector software:flags.3?string = messages.StickerSet; +stickers.createStickerSet#9021ab67 flags:# masks:flags.0?true animated:flags.1?true videos:flags.4?true emojis:flags.5?true text_color:flags.6?true user_id:InputUser title:string short_name:string thumb:flags.2?InputDocument stickers:Vector software:flags.3?string = messages.StickerSet; stickers.removeStickerFromSet#f7760f51 sticker:InputDocument = messages.StickerSet; stickers.changeStickerPosition#ffb6d4ca sticker:InputDocument position:int = messages.StickerSet; stickers.addStickerToSet#8653febe stickerset:InputStickerSet sticker:InputStickerSetItem = messages.StickerSet; -stickers.setStickerSetThumb#9a364e30 stickerset:InputStickerSet thumb:InputDocument = messages.StickerSet; +stickers.setStickerSetThumb#a76a5392 flags:# stickerset:InputStickerSet thumb:flags.0?InputDocument thumb_document_id:flags.1?long = messages.StickerSet; stickers.checkShortName#284b3639 short_name:string = Bool; stickers.suggestShortName#4dafc503 title:string = stickers.SuggestedShortName; +stickers.changeSticker#f5537ebc flags:# sticker:InputDocument emoji:flags.0?string mask_coords:flags.1?MaskCoords keywords:flags.2?string = messages.StickerSet; +stickers.renameStickerSet#124b1c00 stickerset:InputStickerSet title:string = messages.StickerSet; +stickers.deleteStickerSet#87704394 stickerset:InputStickerSet = Bool; phone.getCallConfig#55451fa9 = DataJSON; phone.requestCall#42ff96ed flags:# video:flags.0?true user_id:InputUser random_id:int g_a_hash:bytes protocol:PhoneCallProtocol = phone.PhoneCall; @@ -1671,7 +2004,7 @@ phone.discardCall#b2cbc1c0 flags:# video:flags.0?true peer:InputPhoneCall durati phone.setCallRating#59ead627 flags:# user_initiative:flags.0?true peer:InputPhoneCall rating:int comment:string = Updates; phone.saveCallDebug#277add7e peer:InputPhoneCall debug:DataJSON = Bool; phone.sendSignalingData#ff7a9383 peer:InputPhoneCall data:bytes = Bool; -phone.createGroupCall#48cdc6d8 flags:# peer:InputPeer random_id:int title:flags.0?string schedule_date:flags.1?int = Updates; +phone.createGroupCall#48cdc6d8 flags:# rtmp_stream:flags.2?true peer:InputPeer random_id:int title:flags.0?string schedule_date:flags.1?int = Updates; phone.joinGroupCall#b132ff7b flags:# muted:flags.0?true video_stopped:flags.2?true call:InputGroupCall join_as:InputPeer invite_hash:flags.1?string params:DataJSON = Updates; phone.leaveGroupCall#500377f9 call:InputGroupCall source:int = Updates; phone.inviteToGroupCall#7b393160 call:InputGroupCall users:Vector = Updates; @@ -1690,6 +2023,9 @@ phone.startScheduledGroupCall#5680e342 call:InputGroupCall = Updates; phone.saveDefaultGroupCallJoinAs#575e1f8c peer:InputPeer join_as:InputPeer = Bool; phone.joinGroupCallPresentation#cbea6bc4 call:InputGroupCall params:DataJSON = Updates; phone.leaveGroupCallPresentation#1c50d144 call:InputGroupCall = Updates; +phone.getGroupCallStreamChannels#1ab21940 call:InputGroupCall = phone.GroupCallStreamChannels; +phone.getGroupCallStreamRtmpUrl#deb3abbf peer:InputPeer revoke:Bool = phone.GroupCallStreamRtmpUrl; +phone.saveCallLog#41248786 peer:InputPhoneCall file:InputFile = Bool; langpack.getLangPack#f2f2330a lang_pack:string lang_code:string = LangPackDifference; langpack.getStrings#efea3803 lang_pack:string lang_code:string keys:Vector = Vector; @@ -1698,7 +2034,6 @@ langpack.getLanguages#42c6978f lang_pack:string = Vector; langpack.getLanguage#6a596502 lang_pack:string lang_code:string = LangPackLanguage; folders.editPeerFolders#6847d0ab folder_peers:Vector = Updates; -folders.deleteFolder#1c295881 folder_id:int = Updates; stats.getBroadcastStats#ab42441a flags:# dark:flags.0?true channel:InputChannel = stats.BroadcastStats; stats.loadAsyncGraph#621d5fa0 flags:# token:string x:flags.0?long = StatsGraph; @@ -1706,4 +2041,16 @@ stats.getMegagroupStats#dcdf8607 flags:# dark:flags.0?true channel:InputChannel stats.getMessagePublicForwards#5630281b channel:InputChannel msg_id:int offset_rate:int offset_peer:InputPeer offset_id:int limit:int = messages.Messages; stats.getMessageStats#b6e0a3f5 flags:# dark:flags.0?true channel:InputChannel msg_id:int = stats.MessageStats; -// LAYER 135 \ No newline at end of file +chatlists.exportChatlistInvite#8472478e chatlist:InputChatlist title:string peers:Vector = chatlists.ExportedChatlistInvite; +chatlists.deleteExportedInvite#719c5c5e chatlist:InputChatlist slug:string = Bool; +chatlists.editExportedInvite#653db63d flags:# chatlist:InputChatlist slug:string title:flags.1?string peers:flags.2?Vector = ExportedChatlistInvite; +chatlists.getExportedInvites#ce03da83 chatlist:InputChatlist = chatlists.ExportedInvites; +chatlists.checkChatlistInvite#41c10fff slug:string = chatlists.ChatlistInvite; +chatlists.joinChatlistInvite#a6b1e39a slug:string peers:Vector = Updates; +chatlists.getChatlistUpdates#89419521 chatlist:InputChatlist = chatlists.ChatlistUpdates; +chatlists.joinChatlistUpdates#e089f8f5 chatlist:InputChatlist peers:Vector = Updates; +chatlists.hideChatlistUpdates#66e486fb chatlist:InputChatlist = Bool; +chatlists.getLeaveChatlistSuggestions#fdbcd714 chatlist:InputChatlist = Vector; +chatlists.leaveChatlist#74fae13a chatlist:InputChatlist peers:Vector = Updates; + +// LAYER 158 diff --git a/compiler/api/template/combinator.txt b/compiler/api/template/combinator.txt index e0275dd104..fa7a76976b 100644 --- a/compiler/api/template/combinator.txt +++ b/compiler/api/template/combinator.txt @@ -5,7 +5,7 @@ from io import BytesIO from pyrogram.raw.core.primitives import Int, Long, Int128, Int256, Bool, Bytes, String, Double, Vector from pyrogram.raw.core import TLObject from pyrogram import raw -from typing import List, Union, Any +from typing import List, Optional, Any {warning} @@ -27,7 +27,7 @@ class {name}(TLObject): # type: ignore {read_types} return {name}({return_arguments}) - def write(self) -> bytes: + def write(self, *args) -> bytes: b = BytesIO() b.write(Int(self.ID, False)) diff --git a/compiler/docs/__init__.py b/compiler/docs/__init__.py index 4ad4f32b47..46887cb7a5 100644 --- a/compiler/docs/__init__.py +++ b/compiler/docs/__init__.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2021 Dan +# Copyright (C) 2017-present Dan # # This file is part of Pyrogram. # diff --git a/compiler/docs/compiler.py b/compiler/docs/compiler.py index be726c44eb..12a4d97221 100644 --- a/compiler/docs/compiler.py +++ b/compiler/docs/compiler.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2021 Dan +# Copyright (C) 2017-present Dan # # This file is part of Pyrogram. # @@ -65,13 +65,19 @@ def build(path, level=0): if level: full_path = base + "/" + full_path + namespace = path.split("/")[-1] + if namespace in ["base", "types", "functions"]: + namespace = "" + + full_name = f"{(namespace + '.') if namespace else ''}{name}" + os.makedirs(os.path.dirname(DESTINATION + "/" + full_path), exist_ok=True) with open(DESTINATION + "/" + full_path, "w", encoding="utf-8") as f: f.write( page_template.format( - title=name, - title_markup="=" * len(name), + title=full_name, + title_markup="=" * len(full_name), full_class_path="pyrogram.raw.{}".format( ".".join(full_path.split("/")[:-1]) + "." + name ) @@ -90,7 +96,7 @@ def build(path, level=0): entities = [] for i in v: - entities.append(snek(i).replace("_", "-")) + entities.append(f'{i} <{snek(i).replace("_", "-")}>') if k != base: inner_path = base + "/" + k + "/index" + ".rst" @@ -158,6 +164,7 @@ def get_title_list(s: str) -> list: send_venue send_contact send_cached_media + send_reaction edit_message_text edit_message_caption edit_message_media @@ -170,24 +177,30 @@ def get_title_list(s: str) -> list: delete_messages get_messages get_media_group - get_history - get_history_count - read_history - iter_history + get_chat_history + get_chat_history_count + read_chat_history send_poll vote_poll stop_poll retract_vote send_dice search_messages + search_messages_count search_global + search_global_count download_media + stream_media + get_discussion_message + get_discussion_replies + get_discussion_replies_count + get_custom_emoji_stickers """, chats=""" Chats join_chat leave_chat - kick_chat_member + ban_chat_member unban_chat_member restrict_chat_member promote_chat_member @@ -204,11 +217,9 @@ def get_title_list(s: str) -> list: get_chat_member get_chat_members get_chat_members_count - iter_chat_members get_dialogs - iter_dialogs get_dialogs_count - update_chat_username + set_chat_username get_nearby_chats archive_chats unarchive_chats @@ -223,21 +234,25 @@ def get_title_list(s: str) -> list: mark_chat_unread get_chat_event_log get_chat_online_count + get_send_as_chats + set_send_as_chat + set_chat_protected_content """, users=""" Users get_me get_users - get_profile_photos - get_profile_photos_count - iter_profile_photos + get_chat_photos + get_chat_photos_count set_profile_photo delete_profile_photos - update_username + set_username update_profile block_user unblock_user get_common_chats + get_default_emoji_statuses + set_emoji_status """, invite_links=""" Invite Links @@ -247,12 +262,17 @@ def get_title_list(s: str) -> list: edit_chat_invite_link revoke_chat_invite_link delete_chat_invite_link - get_chat_invite_link_members - get_chat_invite_link_members_count + get_chat_invite_link_joiners + get_chat_invite_link_joiners_count get_chat_admin_invite_links get_chat_admin_invite_links_count get_chat_admins_with_invite_links + get_chat_join_requests delete_chat_admin_invite_links + approve_chat_join_request + approve_all_chat_join_requests + decline_chat_join_request + decline_all_chat_join_requests """, contacts=""" Contacts @@ -278,6 +298,14 @@ def get_title_list(s: str) -> list: send_game set_game_score get_game_high_scores + set_bot_commands + get_bot_commands + delete_bot_commands + set_bot_default_privileges + get_bot_default_privileges + set_chat_menu_button + get_chat_menu_button + answer_web_app_query """, authorization=""" Authorization @@ -299,7 +327,7 @@ def get_title_list(s: str) -> list: """, advanced=""" Advanced - send + invoke resolve_peer save_file """ @@ -327,7 +355,7 @@ def get_title_list(s: str) -> list: f2.write(title + "\n" + "=" * len(title) + "\n\n") f2.write(".. automethod:: pyrogram.Client.{}()".format(method)) - functions = ["idle"] + functions = ["idle", "compose"] for func in functions: with open(root + "/{}.rst".format(func), "w") as f2: @@ -349,13 +377,17 @@ def get_title_list(s: str) -> list: ChatPhoto ChatMember ChatPermissions + ChatPrivileges ChatInviteLink ChatAdminWithInviteLinks ChatEvent ChatEventFilter ChatMemberUpdated + ChatJoinRequest + ChatJoiner Dialog Restriction + EmojiStatus """, messages_media=""" Messages & Media @@ -378,13 +410,17 @@ def get_title_list(s: str) -> list: Poll PollOption Dice - VoiceChatScheduled - VoiceChatStarted - VoiceChatEnded - VoiceChatMembersInvited + Reaction + VideoChatScheduled + VideoChatStarted + VideoChatEnded + VideoChatMembersInvited + WebAppData + MessageReactions + ChatReactions """, - bots_keyboard=""" - Bots & Keyboards + bot_keyboards=""" + Bot keyboards ReplyKeyboardMarkup KeyboardButton ReplyKeyboardRemove @@ -395,7 +431,24 @@ def get_title_list(s: str) -> list: CallbackQuery GameHighScore CallbackGame + WebAppInfo + MenuButton + MenuButtonCommands + MenuButtonWebApp + MenuButtonDefault + SentWebAppMessage + """, + bot_commands=""" + Bot commands BotCommand + BotCommandScope + BotCommandScopeDefault + BotCommandScopeAllPrivateChats + BotCommandScopeAllGroupChats + BotCommandScopeAllChatAdministrators + BotCommandScopeChat + BotCommandScopeChatAdministrators + BotCommandScopeChatMember """, input_media=""" Input Media @@ -411,9 +464,23 @@ def get_title_list(s: str) -> list: Inline Mode InlineQuery InlineQueryResult + InlineQueryResultCachedAudio + InlineQueryResultCachedDocument + InlineQueryResultCachedAnimation + InlineQueryResultCachedPhoto + InlineQueryResultCachedSticker + InlineQueryResultCachedVideo + InlineQueryResultCachedVoice InlineQueryResultArticle - InlineQueryResultPhoto + InlineQueryResultAudio + InlineQueryResultContact + InlineQueryResultDocument InlineQueryResultAnimation + InlineQueryResultLocation + InlineQueryResultPhoto + InlineQueryResultVenue + InlineQueryResultVideo + InlineQueryResultVoice ChosenInlineResult """, input_message_content=""" @@ -491,6 +558,7 @@ def get_title_list(s: str) -> list: Message.reply_video_note Message.reply_voice Message.get_media_group + Message.react """, chat=""" Chat @@ -499,17 +567,18 @@ def get_title_list(s: str) -> list: Chat.set_title Chat.set_description Chat.set_photo - Chat.kick_member + Chat.ban_member Chat.unban_member Chat.restrict_member Chat.promote_member Chat.get_member Chat.get_members - Chat.iter_members Chat.add_members Chat.join Chat.leave Chat.mark_unread + Chat.set_protected_content + Chat.unpin_all_messages """, user=""" User @@ -529,6 +598,11 @@ def get_title_list(s: str) -> list: inline_query=""" InlineQuery InlineQuery.answer + """, + chat_join_request=""" + ChatJoinRequest + ChatJoinRequest.approve + ChatJoinRequest.decline """ ) diff --git a/compiler/docs/template/bound-methods.rst b/compiler/docs/template/bound-methods.rst index 13a51b047e..1e16e32ce9 100644 --- a/compiler/docs/template/bound-methods.rst +++ b/compiler/docs/template/bound-methods.rst @@ -1,12 +1,11 @@ Bound Methods ============= -Some Pyrogram types define what are called bound methods. Bound methods are functions attached to a class which are -accessed via an instance of that class. They make it even easier to call specific methods by automatically inferring +Some Pyrogram types define what are called bound methods. Bound methods are functions attached to a type which are +accessed via an instance of that type. They make it even easier to call specific methods by automatically inferring some of the required arguments. .. code-block:: python - :emphasize-lines: 8 from pyrogram import Client @@ -15,15 +14,11 @@ some of the required arguments. @app.on_message() def hello(client, message) - message.reply_text("hi") + message.reply("hi") app.run() -.. contents:: Contents - :backlinks: none - :local: - ----- .. currentmodule:: pyrogram.types @@ -92,3 +87,17 @@ InlineQuery :hidden: {inline_query_toctree} + +ChatJoinRequest +--------------- + +.. hlist:: + :columns: 2 + + {chat_join_request_hlist} + +.. toctree:: + :hidden: + + {chat_join_request_toctree} + diff --git a/compiler/docs/template/methods.rst b/compiler/docs/template/methods.rst index dc2950dca7..f76e249def 100644 --- a/compiler/docs/template/methods.rst +++ b/compiler/docs/template/methods.rst @@ -2,21 +2,17 @@ Available Methods ================= This page is about Pyrogram methods. All the methods listed here are bound to a :class:`~pyrogram.Client` instance, -except for :meth:`~pyrogram.idle()`, which is a special function that can be found in the main package directly. +except for :meth:`~pyrogram.idle()` and :meth:`~pyrogram.compose()`, which are special functions that can be found in +the main package directly. .. code-block:: python - :emphasize-lines: 6 from pyrogram import Client app = Client("my_account") with app: - app.send_message("haskell", "hi") - -.. contents:: Contents - :backlinks: none - :local: + app.send_message("me", "hi") ----- @@ -41,11 +37,13 @@ Utilities :nosignatures: idle + compose .. toctree:: :hidden: idle + compose .. currentmodule:: pyrogram.Client diff --git a/compiler/docs/template/toctree.txt b/compiler/docs/template/toctree.txt index 717276c40e..7a36e4a592 100644 --- a/compiler/docs/template/toctree.txt +++ b/compiler/docs/template/toctree.txt @@ -4,4 +4,6 @@ .. module:: {module} .. toctree:: + :titlesonly: + {entities} \ No newline at end of file diff --git a/compiler/docs/template/types.rst b/compiler/docs/template/types.rst index 8b34000623..e404ad4cdf 100644 --- a/compiler/docs/template/types.rst +++ b/compiler/docs/template/types.rst @@ -6,7 +6,6 @@ Unless required as argument to a client method, most of the types don't need to are only returned by other methods. You also don't need to import them, unless you want to type-hint your variables. .. code-block:: python - :emphasize-lines: 1 from pyrogram.types import User, Message, ... @@ -18,10 +17,6 @@ are only returned by other methods. You also don't need to import them, unless y To tell whether a field is set or not, do a simple boolean check: ``if message.photo: ...``. -.. contents:: Contents - :backlinks: none - :local: - ----- .. currentmodule:: pyrogram.types @@ -52,18 +47,31 @@ Messages & Media {messages_media} -Bots & Keyboards ----------------- +Bot keyboards +------------- + +.. autosummary:: + :nosignatures: + + {bot_keyboards} + +.. toctree:: + :hidden: + + {bot_keyboards} + +Bot commands +------------- .. autosummary:: :nosignatures: - {bots_keyboard} + {bot_commands} .. toctree:: :hidden: - {bots_keyboard} + {bot_commands} Input Media ----------- diff --git a/compiler/errors/__init__.py b/compiler/errors/__init__.py index 4ad4f32b47..46887cb7a5 100644 --- a/compiler/errors/__init__.py +++ b/compiler/errors/__init__.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2021 Dan +# Copyright (C) 2017-present Dan # # This file is part of Pyrogram. # diff --git a/compiler/errors/compiler.py b/compiler/errors/compiler.py index feda92316c..0c4ef399b6 100644 --- a/compiler/errors/compiler.py +++ b/compiler/errors/compiler.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2021 Dan +# Copyright (C) 2017-present Dan # # This file is part of Pyrogram. # @@ -133,8 +133,6 @@ def start(): with open("{}/all.py".format(DEST), "w", encoding="utf-8") as f: f.write(re.sub("{count}", str(count), content)) - print("Compiling Errors: [100%]") - if "__main__" == __name__: HOME = "." diff --git a/compiler/errors/sort.py b/compiler/errors/sort.py index 69fc0437a8..db94e35162 100644 --- a/compiler/errors/sort.py +++ b/compiler/errors/sort.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2021 Dan +# Copyright (C) 2017-present Dan # # This file is part of Pyrogram. # diff --git a/compiler/errors/source/303_SEE_OTHER.tsv b/compiler/errors/source/303_SEE_OTHER.tsv index 62c1409df2..301660cefe 100644 --- a/compiler/errors/source/303_SEE_OTHER.tsv +++ b/compiler/errors/source/303_SEE_OTHER.tsv @@ -1,6 +1,6 @@ id message -FILE_MIGRATE_X The file to be accessed is currently stored in DC{x} -NETWORK_MIGRATE_X The source IP address is associated with DC{x} (for registration) -PHONE_MIGRATE_X The phone number a user is trying to use for authorization is associated with DC{x} -STATS_MIGRATE_X The statistics of the group/channel are stored in DC{x} -USER_MIGRATE_X The user whose identity is being used to execute queries is associated with DC{x} (for registration) \ No newline at end of file +FILE_MIGRATE_X The file to be accessed is currently stored in DC{value} +NETWORK_MIGRATE_X The source IP address is associated with DC{value} (for registration) +PHONE_MIGRATE_X The phone number a user is trying to use for authorization is associated with DC{value} +STATS_MIGRATE_X The statistics of the group/channel are stored in DC{value} +USER_MIGRATE_X The user whose identity is being used to execute queries is associated with DC{value} (for registration) \ No newline at end of file diff --git a/compiler/errors/source/400_BAD_REQUEST.tsv b/compiler/errors/source/400_BAD_REQUEST.tsv index ab23069722..08ecb7a676 100644 --- a/compiler/errors/source/400_BAD_REQUEST.tsv +++ b/compiler/errors/source/400_BAD_REQUEST.tsv @@ -4,7 +4,7 @@ ACCESS_TOKEN_EXPIRED The bot token has expired ACCESS_TOKEN_INVALID The bot access token is invalid ADMINS_TOO_MUCH The chat has too many administrators ADMIN_RANK_EMOJI_NOT_ALLOWED Emoji are not allowed in custom administrator titles -ADMIN_RANK_INVALID The custom administrator title is invalid or is longer than 16 characters +ADMIN_RANK_INVALID The custom administrator title is invalid or too long ALBUM_PHOTOS_TOO_MANY Too many photos were included in the album API_ID_INVALID The api_id/api_hash combination is invalid API_ID_PUBLISHED_FLOOD You are using an API key that is limited on the server side because it was published somewhere @@ -28,6 +28,7 @@ BOT_INLINE_DISABLED The inline feature of the bot is disabled BOT_INVALID This is not a valid bot BOT_METHOD_INVALID The method can't be used by bots BOT_MISSING This method can only be run by a bot +BOT_ONESIDE_NOT_AVAIL Bots can't pin messages for one side only in private chats BOT_PAYMENTS_DISABLED This method can only be run by a bot BOT_POLLS_DISABLED Sending polls by bots has been disabled BOT_RESPONSE_TIMEOUT The bot did not answer to the callback query in time @@ -35,9 +36,10 @@ BOT_SCORE_NOT_MODIFIED The bot score was not modified BROADCAST_ID_INVALID The channel is invalid BROADCAST_PUBLIC_VOTERS_FORBIDDEN Polls with public voters cannot be sent in channels BROADCAST_REQUIRED The request can only be used with a channel -BUTTON_DATA_INVALID The button callback data contains invalid data or exceeds 64 bytes +BUTTON_DATA_INVALID The button callback data is invalid or too large BUTTON_TYPE_INVALID The type of one of the buttons you provided is invalid BUTTON_URL_INVALID The button url is invalid +BUTTON_USER_PRIVACY_RESTRICTED The privacy settings of the user specified in a keyboard button do not allow creating such button CALL_ALREADY_ACCEPTED The call is already accepted CALL_ALREADY_DECLINED The call is already declined CALL_PEER_INVALID The provided call peer object is invalid @@ -45,13 +47,15 @@ CALL_PROTOCOL_FLAGS_INVALID Call protocol flags invalid CDN_METHOD_INVALID The method can't be used on CDN DCs CHANNELS_ADMIN_PUBLIC_TOO_MUCH You are an administrator of too many public channels CHANNELS_TOO_MUCH You have joined too many channels or supergroups, leave some and try again +CHANNEL_ADD_INVALID Internal error. CHANNEL_BANNED The channel is banned CHANNEL_INVALID The channel parameter is invalid CHANNEL_PRIVATE The channel/supergroup is not accessible -CHANNEL_TOO_LARGE The channel is too large to be deleted; this error is issued when trying to delete channels with more than 1000 members (subject to change) +CHANNEL_TOO_LARGE The channel is too large CHAT_ABOUT_NOT_MODIFIED The chat about text was not modified because you tried to edit it using the same content CHAT_ABOUT_TOO_LONG The chat about text is too long CHAT_ADMIN_REQUIRED The method requires chat admin privileges +CHAT_FORWARDS_RESTRICTED The chat restricts forwarding content CHAT_ID_EMPTY The provided chat id is empty CHAT_ID_INVALID The chat id being used is invalid or not known yet. Make sure you see the chat before interacting with it CHAT_INVALID The chat is invalid @@ -61,6 +65,7 @@ CHAT_NOT_MODIFIED The chat settings (title, permissions, photo, etc..) were not CHAT_RESTRICTED The chat is restricted and cannot be used CHAT_SEND_INLINE_FORBIDDEN You cannot use inline bots to send messages in this chat CHAT_TITLE_EMPTY The chat title is empty +CHAT_TOO_BIG The chat is too big for this action CODE_EMPTY The provided code is empty CODE_HASH_INVALID The provided code hash invalid CODE_INVALID The provided code is invalid (i.e. from email) @@ -86,7 +91,7 @@ DOCUMENT_INVALID The document is invalid EMAIL_HASH_EXPIRED The email hash expired and cannot be used to verify it EMAIL_INVALID The email provided is invalid EMAIL_UNCONFIRMED Email unconfirmed -EMAIL_UNCONFIRMED_X The provided email isn't confirmed, {x} is the length of the verification code that was just sent to the email +EMAIL_UNCONFIRMED_X The provided email isn't confirmed, {value} is the length of the verification code that was just sent to the email EMAIL_VERIFY_EXPIRED The verification email has expired EMOTICON_EMPTY The emoticon parameter is empty EMOTICON_INVALID The emoticon parameter is invalid @@ -97,6 +102,7 @@ ENCRYPTION_ALREADY_DECLINED The secret chat is already declined ENCRYPTION_DECLINED The secret chat was declined ENCRYPTION_ID_INVALID The provided secret chat id is invalid ENTITIES_TOO_LONG The entity provided contains data that is too long, or you passed too many entities to this message +ENTITY_BOUNDS_INVALID The message entity bounds are invalid ENTITY_MENTION_USER_INVALID The mentioned entity is not an user ERROR_TEXT_EMPTY The provided error message is empty EXPIRE_DATE_INVALID The expiration date is invalid @@ -105,15 +111,15 @@ EXTERNAL_URL_INVALID The external media URL is invalid FIELD_NAME_EMPTY The field with the name FIELD_NAME is missing FIELD_NAME_INVALID The field with the name FIELD_NAME is invalid FILE_ID_INVALID The file id is invalid -FILE_MIGRATE_X The file is in Data Center No. {x} -FILE_PARTS_INVALID Invalid number of parts. The value is not between 1 and 4000 +FILE_MIGRATE_X The file is in Data Center No. {value} +FILE_PARTS_INVALID Invalid number of parts. FILE_PART_EMPTY The file part sent is empty -FILE_PART_INVALID The file part number is invalid. The value is not between 0 and 3999 +FILE_PART_INVALID The file part number is invalid. FILE_PART_LENGTH_INVALID The length of a file part is invalid FILE_PART_SIZE_CHANGED The part size is different from the size of one of the previous parts in the same file -FILE_PART_SIZE_INVALID 512 KB cannot be evenly divided by part_size -FILE_PART_TOO_BIG The size limit (512 KB) for the content of the file part has been exceeded -FILE_PART_X_MISSING Part {x} of the file is missing from storage +FILE_PART_SIZE_INVALID The file part size is invalid +FILE_PART_TOO_BIG The size limit for the content of the file part has been exceeded +FILE_PART_X_MISSING Part {value} of the file is missing from storage FILE_REFERENCE_EMPTY The file id contains an empty file reference, you must obtain a valid one by fetching the message from the origin context FILE_REFERENCE_EXPIRED The file id contains an expired file reference, you must obtain a valid one by fetching the message from the origin context FILE_REFERENCE_INVALID The file id contains an invalid file reference, you must obtain a valid one by fetching the message from the origin context @@ -150,6 +156,7 @@ INPUT_USER_DEACTIVATED The target user has been deleted/deactivated INVITE_HASH_EMPTY The invite hash is empty INVITE_HASH_EXPIRED The chat invite link is no longer valid INVITE_HASH_INVALID The invite link hash is invalid +INVITE_REQUEST_SENT The request to join this chat or channel has been successfully sent INVITE_REVOKED_MISSING The action required a chat invite link to be revoked first LANG_PACK_INVALID The provided language pack is invalid LASTNAME_INVALID The last name is invalid @@ -159,7 +166,7 @@ LOCATION_INVALID The file location is invalid MAX_ID_INVALID The max_id parameter is invalid MAX_QTS_INVALID The provided QTS is invalid MD5_CHECKSUM_INVALID The file's checksum did not match the md5_checksum parameter -MEDIA_CAPTION_TOO_LONG The media caption is longer than 1024 characters +MEDIA_CAPTION_TOO_LONG The media caption is too long MEDIA_EMPTY The media you tried to send is invalid MEDIA_INVALID The media is invalid MEDIA_NEW_INVALID The new media to edit the message with is invalid @@ -173,7 +180,7 @@ MESSAGE_IDS_EMPTY The requested message doesn't exist or you provided no message MESSAGE_ID_INVALID The message id is invalid MESSAGE_NOT_MODIFIED The message was not modified because you tried to edit it using the same content MESSAGE_POLL_CLOSED You can't interact with a closed poll -MESSAGE_TOO_LONG The message text is over 4096 characters +MESSAGE_TOO_LONG The message text is too long METHOD_INVALID The API method is invalid and cannot be used MSG_ID_INVALID The message ID used in the peer was invalid MSG_WAIT_FAILED A waiting call returned an error @@ -195,7 +202,7 @@ PASSWORD_HASH_INVALID The two-step verification password is invalid PASSWORD_MISSING The account is missing the two-step verification password PASSWORD_RECOVERY_NA The password recovery e-mail is not available PASSWORD_REQUIRED The two-step verification password is required for this method -PASSWORD_TOO_FRESH_X The two-step verification password was added recently and you are required to wait {x} seconds +PASSWORD_TOO_FRESH_X The two-step verification password was added recently and you are required to wait {value} seconds PAYMENT_PROVIDER_INVALID The payment provider was not recognised or its token was invalid PEER_FLOOD The method can't be used because your account is currently limited PEER_ID_INVALID The peer id being used is invalid or not known yet. Make sure you meet the peer before interacting with it @@ -222,7 +229,7 @@ PHOTO_FILE_MISSING Profile photo file missing PHOTO_ID_INVALID The photo id is invalid PHOTO_INVALID The photo is invalid PHOTO_INVALID_DIMENSIONS The photo dimensions are invalid -PHOTO_SAVE_FILE_INVALID The photo you tried to send cannot be saved by Telegram. A reason may be that it exceeds 10 MB. Try resizing it locally +PHOTO_SAVE_FILE_INVALID The photo you tried to send cannot be saved by Telegram PHOTO_THUMB_URL_EMPTY The photo thumb URL is empty PHOTO_THUMB_URL_INVALID The photo thumb URL is invalid PINNED_DIALOGS_TOO_MUCH Too many pinned dialogs @@ -248,7 +255,7 @@ RANDOM_ID_INVALID The provided random ID is invalid RANDOM_LENGTH_INVALID The random length is invalid RANGES_INVALID Invalid range provided REACTION_EMPTY The reaction provided is empty -REACTION_INVALID Invalid reaction provided (only emoji are allowed) +REACTION_INVALID Invalid reaction provided (only valid emoji are allowed) REFLECTOR_NOT_AVAILABLE The call reflector is not available REPLY_MARKUP_BUY_EMPTY Reply markup for buy button empty REPLY_MARKUP_GAME_EMPTY The provided reply markup for the game is empty @@ -265,9 +272,9 @@ SCHEDULE_BOT_NOT_ALLOWED Bots are not allowed to schedule messages SCHEDULE_DATE_INVALID Invalid schedule date provided SCHEDULE_DATE_TOO_LATE The date you tried to schedule is too far in the future (more than one year) SCHEDULE_STATUS_PRIVATE You cannot schedule a message until the person comes online if their privacy does not show this information -SCHEDULE_TOO_MUCH You cannot schedule more than 100 messages in this chat +SCHEDULE_TOO_MUCH You tried to schedule too many messages in this chat SEARCH_QUERY_EMPTY The search query is empty -SECONDS_INVALID The seconds interval is invalid, for slow mode try with 0 (off), 10, 30, 60 (1m), 300 (5m), 900 (15m) or 3600 (1h) +SECONDS_INVALID The seconds interval is invalid SEND_MESSAGE_MEDIA_INVALID The message media is invalid SEND_MESSAGE_TYPE_INVALID The message type is invalid SESSION_TOO_FRESH_X You can't do this action because the current session was logged-in recently @@ -282,7 +289,9 @@ START_PARAM_EMPTY The start parameter is empty START_PARAM_INVALID The start parameter is invalid START_PARAM_TOO_LONG The start parameter is too long STICKERSET_INVALID The requested sticker set is invalid +STICKERSET_NOT_MODIFIED The sticker set is not modified STICKERS_EMPTY The sticker provided is empty +STICKERS_TOO_MUCH Too many stickers in the set STICKER_DOCUMENT_INVALID The sticker document is invalid STICKER_EMOJI_INVALID The sticker emoji is invalid STICKER_FILE_INVALID The sticker file is invalid @@ -292,6 +301,7 @@ STICKER_PNG_DIMENSIONS The sticker png dimensions are invalid STICKER_PNG_NOPNG Stickers must be png files but the provided image was not a png STICKER_TGS_NOTGS A tgs sticker file was expected, but something else was provided STICKER_THUMB_PNG_NOPNG A png sticker thumbnail file was expected, but something else was provided +STICKER_VIDEO_NOWEBM A webm video file was expected, but something else was provided TAKEOUT_INVALID The takeout id is invalid TAKEOUT_REQUIRED The method must be invoked inside a takeout session TEMP_AUTH_KEY_EMPTY The temporary auth key provided is empty @@ -334,6 +344,7 @@ USER_NOT_MUTUAL_CONTACT The user is not a mutual contact USER_NOT_PARTICIPANT The user is not a member of this chat VIDEO_CONTENT_TYPE_INVALID The video content type is invalid (i.e.: not streamable) VIDEO_FILE_INVALID The video file is invalid +VOICE_MESSAGES_FORBIDDEN Voice messages are restricted VOLUME_LOC_NOT_FOUND The volume location can't be found WALLPAPER_FILE_INVALID The provided file cannot be used as a wallpaper WALLPAPER_INVALID The input wallpaper was not valid diff --git a/compiler/errors/source/401_UNAUTHORIZED.tsv b/compiler/errors/source/401_UNAUTHORIZED.tsv index 1bb07b89ce..32e0265f53 100644 --- a/compiler/errors/source/401_UNAUTHORIZED.tsv +++ b/compiler/errors/source/401_UNAUTHORIZED.tsv @@ -2,7 +2,7 @@ id message ACTIVE_USER_REQUIRED The method is only available to already activated users AUTH_KEY_INVALID The key is invalid AUTH_KEY_PERM_EMPTY The method is unavailable for temporary authorization key, not bound to permanent -AUTH_KEY_UNREGISTERED The key is not registered in the system +AUTH_KEY_UNREGISTERED The key is not registered in the system. Delete your session file and login again SESSION_EXPIRED The authorization has expired SESSION_PASSWORD_NEEDED The two-step verification is enabled and a password is required SESSION_REVOKED The authorization has been invalidated, because of the user terminating all sessions diff --git a/compiler/errors/source/403_FORBIDDEN.tsv b/compiler/errors/source/403_FORBIDDEN.tsv index ff6955f1f1..027f2e852e 100644 --- a/compiler/errors/source/403_FORBIDDEN.tsv +++ b/compiler/errors/source/403_FORBIDDEN.tsv @@ -15,6 +15,7 @@ INLINE_BOT_REQUIRED The action must be performed through an inline bot callback MESSAGE_AUTHOR_REQUIRED You are not the author of this message MESSAGE_DELETE_FORBIDDEN You don't have rights to delete messages in this chat, most likely because you are not the author of them POLL_VOTE_REQUIRED Cast a vote in the poll before calling this method +PREMIUM_ACCOUNT_REQUIRED This action requires a premium account RIGHT_FORBIDDEN You don't have enough rights for this action, or you tried to set one or more admin rights that can't be applied to this kind of chat (channel or supergroup) SENSITIVE_CHANGE_FORBIDDEN Your sensitive content settings can't be changed at this time TAKEOUT_REQUIRED The method must be invoked inside a takeout session diff --git a/compiler/errors/source/406_NOT_ACCEPTABLE.tsv b/compiler/errors/source/406_NOT_ACCEPTABLE.tsv index c1c5b7efb5..c2df2384dd 100644 --- a/compiler/errors/source/406_NOT_ACCEPTABLE.tsv +++ b/compiler/errors/source/406_NOT_ACCEPTABLE.tsv @@ -1,5 +1,6 @@ id message AUTH_KEY_DUPLICATED The same authorization key (session file) was used in more than one place simultaneously. You must delete your session file and log in again with your phone number or bot token +CHANNEL_PRIVATE The channel/supergroup is not accessible FILEREF_UPGRADE_NEEDED The file reference has expired and you must use a refreshed one by obtaining the original media message FRESH_CHANGE_ADMINS_FORBIDDEN You were just elected admin, you can't add or modify other admins yet FRESH_CHANGE_PHONE_FORBIDDEN You can't change your phone number because your session was logged-in recently diff --git a/compiler/errors/source/420_FLOOD.tsv b/compiler/errors/source/420_FLOOD.tsv index 9afa1fa067..575cc2f5b5 100644 --- a/compiler/errors/source/420_FLOOD.tsv +++ b/compiler/errors/source/420_FLOOD.tsv @@ -1,6 +1,6 @@ id message -2FA_CONFIRM_WAIT_X A wait of {x} seconds is required because this account is active and protected by a 2FA password -FLOOD_TEST_PHONE_WAIT_X A wait of {x} seconds is required in the test servers -FLOOD_WAIT_X A wait of {x} seconds is required -SLOWMODE_WAIT_X A wait of {x} seconds is required to send messages in this chat. -TAKEOUT_INIT_DELAY_X You have to confirm the data export request using one of your mobile devices or wait {x} seconds \ No newline at end of file +2FA_CONFIRM_WAIT_X A wait of {value} seconds is required because this account is active and protected by a 2FA password +FLOOD_TEST_PHONE_WAIT_X A wait of {value} seconds is required in the test servers +FLOOD_WAIT_X A wait of {value} seconds is required +SLOWMODE_WAIT_X A wait of {value} seconds is required to send messages in this chat. +TAKEOUT_INIT_DELAY_X You have to confirm the data export request using one of your mobile devices or wait {value} seconds \ No newline at end of file diff --git a/compiler/errors/source/500_INTERNAL_SERVER_ERROR.tsv b/compiler/errors/source/500_INTERNAL_SERVER_ERROR.tsv index 9982fc662f..abfc57a39d 100644 --- a/compiler/errors/source/500_INTERNAL_SERVER_ERROR.tsv +++ b/compiler/errors/source/500_INTERNAL_SERVER_ERROR.tsv @@ -13,8 +13,8 @@ GROUPCALL_ADD_PARTICIPANTS_FAILED Failure while adding voice chat member due to GROUPED_ID_OCCUPY_FAILED Telegram is having internal problems. Please try again later HISTORY_GET_FAILED The chat history couldn't be retrieved due to Telegram having internal problems. Please try again later IMAGE_ENGINE_DOWN Image engine down due to Telegram having internal problems. Please try again later -INTERDC_X_CALL_ERROR An error occurred while Telegram was intercommunicating with DC{x}. Please try again later -INTERDC_X_CALL_RICH_ERROR A rich error occurred while Telegram was intercommunicating with DC{x}. Please try again later +INTERDC_X_CALL_ERROR An error occurred while Telegram was intercommunicating with DC{value}. Please try again later +INTERDC_X_CALL_RICH_ERROR A rich error occurred while Telegram was intercommunicating with DC{value}. Please try again later MEMBER_FETCH_FAILED Telegram is having internal problems. Please try again later MEMBER_NO_LOCATION Couldn't find the member's location due to Telegram having internal problems. Please try again later MEMBER_OCCUPY_PRIMARY_LOC_FAILED Telegram is having internal problems. Please try again later diff --git a/compiler/errors/source/503_SERVICE_UNAVAILABLE.tsv b/compiler/errors/source/503_SERVICE_UNAVAILABLE.tsv index 72b79cb565..c28edb0aeb 100644 --- a/compiler/errors/source/503_SERVICE_UNAVAILABLE.tsv +++ b/compiler/errors/source/503_SERVICE_UNAVAILABLE.tsv @@ -1,2 +1,3 @@ id message +ApiCallError Telegram is having internal problems. Please try again later. Timeout Telegram is having internal problems. Please try again later. \ No newline at end of file diff --git a/dev-requirements.txt b/dev-requirements.txt index 17f8c0348c..d8968085b3 100644 --- a/dev-requirements.txt +++ b/dev-requirements.txt @@ -2,4 +2,5 @@ pytest pytest-asyncio -pytest-cov \ No newline at end of file +pytest-cov +twine \ No newline at end of file diff --git a/docs/Makefile b/docs/Makefile deleted file mode 100644 index ceb7494c14..0000000000 --- a/docs/Makefile +++ /dev/null @@ -1,23 +0,0 @@ -# Minimal makefile for Sphinx documentation -# - -# You can set these variables from the command line. -SPHINXOPTS = -j $(shell nproc --all) -SPHINXBUILD = sphinx-build -SPHINXPROJ = Pyrogram -SOURCEDIR = source -BUILDDIR = build - -# Put it first so that "make" without argument is like "make help". -help: - @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) - -.PHONY: help Makefile - -# Catch-all target: route all unknown targets to Sphinx using the new -# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). -%: Makefile - @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) - -lhtml: # live html - sphinx-autobuild --host $(shell ifconfig | grep "inet " | grep -v 127.0.0.1 | cut -d\ -f2) -b html "$(SOURCEDIR)" "$(BUILDDIR)/html" $(SPHINXOPTS) diff --git a/docs/README.md b/docs/README.md deleted file mode 100644 index 680e2cf488..0000000000 --- a/docs/README.md +++ /dev/null @@ -1,8 +0,0 @@ -# Pyrogram Docs - -- Install requirements. -- Install `pandoc` and `latexmk`. -- HTML: `make html` -- PDF: `make latexpdf` - -TODO: Explain better \ No newline at end of file diff --git a/docs/make.bat b/docs/make.bat deleted file mode 100644 index fea543e258..0000000000 --- a/docs/make.bat +++ /dev/null @@ -1,36 +0,0 @@ -@ECHO OFF - -pushd %~dp0 - -REM Command file for Sphinx documentation - -if "%SPHINXBUILD%" == "" ( - set SPHINXBUILD=sphinx-build -) -set SOURCEDIR=source -set BUILDDIR=build -set SPHINXPROJ=Pyrogram - -if "%1" == "" goto help - -%SPHINXBUILD% >NUL 2>NUL -if errorlevel 9009 ( - echo. - echo.The 'sphinx-build' command was not found. Make sure you have Sphinx - echo.installed, then set the SPHINXBUILD environment variable to point - echo.to the full path of the 'sphinx-build' executable. Alternatively you - echo.may add the Sphinx directory to PATH. - echo. - echo.If you don't have Sphinx installed, grab it from - echo.http://sphinx-doc.org/ - exit /b 1 -) - -%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% -goto end - -:help -%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% - -:end -popd diff --git a/docs/requirements.txt b/docs/requirements.txt deleted file mode 100644 index 2cfd56798c..0000000000 --- a/docs/requirements.txt +++ /dev/null @@ -1,6 +0,0 @@ -sphinx -sphinx_rtd_theme -sphinx_copybutton -pypandoc -requests -sphinx-autobuild \ No newline at end of file diff --git a/docs/robots.txt b/docs/robots.txt deleted file mode 100644 index 3e416f245f..0000000000 --- a/docs/robots.txt +++ /dev/null @@ -1,7 +0,0 @@ -User-agent: * - -Allow: / - -Disallow: /old* - -Sitemap: https://docs.pyrogram.org/sitemap.xml \ No newline at end of file diff --git a/docs/scripts/releases.py b/docs/scripts/releases.py deleted file mode 100644 index 4a32dd298f..0000000000 --- a/docs/scripts/releases.py +++ /dev/null @@ -1,84 +0,0 @@ -# Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2021 Dan -# -# This file is part of Pyrogram. -# -# Pyrogram is free software: you can redistribute it and/or modify -# it under the terms of the GNU Lesser General Public License as published -# by the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# Pyrogram is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public License -# along with Pyrogram. If not, see . - -import shutil -from datetime import datetime -from pathlib import Path - -import pypandoc -import requests - -URL = "https://api.github.com/repos/pyrogram/pyrogram/releases" -DEST = Path("../source/releases") -INTRO = """ -Release Notes -============= - -Release notes for Pyrogram releases will describe what's new in each version, and will also make you aware of any -backwards-incompatible changes made in that version. - -When upgrading to a new version of Pyrogram, you will need to check all the breaking changes in order to find -incompatible code in your application, but also to take advantage of new features and improvements. - -**Contents** - -""".lstrip("\n") - -shutil.rmtree(DEST, ignore_errors=True) -DEST.mkdir(parents=True) - -releases = requests.get(URL).json() - -with open(DEST / "index.rst", "w") as index: - index.write(INTRO) - - tags = [] - - for release in releases: - tag = release["tag_name"] - title = release["name"] - name = title.split(" - ")[1] - - date = datetime.strptime( - release["published_at"], - "%Y-%m-%dT%H:%M:%SZ" - ).strftime("%b %d, %Y") - - body = pypandoc.convert_text( - release["body"].replace(r"\r\n", "\n"), - "rst", - format="markdown_github", - extra_args=["--wrap=none"] - ) - - tarball_url = release["tarball_url"] - zipball_url = release["zipball_url"] - - index.write("- :doc:`{} <{}>`\n".format(title, tag)) - tags.append(tag) - - with open(DEST / "{}.rst".format(tag), "w") as page: - page.write("Pyrogram " + tag + "\n" + "=" * (len(tag) + 9) + "\n\n") - page.write("\t\tReleased on " + str(date) + "\n\n") - page.write("- :download:`Source Code (zip) <{}>`\n".format(zipball_url)) - page.write("- :download:`Source Code (tar.gz) <{}>`\n\n".format(tarball_url)) - page.write(name + "\n" + "-" * len(name) + "\n\n") - page.write(body + "\n\n") - - index.write("\n.. toctree::\n :hidden:\n\n") - index.write("\n".join(" {}".format(tag) for tag in tags)) diff --git a/docs/scripts/sitemap.py b/docs/scripts/sitemap.py deleted file mode 100644 index 9c28bd1c5c..0000000000 --- a/docs/scripts/sitemap.py +++ /dev/null @@ -1,81 +0,0 @@ -# Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2021 Dan -# -# This file is part of Pyrogram. -# -# Pyrogram is free software: you can redistribute it and/or modify -# it under the terms of the GNU Lesser General Public License as published -# by the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# Pyrogram is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public License -# along with Pyrogram. If not, see . - -import datetime -import os - -canonical = "https://docs.pyrogram.org/" - -dirs = { - ".": ("weekly", 1.0), - "intro": ("weekly", 0.9), - "start": ("weekly", 0.9), - "api": ("weekly", 0.8), - "topics": ("weekly", 0.8), - "releases": ("weekly", 0.8), - "telegram": ("weekly", 0.6) -} - - -def now(): - return datetime.datetime.today().strftime("%Y-%m-%d") - - -with open("sitemap.xml", "w") as f: - f.write('\n') - f.write('\n') - - urls = [] - - - def search(path): - try: - for j in os.listdir(path): - search(f"{path}/{j}") - except NotADirectoryError: - if not path.endswith(".rst"): - return - - path = path.split("/")[2:] - - if path[0].endswith(".rst"): - folder = "." - else: - folder = path[0] - - path = f"{canonical}{'/'.join(path)}"[:-len(".rst")] - - if path.endswith("index"): - path = path[:-len("index")] - - urls.append((path, now(), *dirs[folder])) - - - search("../source") - - urls.sort(key=lambda x: x[3], reverse=True) - - for i in urls: - f.write(f" \n") - f.write(f" {i[0]}\n") - f.write(f" {i[1]}\n") - f.write(f" {i[2]}\n") - f.write(f" {i[3]}\n") - f.write(f" \n") - - f.write("") diff --git a/docs/source/_images/favicon.ico b/docs/source/_images/favicon.ico deleted file mode 100644 index 4165f9edce..0000000000 Binary files a/docs/source/_images/favicon.ico and /dev/null differ diff --git a/docs/source/_images/pyrogram.png b/docs/source/_images/pyrogram.png deleted file mode 100644 index caadf9838f..0000000000 Binary files a/docs/source/_images/pyrogram.png and /dev/null differ diff --git a/docs/source/api/client.rst b/docs/source/api/client.rst deleted file mode 100644 index 4190af5b1d..0000000000 --- a/docs/source/api/client.rst +++ /dev/null @@ -1,25 +0,0 @@ -Pyrogram Client -=============== - -You have entered the API Reference section where you can find detailed information about Pyrogram's API. The main Client -class, all available methods and types, filters, handlers, decorators and bound-methods detailed descriptions can be -found starting from this page. - -This page is about the Client class, which exposes high-level methods for an easy access to the API. - -.. code-block:: python - :emphasize-lines: 1-3 - - from pyrogram import Client - - app = Client("my_account") - - with app: - app.send_message("me", "Hi!") - ------ - -Details -------- - -.. autoclass:: pyrogram.Client() diff --git a/docs/source/api/decorators.rst b/docs/source/api/decorators.rst deleted file mode 100644 index c859063a70..0000000000 --- a/docs/source/api/decorators.rst +++ /dev/null @@ -1,68 +0,0 @@ -Decorators -========== - -While still being methods bound to the :class:`~pyrogram.Client` class, decorators are of a special kind and thus -deserve a dedicated page. - -Decorators are able to register callback functions for handling updates in a much easier and cleaner way compared to -:doc:`Handlers `; they do so by instantiating the correct handler and calling -:meth:`~pyrogram.Client.add_handler` automatically. All you need to do is adding the decorators on top of your -functions. - -.. code-block:: python - :emphasize-lines: 6 - - from pyrogram import Client - - app = Client("my_account") - - - @app.on_message() - def log(client, message): - print(message) - - - app.run() - -.. contents:: Contents - :backlinks: none - :depth: 1 - :local: - ------ - -.. currentmodule:: pyrogram - -Index ------ - -.. hlist:: - :columns: 3 - - - :meth:`~Client.on_message` - - :meth:`~Client.on_callback_query` - - :meth:`~Client.on_inline_query` - - :meth:`~Client.on_chosen_inline_result` - - :meth:`~Client.on_chat_member_updated` - - :meth:`~Client.on_deleted_messages` - - :meth:`~Client.on_user_status` - - :meth:`~Client.on_poll` - - :meth:`~Client.on_disconnect` - - :meth:`~Client.on_raw_update` - ------ - -Details -------- - -.. Decorators -.. autodecorator:: pyrogram.Client.on_message() -.. autodecorator:: pyrogram.Client.on_callback_query() -.. autodecorator:: pyrogram.Client.on_inline_query() -.. autodecorator:: pyrogram.Client.on_chosen_inline_result() -.. autodecorator:: pyrogram.Client.on_chat_member_updated() -.. autodecorator:: pyrogram.Client.on_deleted_messages() -.. autodecorator:: pyrogram.Client.on_user_status() -.. autodecorator:: pyrogram.Client.on_poll() -.. autodecorator:: pyrogram.Client.on_disconnect() -.. autodecorator:: pyrogram.Client.on_raw_update() \ No newline at end of file diff --git a/docs/source/api/errors/bad-request.rst b/docs/source/api/errors/bad-request.rst deleted file mode 100644 index ab13fdabb2..0000000000 --- a/docs/source/api/errors/bad-request.rst +++ /dev/null @@ -1,7 +0,0 @@ -400 - BadRequest ----------------- - -.. csv-table:: - :file: ../../../../compiler/errors/source/400_BAD_REQUEST.tsv - :delim: tab - :header-rows: 1 diff --git a/docs/source/api/errors/flood.rst b/docs/source/api/errors/flood.rst deleted file mode 100644 index e01e012274..0000000000 --- a/docs/source/api/errors/flood.rst +++ /dev/null @@ -1,7 +0,0 @@ -420 - Flood ------------ - -.. csv-table:: - :file: ../../../../compiler/errors/source/420_FLOOD.tsv - :delim: tab - :header-rows: 1 \ No newline at end of file diff --git a/docs/source/api/errors/forbidden.rst b/docs/source/api/errors/forbidden.rst deleted file mode 100644 index c2a8dcb80b..0000000000 --- a/docs/source/api/errors/forbidden.rst +++ /dev/null @@ -1,7 +0,0 @@ -403 - Forbidden ---------------- - -.. csv-table:: - :file: ../../../../compiler/errors/source/403_FORBIDDEN.tsv - :delim: tab - :header-rows: 1 \ No newline at end of file diff --git a/docs/source/api/errors/index.rst b/docs/source/api/errors/index.rst deleted file mode 100644 index be2b80d46a..0000000000 --- a/docs/source/api/errors/index.rst +++ /dev/null @@ -1,37 +0,0 @@ -RPC Errors -========== - -All Pyrogram API errors live inside the ``errors`` sub-package: ``pyrogram.errors``. -The errors ids listed here are shown as *UPPER_SNAKE_CASE*, but the actual exception names to import from Pyrogram -follow the usual *PascalCase* convention. - -.. code-block:: python - - from pyrogram.errors import FloodWait - - try: - ... - except FloodWait as e: - ... - -.. hlist:: - :columns: 1 - - - :doc:`see-other` - - :doc:`bad-request` - - :doc:`unauthorized` - - :doc:`forbidden` - - :doc:`not-acceptable` - - :doc:`flood` - - :doc:`internal-server-error` - -.. toctree:: - :hidden: - - see-other - bad-request - unauthorized - forbidden - not-acceptable - flood - internal-server-error \ No newline at end of file diff --git a/docs/source/api/errors/internal-server-error.rst b/docs/source/api/errors/internal-server-error.rst deleted file mode 100644 index 996d77eba5..0000000000 --- a/docs/source/api/errors/internal-server-error.rst +++ /dev/null @@ -1,7 +0,0 @@ -500 - InternalServerError -------------------------- - -.. csv-table:: - :file: ../../../../compiler/errors/source/500_INTERNAL_SERVER_ERROR.tsv - :delim: tab - :header-rows: 1 \ No newline at end of file diff --git a/docs/source/api/errors/not-acceptable.rst b/docs/source/api/errors/not-acceptable.rst deleted file mode 100644 index 79cfa8bcaf..0000000000 --- a/docs/source/api/errors/not-acceptable.rst +++ /dev/null @@ -1,7 +0,0 @@ -406 - NotAcceptable -------------------- - -.. csv-table:: - :file: ../../../../compiler/errors/source/406_NOT_ACCEPTABLE.tsv - :delim: tab - :header-rows: 1 \ No newline at end of file diff --git a/docs/source/api/errors/see-other.rst b/docs/source/api/errors/see-other.rst deleted file mode 100644 index 629b955764..0000000000 --- a/docs/source/api/errors/see-other.rst +++ /dev/null @@ -1,7 +0,0 @@ -303 - SeeOther --------------- - -.. csv-table:: - :file: ../../../../compiler/errors/source/303_SEE_OTHER.tsv - :delim: tab - :header-rows: 1 \ No newline at end of file diff --git a/docs/source/api/errors/unauthorized.rst b/docs/source/api/errors/unauthorized.rst deleted file mode 100644 index c56abeecc3..0000000000 --- a/docs/source/api/errors/unauthorized.rst +++ /dev/null @@ -1,7 +0,0 @@ -401 - Unauthorized ------------------- - -.. csv-table:: - :file: ../../../../compiler/errors/source/401_UNAUTHORIZED.tsv - :delim: tab - :header-rows: 1 diff --git a/docs/source/api/filters.rst b/docs/source/api/filters.rst deleted file mode 100644 index eb3c9522b3..0000000000 --- a/docs/source/api/filters.rst +++ /dev/null @@ -1,11 +0,0 @@ -Update Filters -============== - -Filters are objects that can be used to filter the content of incoming updates. -:doc:`Read more about how filters work <../topics/use-filters>`. - -Details -------- - -.. automodule:: pyrogram.filters - :members: diff --git a/docs/source/api/handlers.rst b/docs/source/api/handlers.rst deleted file mode 100644 index 2d83a05d8a..0000000000 --- a/docs/source/api/handlers.rst +++ /dev/null @@ -1,65 +0,0 @@ -Update Handlers -=============== - -Handlers are used to instruct Pyrogram about which kind of updates you'd like to handle with your callback functions. -For a much more convenient way of registering callback functions have a look at :doc:`Decorators ` instead. - -.. code-block:: python - :emphasize-lines: 2, 11 - - from pyrogram import Client - from pyrogram.handlers import MessageHandler - - app = Client("my_account") - - - def dump(client, message): - print(message) - - - app.add_handler(MessageHandler(dump)) - - app.run() - -.. contents:: Contents - :backlinks: none - :depth: 1 - :local: - ------ - -.. currentmodule:: pyrogram.handlers - -Index ------ - -.. hlist:: - :columns: 3 - - - :class:`MessageHandler` - - :class:`DeletedMessagesHandler` - - :class:`CallbackQueryHandler` - - :class:`InlineQueryHandler` - - :class:`ChosenInlineResultHandler` - - :class:`ChatMemberUpdatedHandler` - - :class:`UserStatusHandler` - - :class:`PollHandler` - - :class:`DisconnectHandler` - - :class:`RawUpdateHandler` - ------ - -Details -------- - -.. Handlers -.. autoclass:: MessageHandler() -.. autoclass:: DeletedMessagesHandler() -.. autoclass:: CallbackQueryHandler() -.. autoclass:: InlineQueryHandler() -.. autoclass:: ChosenInlineResultHandler() -.. autoclass:: ChatMemberUpdatedHandler() -.. autoclass:: UserStatusHandler() -.. autoclass:: PollHandler() -.. autoclass:: DisconnectHandler() -.. autoclass:: RawUpdateHandler() diff --git a/docs/source/conf.py b/docs/source/conf.py deleted file mode 100644 index a9fcef52cb..0000000000 --- a/docs/source/conf.py +++ /dev/null @@ -1,87 +0,0 @@ -# Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2021 Dan -# -# This file is part of Pyrogram. -# -# Pyrogram is free software: you can redistribute it and/or modify -# it under the terms of the GNU Lesser General Public License as published -# by the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# Pyrogram is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public License -# along with Pyrogram. If not, see . - -import os -import sys -from datetime import datetime - -sys.path.insert(0, os.path.abspath("../..")) - -from pyrogram import __version__ - -from pygments.styles.friendly import FriendlyStyle - -FriendlyStyle.background_color = "#f3f2f1" - -project = "Pyrogram" -copyright = f"2017-{datetime.now().year}, Dan" -author = "Dan" - -extensions = [ - "sphinx.ext.autodoc", - "sphinx.ext.napoleon", - "sphinx.ext.autosummary", - "sphinx_copybutton", - "sphinx_tabs.tabs" -] - -master_doc = "index" -source_suffix = ".rst" -autodoc_member_order = "bysource" - -version = __version__ -release = version - -templates_path = ["_templates"] - -napoleon_use_rtype = False - -pygments_style = "friendly" - -copybutton_prompt_text = "$ " - -html_title = "Pyrogram Documentation" -html_theme = "sphinx_rtd_theme" -html_static_path = ["_static"] -html_show_sourcelink = True -html_show_copyright = False -html_theme_options = { - "canonical_url": "https://docs.pyrogram.org/", - "collapse_navigation": True, - "sticky_navigation": False, - "logo_only": True, - "display_version": True, - "style_external_links": True -} - -napoleon_use_param = False - -html_logo = "_images/pyrogram.png" -html_favicon = "_images/favicon.ico" - -latex_engine = "xelatex" -latex_logo = "_images/pyrogram.png" - -latex_elements = { - "pointsize": "12pt", - "fontpkg": r""" - \setmainfont{Open Sans} - \setsansfont{Bitter} - \setmonofont{Ubuntu Mono} - """ -} diff --git a/docs/source/faq.rst b/docs/source/faq.rst deleted file mode 100644 index 4bfc7135c7..0000000000 --- a/docs/source/faq.rst +++ /dev/null @@ -1,407 +0,0 @@ -Pyrogram FAQ -============ - -.. role:: strike - :class: strike - -This FAQ page provides answers to common questions about Pyrogram and, to some extent, Telegram in general. - -.. tip:: - - If you think something interesting could be added here, feel free to propose it by opening a `Feature Request`_. - -.. contents:: Contents - :backlinks: none - :depth: 1 - :local: - ------ - -What is Pyrogram? ------------------ - -**Pyrogram** is an elegant, easy-to-use Telegram_ client library and framework written from the ground up in Python and -C. It enables you to easily create custom applications for both user and bot identities (bot API alternative) via the -:doc:`MTProto API ` with the Python programming language. - -.. _Telegram: https://telegram.org - -Where does the name come from? ------------------------------- - -The name "Pyrogram" is composed by **pyro**, which comes from the Greek word *πῦρ (pyr)*, meaning fire, and **gram**, -from *Telegram*. The word *pyro* itself is built from *Python*, **py** for short, and the suffix **ro** to come up with -the word *fire*, which also inspired the project logo. - -How old is Pyrogram? --------------------- - -Pyrogram was first released on December 12, 2017. The actual work on the framework began roughly three months prior to the -initial public release on `GitHub`_. - -.. _GitHub: https://github.com/pyrogram/pyrogram - -Why Pyrogram? -------------- - -- **Easy**: You can install Pyrogram with pip and start building your applications right away. -- **Elegant**: Low-level details are abstracted and re-presented in a much nicer and easier way. -- **Fast**: Crypto parts are boosted up by TgCrypto_, a high-performance library written in pure C. -- **Asynchronous**: Allows both synchronous and asynchronous models to fit all usage needs. -- **Documented**: API methods, types and public interfaces are all well documented. -- **Type-hinted**: Types and methods are all type-hinted, enabling excellent editor support. -- **Updated**, to make use of the latest Telegram API version and features. -- **Bot API-like**: Similar to the Bot API in its simplicity, but much more powerful and detailed. -- **Pluggable**: The :doc:`Smart Plugin ` system allows to write components with minimal - boilerplate code. -- **Comprehensive**: Execute any :doc:`advanced action ` an official client is able to do, and - even more. - -.. _TgCrypto: https://github.com/pyrogram/tgcrypto - -Why is Pyrogram defined as both Client Library and Framework? -------------------------------------------------------------- - -Simply because it falls in both categories, depending on how you use it. - -Pyrogram as a client library makes it easy and intuitive accessing the Telegram API by offering idiomatic Python code -that is generated or hand-written. Low-level details and client-server communication protocols are handled under the -hood. Pyrogram acts as a client library when *you call* its methods and use its types in a batch application that -executes a set of instructions. - -Pyrogram as a framework makes it easy to handle live events by allowing you to register event handlers that will be -executed as soon as they arrive from the server side. Pyrogram acts as a framework when it's Pyrogram itself that -*calls your code*, that is, your registered event handlers. Such applications are usually started and left online -indefinitely, until you decide to stop them. - -What can MTProto do more than the Bot API? ------------------------------------------- - -For a detailed answer, please refer to the :doc:`MTProto vs. Bot API ` page. - -Why do I need an API key for bots? ----------------------------------- - -Requests against the official bot API endpoint are made via JSON/HTTP, but are handled by an intermediate server -application that implements the MTProto protocol -- just like Pyrogram -- and uses its own API key, which is always -required, but hidden to the public. - -.. figure:: https://i.imgur.com/WvwBoZo.png - :align: center - -Using MTProto is the only way to communicate with the actual Telegram servers, and the main API requires developers to -identify applications by means of a unique key; the bot token identifies a bot as a user and replaces the user's phone -number only. - -Can I use Webhooks? -------------------- - -Lots of people ask this question because they are used to the bot API, but things are different in Pyrogram! - -There is no webhook in Pyrogram, simply because there is no HTTP involved, by default. However, a similar technique is -being used to make receiving updates efficient. - -Pyrogram uses persistent connections via TCP sockets to interact with the server and instead of actively asking for -updates every time (polling), Pyrogram will simply sit down and wait for the server to send updates by itself -the very moment they are available (server push). - -Can I use the same file_id across different accounts? ------------------------------------------------------ - -No, Telegram doesn't allow this. - -File ids are personal and bound to a specific account; an attempt in using a foreign file id will result in errors such -as ``[400 MEDIA_EMPTY]``. - -The only exception are stickers' file ids; you can use them across different accounts without any problem, like this -one: ``CAADBAADyg4AAvLQYAEYD4F7vcZ43AI``. - -Can I use Bot API's file_id values in Pyrogram? ------------------------------------------------ - -Yes! All file ids you take or might have taken from the Bot API are 100% compatible and re-usable in Pyrogram. -The opposite is also valid, you can take any file id generated by Pyrogram and re-use in the Bot API. - -Can I use multiple clients at once on the same account? -------------------------------------------------------- - -Yes, you can. Both user and bot accounts are able to run multiple sessions in parallel (up to 10 per account). However, -you must pay attention and not use the *same* exact session in more than one client at the same time. In other words: - -- Avoid copying your session file: even if you rename the file, the copied sessions will still point to a specific one - stored in the server. - -- Make sure that only one instance of your script runs, using your session file. - -If you -- even accidentally -- fail to do so, all the previous session copies will immediately stop receiving updates -and eventually the server will start throwing the error ``[406 AUTH_KEY_DUPLICATED]``, inviting you to login again. - -Why is that so? Because the server has recognized two identical sessions are running in two different locations, and -concludes it could possibly be due to a cloned/stolen device. Having the session terminated in such occasions will -protect the user's privacy. - -So, the only correct way to run multiple clients on the same account is authorizing your account (either user or bot) -from the beginning every time, and use one separate session for each parallel client you are going to use. - -I started a client and nothing happens! ---------------------------------------- - -If you are connecting from Russia, China or Iran :doc:`you need a proxy `, because Telegram could be -partially or totally blocked in those countries. More information about this block can be found at -`Wikipedia `_. - -Another possible cause might be network issues, either yours or Telegram's. To confirm this, add the following code on -the top of your script and run it again. You should see some error mentioning a socket timeout or an unreachable network -in a bunch of seconds: - -.. code-block:: python - - import logging - logging.basicConfig(level=logging.INFO) - -Another way to confirm you aren't able to connect to Telegram is by pinging the IP addresses below and see whether ping -fails or not. - -What are the IP addresses of Telegram Data Centers? ---------------------------------------------------- - -The Telegram cloud is currently composed by a decentralized, multi-DC infrastructure (currently 5 DCs, each of which can -work independently) spread in different locations worldwide. However, some of the less busy DCs have been lately -dismissed and their IP addresses are now kept as aliases to the nearest one. - -.. csv-table:: Production Environment - :header: ID, Location, IPv4, IPv6 - :widths: auto - :align: center - - DC1, "MIA, Miami FL, USA", ``149.154.175.53``, ``2001:b28:f23d:f001::a`` - DC2, "AMS, Amsterdam, NL", ``149.154.167.51``, ``2001:67c:4e8:f002::a`` - DC3*, "MIA, Miami FL, USA", ``149.154.175.100``, ``2001:b28:f23d:f003::a`` - DC4, "AMS, Amsterdam, NL", ``149.154.167.91``, ``2001:67c:4e8:f004::a`` - DC5, "SIN, Singapore, SG", ``91.108.56.130``, ``2001:b28:f23f:f005::a`` - -.. csv-table:: Test Environment - :header: ID, Location, IPv4, IPv6 - :widths: auto - :align: center - - DC1, "MIA, Miami FL, USA", ``149.154.175.10``, ``2001:b28:f23d:f001::e`` - DC2, "AMS, Amsterdam, NL", ``149.154.167.40``, ``2001:67c:4e8:f002::e`` - DC3*, "MIA, Miami FL, USA", ``149.154.175.117``, ``2001:b28:f23d:f003::e`` - -.. centered:: More info about the Test Environment can be found :doc:`here `. - -***** Alias DC - -Thanks to `@FrayxRulez `_ for telling about alias DCs. - -I want to migrate my account from DCX to DCY. ---------------------------------------------- - -This question is often asked by people who find their account(s) always being connected to DC1 - USA (for example), but -are connecting from a place far away (e.g DC4 - Europe), thus resulting in slower interactions when using the API -because of the great physical distance between the user and its associated DC. - -When registering an account for the first time, is up to Telegram to decide which DC the new user is going to be created -in, based on the phone number origin. - -Even though Telegram `documentations `_ state the server might -decide to automatically migrate a user in case of prolonged usages from a distant, unusual location and albeit this -mechanism is also `confirmed `_ to exist by Telegram itself, -it's currently not possible to have your account migrated, in any way, simply because the feature was once planned but -not yet implemented. - -Thanks to `@gabriel `_ for confirming the feature was not implemented yet. - -Why is my client reacting slowly in supergroups? ------------------------------------------------- - -This issue affects only some supergroups or only some members within the same supergroup. Mostly, it affects supergroups -whose creator's account (and thus the supergroup itself) lives inside a **different DC**, far away from yours, but could -also depend on where a member is connecting from. - -Because of how Telegram works internally, every single message you receive from and send to other members must pass -through the creator's DC, and in the worst case where you, the creator and another member all belong to three different -DCs, the other member messages have to go through from its DC to the creator's DC and finally to your DC. This process -will inevitably take its time. - - To confirm this theory and see it by yourself, you can test in a supergroup where you are sure all parties live - inside the same DC. In this case the responses will be faster. - -Another reason that makes responses come slowly is that messages are **dispatched by priority**. Depending on the kind -of member, some users receive messages faster than others and for big and busy supergroups the delay might become -noticeable, especially if you are among the lower end of the priority list: - -1. Creator. -2. Administrators. -3. Bots. -4. Mentioned users. -5. Recent online users. -6. Everyone else. - -Thanks to `@Manuel15 `_ for the priority list. - -I keep getting PEER_ID_INVALID error! -------------------------------------- - -The error in question is ``[400 PEER_ID_INVALID]``, and could mean several things: - -- The chat id you tried to use is simply wrong, double check it. -- The chat id refers to a group or channel you are not a member of. -- The chat id argument you passed is in form of a string; you have to convert it into an integer with ``int(chat_id)``. -- The chat id refers to a user or chat your current session hasn't met yet. - -About the last point: in order for you to meet a user and thus communicate with them, you should ask yourself how to -contact people using official apps. The answer is the same for Pyrogram too and involves normal usages such as searching -for usernames, meeting them in a common group, having their phone contacts saved, getting a message mentioning them -(either a forward or a mention in the message text) or obtaining the dialogs list. - -Code hangs when I stop, restart, add/remove_handler ---------------------------------------------------- - -You tried to ``.stop()``, ``.restart()``, ``.add_handler()`` or ``.remove_handler()`` *inside* a running handler, but -that can't be done because the way Pyrogram deals with handlers would make it hang. - -When calling one of the methods above inside an event handler, Pyrogram needs to wait for all running handlers to finish -in order to safely continue. In other words, since your handler is blocking the execution by waiting for the called -method to finish and since Pyrogram needs to wait for your handler to finish, you are left with a deadlock. - -The solution to this problem is to pass ``block=False`` to such methods so that they return immediately and the actual -code called asynchronously. - -UnicodeEncodeError: '' codec can't encode … ------------------------------------------------------ - -Where ```` might be *ascii*, *cp932*, *charmap* or anything else other than **utf-8**. This error usually -shows up when you try to print something and has very little to do with Pyrogram itself as it is strictly related to -your own terminal. To fix it, either find a way to change the encoding settings of your terminal to UTF-8 or switch to a -better terminal altogether. - -Uploading with URLs gives error WEBPAGE_CURL_FAILED ---------------------------------------------------- - -When uploading media files using an URL, the server automatically tries to download the media and uploads it to the -Telegram cloud. This error usually happens in case the provided URL is not publicly accessible by Telegram itself or the -media exceeds 20 MB in size. In such cases, your only option is to download the media yourself and upload from your -local machine. - -sqlite3.OperationalError: database is locked --------------------------------------------- - -This error occurs when more than one process is using the same session file, that is, when you run two or more clients -at the same time using the same session name. - -It could also occur when a background script is still running and you forgot about it. In this case, you either restart -your system or find and kill the process that is locking the database. On Unix based systems, you can do the following: - -#. ``cd`` into your session file directory. -#. ``fuser my_account.session`` to find the process id. -#. ``kill 1234`` to gracefully stop the process. -#. If the last command doesn't help, use ``kill -9 1234`` instead. - -If you want to run multiple clients on the same account, you must authorize your account (either user or bot) -from the beginning every time, and use different session names for each parallel client you are going to use. - -sqlite3.OperationalError: unable to open database file ------------------------------------------------------- - -Stackoverflow to the rescue: https://stackoverflow.com/questions/4636970 - -sqlite3.InterfaceError: Error binding parameter 0 -------------------------------------------------- - -This error occurs when you pass a chat id value of the wrong type when trying to call a method. Most likely, you -accidentally passed the whole user or chat object instead of the id or username. - -.. code-block:: python - - # Wrong. You passed the whole Chat instance - app.send_message(chat, "text") - - # Correct - app.send_message(chat.id, "text") - -My verification code expires immediately! ------------------------------------------ - -That is because you likely shared it across any of your Telegram chats. Yes, that's right: the server keeps scanning the -messages you send and if an active verification code is found it will immediately expire, automatically. - -The reason behind this is to protect unaware users from giving their account access to any potential scammer, but if you -legitimately want to share your account(s) verification codes, consider scrambling them, e.g. ``12345`` → ``1-2-3-4-5``. - -How can avoid Flood Waits? --------------------------- - -Long story short: make less requests, and remember that the API is designed to be used by official apps, by real people; -anything above normal usage could be limited. - -This question is being asked quite a lot of times, but the bottom line is that nobody knows the exact limits and it's -unlikely that such information will be ever disclosed, because otherwise people could easily circumvent them and defeat -their whole purpose. - -Do also note that Telegram wants to be a safe and reliable place and that limits exist to protect itself from abuses. -Having said that, here's some insights about limits: - -- They are tuned by Telegram based on real people usage and can change anytime. -- Some limits are be applied to single sessions, some others apply to the whole account. -- Limits vary based on methods and the arguments passed to methods. For example: log-ins are expensive and thus have - stricter limits; replying to a user command could cause a flood wait in case the user starts flooding, but - such limit will only be applied to that particular chat (i.e.: other users are not affected). -- You can catch Flood Wait exceptions in your code and wait the required seconds before continuing, this way: - - .. code-block:: python - - import time - from pyrogram.errors import FloodWait - - try: - ... # Your code - except FloodWait as e: - time.sleep(e.x) # Wait "x" seconds before continuing - - - More info about error handling can be found `here `_. - -My account has been deactivated/limited! ----------------------------------------- - -First of all, you should understand that Telegram wants to be a safe place for people to stay in, and to pursue this -goal there are automatic protection systems running to prevent flood and spam, as well as a moderation team of humans -who review reports. - -.. centered:: Pyrogram is a tool at your commands; it only does what you tell it to do, the rest is up to you. - -Having said that, here's a list of what Telegram definitely doesn't like: - -- Flood, abusing the API. -- Spam, sending unsolicited messages or adding people to unwanted groups and channels. -- Virtual/VoIP and cheap real numbers, because they are relatively easy to get and likely used for spam/flood. - -And thanks to `@koteeq `_, here's a good explanation of how, probably, the system works: - -.. raw:: html - - -

- -However, you might be right, and your account was deactivated/limited without any good reason. This could happen because -of mistakes by either the automatic systems or a moderator. In such cases you can kindly email Telegram at -recover@telegram.org, contact `@smstelegram`_ on Twitter or use `this form`_. - -Are there any secret easter eggs? ---------------------------------- - -Yes. If you found one, `let me know`_! - -.. _let me know: https://t.me/pyrogram - -.. _@smstelegram: https://twitter.com/smstelegram -.. _this form: https://telegram.org/support - -.. _Bug Report: https://github.com/pyrogram/pyrogram/issues/new?labels=bug&template=bug_report.md -.. _Feature Request: https://github.com/pyrogram/pyrogram/issues/new?labels=enhancement&template=feature_request.md diff --git a/docs/source/glossary.rst b/docs/source/glossary.rst deleted file mode 100644 index fa6086ce6c..0000000000 --- a/docs/source/glossary.rst +++ /dev/null @@ -1,86 +0,0 @@ -Pyrogram Glossary -================= - -This page contains a list of common words with brief explanations related to Pyrogram and, to some extent, Telegram in -general. Some words may as well link to dedicated articles in case the topic is covered in a more detailed fashion. - -.. tip:: - - If you think something interesting could be added here, feel free to propose it by opening a `Feature Request`_. - -.. contents:: Contents - :backlinks: none - :depth: 1 - :local: - ------ - -Terms ------ - -.. glossary:: - :sorted: - - API - Application Programming Interface: a set of methods, protocols and tools that make it easier to develop programs - by providing useful building blocks to the developer. - - API key - A secret code used to authenticate and/or authorize a specific application to Telegram in order for it to - control how the API is being used, for example, to prevent abuses of the API. - :doc:`More on API keys `. - - DC - Also known as *data center*, is a place where lots of computer systems are housed and used together in order to - achieve high quality and availability for services. - - RPC - Acronym for Remote Procedure Call, that is, a function which gets executed at some remote place (i.e. Telegram - server) and not in your local machine. - - RPCError - An error caused by an RPC which must be returned in place of the successful result in order to let the caller - know something went wrong. :doc:`More on RPCError `. - - MTProto - The name of the custom-made, open and encrypted protocol by Telegram, implemented in Pyrogram. - :doc:`More on MTProto `. - - MTProto API - The Telegram main API Pyrogram makes use of, which is able to connect both users and normal bots to Telegram - using MTProto as application layer protocol and execute any method Telegram provides from its public TL-schema. - :doc:`More on MTProto API `. - - Bot API - The Telegram Bot API that is able to only connect normal bots only to Telegram using HTTP as application layer - protocol and allows to execute a sub-set of the main Telegram API. - :doc:`More on Bot API `. - - Pyrogrammer - A developer that uses Pyrogram to build Telegram applications. - - Userbot - Also known as *user bot* or *ubot* for short, is a user logged in by third-party Telegram libraries --- such as - Pyrogram --- to automate some behaviours, like sending messages or reacting to text commands or any other event. - Not to be confused with *bot*, that is, a normal Telegram bot created by `@BotFather `_. - - Session - Also known as *login session*, is a strictly personal piece of data created and held by both parties - (client and server) which is used to grant permission into a single account without having to start a new - authorization process from scratch. - - Callback - Also known as *callback function*, is a user-defined generic function that *can be* registered to and then - called-back by the framework when specific events occurs. - - Handler - An object that wraps around a callback function that is *actually meant* to be registered into the framework, - which will then be able to handle a specific kind of events, such as a new incoming message, for example. - :doc:`More on Handlers `. - - Decorator - Also known as *function decorator*, in Python, is a callable object that is used to modify another function. - Decorators in Pyrogram are used to automatically register callback functions for handling updates. - :doc:`More on Decorators `. - -.. _Feature Request: https://github.com/pyrogram/pyrogram/issues/new?labels=enhancement&template=feature_request.md diff --git a/docs/source/index.rst b/docs/source/index.rst deleted file mode 100644 index 3834853434..0000000000 --- a/docs/source/index.rst +++ /dev/null @@ -1,169 +0,0 @@ -Welcome to Pyrogram -=================== - -.. raw:: html - -
- -
Pyrogram Logo
-
-
- -

- Telegram MTProto API Framework for Python - -
- - Source Code - - • - - Releases - - • - - Community - -

- -.. code-block:: python - - from pyrogram import Client, filters - - app = Client("my_account") - - - @app.on_message(filters.private) - async def hello(client, message): - await message.reply_text(f"Hello {message.from_user.mention}") - - - app.run() - -**Pyrogram** is a modern, elegant and easy-to-use Telegram_ framework written from the ground up in Python and C. -It enables you to easily create custom apps for both user and bot identities (bot API alternative) via the -:doc:`MTProto API `. - -.. _Telegram: https://telegram.org - -How the Documentation is Organized ----------------------------------- - -Contents are organized into sections composed of self-contained topics which can be all accessed from the sidebar, or by -following them in order using the :guilabel:`Next` button at the end of each page. Here below you can, instead, find a -list of the most relevant pages for a quick access. - -.. admonition :: Cloud Credits - :class: tip - - If you need a cloud server to host your applications, we recommend using **Hetzner Cloud**. Sign up with - `this link `_ to get €20 in cloud credits and help support Pyrogram as - well. - -First Steps -^^^^^^^^^^^ - -.. hlist:: - :columns: 2 - - - :doc:`Quick Start `: Overview to get you started quickly. - - :doc:`Calling Methods `: How to call Pyrogram's methods. - - :doc:`Handling Updates `: How to handle Telegram updates. - - :doc:`Error Handling `: How to handle API errors correctly. - -API Reference -^^^^^^^^^^^^^ - -.. hlist:: - :columns: 2 - - - :doc:`Pyrogram Client `: Reference details about the Client class. - - :doc:`Available Methods `: List of available high-level methods. - - :doc:`Available Types `: List of available high-level types. - - :doc:`Bound Methods `: List of convenient bound methods. - -Meta -^^^^ - -.. hlist:: - :columns: 2 - - - :doc:`Pyrogram FAQ `: Answers to common Pyrogram questions. - - :doc:`Pyrogram Glossary `: List of words with brief explanations. - - :doc:`Support Pyrogram `: Ways to show your appreciation. - - :doc:`About the License `: Information about the Project license. - - :doc:`Release Notes `: Release notes for Pyrogram releases. - -Last updated on |today| - -.. toctree:: - :hidden: - :caption: Introduction - - intro/quickstart - intro/install - intro/setup - -.. toctree:: - :hidden: - :caption: Getting Started - - start/auth - start/invoking - start/updates - start/errors - start/examples/index - -.. toctree:: - :hidden: - :caption: API Reference - - api/client - api/methods/index - api/types/index - api/bound-methods/index - api/handlers - api/decorators - api/errors/index - api/filters - -.. toctree:: - :hidden: - :caption: Topic Guides - - topics/use-filters - topics/create-filters - topics/more-on-updates - topics/config-file - topics/smart-plugins - topics/session-settings - topics/tgcrypto - topics/storage-engines - topics/text-formatting - topics/serializing - topics/proxy - topics/scheduling - topics/bots-interaction - topics/mtproto-vs-botapi - topics/debugging - topics/test-servers - topics/advanced-usage - topics/voice-calls - -.. toctree:: - :hidden: - :caption: Meta - - faq - glossary - support - license - releases/index - -.. toctree:: - :hidden: - :caption: Telegram API - - telegram/functions/index - telegram/types/index - telegram/base/index \ No newline at end of file diff --git a/docs/source/intro/install.rst b/docs/source/intro/install.rst deleted file mode 100644 index 813961dcac..0000000000 --- a/docs/source/intro/install.rst +++ /dev/null @@ -1,62 +0,0 @@ -Install Guide -============= - -Being a modern Python library, **Pyrogram** requires Python 3.6+ to be installed in your system. -We recommend using the latest versions of both Python 3 and pip. - -- Get **Python 3** from https://www.python.org/downloads/ (or with your package manager). -- Get **pip** by following the instructions at https://pip.pypa.io/en/latest/installing/. - -.. important:: - - Pyrogram supports **Python 3** only, starting from version 3.6. **PyPy** is supported too. - -.. contents:: Contents - :backlinks: none - :depth: 1 - :local: - ------ - -Install Pyrogram ----------------- - -- The easiest way to install and upgrade Pyrogram to its latest stable version is by using **pip**: - - .. code-block:: text - - $ pip3 install -U pyrogram - -- or, with :doc:`TgCrypto <../topics/tgcrypto>` as extra requirement (recommended): - - .. code-block:: text - - $ pip3 install -U pyrogram tgcrypto - -Bleeding Edge -------------- - -Pyrogram is always evolving, although new releases on PyPI are published only when enough changes are added, but this -doesn't mean you can't try new features right now! - -In case you'd like to try out the latest Pyrogram features, the `GitHub repo`_ is always kept updated with new changes; -you can install the development version straight from the ``master`` branch using this command (note "master.zip" in -the link): - -.. code-block:: text - - $ pip3 install -U https://github.com/pyrogram/pyrogram/archive/master.zip - -Verifying ---------- - -To verify that Pyrogram is correctly installed, open a Python shell and import it. -If no error shows up you are good to go. - -.. parsed-literal:: - - >>> import pyrogram - >>> pyrogram.__version__ - '|version|' - -.. _`Github repo`: http://github.com/pyrogram/pyrogram diff --git a/docs/source/intro/quickstart.rst b/docs/source/intro/quickstart.rst deleted file mode 100644 index eeb9848264..0000000000 --- a/docs/source/intro/quickstart.rst +++ /dev/null @@ -1,49 +0,0 @@ -Quick Start -=========== - -The next few steps serve as a quick start for all new :term:`Pyrogrammers ` that want to see Pyrogram in -action as fast as possible. Let's go! - -Get Pyrogram Real Fast ----------------------- - -1. Install Pyrogram with ``pip3 install -U pyrogram``. - -2. Get your own Telegram API key from https://my.telegram.org/apps. - -3. Open your best text editor and paste the following: - - .. code-block:: python - - from pyrogram import Client - - api_id = 12345 - api_hash = "0123456789abcdef0123456789abcdef" - - with Client("my_account", api_id, api_hash) as app: - app.send_message("me", "Greetings from **Pyrogram**!") - -4. Replace *api_id* and *api_hash* values with your own. - -5. Save the file as ``pyro.py``. - -6. Run the script with ``python3 pyro.py`` - -7. Follow the instructions on your terminal to login. - -8. Watch Pyrogram send a message to yourself. - -9. Join our `community`_. - -10. Say, "hi!". - -Enjoy the API -------------- - -That was just a quick overview that barely scratched the surface! -In the next few pages of the introduction, we'll take a much more in-depth look of what we have just done above. - -Feeling eager to continue? You can take a shortcut to :doc:`Calling Methods <../start/invoking>` and come back later to -learn some more details. - -.. _community: https://t.me/Pyrogram diff --git a/docs/source/intro/setup.rst b/docs/source/intro/setup.rst deleted file mode 100644 index e4c6e64065..0000000000 --- a/docs/source/intro/setup.rst +++ /dev/null @@ -1,66 +0,0 @@ -Project Setup -============= - -We have just :doc:`installed Pyrogram `. In this page we'll discuss what you need to do in order to set up a -project with the library. Let's see how it's done. - -.. contents:: Contents - :backlinks: none - :depth: 1 - :local: - ------ - -API Keys --------- - -The very first step requires you to obtain a valid Telegram API key (API id/hash pair): - -#. Visit https://my.telegram.org/apps and log in with your Telegram Account. -#. Fill out the form to register a new Telegram application. -#. Done! The API key consists of two parts: **api_id** and **api_hash**. - -.. important:: - - The API key is personal and must be kept secret. - -.. note:: - - The API key is unique for each user, but defines a token for a Telegram *application* you are going to build. This - means that you are able to authorize multiple users (and bots too) to access the Telegram database through the - MTProto API by a single API key. - -Configuration -------------- - -Having the API key from the previous step in handy, we can now begin to configure a Pyrogram project. -There are two ways to do so, and you can choose what fits better for you: - -- First option (recommended): create a new ``config.ini`` file next to your main script, copy-paste the following and - replace the **api_id** and **api_hash** values with your own. This is the preferred method because allows you to - keep your credentials out of your code without having to deal with how to load them: - - .. code-block:: ini - - [pyrogram] - api_id = 12345 - api_hash = 0123456789abcdef0123456789abcdef - -- Alternatively, you can pass your API key to Pyrogram by simply using the *api_id* and *api_hash* parameters of the - Client class. This way you can have full control on how to store and load your credentials (e.g., you can load the - credentials from the environment variables and directly pass the values into Pyrogram): - - .. code-block:: python - - from pyrogram import Client - - app = Client( - "my_account", - api_id=12345, - api_hash="0123456789abcdef0123456789abcdef" - ) - -.. note:: - - To keep code snippets clean and concise, from now on it is assumed you are making use of the ``config.ini`` file, - thus, the *api_id* and *api_hash* parameters usage won't be shown anymore. diff --git a/docs/source/license.rst b/docs/source/license.rst deleted file mode 100644 index 5f1d25ee0d..0000000000 --- a/docs/source/license.rst +++ /dev/null @@ -1,16 +0,0 @@ -About the License -================= - -.. image:: https://www.gnu.org/graphics/lgplv3-with-text-154x68.png - :align: left - -Pyrogram is free software and is currently licensed under the terms of the -`GNU Lesser General Public License v3 or later (LGPLv3+)`_. In short: you may use, redistribute and/or modify it -provided that modifications are described and licensed for free under LGPLv3+. - -In other words: you can use and integrate Pyrogram into your code (either open source, under the same or a different -license, or even proprietary) for any purpose whatsoever without being required to release the source code of your -applications. Derivative works, including modifications to the library itself can only be redistributed under the same -LGPLv3+ license. - -.. _GNU Lesser General Public License v3 or later (LGPLv3+): https://github.com/pyrogram/pyrogram/blob/develop/COPYING.lesser diff --git a/docs/source/start/auth.rst b/docs/source/start/auth.rst deleted file mode 100644 index 23ff9fe276..0000000000 --- a/docs/source/start/auth.rst +++ /dev/null @@ -1,75 +0,0 @@ -Authorization -============= - -Once a :doc:`project is set up <../intro/setup>`, you will still have to follow a few steps before you can actually use Pyrogram to make -API calls. This section provides all the information you need in order to authorize yourself as user or bot. - -.. contents:: Contents - :backlinks: none - :depth: 1 - :local: - ------ - -User Authorization ------------------- - -In order to use the API, Telegram requires that users be authorized via their phone numbers. -Pyrogram automatically manages this process, all you need to do is create an instance of the -:class:`~pyrogram.Client` class by passing to it a ``session_name`` of your choice (e.g.: "my_account") and call -the :meth:`~pyrogram.Client.run` method: - -.. code-block:: python - - from pyrogram import Client - - app = Client("my_account") - app.run() - -This starts an interactive shell asking you to input your **phone number** (including your `Country Code`_) and the -**phone code** you will receive in your devices that are already authorized or via SMS: - -.. code-block:: text - - Enter phone number: +39********** - Is "+39**********" correct? (y/n): y - Enter phone code: 32768 - Logged in successfully as Dan - -After successfully authorizing yourself, a new file called ``my_account.session`` will be created allowing Pyrogram to -execute API calls with your identity. This file will be loaded again when you restart your app, and as long as you -keep the session alive, Pyrogram won't ask you again to enter your phone number. - -.. important:: - - Your ``*.session`` file is personal and must be kept secret. - -.. note:: - - The code above does nothing except asking for credentials and keeping the client online, hit :guilabel:`CTRL+C` now - to stop your application and keep reading. - -Bot Authorization ------------------ - -Bots are a special kind of users that are authorized via their tokens (instead of phone numbers), which are created by -the `Bot Father`_. Bot tokens replace the users' phone numbers only — you still need to -:doc:`configure a Telegram API key <../intro/setup>` with Pyrogram, even when using bots. - -The authorization process is automatically managed. All you need to do is choose a ``session_name`` (can be anything, -usually your bot username) and pass your bot token using the ``bot_token`` parameter. The session file will be named -after the session name, which will be ``my_bot.session`` for the example below. - -.. code-block:: python - - from pyrogram import Client - - app = Client( - "my_bot", - bot_token="123456:ABC-DEF1234ghIkl-zyx57W2v1u123ew11" - ) - - app.run() - -.. _Country Code: https://en.wikipedia.org/wiki/List_of_country_calling_codes -.. _Bot Father: https://t.me/botfather \ No newline at end of file diff --git a/docs/source/start/errors.rst b/docs/source/start/errors.rst deleted file mode 100644 index 8e592f18c7..0000000000 --- a/docs/source/start/errors.rst +++ /dev/null @@ -1,107 +0,0 @@ -Error Handling -============== - -Errors are inevitable when working with the API, and they can be correctly handled with ``try...except`` blocks in order -to control the behaviour of your application. Pyrogram errors all live inside the ``errors`` package: - -.. code-block:: python - - from pyrogram import errors - -.. contents:: Contents - :backlinks: none - :depth: 1 - :local: - ------ - -RPCError --------- - -The father of all errors is named ``RPCError`` and is able to catch all Telegram API related errors. -This error is raised every time a method call against Telegram's API was unsuccessful. - -.. code-block:: python - - from pyrogram.errors import RPCError - -.. warning:: - - It must be noted that catching this error is bad practice, especially when no feedback is given (i.e. by - logging/printing the full error traceback), because it makes it impossible to understand what went wrong. - -Error Categories ----------------- - -The ``RPCError`` packs together all the possible errors Telegram could raise, but to make things tidier, Pyrogram -provides categories of errors, which are named after the common HTTP errors and are subclass-ed from the ``RPCError``: - -.. code-block:: python - - from pyrogram.errors import BadRequest, Forbidden, ... - -- :doc:`303 - SeeOther <../api/errors/see-other>` -- :doc:`400 - BadRequest <../api/errors/bad-request>` -- :doc:`401 - Unauthorized <../api/errors/unauthorized>` -- :doc:`403 - Forbidden <../api/errors/forbidden>` -- :doc:`406 - NotAcceptable <../api/errors/not-acceptable>` -- :doc:`420 - Flood <../api/errors/flood>` -- :doc:`500 - InternalServerError <../api/errors/internal-server-error>` - -Single Errors -------------- - -For a fine-grained control over every single error, Pyrogram does also expose errors that deal each with a specific -issue. For example: - -.. code-block:: python - - from pyrogram.errors import FloodWait - -These errors subclass directly from the category of errors they belong to, which in turn subclass from the father -``RPCError``, thus building a class of error hierarchy such as this: - -- RPCError - - BadRequest - - ``MessageEmpty`` - - ``UsernameOccupied`` - - ``...`` - - InternalServerError - - ``RpcCallFail`` - - ``InterDcCallError`` - - ``...`` - - ``...`` - -.. _Errors: api/errors - -Unknown Errors --------------- - -In case Pyrogram does not know anything about a specific error yet, it raises a generic error from its known category, -for example, an unknown error with error code ``400``, will be raised as a ``BadRequest``. This way you can catch the -whole category of errors and be sure to also handle these unknown errors. - -In case a whole class of errors is unknown (that is, an error code that is unknown), Pyrogram will raise a special -``520 UnknownError`` exception. - -In both cases, Pyrogram will log them in the ``unknown_errors.txt`` file. Users are invited to report -these unknown errors in the `discussion group `_. - -Errors with Values ------------------- - -Exception objects may also contain some informative values. For example, ``FloodWait`` holds the amount of seconds you -have to wait before you can try again, some other errors contain the DC number on which the request must be repeated on. -The value is stored in the ``x`` attribute of the exception object: - -.. code-block:: python - - import asyncio - from pyrogram.errors import FloodWait - - ... - try: - ... # Your code - except FloodWait as e: - await asyncio.sleep(e.x) # Wait "x" seconds before continuing - ... \ No newline at end of file diff --git a/docs/source/start/examples/bot_keyboards.rst b/docs/source/start/examples/bot_keyboards.rst deleted file mode 100644 index 6e288c4314..0000000000 --- a/docs/source/start/examples/bot_keyboards.rst +++ /dev/null @@ -1,62 +0,0 @@ -bot_keyboards -============= - -This example will show you how to send normal and inline keyboards (as bot). - -You must log-in as a regular bot in order to send keyboards (use the token from @BotFather). -Any attempt in sending keyboards with a user account will be simply ignored by the server. - -send_message() is used as example, but a keyboard can be sent with any other send_* methods, -like send_audio(), send_document(), send_location(), etc... - -.. code-block:: python - - from pyrogram import Client - from pyrogram.types import ReplyKeyboardMarkup, InlineKeyboardMarkup, InlineKeyboardButton - - # Create a client using your bot token - app = Client("my_bot", bot_token="123456:ABC-DEF1234ghIkl-zyx57W2v1u123ew11") - - with app: - app.send_message( - "haskell", # Edit this - "This is a ReplyKeyboardMarkup example", - reply_markup=ReplyKeyboardMarkup( - [ - ["A", "B", "C", "D"], # First row - ["E", "F", "G"], # Second row - ["H", "I"], # Third row - ["J"] # Fourth row - ], - resize_keyboard=True # Make the keyboard smaller - ) - ) - - app.send_message( - "haskell", # Edit this - "This is a InlineKeyboardMarkup example", - reply_markup=InlineKeyboardMarkup( - [ - [ # First row - InlineKeyboardButton( # Generates a callback query when pressed - "Button", - callback_data="data" - ), - InlineKeyboardButton( # Opens a web URL - "URL", - url="https://docs.pyrogram.org" - ), - ], - [ # Second row - InlineKeyboardButton( # Opens the inline interface - "Choose chat", - switch_inline_query="pyrogram" - ), - InlineKeyboardButton( # Opens the inline interface in the current chat - "Inline here", - switch_inline_query_current_chat="pyrogram" - ) - ] - ] - ) - ) diff --git a/docs/source/start/examples/callback_queries.rst b/docs/source/start/examples/callback_queries.rst deleted file mode 100644 index 73273058a1..0000000000 --- a/docs/source/start/examples/callback_queries.rst +++ /dev/null @@ -1,19 +0,0 @@ -callback_queries -================ - -This example shows how to handle callback queries, i.e.: queries coming from inline button presses. -It uses the @on_callback_query decorator to register a CallbackQueryHandler. - -.. code-block:: python - - from pyrogram import Client - - app = Client("my_bot", bot_token="123456:ABC-DEF1234ghIkl-zyx57W2v1u123ew11") - - - @app.on_callback_query() - def answer(client, callback_query): - callback_query.answer(f"Button contains: '{callback_query.data}'", show_alert=True) - - - app.run() # Automatically start() and idle() \ No newline at end of file diff --git a/docs/source/start/examples/echobot.rst b/docs/source/start/examples/echobot.rst deleted file mode 100644 index 61dc9929da..0000000000 --- a/docs/source/start/examples/echobot.rst +++ /dev/null @@ -1,21 +0,0 @@ -echobot -======= - -This simple echo bot replies to every private text message. - -It uses the ``@on_message`` decorator to register a ``MessageHandler`` and applies two filters on it: -``filters.text`` and ``filters.private`` to make sure it will reply to private text messages only. - -.. code-block:: python - - from pyrogram import Client, filters - - app = Client("my_account") - - - @app.on_message(filters.text & filters.private) - def echo(client, message): - message.reply_text(message.text) - - - app.run() # Automatically start() and idle() \ No newline at end of file diff --git a/docs/source/start/examples/get_chat_members.rst b/docs/source/start/examples/get_chat_members.rst deleted file mode 100644 index 8d47797604..0000000000 --- a/docs/source/start/examples/get_chat_members.rst +++ /dev/null @@ -1,15 +0,0 @@ -get_chat_members -================ - -This example shows how to get all the members of a chat. - -.. code-block:: python - - from pyrogram import Client - - app = Client("my_account") - target = "pyrogramchat" # Target channel/supergroup - - with app: - for member in app.iter_chat_members(target): - print(member.user.first_name) \ No newline at end of file diff --git a/docs/source/start/examples/get_dialogs.rst b/docs/source/start/examples/get_dialogs.rst deleted file mode 100644 index b1a4806427..0000000000 --- a/docs/source/start/examples/get_dialogs.rst +++ /dev/null @@ -1,14 +0,0 @@ -get_dialogs -=========== - -This example shows how to get the full dialogs list (as user). - -.. code-block:: python - - from pyrogram import Client - - app = Client("my_account") - - with app: - for dialog in app.iter_dialogs(): - print(dialog.chat.title or dialog.chat.first_name) \ No newline at end of file diff --git a/docs/source/start/examples/get_history.rst b/docs/source/start/examples/get_history.rst deleted file mode 100644 index 01433d91fe..0000000000 --- a/docs/source/start/examples/get_history.rst +++ /dev/null @@ -1,15 +0,0 @@ -get_history -=========== - -This example shows how to get the full message history of a chat, starting from the latest message. - -.. code-block:: python - - from pyrogram import Client - - app = Client("my_account") - target = "me" # "me" refers to your own chat (Saved Messages) - - with app: - for message in app.iter_history(target): - print(message.text) \ No newline at end of file diff --git a/docs/source/start/examples/hello_world.rst b/docs/source/start/examples/hello_world.rst deleted file mode 100644 index e68847798a..0000000000 --- a/docs/source/start/examples/hello_world.rst +++ /dev/null @@ -1,21 +0,0 @@ -hello_world -=========== - -This example demonstrates a basic API usage - -.. code-block:: python - - from pyrogram import Client - - # Create a new Client instance - app = Client("my_account") - - with app: - # Send a message, Markdown is enabled by default - app.send_message("me", "Hi there! I'm using **Pyrogram**") - - # Send a location - app.send_location("me", 51.500729, -0.124583) - - # Send a sticker - app.send_sticker("me", "CAADBAADzg4AAvLQYAEz_x2EOgdRwBYE") \ No newline at end of file diff --git a/docs/source/start/examples/index.rst b/docs/source/start/examples/index.rst deleted file mode 100644 index 7d8a69a41d..0000000000 --- a/docs/source/start/examples/index.rst +++ /dev/null @@ -1,46 +0,0 @@ -Examples -======== - -This page contains example scripts to show you how Pyrogram looks like. - -Every script is working right away (provided you correctly set up your credentials), meaning you can simply copy-paste -and run. The only things you have to change are session names and target chats, where applicable. - -The examples listed below can be treated as building blocks for your own applications and are meant to be simple enough -to give you a basic idea. - ------ - -.. csv-table:: - :header: Example, Description - :widths: auto - :align: center - - :doc:`hello_world`, "Demonstration of basic API usage" - :doc:`echobot`, "Echo every private text message" - :doc:`welcomebot`, "The Welcome Bot in @PyrogramChat" - :doc:`get_history`, "Get the full message history of a chat" - :doc:`get_chat_members`, "Get all the members of a chat" - :doc:`get_dialogs`, "Get all of your dialog chats" - :doc:`callback_queries`, "Handle callback queries (as bot) coming from inline button presses" - :doc:`inline_queries`, "Handle inline queries (as bot) and answer with results" - :doc:`use_inline_bots`, "Query an inline bot (as user) and send a result to a chat" - :doc:`bot_keyboards`, "Send normal and inline keyboards using regular bots" - :doc:`raw_updates`, "Handle raw updates (old, should be avoided)" - -For more advanced examples, see https://snippets.pyrogram.org. - -.. toctree:: - :hidden: - - hello_world - echobot - welcomebot - get_history - get_chat_members - get_dialogs - callback_queries - inline_queries - use_inline_bots - bot_keyboards - raw_updates diff --git a/docs/source/start/examples/inline_queries.rst b/docs/source/start/examples/inline_queries.rst deleted file mode 100644 index 023b9c6ed1..0000000000 --- a/docs/source/start/examples/inline_queries.rst +++ /dev/null @@ -1,61 +0,0 @@ -inline_queries -============== - -This example shows how to handle inline queries. - -Two results are generated when users invoke the bot inline mode, e.g.: @pyrogrambot hi. -It uses the @on_inline_query decorator to register an InlineQueryHandler. - -.. code-block:: python - - from pyrogram import Client - from pyrogram.types import (InlineQueryResultArticle, InputTextMessageContent, - InlineKeyboardMarkup, InlineKeyboardButton) - - app = Client("my_bot", bot_token="123456:ABC-DEF1234ghIkl-zyx57W2v1u123ew11") - - - @app.on_inline_query() - def answer(client, inline_query): - inline_query.answer( - results=[ - InlineQueryResultArticle( - title="Installation", - input_message_content=InputTextMessageContent( - "Here's how to install **Pyrogram**" - ), - url="https://docs.pyrogram.org/intro/install", - description="How to install Pyrogram", - thumb_url="https://i.imgur.com/JyxrStE.png", - reply_markup=InlineKeyboardMarkup( - [ - [InlineKeyboardButton( - "Open website", - url="https://docs.pyrogram.org/intro/install" - )] - ] - ) - ), - InlineQueryResultArticle( - title="Usage", - input_message_content=InputTextMessageContent( - "Here's how to use **Pyrogram**" - ), - url="https://docs.pyrogram.org/start/invoking", - description="How to use Pyrogram", - thumb_url="https://i.imgur.com/JyxrStE.png", - reply_markup=InlineKeyboardMarkup( - [ - [InlineKeyboardButton( - "Open website", - url="https://docs.pyrogram.org/start/invoking" - )] - ] - ) - ) - ], - cache_time=1 - ) - - - app.run() # Automatically start() and idle() \ No newline at end of file diff --git a/docs/source/start/examples/raw_updates.rst b/docs/source/start/examples/raw_updates.rst deleted file mode 100644 index 6086a9686c..0000000000 --- a/docs/source/start/examples/raw_updates.rst +++ /dev/null @@ -1,18 +0,0 @@ -raw_updates -=========== - -This example shows how to handle raw updates. - -.. code-block:: python - - from pyrogram import Client - - app = Client("my_account") - - - @app.on_raw_update() - def raw(client, update, users, chats): - print(update) - - - app.run() # Automatically start() and idle() diff --git a/docs/source/start/examples/use_inline_bots.rst b/docs/source/start/examples/use_inline_bots.rst deleted file mode 100644 index 284432d8a0..0000000000 --- a/docs/source/start/examples/use_inline_bots.rst +++ /dev/null @@ -1,18 +0,0 @@ -use_inline_bots -=============== - -This example shows how to query an inline bot (as user). - -.. code-block:: python - - from pyrogram import Client - - # Create a new Client - app = Client("my_account") - - with app: - # Get bot results for "Fuzz Universe" from the inline bot @vid - bot_results = app.get_inline_bot_results("vid", "Fuzz Universe") - - # Send the first result (bot_results.results[0]) to your own chat (Saved Messages) - app.send_inline_bot_result("me", bot_results.query_id, bot_results.results[0].id) \ No newline at end of file diff --git a/docs/source/start/examples/welcomebot.rst b/docs/source/start/examples/welcomebot.rst deleted file mode 100644 index cdd7f022d7..0000000000 --- a/docs/source/start/examples/welcomebot.rst +++ /dev/null @@ -1,31 +0,0 @@ -welcomebot -========== - -This example uses the ``emoji`` module to easily add emoji in your text messages and ``filters`` -to make it only work for specific messages in a specific chat. - -.. code-block:: python - - from pyrogram import Client, emoji, filters - - TARGET = "PyrogramChat" # Target chat. Can also be a list of multiple chat ids/usernames - MENTION = "[{}](tg://user?id={})" # User mention markup - MESSAGE = "{} Welcome to [Pyrogram](https://docs.pyrogram.org/)'s group chat {}!" # Welcome message - - app = Client("my_account") - - - # Filter in only new_chat_members updates generated in TARGET chat - @app.on_message(filters.chat(TARGET) & filters.new_chat_members) - def welcome(client, message): - # Build the new members list (with mentions) by using their first_name - new_members = [u.mention for u in message.new_chat_members] - - # Build the welcome message by using an emoji and the list we built above - text = MESSAGE.format(emoji.SPARKLES, ", ".join(new_members)) - - # Send the welcome message, without the web page preview - message.reply_text(text, disable_web_page_preview=True) - - - app.run() # Automatically start() and idle() \ No newline at end of file diff --git a/docs/source/start/invoking.rst b/docs/source/start/invoking.rst deleted file mode 100644 index b8eddb9e62..0000000000 --- a/docs/source/start/invoking.rst +++ /dev/null @@ -1,119 +0,0 @@ -Calling Methods -=============== - -At this point, we have successfully :doc:`installed Pyrogram <../intro/install>` and :doc:`authorized ` our -account; we are now aiming towards the core of the library. It's time to start playing with the API! - -.. contents:: Contents - :backlinks: none - :depth: 1 - :local: - ------ - -Basic Usage ------------ - -Making API method calls with Pyrogram is very simple. Here's a basic example we are going to examine step by step: - -.. code-block:: python - - from pyrogram import Client - - app = Client("my_account") - - with app: - app.send_message("me", "Hi!") - -Basic step-by-step -^^^^^^^^^^^^^^^^^^ - -#. Let's begin by importing the Client class: - - .. code-block:: python - - from pyrogram import Client - -#. Now instantiate a new Client object, "my_account" is a session name of your choice: - - .. code-block:: python - - app = Client("my_account") - -#. The ``with`` context manager is a shortcut for starting, executing and stopping the Client: - - .. code-block:: python - - with app: - -#. Now, you can call any method you like: - - .. code-block:: python - - app.send_message("me", "Hi!") - -Context Manager ---------------- - -The ``with`` statement starts a context manager used as a shortcut to automatically call :meth:`~pyrogram.Client.start` -and :meth:`~pyrogram.Client.stop`, which are methods required for Pyrogram to work properly. The context manager does -also gracefully stop the client, even in case of unhandled exceptions in your code. - -This is how Pyrogram looks without the context manager: - -.. code-block:: python - - from pyrogram import Client - - app = Client("my_account") - - app.start() - app.send_message("me", "Hi!") - app.stop() - -Asynchronous Calls ------------------- - -In case you want Pyrogram to run asynchronously (e.g.: if you are using third party libraries that require you to call -them with ``await``), use the asynchronous context manager: - -.. code-block:: python - - from pyrogram import Client - - app = Client("my_account") - - async def main(): - async with app: - await app.send_message("me", "Hi!") - - app.run(main()) - -Asynchronous step-by-step -^^^^^^^^^^^^^^^^^^^^^^^^^ - -#. Import the Client class and create an instance: - - .. code-block:: python - - from pyrogram import Client - - app = Client("my_account") - -#. Async methods can't normally be executed at the top level, because they must be inside an async-defined function; - here we define one and put our code inside; the context manager is also being used differently in asyncio and - method calls require the await keyword: - - .. code-block:: python - - async def main(): - async with app: - await app.send_message("me", "Hi!") - -#. Finally, we tell Python to schedule our ``main()`` async function, which in turn will execute Pyrogram's methods. - Using :meth:`~pyrogram.Client.run` this way is a friendly alternative for the much more verbose - ``asyncio.get_event_loop().run_until_complete(main())``: - - .. code-block:: python - - app.run(main()) diff --git a/docs/source/start/updates.rst b/docs/source/start/updates.rst deleted file mode 100644 index 27dd0316b0..0000000000 --- a/docs/source/start/updates.rst +++ /dev/null @@ -1,106 +0,0 @@ -Handling Updates -================ - -Calling :doc:`API methods ` sequentially is cool, but how to react when, for example, a new message arrives? -This page deals with updates and how to handle such events in Pyrogram. Let's have a look at how they work. - -.. contents:: Contents - :backlinks: none - :depth: 1 - :local: - ------ - -Defining Updates ----------------- - -First, let's define what are these updates. As hinted already, updates are simply events that happen in your Telegram -account (incoming messages, new members join, bot button presses, etc...), which are meant to notify you about a new -specific state that has changed. These updates are handled by registering one or more callback functions in your app -using :doc:`Handlers <../api/handlers>`. - -Each handler deals with a specific event and once a matching update arrives from Telegram, your registered callback -function will be called back by the framework and its body executed. - -Registering a Handler ---------------------- - -To explain how handlers work let's examine the one which will be in charge for handling :class:`~pyrogram.types.Message` -updates coming from all around your chats. Every other kind of handler shares the same setup logic and you should not -have troubles settings them up once you learn from this section. - -Using Decorators -^^^^^^^^^^^^^^^^ - -The most elegant way to register a message handler is by using the :meth:`~pyrogram.Client.on_message` decorator: - -.. code-block:: python - - from pyrogram import Client - - app = Client("my_account") - - - @app.on_message() - def my_handler(client, message): - message.forward("me") - - - app.run() - -The defined function ``my_handler``, which accepts the two arguments *(client, message)*, will be the function that gets -executed every time a new message arrives. - -Asynchronous handlers -^^^^^^^^^^^^^^^^^^^^^ - -You can also have asynchronous handlers; you only need to define the callback function using ``async def`` and call API -methods by placing ``await`` in front of them: - -.. code-block:: python - - @app.on_message() - async def my_handler(client, message): - await message.forward("me") - -.. note:: - - You can mix ``def`` and ``async def`` handlers as much as you need, Pyrogram will still work concurrently and - efficiently regardless of what you choose. - -Using add_handler() -^^^^^^^^^^^^^^^^^^^ - -The :meth:`~pyrogram.Client.add_handler` method takes any handler instance that wraps around your defined callback -function and registers it in your Client. It is useful in case you want to programmatically add handlers (or in case, -for some reason, you don't like to use decorators). - -.. code-block:: python - - from pyrogram import Client - from pyrogram.handlers import MessageHandler - - - def my_function(client, message): - message.forward("me") - - - app = Client("my_account") - - my_handler = MessageHandler(my_function) - app.add_handler(my_handler) - - app.run() - -The same about asynchronous handlers applies for :meth:`~pyrogram.Client.add_handler`: - -.. code-block:: python - - async def my_function(client, message): - await message.forward("me") - -.. note:: - - From now on, you'll see examples using synchronous code (i.e.: without ``async`` and ``await``, unless when actually - relevant). This is done to keep snippets concise and more readable. Once you get the idea behind a feature, you can - easily turn examples asynchronous later on. diff --git a/docs/source/support.rst b/docs/source/support.rst deleted file mode 100644 index 029eeec043..0000000000 --- a/docs/source/support.rst +++ /dev/null @@ -1,66 +0,0 @@ -Support Pyrogram -================ - -As a developer, you probably understand that "open source" doesn't mean "free work". If you wish to tip me for Pyrogram --- or any of my `other works`_ -- you can do so by the ways shown below. Your appreciation means a lot and helps -staying motivated! - ---- `Dan`_ - ------ - -Star ----- - -Pyrogram is free and open source software, and thus powered by your love and support! If you like the project and have -found it to be useful, give Pyrogram a `Star on GitHub`_. - -.. raw:: html - - Star -

- ------ - -Sponsor -------- - -You can become a GitHub sponsor: - -.. raw:: html - - - ------ - -Donate ------- - -You can donate via PayPal using the button below: - -.. raw:: html - -
- - - -
- ------ - -Cloud Credits -------------- - -If you need a cloud server to host your applications, try **Hetzner Cloud**. You can sign up with -`this link `_ to get €20 in cloud credits and help support Pyrogram and -my `other projects`_. - -.. _Star on GitHub: https://github.com/pyrogram/pyrogram -.. _other projects: https://github.com/delivrance -.. _other works: https://github.com/delivrance -.. _Dan: https://t.me/haskell diff --git a/docs/source/topics/advanced-usage.rst b/docs/source/topics/advanced-usage.rst deleted file mode 100644 index 9df028ad31..0000000000 --- a/docs/source/topics/advanced-usage.rst +++ /dev/null @@ -1,137 +0,0 @@ -Advanced Usage -============== - -Pyrogram's API, which consists of well documented convenience :doc:`methods <../api/methods/index>` and facade -:doc:`types <../api/types/index>`, exists to provide a much easier interface to the undocumented and often confusing -Telegram API. - -In this section, you'll be shown the alternative way of communicating with Telegram using Pyrogram: the main "raw" -Telegram API with its functions and types. - -.. contents:: Contents - :backlinks: none - :depth: 1 - :local: - ------ - -Telegram Raw API ----------------- - -If you can't find a high-level method for your needs or if you want complete, low-level access to the whole -Telegram API, you have to use the raw :mod:`~pyrogram.raw.functions` and :mod:`~pyrogram.raw.types`. - -As already hinted, raw functions and types can be really confusing, mainly because people don't realize soon enough they -accept *only* the right types and that all required parameters must be filled in. This section will therefore explain -some pitfalls to take into consideration when working with the raw API. - -.. hint:: - - Every available high-level methods in Pyrogram is built on top of these raw functions. - - Nothing stops you from using the raw functions only, but they are rather complex and - :doc:`plenty of them <../api/methods/index>` are already re-implemented by providing a much simpler and cleaner - interface which is very similar to the Bot API (yet much more powerful). - - If you think a raw function should be wrapped and added as a high-level method, feel free to ask in our Community_! - -Invoking Functions -^^^^^^^^^^^^^^^^^^ - -Unlike the :doc:`methods <../api/methods/index>` found in Pyrogram's API, which can be called in the usual simple way, -functions to be invoked from the raw Telegram API have a different way of usage and are more complex. - -First of all, both :doc:`raw functions <../telegram/functions/index>` and :doc:`raw types <../telegram/types/index>` -live in their respective packages (and sub-packages): ``pyrogram.raw.functions``, ``pyrogram.raw.types``. They all exist -as Python classes, meaning you need to create an instance of each every time you need them and fill them in with the -correct values using named arguments. - -Next, to actually invoke the raw function you have to use the :meth:`~pyrogram.Client.send` method provided by the -Client class and pass the function object you created. - -Here's some examples: - -- Update first name, last name and bio: - - .. code-block:: python - - from pyrogram import Client - from pyrogram.raw import functions - - with Client("my_account") as app: - app.send( - functions.account.UpdateProfile( - first_name="Dan", last_name="Tès", - about="Bio written from Pyrogram" - ) - ) - -- Disable links to your account when someone forwards your messages: - - .. code-block:: python - - from pyrogram import Client - from pyrogram.raw import functions, types - - with Client("my_account") as app: - app.send( - functions.account.SetPrivacy( - key=types.PrivacyKeyForwards(), - rules=[types.InputPrivacyValueDisallowAll()] - ) - ) - -- Invite users to your channel/supergroup: - - .. code-block:: python - - from pyrogram import Client - from pyrogram.raw import functions, types - - with Client("my_account") as app: - app.send( - functions.channels.InviteToChannel( - channel=app.resolve_peer(123456789), # ID or Username - users=[ # The users you want to invite - app.resolve_peer(23456789), # By ID - app.resolve_peer("username"), # By username - app.resolve_peer("+393281234567"), # By phone number - ] - ) - ) - -Chat IDs -^^^^^^^^ - -The way Telegram works makes it impossible to directly send a message to a user or a chat by using their IDs only. -Instead, a pair of ``id`` and ``access_hash`` wrapped in a so called ``InputPeer`` is always needed. Pyrogram allows -sending messages with IDs only thanks to cached access hashes. - -There are three different InputPeer types, one for each kind of Telegram entity. -Whenever an InputPeer is needed you must pass one of these: - -- :class:`~pyrogram.raw.types.InputPeerUser` - Users -- :class:`~pyrogram.raw.types.InputPeerChat` - Basic Chats -- :class:`~pyrogram.raw.types.InputPeerChannel` - Either Channels or Supergroups - -But you don't necessarily have to manually instantiate each object because, luckily for you, Pyrogram already provides -:meth:`~pyrogram.Client.resolve_peer` as a convenience utility method that returns the correct InputPeer -by accepting a peer ID only. - -Another thing to take into consideration about chat IDs is the way they are represented: they are all integers and -all positive within their respective raw types. - -Things are different when working with Pyrogram's API because having them in the same space can theoretically lead to -collisions, and that's why Pyrogram (as well as the official Bot API) uses a slightly different representation for each -kind of ID. - -For example, given the ID *123456789*, here's how Pyrogram can tell entities apart: - -- ``+ID`` User: *123456789* -- ``-ID`` Chat: *-123456789* -- ``-100ID`` Channel or Supergroup: *-100123456789* - -So, every time you take a raw ID, make sure to translate it into the correct ID when you want to use it with an -high-level method. - -.. _Community: https://t.me/Pyrogram \ No newline at end of file diff --git a/docs/source/topics/bots-interaction.rst b/docs/source/topics/bots-interaction.rst deleted file mode 100644 index 1fad206988..0000000000 --- a/docs/source/topics/bots-interaction.rst +++ /dev/null @@ -1,50 +0,0 @@ -Bots Interaction -================ - -Users can interact with other bots via plain text messages as well as inline queries. - -.. contents:: Contents - :backlinks: none - :depth: 1 - :local: - ------ - -Inline Bots ------------ - -- If a bot accepts inline queries, you can call it by using - :meth:`~pyrogram.Client.get_inline_bot_results` to get the list of its inline results for a query: - - .. code-block:: python - - # Get bot results for "Fuzz Universe" from the inline bot @vid - bot_results = app.get_inline_bot_results("vid", "Fuzz Universe") - - .. figure:: https://i.imgur.com/IAqLs54.png - :width: 90% - :align: center - :figwidth: 60% - - ``get_inline_bot_results()`` is the equivalent action of writing ``@vid Fuzz Universe`` and getting the - results list. - -- After you retrieved the bot results, you can use - :meth:`~pyrogram.Client.send_inline_bot_result` to send a chosen result to any chat: - - .. code-block:: python - - # Send the first result to your own chat - app.send_inline_bot_result( - "me", - bot_results.query_id, - bot_results.results[0].id - ) - - .. figure:: https://i.imgur.com/wwxr7B7.png - :width: 90% - :align: center - :figwidth: 60% - - ``send_inline_bot_result()`` is the equivalent action of choosing a result from the list and sending it - to a chat. diff --git a/docs/source/topics/config-file.rst b/docs/source/topics/config-file.rst deleted file mode 100644 index 38bb9e3375..0000000000 --- a/docs/source/topics/config-file.rst +++ /dev/null @@ -1,97 +0,0 @@ -Configuration File -================== - -As already mentioned in previous pages, Pyrogram can be configured by the use of an INI file. -This page explains how this file is structured, how to use it and why. - -.. contents:: Contents - :backlinks: none - :depth: 1 - :local: - ------ - -Introduction ------------- - -The idea behind using a configuration file is to help keeping your code free of private settings information such as -the API Key and Proxy, without having you to even deal with how to load such settings. The configuration file, usually -referred as ``config.ini`` file, is automatically loaded from the root of your working directory; all you need to do is -fill in the necessary parts. - -.. note:: - - The configuration file is optional, but recommended. If, for any reason, you prefer not to use it, there's always an - alternative way to configure Pyrogram via Client's parameters. Doing so, you can have full control on how to store - and load your settings (e.g.: from environment variables). - - Settings specified via Client's parameter have higher priority and will override any setting stored in the - configuration file. - - -The config.ini File -------------------- - -By default, Pyrogram will look for a file named ``config.ini`` placed at the root of your working directory, that is, -the same folder of your running script. You can change the name or location of your configuration file by specifying it -in your Client's parameter *config_file*. - -- Replace the default *config.ini* file with *my_configuration.ini*: - - .. code-block:: python - - from pyrogram import Client - - app = Client("my_account", config_file="my_configuration.ini") - - -Configuration Sections ----------------------- - -These are all the sections Pyrogram uses in its configuration file: - -Pyrogram -^^^^^^^^ - -The ``[pyrogram]`` section contains your Telegram API credentials: *api_id* and *api_hash*. - -.. code-block:: ini - - [pyrogram] - api_id = 12345 - api_hash = 0123456789abcdef0123456789abcdef - -`More info about API Key. <../intro/setup#api-keys>`_ - -Proxy -^^^^^ - -The ``[proxy]`` section contains settings about your SOCKS5 proxy. - -.. code-block:: ini - - [proxy] - enabled = True - hostname = 11.22.33.44 - port = 1080 - username = - password = - -`More info about SOCKS5 Proxy. `_ - -Plugins -^^^^^^^ - -The ``[plugins]`` section contains settings about Smart Plugins. - -.. code-block:: ini - - [plugins] - root = plugins - include = - module - folder.module - exclude = - module fn2 - -`More info about Smart Plugins. `_ diff --git a/docs/source/topics/create-filters.rst b/docs/source/topics/create-filters.rst deleted file mode 100644 index 191eeb05b8..0000000000 --- a/docs/source/topics/create-filters.rst +++ /dev/null @@ -1,151 +0,0 @@ -Creating Filters -================ - -Pyrogram already provides lots of built-in :class:`~pyrogram.filters` to work with, but in case you can't find a -specific one for your needs or want to build a custom filter by yourself you can use -:meth:`filters.create() `. - -.. contents:: Contents - :backlinks: none - :depth: 1 - :local: - ------ - -Custom Filters --------------- - -An example to demonstrate how custom filters work is to show how to create and use one for the -:class:`~pyrogram.handlers.CallbackQueryHandler`. Note that callback queries updates are only received by bots as result -of a user pressing an inline button attached to the bot's message; create and :doc:`authorize your bot <../start/auth>`, -then send a message with an inline keyboard to yourself. This allows you to test your filter by pressing the inline -button: - -.. code-block:: python - - from pyrogram.types import InlineKeyboardMarkup, InlineKeyboardButton - - app.send_message( - "username", # Change this to your username or id - "Pyrogram custom filter test", - reply_markup=InlineKeyboardMarkup( - [[InlineKeyboardButton("Press me", "pyrogram")]] - ) - ) - -Basic Filters -------------- - -For this basic filter we will be using only the first parameter of :meth:`~pyrogram.filters.create`. - -The heart of a filter is its callback function, which accepts three arguments *(self, client, update)* and returns -either ``True``, in case you want the update to pass the filter or ``False`` otherwise. - -In this example we are matching the query data to "pyrogram", which means that the filter will only allow callback -queries containing "pyrogram" as data: - -.. code-block:: python - - from pyrogram import filters - - static_data_filter = filters.create(lambda _, __, query: query.data == "pyrogram") - -The first two arguments of the callback function are unused here and because of this we named them using underscores. - -The ``lambda`` operator in python is used to create small anonymous functions and is perfect for this example. The same -can be achieved with a normal function, but we don't really need it as it makes sense only inside the filter's scope: - -.. code-block:: python - - from pyrogram import filters - - def func(_, __, query): - return query.data == "pyrogram" - - static_data_filter = filters.create(func) - -Asynchronous filters are also possible. Sadly, Python itself doesn't have an ``async lambda``, so we are left with -using a named function: - -.. code-block:: python - - from pyrogram import filters - - async def func(_, __, query): - return query.data == "pyrogram" - - static_data_filter = filters.create(func) - -Finally, the filter usage remains the same: - -.. code-block:: python - - @app.on_callback_query(static_data_filter) - def pyrogram_data(_, query): - query.answer("it works!") - -Filters with Arguments ----------------------- - -A much cooler filter would be one that accepts "pyrogram" or any other string as argument at usage time. -A dynamic filter like this will make use of named arguments for the :meth:`~pyrogram.filters.create` method and the -first argument of the callback function, which is a reference to the filter object itself holding the extra data passed -via named arguments. - -This is how a dynamic custom filter looks like: - -.. code-block:: python - - from pyrogram import filters - - def dynamic_data_filter(data): - return filters.create( - lambda flt, _, query: flt.data == query.data, - data=data # "data" kwarg is accessed with "flt.data" above - ) - -And its asynchronous variant: - -.. code-block:: python - - from pyrogram import filters - - def dynamic_data_filter(data): - async def func(flt, _, query): - return flt.data == query.data - - # "data" kwarg is accessed with "flt.data" above - return filters.create(func, data=data) - -And finally its usage: - -.. code-block:: python - - @app.on_callback_query(dynamic_data_filter("pyrogram")) - def pyrogram_data(_, query): - query.answer("it works!") - - -Method Calls Inside Filters ---------------------------- - -The missing piece we haven't covered yet is the second argument of a filter callback function, namely, the ``client`` -argument. This is a reference to the :obj:`~pyrogram.Client` instance that is running the filter and it is useful in -case you would like to make some API calls before deciding whether the filter should allow the update or not: - -.. code-block:: python - - def func(_, client, query): - # r = client.some_api_method() - # check response "r" and decide to return True or False - ... - -Asynchronous filters making API calls work fine as well. Just remember that you need to put ``async`` in front of -function definitions and ``await`` in front of method calls: - -.. code-block:: python - - async def func(_, client, query): - # r = await client.some_api_method() - # check response "r" and decide to return True or False - ... \ No newline at end of file diff --git a/docs/source/topics/debugging.rst b/docs/source/topics/debugging.rst deleted file mode 100644 index ac0b396fd0..0000000000 --- a/docs/source/topics/debugging.rst +++ /dev/null @@ -1,142 +0,0 @@ -Debugging -========= - -When working with the API, chances are you'll stumble upon bugs, get stuck and start wondering how to continue. Nothing -to actually worry about -- that's normal -- and luckily for you, Pyrogram provides some commodities to help you in this. - -.. contents:: Contents - :backlinks: none - :depth: 1 - :local: - ------ - -Caveman Debugging ------------------ - - *The most effective debugging tool is still careful thought, coupled with judiciously placed print statements.* - - -- Brian Kernighan, "Unix for Beginners" (1979) - -Adding ``print()`` statements in crucial parts of your code is by far the most ancient, yet efficient technique for -debugging programs, especially considering the concurrent nature of the framework itself. Pyrogram goodness in this -respect comes with the fact that any object can be nicely printed just by calling ``print(obj)``, thus giving to you -an insight of all its inner details. - -Consider the following code: - -.. code-block:: python - - dan = app.get_users("haskell") - print(dan) # User - -This will show a JSON representation of the object returned by :meth:`~pyrogram.Client.get_users`, which is a -:class:`~pyrogram.types.User` instance, in this case. The output on your terminal will be something similar to this: - -.. code-block:: json - - { - "_": "pyrogram.User", - "id": 23122162, - "is_self": false, - "is_contact": false, - "is_mutual_contact": false, - "is_deleted": false, - "is_bot": false, - "is_verified": false, - "is_restricted": false, - "is_support": false, - "is_scam": false, - "first_name": "Dan", - "status": { - "_": "pyrogram.UserStatus", - "user_id": 23122162, - "recently": true - }, - "username": "haskell", - "language_code": "en", - "photo": { - "_": "pyrogram.ChatPhoto", - "small_file_id": "AQADBAAD8tBgAQAEJjCxGgAEo5IBAAIC", - "big_file_id": "AQADBAAD8tBgAQAEJjCxGgAEpZIBAAEBAg" - } - } - -As you've probably guessed already, Pyrogram objects can be nested. That's how compound data are built, and nesting -keeps going until we are left with base data types only, such as ``str``, ``int``, ``bool``, etc. - -Accessing Attributes --------------------- - -Even though you see a JSON output, it doesn't mean we are dealing with dictionaries; in fact, all Pyrogram types are -full-fledged Python objects and the correct way to access any attribute of them is by using the dot notation ``.``: - -.. code-block:: python - - dan_photo = dan.photo - print(dan_photo) # ChatPhoto - -.. code-block:: json - - { - "_": "pyrogram.ChatPhoto", - "small_file_id": "AQADBAAD8tBgAQAEJjCxGgAEo5IBAAIC", - "big_file_id": "AQADBAAD8tBgAQAEJjCxGgAEpZIBAAEBAg" - } - -However, the bracket notation ``[]`` is also supported, but its usage is discouraged: - -.. warning:: - - Bracket notation in Python is not commonly used for getting/setting object attributes. While it works for Pyrogram - objects, it might not work for anything else and you should not rely on this. - -.. code-block:: python - - dan_photo_big = dan["photo"]["big_file_id"] - print(dan_photo_big) # str - -.. code-block:: text - - AQADBAAD8tBgAQAEJjCxGgAEpZIBAAEBAg - -Checking an Object's Type -------------------------- - -Another thing worth talking about is how to tell and check for an object's type. - -As you noticed already, when printing an object you'll see the special attribute ``"_"``. This is just a visual thing -useful to show humans the object type, but doesn't really exist anywhere; any attempt in accessing it will lead to an -error. The correct way to get the object type is by using the built-in function ``type()``: - -.. code-block:: python - - dan_status = dan.status - print(type(dan_status)) - -.. code-block:: text - - - -And to check if an object is an instance of a given class, you use the built-in function ``isinstance()``: - -.. code-block:: python - :name: this-py - - from pyrogram.types import UserStatus - - dan_status = dan.status - print(isinstance(dan_status, UserStatus)) - -.. code-block:: text - - True - -.. raw:: html - - \ No newline at end of file diff --git a/docs/source/topics/more-on-updates.rst b/docs/source/topics/more-on-updates.rst deleted file mode 100644 index a636008ace..0000000000 --- a/docs/source/topics/more-on-updates.rst +++ /dev/null @@ -1,227 +0,0 @@ -More on Updates -=============== - -Here we'll show some advanced usages when working with :doc:`update handlers <../start/updates>` and -:doc:`filters `. - -.. contents:: Contents - :backlinks: none - :depth: 1 - :local: - ------ - -Handler Groups --------------- - -If you register handlers with overlapping (conflicting) filters, only the first one is executed and any other handler -will be ignored. This is intended by design. - -In order to handle the very same update more than once, you have to register your handler in a different dispatching -group. Dispatching groups hold one or more handlers and are processed sequentially, they are identified by a number -(number 0 being the default) and sorted, that is, a lower group number has a higher priority: - -For example, take these two handlers: - -.. code-block:: python - :emphasize-lines: 1, 6 - - @app.on_message(filters.text | filters.sticker) - def text_or_sticker(client, message): - print("Text or Sticker") - - - @app.on_message(filters.text) - def just_text(client, message): - print("Just Text") - -Here, ``just_text`` is never executed because ``text_or_sticker``, which has been registered first, already handles -texts (``filters.text`` is shared and conflicting). To enable it, register the handler using a different group: - -.. code-block:: python - - @app.on_message(filters.text, group=1) - def just_text(client, message): - print("Just Text") - -Or, if you want ``just_text`` to be executed *before* ``text_or_sticker`` (note ``-1``, which is less than ``0``): - -.. code-block:: python - - @app.on_message(filters.text, group=-1) - def just_text(client, message): - print("Just Text") - -With :meth:`~pyrogram.Client.add_handler` (without decorators) the same can be achieved with: - -.. code-block:: python - - app.add_handler(MessageHandler(just_text, filters.text), -1) - -Update propagation ------------------- - -Registering multiple handlers, each in a different group, becomes useful when you want to handle the same update more -than once. Any incoming update will be sequentially processed by all of your registered functions by respecting the -groups priority policy described above. Even in case any handler raises an unhandled exception, Pyrogram will still -continue to propagate the same update to the next groups until all the handlers are done. Example: - -.. code-block:: python - - @app.on_message(filters.private) - def _(client, message): - print(0) - - - @app.on_message(filters.private, group=1) - def _(client, message): - raise Exception("Unhandled exception!") # Simulate an unhandled exception - - - @app.on_message(filters.private, group=2) - def _(client, message): - print(2) - -All these handlers will handle the same kind of messages, that are, messages sent or received in private chats. -The output for each incoming update will therefore be: - -.. code-block:: text - - 0 - Exception: Unhandled exception! - 2 - -Stop Propagation -^^^^^^^^^^^^^^^^ - -In order to prevent further propagation of an update in the dispatching phase, you can do *one* of the following: - -- Call the update's bound-method ``.stop_propagation()`` (preferred way). -- Manually ``raise StopPropagation`` exception (more suitable for raw updates only). - -.. note:: - - Internally, the propagation is stopped by handling a custom exception. ``.stop_propagation()`` is just an elegant - and intuitive way to ``raise StopPropagation``; this also means that any code coming *after* calling the method - won't be executed as your function just raised an exception to signal the dispatcher not to propagate the - update anymore. - -Example with ``stop_propagation()``: - -.. code-block:: python - - @app.on_message(filters.private) - def _(client, message): - print(0) - - - @app.on_message(filters.private, group=1) - def _(client, message): - print(1) - message.stop_propagation() - - - @app.on_message(filters.private, group=2) - def _(client, message): - print(2) - -Example with ``raise StopPropagation``: - -.. code-block:: python - - from pyrogram import StopPropagation - - @app.on_message(filters.private) - def _(client, message): - print(0) - - - @app.on_message(filters.private, group=1) - def _(client, message): - print(1) - raise StopPropagation - - - @app.on_message(filters.private, group=2) - def _(client, message): - print(2) - -Each handler is registered in a different group, but the handler in group number 2 will never be executed because the -propagation was stopped earlier. The output of both (equivalent) examples will be: - -.. code-block:: text - - 0 - 1 - -Continue Propagation -^^^^^^^^^^^^^^^^^^^^ - -As opposed to `stopping the update propagation <#stop-propagation>`_ and also as an alternative to the -`handler groups <#handler-groups>`_, you can signal the internal dispatcher to continue the update propagation within -**the same group** despite having conflicting filters in the next registered handler. This allows you to register -multiple handlers with overlapping filters in the same group; to let the dispatcher process the next handler you can do -*one* of the following in each handler you want to grant permission to continue: - -- Call the update's bound-method ``.continue_propagation()`` (preferred way). -- Manually ``raise ContinuePropagation`` exception (more suitable for raw updates only). - -.. note:: - - Internally, the propagation is continued by handling a custom exception. ``.continue_propagation()`` is just an - elegant and intuitive way to ``raise ContinuePropagation``; this also means that any code coming *after* calling the - method won't be executed as your function just raised an exception to signal the dispatcher to continue with the - next available handler. - - -Example with ``continue_propagation()``: - -.. code-block:: python - - @app.on_message(filters.private) - def _(client, message): - print(0) - message.continue_propagation() - - - @app.on_message(filters.private) - def _(client, message): - print(1) - message.continue_propagation() - - - @app.on_message(filters.private) - def _(client, message): - print(2) - -Example with ``raise ContinuePropagation``: - -.. code-block:: python - - from pyrogram import ContinuePropagation - - @app.on_message(filters.private) - def _(client, message): - print(0) - raise ContinuePropagation - - - @app.on_message(filters.private) - def _(client, message): - print(1) - raise ContinuePropagation - - - @app.on_message(filters.private) - def _(client, message): - print(2) - -Three handlers are registered in the same group, and all of them will be executed because the propagation was continued -in each handler (except in the last one, where is useless to do so since there is no more handlers after). -The output of both (equivalent) examples will be: - -.. code-block:: text - - 0 - 1 - 2 diff --git a/docs/source/topics/mtproto-vs-botapi.rst b/docs/source/topics/mtproto-vs-botapi.rst deleted file mode 100644 index c73d669219..0000000000 --- a/docs/source/topics/mtproto-vs-botapi.rst +++ /dev/null @@ -1,111 +0,0 @@ -MTProto vs. Bot API -=================== - -Pyrogram is a framework that acts as a fully-fledged Telegram client based on MTProto, and this very feature makes it -already superior to, what is usually called, the official Bot API, in many respects. This page will therefore show you -why Pyrogram might be a better choice for your project by comparing the two APIs, but first, let's make it clear what -actually is the MTProto and the Bot API. - -.. contents:: Contents - :backlinks: none - :depth: 1 - :local: - ------ - -What is the MTProto API? ------------------------- - -`MTProto`_, took alone, is the name of the custom-made, open and encrypted communication protocol created by Telegram -itself --- it's the only protocol used to exchange information between a client and the actual Telegram servers. - -The MTProto **API** on the other hand, is what people, for convenience, call the main Telegram API as a whole. This API -is able to authorize both users and bots and is built on top of the MTProto encryption protocol by means of -`binary data serialized`_ in a specific way, as described by the `TL language`_, and delivered using UDP, TCP or even -HTTP as transport-layer protocol. - -.. _MTProto: https://core.telegram.org/mtproto -.. _binary data serialized: https://core.telegram.org/mtproto/serialize -.. _TL language: https://core.telegram.org/mtproto/TL - -What is the Bot API? --------------------- - -The `Bot API`_ is an HTTP(S) interface for building normal bots using a sub-set of the main MTProto API. Bots are special -accounts that are authorized via tokens instead of phone numbers. The Bot API is built yet again on top of the main -Telegram API, but runs on an intermediate server application that in turn communicates with the actual Telegram servers -using MTProto. - -.. figure:: https://i.imgur.com/WvwBoZo.png - :align: center - -.. _Bot API: https://core.telegram.org/bots/api - -Advantages of the MTProto API ------------------------------ - -Here is a list of all the advantages in using MTProto-based libraries -- such as Pyrogram -- instead of the official -HTTP Bot API. Using Pyrogram you can: - -.. hlist:: - :columns: 1 - - - :guilabel:`+` **Authorize both user and bot identities** - - :guilabel:`--` The Bot API only allows bot accounts - -.. hlist:: - :columns: 1 - - - :guilabel:`+` **Upload & download any file, up to 2000 MiB each (~2 GB)** - - :guilabel:`--` The Bot API allows uploads and downloads of files only up to 50 MB / 20 MB in size (respectively). - -.. hlist:: - :columns: 1 - - - :guilabel:`+` **Has less overhead due to direct connections to Telegram** - - :guilabel:`--` The Bot API uses an intermediate server to handle HTTP requests before they are sent to the actual - Telegram servers. - -.. hlist:: - :columns: 1 - - - :guilabel:`+` **Run multiple sessions at once, up to 10 per account (either bot or user)** - - :guilabel:`--` The Bot API intermediate server will terminate any other session in case you try to use the same - bot again in a parallel connection. - -.. hlist:: - :columns: 1 - - - :guilabel:`+` **Has much more detailed types and powerful methods** - - :guilabel:`--` The Bot API types often miss some useful information about Telegram entities and some of the - methods are limited as well. - -.. hlist:: - :columns: 1 - - - :guilabel:`+` **Obtain information about any message existing in a chat using their ids** - - :guilabel:`--` The Bot API simply doesn't support this - -.. hlist:: - :columns: 1 - - - :guilabel:`+` **Retrieve the whole chat members list of either public or private chats** - - :guilabel:`--` The Bot API simply doesn't support this - -.. hlist:: - :columns: 1 - - - :guilabel:`+` **Receive extra updates, such as the one about a user name change** - - :guilabel:`--` The Bot API simply doesn't support this - -.. hlist:: - :columns: 1 - - - :guilabel:`+` **Has more meaningful errors in case something went wrong** - - :guilabel:`--` The Bot API reports less detailed errors - -.. hlist:: - :columns: 1 - - - :guilabel:`+` **Get API version updates, and thus new features, sooner** - - :guilabel:`--` The Bot API is simply slower in implementing new features diff --git a/docs/source/topics/proxy.rst b/docs/source/topics/proxy.rst deleted file mode 100644 index 7d7e2c88db..0000000000 --- a/docs/source/topics/proxy.rst +++ /dev/null @@ -1,57 +0,0 @@ -SOCKS5 Proxy -============ - -Pyrogram supports proxies with and without authentication. This feature allows Pyrogram to exchange data with Telegram -through an intermediate SOCKS5 proxy server. - -.. contents:: Contents - :backlinks: none - :depth: 1 - :local: - ------ - -Usage ------ - -- To use Pyrogram with a proxy, simply append the following to your ``config.ini`` file and replace the values - with your own settings: - - .. code-block:: ini - - [proxy] - enabled = True - hostname = 11.22.33.44 - port = 1080 - username = - password = - - To enable or disable the proxy without deleting your settings from the config file, - change the ``enabled`` value as follows: - - - ``1``, ``yes``, ``True`` or ``on``: Enables the proxy - - ``0``, ``no``, ``False`` or ``off``: Disables the proxy - -- Alternatively, you can setup your proxy without the need of the ``config.ini`` file by using the *proxy* parameter - in the Client class: - - .. code-block:: python - - from pyrogram import Client - - app = Client( - session_name="example", - proxy=dict( - hostname="11.22.33.44", - port=1080, - username="", - password="" - ) - ) - - app.start() - - ... - -.. note:: If your proxy doesn't require authorization you can omit ``username`` and ``password`` by either leaving the - values blank/empty or completely delete the lines. \ No newline at end of file diff --git a/docs/source/topics/scheduling.rst b/docs/source/topics/scheduling.rst deleted file mode 100644 index f96e7b07f2..0000000000 --- a/docs/source/topics/scheduling.rst +++ /dev/null @@ -1,65 +0,0 @@ -Scheduling Tasks -================ - -Scheduling tasks means executing one or more functions periodically at pre-defined intervals or after a delay. This is -useful, for example, to send recurring messages to specific chats or users. - -This page will show examples on how to integrate Pyrogram with ``apscheduler`` in both asynchronous and -non-asynchronous contexts. For more detailed information, you can visit and learn from the library documentation. - -.. contents:: Contents - :backlinks: none - :depth: 1 - :local: - ------ - -Using ``apscheduler`` ---------------------- - -- Install with ``pip3 install apscheduler`` -- Documentation: https://apscheduler.readthedocs.io - -Asynchronously -^^^^^^^^^^^^^^ - -.. code-block:: python - - from apscheduler.schedulers.asyncio import AsyncIOScheduler - - from pyrogram import Client - - app = Client("my_account") - - - async def job(): - await app.send_message("me", "Hi!") - - - scheduler = AsyncIOScheduler() - scheduler.add_job(job, "interval", seconds=3) - - scheduler.start() - app.run() - -Non-Asynchronously -^^^^^^^^^^^^^^^^^^ - -.. code-block:: python - - from apscheduler.schedulers.background import BackgroundScheduler - - from pyrogram import Client - - app = Client("my_account") - - - def job(): - app.send_message("me", "Hi!") - - - scheduler = BackgroundScheduler() - scheduler.add_job(job, "interval", seconds=3) - - scheduler.start() - app.run() diff --git a/docs/source/topics/serializing.rst b/docs/source/topics/serializing.rst deleted file mode 100644 index 7e01357330..0000000000 --- a/docs/source/topics/serializing.rst +++ /dev/null @@ -1,59 +0,0 @@ -Object Serialization -==================== - -Serializing means converting a Pyrogram object, which exists as Python class instance, to a text string that can be -easily shared and stored anywhere. Pyrogram provides two formats for serializing its objects: one good looking for -humans and another more compact for machines that is able to recover the original structures. - -.. contents:: Contents - :backlinks: none - :depth: 1 - :local: - ------ - -For Humans - str(obj) ---------------------- - -If you want a nicely formatted, human readable JSON representation of any object in the API -- namely, any object from -:doc:`Pyrogram types <../api/types/index>`, :doc:`raw functions <../telegram/functions/index>` and -:doc:`raw types <../telegram/types/index>` -- you can use ``str(obj)``. - -.. code-block:: python - - ... - - with app: - r = app.get_chat("haskell") - - print(str(r)) - -.. tip:: - - When using ``print()`` you don't actually need to use ``str()`` on the object because it is called automatically, we - have done that above just to show you how to explicitly convert a Pyrogram object to JSON. - -For Machines - repr(obj) ------------------------- - -If you want to share or store objects for future references in a more compact way, you can use ``repr(obj)``. While -still pretty much readable, this format is not intended for humans. The advantage of this format is that once you -serialize your object, you can use ``eval()`` to get back the original structure; just make sure to ``import pyrogram``, -as the process requires the package to be in scope. - -.. code-block:: python - - import pyrogram - - ... - - with app: - r = app.get_chat("haskell") - - print(repr(r)) - print(eval(repr(r)) == r) # True - -.. note:: - - Type definitions are subject to changes between versions. You should make sure to store and load objects using the - same Pyrogram version. \ No newline at end of file diff --git a/docs/source/topics/session-settings.rst b/docs/source/topics/session-settings.rst deleted file mode 100644 index 847427cf25..0000000000 --- a/docs/source/topics/session-settings.rst +++ /dev/null @@ -1,72 +0,0 @@ -Session Settings -================ - -As you may probably know, Telegram allows users (and bots) having more than one session (authorizations) registered -in the system at the same time. - -Briefly explaining, sessions are simply new logins in your account. They can be reviewed in the settings of an official -app (or by invoking :class:`~pyrogram.api.functions.account.GetAuthorizations` with Pyrogram). They -store some useful information such as the client who's using them and from which country and IP address. - -.. figure:: https://i.imgur.com/YaqtMLO.png - :width: 600 - :align: center - - **A Pyrogram session running on Linux, Python 3.7.** - -That's how a session looks like on the Android app, showing the three main pieces of information. - -- ``app_version``: **Pyrogram 0.13.0** -- ``device_model``: **CPython 3.7.2** -- ``system_version``: **Linux 4.15.0-23-generic** - -.. contents:: Contents - :backlinks: none - :depth: 1 - :local: - ------ - -Set Custom Values ------------------ - -To set custom values, you can either make use of the ``config.ini`` file, this way: - -.. code-block:: ini - - [pyrogram] - app_version = 1.2.3 - device_model = PC - system_version = Linux - -Or, pass the arguments directly in the Client's constructor. - -.. code-block:: python - - app = Client( - "my_account", - app_version="1.2.3", - device_model="PC", - system_version="Linux" - ) - -Set Custom Languages --------------------- - -To tell Telegram in which language should speak to you (terms of service, bots, service messages, ...) you can -set ``lang_code`` in `ISO 639-1 `_ standard (defaults to "en", -English). - -With the following code we make Telegram know we want it to speak in Italian (it): - -.. code-block:: ini - - [pyrogram] - lang_code = it - -.. code-block:: python - - app = Client( - "my_account", - lang_code="it", - ) \ No newline at end of file diff --git a/docs/source/topics/smart-plugins.rst b/docs/source/topics/smart-plugins.rst deleted file mode 100644 index fece636622..0000000000 --- a/docs/source/topics/smart-plugins.rst +++ /dev/null @@ -1,362 +0,0 @@ -Smart Plugins -============= - -Pyrogram embeds a **smart**, lightweight yet powerful plugin system that is meant to further simplify the organization -of large projects and to provide a way for creating pluggable (modular) components that can be **easily shared** across -different Pyrogram applications with **minimal boilerplate code**. - -.. tip:: - - Smart Plugins are completely optional and disabled by default. - -.. contents:: Contents - :backlinks: none - :depth: 1 - :local: - ------ - -Introduction ------------- - -Prior to the Smart Plugin system, pluggable handlers were already possible. For example, if you wanted to modularize -your applications, you had to put your function definitions in separate files and register them inside your main script -after importing your modules, like this: - -.. note:: - - This is an example application that replies in private chats with two messages: one containing the same - text message you sent and the other containing the reversed text message. - - Example: *"Pyrogram"* replies with *"Pyrogram"* and *"margoryP"* - -.. code-block:: text - - myproject/ - config.ini - handlers.py - main.py - -- ``handlers.py`` - - .. code-block:: python - - def echo(client, message): - message.reply(message.text) - - - def echo_reversed(client, message): - message.reply(message.text[::-1]) - -- ``main.py`` - - .. code-block:: python - - from pyrogram import Client, filters - from pyrogram.handlers import MessageHandler - - from handlers import echo, echo_reversed - - app = Client("my_account") - - app.add_handler( - MessageHandler( - echo, - filters.text & filters.private)) - - app.add_handler( - MessageHandler( - echo_reversed, - filters.text & filters.private), - group=1) - - app.run() - -This is already nice and doesn't add *too much* boilerplate code, but things can get boring still; you have to -manually ``import``, manually :meth:`~pyrogram.Client.add_handler` and manually instantiate each -:class:`~pyrogram.handlers.MessageHandler` object because **you can't use those cool decorators** for your -functions. So, what if you could? Smart Plugins solve this issue by taking care of handlers registration automatically. - -Using Smart Plugins -------------------- - -Setting up your Pyrogram project to accommodate Smart Plugins is pretty straightforward: - -#. Create a new folder to store all the plugins (e.g.: "plugins", "handlers", ...). -#. Put your python files full of plugins inside. Organize them as you wish. -#. Enable plugins in your Client or via the *config.ini* file. - -.. note:: - - This is the same example application as shown above, written using the Smart Plugin system. - -.. code-block:: text - :emphasize-lines: 2, 3 - - myproject/ - plugins/ - handlers.py - config.ini - main.py - -- ``plugins/handlers.py`` - - .. code-block:: python - :emphasize-lines: 4, 9 - - from pyrogram import Client, filters - - - @Client.on_message(filters.text & filters.private) - def echo(client, message): - message.reply(message.text) - - - @Client.on_message(filters.text & filters.private, group=1) - def echo_reversed(client, message): - message.reply(message.text[::-1]) - -- ``config.ini`` - - .. code-block:: ini - - [plugins] - root = plugins - -- ``main.py`` - - .. code-block:: python - - from pyrogram import Client - - Client("my_account").run() - - Alternatively, without using the *config.ini* file: - - .. code-block:: python - - from pyrogram import Client - - plugins = dict(root="plugins") - - Client("my_account", plugins=plugins).run() - - -The first important thing to note is the new ``plugins`` folder. You can put *any python file* in *any subfolder* and -each file can contain *any decorated function* (handlers) with one limitation: within a single module (file) you must -use different names for each decorated function. - -The second thing is telling Pyrogram where to look for your plugins: you can either use the *config.ini* file or -the Client parameter "plugins"; the *root* value must match the name of your plugins root folder. Your Pyrogram Client -instance will **automatically** scan the folder upon starting to search for valid handlers and register them for you. - -Then you'll notice you can now use decorators. That's right, you can apply the usual decorators to your callback -functions in a static way, i.e. **without having the Client instance around**: simply use ``@Client`` (Client class) -instead of the usual ``@app`` (Client instance) and things will work just the same. - -Specifying the Plugins to include ---------------------------------- - -By default, if you don't explicitly supply a list of plugins, every valid one found inside your plugins root folder will -be included by following the alphabetical order of the directory structure (files and subfolders); the single handlers -found inside each module will be, instead, loaded in the order they are defined, from top to bottom. - -.. note:: - - Remember: there can be at most one handler, within a group, dealing with a specific update. Plugins with overlapping - filters included a second time will not work. Learn more at :doc:`More on Updates `. - -This default loading behaviour is usually enough, but sometimes you want to have more control on what to include (or -exclude) and in which exact order to load plugins. The way to do this is to make use of ``include`` and ``exclude`` -directives, either in the *config.ini* file or in the dictionary passed as Client argument. Here's how they work: - -- If both ``include`` and ``exclude`` are omitted, all plugins are loaded as described above. -- If ``include`` is given, only the specified plugins will be loaded, in the order they are passed. -- If ``exclude`` is given, the plugins specified here will be unloaded. - -The ``include`` and ``exclude`` value is a **list of strings**. Each string containing the path of the module relative -to the plugins root folder, in Python notation (dots instead of slashes). - - E.g.: ``subfolder.module`` refers to ``plugins/subfolder/module.py``, with ``root="plugins"``. - -You can also choose the order in which the single handlers inside a module are loaded, thus overriding the default -top-to-bottom loading policy. You can do this by appending the name of the functions to the module path, each one -separated by a blank space. - - E.g.: ``subfolder.module fn2 fn1 fn3`` will load *fn2*, *fn1* and *fn3* from *subfolder.module*, in this order. - -Examples -^^^^^^^^ - -Given this plugins folder structure with three modules, each containing their own handlers (fn1, fn2, etc...), which are -also organized in subfolders: - -.. code-block:: text - - myproject/ - plugins/ - subfolder1/ - plugins1.py - - fn1 - - fn2 - - fn3 - subfolder2/ - plugins2.py - ... - plugins0.py - ... - ... - -- Load every handler from every module, namely *plugins0.py*, *plugins1.py* and *plugins2.py* in alphabetical order - (files) and definition order (handlers inside files): - - Using *config.ini* file: - - .. code-block:: ini - - [plugins] - root = plugins - - Using *Client*'s parameter: - - .. code-block:: python - - plugins = dict(root="plugins") - - Client("my_account", plugins=plugins).run() - -- Load only handlers defined inside *plugins2.py* and *plugins0.py*, in this order: - - Using *config.ini* file: - - .. code-block:: ini - - [plugins] - root = plugins - include = - subfolder2.plugins2 - plugins0 - - Using *Client*'s parameter: - - .. code-block:: python - - plugins = dict( - root="plugins", - include=[ - "subfolder2.plugins2", - "plugins0" - ] - ) - - Client("my_account", plugins=plugins).run() - -- Load everything except the handlers inside *plugins2.py*: - - Using *config.ini* file: - - .. code-block:: ini - - [plugins] - root = plugins - exclude = subfolder2.plugins2 - - Using *Client*'s parameter: - - .. code-block:: python - - plugins = dict( - root="plugins", - exclude=["subfolder2.plugins2"] - ) - - Client("my_account", plugins=plugins).run() - -- Load only *fn3*, *fn1* and *fn2* (in this order) from *plugins1.py*: - - Using *config.ini* file: - - .. code-block:: ini - - [plugins] - root = plugins - include = subfolder1.plugins1 fn3 fn1 fn2 - - Using *Client*'s parameter: - - .. code-block:: python - - plugins = dict( - root="plugins", - include=["subfolder1.plugins1 fn3 fn1 fn2"] - ) - - Client("my_account", plugins=plugins).run() - -Load/Unload Plugins at Runtime ------------------------------- - -In the previous section we've explained how to specify which plugins to load and which to ignore before your Client -starts. Here we'll show, instead, how to unload and load again a previously registered plugin at runtime. - -Each function decorated with the usual ``on_message`` decorator (or any other decorator that deals with Telegram -updates) will be modified in such a way that a special ``handler`` attribute pointing to a tuple of -*(handler: Handler, group: int)* is attached to the function object itself. - -- ``plugins/handlers.py`` - - .. code-block:: python - :emphasize-lines: 5, 6 - - @Client.on_message(filters.text & filters.private) - def echo(client, message): - message.reply(message.text) - - print(echo) - print(echo.handler) - -- Printing ``echo`` will show something like ````. - -- Printing ``echo.handler`` will reveal the handler, that is, a tuple containing the actual handler and the group it - was registered on ``(, 0)``. - -Unloading -^^^^^^^^^ - -In order to unload a plugin, all you need to do is obtain a reference to it by importing the relevant module and call -:meth:`~pyrogram.Client.remove_handler` Client's method with your function's *handler* special attribute preceded by the -star ``*`` operator as argument. Example: - -- ``main.py`` - - .. code-block:: python - - from plugins.handlers import echo - - ... - - app.remove_handler(*echo.handler) - -The star ``*`` operator is used to unpack the tuple into positional arguments so that *remove_handler* will receive -exactly what is needed. The same could have been achieved with: - -.. code-block:: python - - handler, group = echo.handler - app.remove_handler(handler, group) - -Loading -^^^^^^^ - -Similarly to the unloading process, in order to load again a previously unloaded plugin you do the same, but this time -using :meth:`~pyrogram.Client.add_handler` instead. Example: - -- ``main.py`` - - .. code-block:: python - - from plugins.handlers import echo - - ... - - app.add_handler(*echo.handler) \ No newline at end of file diff --git a/docs/source/topics/storage-engines.rst b/docs/source/topics/storage-engines.rst deleted file mode 100644 index 1a86cad8b5..0000000000 --- a/docs/source/topics/storage-engines.rst +++ /dev/null @@ -1,106 +0,0 @@ -Storage Engines -=============== - -Every time you login to Telegram, some personal piece of data are created and held by both parties (the client, Pyrogram -and the server, Telegram). This session data is uniquely bound to your own account, indefinitely (until you logout or -decide to manually terminate it) and is used to authorize a client to execute API calls on behalf of your identity. - -.. contents:: Contents - :backlinks: none - :depth: 1 - :local: - ------ - -Persisting Sessions -------------------- - -In order to make a client reconnect successfully between restarts, that is, without having to start a new -authorization process from scratch each time, Pyrogram needs to store the generated session data somewhere. - -Other useful data being stored is peers' cache. In short, peers are all those entities you can chat with, such as users -or bots, basic groups, but also channels and supergroups. Because of how Telegram works, a unique pair of **id** and -**access_hash** is needed to contact a peer. This, plus other useful info such as the peer type, is what is stored -inside a session storage. - -So, if you ever wondered how is Pyrogram able to contact peers just by asking for their ids, it's because of this very -reason: the peer *id* is looked up in the internal database and the available *access_hash* is retrieved, which is then -used to correctly invoke API methods. - -Different Storage Engines -------------------------- - -Let's now talk about how Pyrogram actually stores all the relevant data. Pyrogram offers two different types of storage -engines: a **File Storage** and a **Memory Storage**. These engines are well integrated in the library and require a -minimal effort to set up. Here's how they work: - -File Storage -^^^^^^^^^^^^ - -This is the most common storage engine. It is implemented by using **SQLite**, which will store the session and peers -details. The database will be saved to disk as a single portable file and is designed to efficiently store and retrieve -peers whenever they are needed. - -To use this type of engine, simply pass any name of your choice to the ``session_name`` parameter of the -:obj:`~pyrogram.Client` constructor, as usual: - -.. code-block:: python - - from pyrogram import Client - - with Client("my_account") as app: - print(app.get_me()) - -Once you successfully log in (either with a user or a bot identity), a session file will be created and saved to disk as -``my_account.session``. Any subsequent client restart will make Pyrogram search for a file named that way and the -session database will be automatically loaded. - -Memory Storage -^^^^^^^^^^^^^^ - -In case you don't want to have any session file saved to disk, you can use an in-memory storage by passing the special -session name "**:memory:**" to the ``session_name`` parameter of the :obj:`~pyrogram.Client` constructor: - -.. code-block:: python - - from pyrogram import Client - - with Client(":memory:") as app: - print(app.get_me()) - -This storage engine is still backed by SQLite, but the database exists purely in memory. This means that, once you stop a -client, the entire database is discarded and the session details used for logging in again will be lost forever. - -Session Strings ---------------- - -In case you want to use an in-memory storage, but also want to keep access to the session you created, call -:meth:`~pyrogram.Client.export_session_string` anytime before stopping the client... - -.. code-block:: python - - from pyrogram import Client - - with Client(":memory:") as app: - print(app.export_session_string()) - -...and save the resulting (quite long) string somewhere. You can use this string as session name the next time you want -to login using the same session; the storage used will still be completely in-memory: - -.. code-block:: python - - from pyrogram import Client - - session_string = "...ZnUIFD8jsjXTb8g_vpxx48k1zkov9sapD-tzjz-S4WZv70M..." - - with Client(session_string) as app: - print(app.get_me()) - -Session strings are useful when you want to run authorized Pyrogram clients on platforms like -`Heroku `_, where their ephemeral filesystems makes it much harder for a file-based storage -engine to properly work as intended. - -But, why is the session string so long? Can't it be shorter? No, it can't. The session string already packs the bare -minimum data Pyrogram needs to successfully reconnect to an authorized session, and the 2048-bits auth key is the major -contributor to the overall length. Needless to say that this string, as well as any other session storage, represent -strictly personal data. Keep them safe. diff --git a/docs/source/topics/test-servers.rst b/docs/source/topics/test-servers.rst deleted file mode 100644 index 3ed996eea9..0000000000 --- a/docs/source/topics/test-servers.rst +++ /dev/null @@ -1,46 +0,0 @@ -Test Servers -============ - -If you wish to test your application in a separate environment, Pyrogram is able to authorize your account into -Telegram's test servers without hassle. All you need to do is start a new session (e.g.: "my_account_test") using -``test_mode=True``: - -.. code-block:: python - - from pyrogram import Client - - with Client("my_account_test", test_mode=True) as app: - print(app.get_me()) - -.. note:: - - If this is the first time you login into test servers, you will be asked to register your account first. - Don't worry about your contacts and chats, they will be kept untouched inside the production environment; - accounts authorized on test servers reside in a different, parallel instance of a Telegram database. - -.. contents:: Contents - :backlinks: none - :depth: 1 - :local: - ------ - -Test Mode in Official Apps --------------------------- - -You can also login yourself into test servers using official desktop apps, such as Webogram and TDesktop: - -- **Webogram**: Login here: https://web.telegram.org/?test=1 -- **TDesktop**: Hold ``Alt+Shift`` and right click on "Add account", then choose "Test server". - -Test Numbers ------------- - -Beside normal numbers, the test environment allows you to login with reserved test numbers. -Valid phone numbers follow the pattern ``99966XYYYY``, where ``X`` is the DC number (1 to 3) and ``YYYY`` are random -numbers. Users with such numbers always get ``XXXXX`` as the confirmation code (the DC number, repeated five times). - -.. important:: - - Do not store any important or private information in such test users' accounts; anyone can make use of the - simplified authorization mechanism and login at any time. diff --git a/docs/source/topics/text-formatting.rst b/docs/source/topics/text-formatting.rst deleted file mode 100644 index 5d9a9376c2..0000000000 --- a/docs/source/topics/text-formatting.rst +++ /dev/null @@ -1,232 +0,0 @@ -Text Formatting -=============== - -.. role:: strike - :class: strike - -.. role:: underline - :class: underline - -.. role:: bold-underline - :class: bold-underline - -.. role:: strike-italic - :class: strike-italic - -Pyrogram uses a custom Markdown dialect for text formatting which adds some unique features that make writing styled -texts easier in both Markdown and HTML. You can send sophisticated text messages and media captions using a great -variety of decorations that can also be nested in order to combine multiple styles together. - -.. contents:: Contents - :backlinks: none - :depth: 1 - :local: - ------ - -Basic Styles ------------- - -When formatting your messages, you can choose between Markdown-style, HTML-style or both (default). The following is a -list of the basic styles currently supported by Pyrogram. - -- **bold** -- *italic* -- :strike:`strike` -- :underline:`underline` -- `text URL `_ -- `user text mention `_ -- ``inline fixed-width code`` -- .. code-block:: text - - pre-formatted - fixed-width - code block - -.. note:: - - User text mentions are only guaranteed to work if you have already met the user (in groups or private chats). - -Markdown Style --------------- - -To strictly use this mode, pass "markdown" to the *parse_mode* parameter when using -:meth:`~pyrogram.Client.send_message`. Use the following syntax in your message: - -.. code-block:: text - - **bold** - - __italic__ - - --underline-- - - ~~strike~~ - - [text URL](https://docs.pyrogram.org/) - - [text user mention](tg://user?id=23122162) - - `inline fixed-width code` - - ``` - pre-formatted - fixed-width - code block - ``` - -**Example**: - -.. code-block:: python - - app.send_message( - "haskell", - ( - "**bold**, " - "__italic__, " - "--underline--, " - "~~strike~~, " - "[mention](tg://user?id=23122162), " - "[URL](https://pyrogram.org), " - "`code`, " - "```" - "for i in range(10):\n" - " print(i)" - "```" - ), - parse_mode="markdown" - ) - -HTML Style ----------- - -To strictly use this mode, pass "html" to the *parse_mode* parameter when using :meth:`~pyrogram.Client.send_message`. -The following tags are currently supported: - -.. code-block:: text - - bold, bold - - italic, italic - - underline - - strike, strike, strike - - text URL - - inline mention - - inline fixed-width code - -
-    pre-formatted
-      fixed-width
-        code block
-    
- -**Example**: - -.. code-block:: python - - app.send_message( - "haskell", - ( - "bold, " - "italic, " - "underline, " - "strike, " - "mention, " - "URL, " - "code\n\n" - "
"
-            "for i in range(10):\n"
-            "    print(i)"
-            "
" - ), - parse_mode="html" - ) - -.. note:: - - All ``<``, ``>`` and ``&`` symbols that are not a part of a tag or an HTML entity must be replaced with the - corresponding HTML entities (``<`` with ``<``, ``>`` with ``>`` and ``&`` with ``&``). You can use this - snippet to quickly escape those characters: - - .. code-block:: python - - import html - - text = "" - text = html.escape(text) - - print(text) - - .. code-block:: text - - <my text> - -Different Styles ----------------- - -By default, when ignoring the *parse_mode* parameter, both Markdown and HTML styles are enabled together. -This means you can combine together both syntaxes in the same text: - -.. code-block:: python - - app.send_message("haskell", "**bold**, italic") - -Result: - - **bold**, *italic* - -If you don't like this behaviour you can always choose to only enable either Markdown or HTML in strict mode by passing -"markdown" or "html" as argument to the *parse_mode* parameter. - -.. code-block:: - - app.send_message("haskell", "**bold**, italic", parse_mode="markdown") - app.send_message("haskell", "**bold**, italic", parse_mode="html") - -Result: - - **bold**, italic - - \*\*bold**, *italic* - -In case you want to completely turn off the style parser, simply pass ``None`` to *parse_mode*. The text will be sent -as-is. - -.. code-block:: python - - app.send_message("haskell", "**bold**, italic", parse_mode=None) - -Result: - - \*\*bold**, italic - -Nested and Overlapping Entities -------------------------------- - -You can also style texts with more than one decoration at once by nesting entities together. For example, you can send -a text message with both :bold-underline:`bold and underline` styles, or a text that has both :strike-italic:`italic and -strike` styles, and you can still combine both Markdown and HTML together. - -Here there are some example texts you can try sending: - -**Markdown**: - -- ``**bold, --underline--**`` -- ``**bold __italic --underline ~~strike~~--__**`` -- ``**bold __and** italic__`` - -**HTML**: - -- ``bold, underline`` -- ``bold italic underline strike`` -- ``bold and italic`` - -**Combined**: - -- ``--you can combine HTML with **Markdown**--`` -- ``**and also overlap** --entities this way--`` diff --git a/docs/source/topics/tgcrypto.rst b/docs/source/topics/tgcrypto.rst deleted file mode 100644 index e0d9beb67f..0000000000 --- a/docs/source/topics/tgcrypto.rst +++ /dev/null @@ -1,32 +0,0 @@ -Fast Crypto -=========== - -Pyrogram's speed can be *dramatically* boosted up by TgCrypto_, a high-performance, easy-to-install Telegram Crypto -Library specifically written in C for Pyrogram [1]_ as a Python extension. - -TgCrypto is a replacement for the much slower PyAES and implements the crypto algorithms Telegram requires, namely -**AES-IGE 256 bit** (used in MTProto v2.0) and **AES-CTR 256 bit** (used for CDN encrypted files). - -Installation ------------- - -.. code-block:: bash - - $ pip3 install -U tgcrypto - -.. note:: Being a C extension for Python, TgCrypto is an optional but *highly recommended* dependency; when TgCrypto is - not detected in your system, Pyrogram will automatically fall back to PyAES and will show you a warning. - -The reason about being an optional package is that TgCrypto requires some extra system tools in order to be compiled. -The errors you receive when trying to install TgCrypto are system dependent, but also descriptive enough to understand -what you should do next: - -- **Windows**: Install `Visual C++ 2015 Build Tools `_. -- **macOS**: A pop-up will automatically ask you to install the command line developer tools. -- **Linux**: Install a proper C compiler (``gcc``, ``clang``) and the Python header files (``python3-dev``). -- **Termux**: Install ``clang`` package. - -.. _TgCrypto: https://github.com/pyrogram/tgcrypto - -.. [1] Although TgCrypto is intended for Pyrogram, it is shipped as a standalone package and can thus be used for - other Python projects too. diff --git a/docs/source/topics/use-filters.rst b/docs/source/topics/use-filters.rst deleted file mode 100644 index 7e5219e14f..0000000000 --- a/docs/source/topics/use-filters.rst +++ /dev/null @@ -1,116 +0,0 @@ -Using Filters -============= - -So far we've seen :doc:`how to register a callback function <../start/updates>` that executes every time an update comes -from the server, but there's much more than that to come. - -Here we'll discuss about :obj:`~pyrogram.filters`. Filters enable a fine-grain control over what kind of -updates are allowed or not to be passed in your callback functions, based on their inner details. - -.. contents:: Contents - :backlinks: none - :depth: 1 - :local: - ------ - -Single Filters --------------- - -Let's start right away with a simple example: - -- This example will show you how to **only** handle messages containing an :class:`~pyrogram.Audio` object and - ignore any other message. Filters are passed as the first argument of the decorator: - - .. code-block:: python - :emphasize-lines: 4 - - from pyrogram import filters - - - @app.on_message(filters.audio) - def my_handler(client, message): - print(message) - -- or, without decorators. Here filters are passed as the second argument of the handler constructor; the first is the - callback function itself: - - .. code-block:: python - :emphasize-lines: 9 - - from pyrogram import filters - from pyrogram.handlers import MessageHandler - - - def my_handler(client, message): - print(message) - - - app.add_handler(MessageHandler(my_handler, filters.audio)) - -Combining Filters ------------------ - -Filters can also be used in a more advanced way by inverting and combining more filters together using bitwise -operators ``~``, ``&`` and ``|``: - -- Use ``~`` to invert a filter (behaves like the ``not`` operator). -- Use ``&`` and ``|`` to merge two filters (behave like ``and``, ``or`` operators respectively). - -Here are some examples: - -- Message is a **text** message **and** is **not edited**. - - .. code-block:: python - - @app.on_message(filters.text & ~filters.edited) - def my_handler(client, message): - print(message) - -- Message is a **sticker** **and** is coming from a **channel or** a **private** chat. - - .. code-block:: python - - @app.on_message(filters.sticker & (filters.channel | filters.private)) - def my_handler(client, message): - print(message) - -Advanced Filters ----------------- - -Some filters, like :meth:`~pyrogram.filters.command` or :meth:`~pyrogram.filters.regex` -can also accept arguments: - -- Message is either a */start* or */help* **command**. - - .. code-block:: python - - @app.on_message(filters.command(["start", "help"])) - def my_handler(client, message): - print(message) - -- Message is a **text** message or a media **caption** matching the given **regex** pattern. - - .. code-block:: python - - @app.on_message(filters.regex("pyrogram")) - def my_handler(client, message): - print(message) - -More handlers using different filters can also live together. - -.. code-block:: python - - @app.on_message(filters.command("start")) - def start_command(client, message): - print("This is the /start command") - - - @app.on_message(filters.command("help")) - def help_command(client, message): - print("This is the /help command") - - - @app.on_message(filters.chat("PyrogramChat")) - def from_pyrogramchat(client, message): - print("New message in @PyrogramChat") diff --git a/docs/source/topics/voice-calls.rst b/docs/source/topics/voice-calls.rst deleted file mode 100644 index 19950bf40e..0000000000 --- a/docs/source/topics/voice-calls.rst +++ /dev/null @@ -1,19 +0,0 @@ -Voice Calls -=========== - -Both private voice calls and group voice calls are currently supported by third-party libraries that integrate with -Pyrogram. - -Libraries ---------- - -There are currently two main libraries (with very similar names) you can use: - -1. https://github.com/pytgcalls/pytgcalls -2. https://github.com/MarshalX/tgcalls - -Older implementations ---------------------- - -An older implementation of Telegram voice calls can be found at https://github.com/bakatrouble/pylibtgvoip (currently -outdated due to the deprecation of the Telegram VoIP library used underneath). \ No newline at end of file diff --git a/pyrogram/__init__.py b/pyrogram/__init__.py index c0c7b21916..9a6a44accb 100644 --- a/pyrogram/__init__.py +++ b/pyrogram/__init__.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2021 Dan +# Copyright (C) 2017-present Dan # # This file is part of Pyrogram. # @@ -16,14 +16,14 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . -__version__ = "1.2.9" -__license__ = "GNU Lesser General Public License v3 or later (LGPLv3+)" -__copyright__ = "Copyright (C) 2017-2021 Dan " +__version__ = "2.0.106" +__license__ = "GNU Lesser General Public License v3.0 (LGPL-3.0)" +__copyright__ = "Copyright (C) 2017-present Dan " from concurrent.futures.thread import ThreadPoolExecutor -class StopTransmission(StopAsyncIteration): +class StopTransmission(Exception): pass @@ -35,15 +35,8 @@ class ContinuePropagation(StopAsyncIteration): pass -import asyncio - -from . import raw, types, filters, handlers, emoji +from . import raw, types, filters, handlers, emoji, enums from .client import Client -from .sync import idle - -# Save the main thread loop for future references -main_event_loop = asyncio.get_event_loop() - -CRYPTO_EXECUTOR_SIZE_THRESHOLD = 512 +from .sync import idle, compose crypto_executor = ThreadPoolExecutor(1, thread_name_prefix="CryptoWorker") diff --git a/pyrogram/client.py b/pyrogram/client.py index b54794fad2..c74634ea8e 100644 --- a/pyrogram/client.py +++ b/pyrogram/client.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2021 Dan +# Copyright (C) 2017-present Dan # # This file is part of Pyrogram. # @@ -21,84 +21,85 @@ import inspect import logging import os +import platform import re import shutil -import tempfile +import sys from concurrent.futures.thread import ThreadPoolExecutor -from configparser import ConfigParser +from datetime import datetime, timedelta from hashlib import sha256 from importlib import import_module +from io import StringIO, BytesIO +from mimetypes import MimeTypes from pathlib import Path -from typing import Union, List, Optional +from typing import Union, List, Optional, Callable, AsyncGenerator import pyrogram +from pyrogram import __version__, __license__ +from pyrogram import enums from pyrogram import raw from pyrogram import utils from pyrogram.crypto import aes +from pyrogram.errors import CDNFileHashMismatch from pyrogram.errors import ( SessionPasswordNeeded, VolumeLocNotFound, ChannelPrivate, - AuthBytesInvalid, BadRequest + BadRequest ) from pyrogram.handlers.handler import Handler from pyrogram.methods import Methods from pyrogram.session import Auth, Session -from pyrogram.storage import Storage, FileStorage, MemoryStorage +from pyrogram.storage import FileStorage, MemoryStorage from pyrogram.types import User, TermsOfService from pyrogram.utils import ainput from .dispatcher import Dispatcher from .file_id import FileId, FileType, ThumbnailSource -from .scaffold import Scaffold +from .mime_types import mime_types +from .parser import Parser +from .session.internals import MsgId log = logging.getLogger(__name__) -class Client(Methods, Scaffold): +class Client(Methods): """Pyrogram Client, the main means for interacting with Telegram. Parameters: - session_name (``str``): - Pass a string of your choice to give a name to the client session, e.g.: "*my_account*". This name will be - used to save a file on disk that stores details needed to reconnect without asking again for credentials. - Alternatively, if you don't want a file to be saved on disk, pass the special name ``":memory:"`` to start - an in-memory session that will be discarded as soon as you stop the Client. In order to reconnect again - using a memory storage without having to login again, you can use - :meth:`~pyrogram.Client.export_session_string` before stopping the client to get a session string you can - pass here as argument. + name (``str``): + A name for the client, e.g.: "my_account". api_id (``int`` | ``str``, *optional*): - The *api_id* part of your Telegram API Key, as integer. E.g.: "12345". - This is an alternative way to pass it if you don't want to use the *config.ini* file. + The *api_id* part of the Telegram API key, as integer or string. + E.g.: 12345 or "12345". api_hash (``str``, *optional*): - The *api_hash* part of your Telegram API Key, as string. E.g.: "0123456789abcdef0123456789abcdef". - This is an alternative way to set it if you don't want to use the *config.ini* file. + The *api_hash* part of the Telegram API key, as string. + E.g.: "0123456789abcdef0123456789abcdef". app_version (``str``, *optional*): - Application version. Defaults to "Pyrogram |version|". - This is an alternative way to set it if you don't want to use the *config.ini* file. + Application version. + Defaults to "Pyrogram x.y.z". device_model (``str``, *optional*): - Device model. Defaults to *platform.python_implementation() + " " + platform.python_version()*. - This is an alternative way to set it if you don't want to use the *config.ini* file. + Device model. + Defaults to *platform.python_implementation() + " " + platform.python_version()*. system_version (``str``, *optional*): - Operating System version. Defaults to *platform.system() + " " + platform.release()*. - This is an alternative way to set it if you don't want to use the *config.ini* file. + Operating System version. + Defaults to *platform.system() + " " + platform.release()*. lang_code (``str``, *optional*): - Code of the language used on the client, in ISO 639-1 standard. Defaults to "en". - This is an alternative way to set it if you don't want to use the *config.ini* file. + Code of the language used on the client, in ISO 639-1 standard. + Defaults to "en". ipv6 (``bool``, *optional*): Pass True to connect to Telegram using IPv6. Defaults to False (IPv4). proxy (``dict``, *optional*): - Your SOCKS5 Proxy settings as dict, - e.g.: *dict(hostname="11.22.33.44", port=1080, username="user", password="pass")*. - The *username* and *password* can be omitted if your proxy doesn't require authorization. - This is an alternative way to setup a proxy if you don't want to use the *config.ini* file. + The Proxy settings as dict. + E.g.: *dict(scheme="socks5", hostname="11.22.33.44", port=1234, username="user", password="pass")*. + The *username* and *password* can be omitted if the proxy doesn't require authorization. test_mode (``bool``, *optional*): Enable or disable login to the test servers. @@ -106,12 +107,22 @@ class Client(Methods, Scaffold): Defaults to False. bot_token (``str``, *optional*): - Pass your Bot API token to create a bot session, e.g.: "123456:ABC-DEF1234ghIkl-zyx57W2v1u123ew11" + Pass the Bot API token to create a bot session, e.g.: "123456:ABC-DEF1234ghIkl-zyx57W2v1u123ew11" Only applicable for new sessions. - This is an alternative way to set it if you don't want to use the *config.ini* file. + + session_string (``str``, *optional*): + Pass a session string to load the session in-memory. + Implies ``in_memory=True``. + + in_memory (``bool``, *optional*): + Pass True to start an in-memory session that will be discarded as soon as the client stops. + In order to reconnect again using an in-memory session without having to login again, you can use + :meth:`~pyrogram.Client.export_session_string` before stopping the client to get a session string you can + pass to the ``session_string`` parameter. + Defaults to False. phone_number (``str``, *optional*): - Pass your phone number as string (with your Country Code prefix included) to avoid entering it manually. + Pass the phone number as string (with the Country Code prefix included) to avoid entering it manually. Only applicable for new sessions. phone_code (``str``, *optional*): @@ -119,45 +130,34 @@ class Client(Methods, Scaffold): Only applicable for new sessions. password (``str``, *optional*): - Pass your Two-Step Verification password as string (if you have one) to avoid entering it manually. - Only applicable for new sessions. - - force_sms (``bool``, *optional*): - Pass True to force Telegram sending the authorization code via SMS. + Pass the Two-Step Verification password as string (if required) to avoid entering it manually. Only applicable for new sessions. - Defaults to False. workers (``int``, *optional*): Number of maximum concurrent workers for handling incoming updates. Defaults to ``min(32, os.cpu_count() + 4)``. workdir (``str``, *optional*): - Define a custom working directory. The working directory is the location in your filesystem where Pyrogram - will store your session files. + Define a custom working directory. + The working directory is the location in the filesystem where Pyrogram will store the session files. Defaults to the parent directory of the main script. - config_file (``str``, *optional*): - Path of the configuration file. - Defaults to ./config.ini - plugins (``dict``, *optional*): - Your Smart Plugins settings as dict, e.g.: *dict(root="plugins")*. - This is an alternative way to setup plugins if you don't want to use the *config.ini* file. + Smart Plugins settings as dict, e.g.: *dict(root="plugins")*. - parse_mode (``str``, *optional*): - The parse mode, can be any of: *"combined"*, for the default combined mode. *"markdown"* or *"md"* - to force Markdown-only styles. *"html"* to force HTML-only styles. *None* to disable the parser - completely. + parse_mode (:obj:`~pyrogram.enums.ParseMode`, *optional*): + Set the global parse mode of the client. By default, texts are parsed using both Markdown and HTML styles. + You can combine both syntaxes together. no_updates (``bool``, *optional*): - Pass True to completely disable incoming updates for the current session. - When updates are disabled your client can't receive any new message. + Pass True to disable incoming updates. + When updates are disabled the client can't receive messages or other updates. Useful for batch programs that don't need to deal with updates. - Defaults to False (updates enabled and always received). + Defaults to False (updates enabled and received). takeout (``bool``, *optional*): Pass True to let the client use a takeout session instead of a normal one, implies *no_updates=True*. - Useful for exporting your Telegram data. Methods invoked inside a takeout session (such as get_history, + Useful for exporting Telegram data. Methods invoked inside a takeout session (such as get_chat_history, download_media, ...) are less prone to throw FloodWait exceptions. Only available for users, bots will ignore this parameter. Defaults to False (normal session). @@ -172,77 +172,130 @@ class Client(Methods, Scaffold): Pass True to hide the password when typing it during the login. Defaults to False, because ``getpass`` (the library used) is known to be problematic in some terminal environments. + + max_concurrent_transmissions (``bool``, *optional*): + Set the maximum amount of concurrent transmissions (uploads & downloads). + A value that is too high may result in network related issues. + Defaults to 1. """ + APP_VERSION = f"Pyrogram {__version__}" + DEVICE_MODEL = f"{platform.python_implementation()} {platform.python_version()}" + SYSTEM_VERSION = f"{platform.system()} {platform.release()}" + + LANG_CODE = "en" + + PARENT_DIR = Path(sys.argv[0]).parent + + INVITE_LINK_RE = re.compile(r"^(?:https?://)?(?:www\.)?(?:t(?:elegram)?\.(?:org|me|dog)/(?:joinchat/|\+))([\w-]+)$") + WORKERS = min(32, (os.cpu_count() or 0) + 4) # os.cpu_count() can be None + WORKDIR = PARENT_DIR + + # Interval of seconds in which the updates watchdog will kick in + UPDATES_WATCHDOG_INTERVAL = 5 * 60 + + MAX_CONCURRENT_TRANSMISSIONS = 1 + + mimetypes = MimeTypes() + mimetypes.readfp(StringIO(mime_types)) + def __init__( self, - session_name: Union[str, Storage], + name: str, api_id: Union[int, str] = None, api_hash: str = None, - app_version: str = None, - device_model: str = None, - system_version: str = None, - lang_code: str = None, + app_version: str = APP_VERSION, + device_model: str = DEVICE_MODEL, + system_version: str = SYSTEM_VERSION, + lang_code: str = LANG_CODE, ipv6: bool = False, proxy: dict = None, test_mode: bool = False, bot_token: str = None, + session_string: str = None, + in_memory: bool = None, phone_number: str = None, phone_code: str = None, password: str = None, - force_sms: bool = False, - workers: int = Scaffold.WORKERS, - workdir: str = Scaffold.WORKDIR, - config_file: str = Scaffold.CONFIG_FILE, + workers: int = WORKERS, + workdir: str = WORKDIR, plugins: dict = None, - parse_mode: str = Scaffold.PARSE_MODES[0], + parse_mode: "enums.ParseMode" = enums.ParseMode.DEFAULT, no_updates: bool = None, takeout: bool = None, sleep_threshold: int = Session.SLEEP_THRESHOLD, - hide_password: bool = False + hide_password: bool = False, + max_concurrent_transmissions: int = MAX_CONCURRENT_TRANSMISSIONS ): super().__init__() - self.session_name = session_name + self.name = name self.api_id = int(api_id) if api_id else None self.api_hash = api_hash self.app_version = app_version self.device_model = device_model self.system_version = system_version - self.lang_code = lang_code + self.lang_code = lang_code.lower() self.ipv6 = ipv6 - # TODO: Make code consistent, use underscore for private/protected fields - self._proxy = proxy + self.proxy = proxy self.test_mode = test_mode self.bot_token = bot_token + self.session_string = session_string + self.in_memory = in_memory self.phone_number = phone_number self.phone_code = phone_code self.password = password - self.force_sms = force_sms self.workers = workers self.workdir = Path(workdir) - self.config_file = Path(config_file) self.plugins = plugins self.parse_mode = parse_mode self.no_updates = no_updates self.takeout = takeout self.sleep_threshold = sleep_threshold self.hide_password = hide_password + self.max_concurrent_transmissions = max_concurrent_transmissions self.executor = ThreadPoolExecutor(self.workers, thread_name_prefix="Handler") - if isinstance(session_name, str): - if session_name == ":memory:" or len(session_name) >= MemoryStorage.SESSION_STRING_SIZE: - session_name = re.sub(r"[\n\s]+", "", session_name) - self.storage = MemoryStorage(session_name) - else: - self.storage = FileStorage(session_name, self.workdir) - elif isinstance(session_name, Storage): - self.storage = session_name + if self.session_string: + self.storage = MemoryStorage(self.name, self.session_string) + elif self.in_memory: + self.storage = MemoryStorage(self.name) else: - raise ValueError("Unknown storage engine") + self.storage = FileStorage(self.name, self.workdir) self.dispatcher = Dispatcher(self) + + self.rnd_id = MsgId + + self.parser = Parser(self) + + self.session = None + + self.media_sessions = {} + self.media_sessions_lock = asyncio.Lock() + + self.save_file_semaphore = asyncio.Semaphore(self.max_concurrent_transmissions) + self.get_file_semaphore = asyncio.Semaphore(self.max_concurrent_transmissions) + + self.is_connected = None + self.is_initialized = None + + self.takeout_id = None + + self.disconnect_handler = None + + self.me: Optional[User] = None + + self.message_cache = Cache(10000) + + # Sometimes, for some reason, the server will stop sending updates and will only respond to pings. + # This watchdog will invoke updates.GetState in order to wake up the server and enable it sending updates again + # after some idle time has been detected. + self.updates_watchdog_task = None + self.updates_watchdog_event = asyncio.Event() + self.last_update_time = datetime.now() + self.loop = asyncio.get_event_loop() def __enter__(self): @@ -258,28 +311,31 @@ async def __aenter__(self): return await self.start() async def __aexit__(self, *args): - await self.stop() - - @property - def proxy(self): - return self._proxy - - @proxy.setter - def proxy(self, value): - if value is None: - self._proxy = None - return + try: + await self.stop() + except ConnectionError: + pass - if self._proxy is None: - self._proxy = {} + async def updates_watchdog(self): + while True: + try: + await asyncio.wait_for(self.updates_watchdog_event.wait(), self.UPDATES_WATCHDOG_INTERVAL) + except asyncio.TimeoutError: + pass + else: + break - self._proxy["enabled"] = bool(value.get("enabled", True)) - self._proxy.update(value) + if datetime.now() - self.last_update_time > timedelta(seconds=self.UPDATES_WATCHDOG_INTERVAL): + await self.invoke(raw.functions.updates.GetState()) async def authorize(self) -> User: if self.bot_token: return await self.sign_in_bot(self.bot_token) + print(f"Welcome to Pyrogram (version {__version__})") + print(f"Pyrogram is free software and comes with ABSOLUTELY NO WARRANTY. Licensed\n" + f"under the terms of the {__license__}.\n") + while True: try: if not self.phone_number: @@ -308,17 +364,16 @@ async def authorize(self) -> User: else: break - if self.force_sms: - sent_code = await self.resend_code(self.phone_number, sent_code.phone_code_hash) + sent_code_descriptions = { + enums.SentCodeType.APP: "Telegram app", + enums.SentCodeType.SMS: "SMS", + enums.SentCodeType.CALL: "phone call", + enums.SentCodeType.FLASH_CALL: "phone flash call", + enums.SentCodeType.FRAGMENT_SMS: "Fragment SMS", + enums.SentCodeType.EMAIL_CODE: "email code" + } - print("The confirmation code has been sent via {}".format( - { - "app": "Telegram app", - "sms": "SMS", - "call": "phone call", - "flash_call": "phone flash call" - }[sent_code.type] - )) + print(f"The confirmation code has been sent via {sent_code_descriptions[sent_code.type]}") while True: if not self.phone_code: @@ -354,7 +409,7 @@ async def authorize(self) -> User: except BadRequest as e: print(e.MESSAGE) except Exception as e: - log.error(e, exc_info=True) + log.exception(e) raise else: self.password = None @@ -391,67 +446,40 @@ async def authorize(self) -> User: return signed_up - @property - def parse_mode(self): - return self._parse_mode - - @parse_mode.setter - def parse_mode(self, parse_mode: Optional[str] = "combined"): - if isinstance(parse_mode, str): - parse_mode = parse_mode.lower() - - if parse_mode not in self.PARSE_MODES: - raise ValueError('parse_mode must be one of {} or None. Not "{}"'.format( - ", ".join(f'"{m}"' for m in self.PARSE_MODES[:-1]), - parse_mode - )) - - self._parse_mode = parse_mode - - # TODO: redundant, remove in next major version - def set_parse_mode(self, parse_mode: Optional[str] = "combined"): + def set_parse_mode(self, parse_mode: Optional["enums.ParseMode"]): """Set the parse mode to be used globally by the client. When setting the parse mode with this method, all other methods having a *parse_mode* parameter will follow the - global value by default. The default value *"combined"* enables both Markdown and HTML styles to be used and - combined together. + global value by default. Parameters: - parse_mode (``str``): - The new parse mode, can be any of: *"combined"*, for the default combined mode. *"markdown"* or *"md"* - to force Markdown-only styles. *"html"* to force HTML-only styles. *None* to disable the parser - completely. - - Raises: - ValueError: In case the provided *parse_mode* is not a valid parse mode. + parse_mode (:obj:`~pyrogram.enums.ParseMode`): + By default, texts are parsed using both Markdown and HTML styles. + You can combine both syntaxes together. Example: .. code-block:: python - :emphasize-lines: 10,14,18,22 - from pyrogram import Client + from pyrogram import enums - app = Client("my_account") + # Default combined mode: Markdown + HTML + await app.send_message("me", "1. **markdown** and html") - with app: - # Default combined mode: Markdown + HTML - app.send_message("haskell", "1. **markdown** and html") + # Force Markdown-only, HTML is disabled + app.set_parse_mode(enums.ParseMode.MARKDOWN) + await app.send_message("me", "2. **markdown** and html") - # Force Markdown-only, HTML is disabled - app.set_parse_mode("markdown") - app.send_message("haskell", "2. **markdown** and html") + # Force HTML-only, Markdown is disabled + app.set_parse_mode(enums.ParseMode.HTML) + await app.send_message("me", "3. **markdown** and html") - # Force HTML-only, Markdown is disabled - app.set_parse_mode("html") - app.send_message("haskell", "3. **markdown** and html") + # Disable the parser completely + app.set_parse_mode(enums.ParseMode.DISABLED) + await app.send_message("me", "4. **markdown** and html") - # Disable the parser completely - app.set_parse_mode(None) - app.send_message("haskell", "4. **markdown** and html") - - # Bring back the default combined mode - app.set_parse_mode() - app.send_message("haskell", "5. **markdown** and html") + # Bring back the default combined mode + app.set_parse_mode(enums.ParseMode.DEFAULT) + await app.send_message("me", "5. **markdown** and html") """ self.parse_mode = parse_mode @@ -471,17 +499,29 @@ async def fetch_peers(self, peers: List[Union[raw.types.User, raw.types.Chat, ra if isinstance(peer, raw.types.User): peer_id = peer.id access_hash = peer.access_hash - username = (peer.username or "").lower() or None + username = ( + peer.username.lower() if peer.username + else peer.usernames[0].username.lower() if peer.usernames + else None + ) phone_number = peer.phone peer_type = "bot" if peer.bot else "user" elif isinstance(peer, (raw.types.Chat, raw.types.ChatForbidden)): peer_id = -peer.id access_hash = 0 peer_type = "group" - elif isinstance(peer, (raw.types.Channel, raw.types.ChannelForbidden)): + elif isinstance(peer, raw.types.Channel): + peer_id = utils.get_channel_id(peer.id) + access_hash = peer.access_hash + username = ( + peer.username.lower() if peer.username + else peer.usernames[0].username.lower() if peer.usernames + else None + ) + peer_type = "channel" if peer.broadcast else "supergroup" + elif isinstance(peer, raw.types.ChannelForbidden): peer_id = utils.get_channel_id(peer.id) access_hash = peer.access_hash - username = (getattr(peer, "username", None) or "").lower() or None peer_type = "channel" if peer.broadcast else "supergroup" else: continue @@ -492,37 +532,14 @@ async def fetch_peers(self, peers: List[Union[raw.types.User, raw.types.Chat, ra return is_min - async def handle_download(self, packet): - temp_file_path = "" - final_file_path = "" - - try: - file_id, directory, file_name, file_size, progress, progress_args = packet - - temp_file_path = await self.get_file( - file_id=file_id, - file_size=file_size, - progress=progress, - progress_args=progress_args - ) - - if temp_file_path: - final_file_path = os.path.abspath(re.sub("\\\\", "/", os.path.join(directory, file_name))) - os.makedirs(directory, exist_ok=True) - shutil.move(temp_file_path, final_file_path) - except Exception as e: - log.error(e, exc_info=True) - - try: - os.remove(temp_file_path) - except OSError: - pass - else: - return final_file_path or None - async def handle_updates(self, updates): + self.last_update_time = datetime.now() + if isinstance(updates, (raw.types.Updates, raw.types.UpdatesCombined)): - is_min = (await self.fetch_peers(updates.users)) or (await self.fetch_peers(updates.chats)) + is_min = any(( + await self.fetch_peers(updates.users), + await self.fetch_peers(updates.chats), + )) users = {u.id: u for u in updates.users} chats = {c.id: c for c in updates.chats} @@ -540,14 +557,14 @@ async def handle_updates(self, updates): pts_count = getattr(update, "pts_count", None) if isinstance(update, raw.types.UpdateChannelTooLong): - log.warning(update) + log.info(update) if isinstance(update, raw.types.UpdateNewChannelMessage) and is_min: message = update.message if not isinstance(message, raw.types.MessageEmpty): try: - diff = await self.send( + diff = await self.invoke( raw.functions.updates.GetChannelDifference( channel=await self.resolve_peer(utils.get_channel_id(channel_id)), filter=raw.types.ChannelMessagesFilter( @@ -569,7 +586,7 @@ async def handle_updates(self, updates): self.dispatcher.updates_queue.put_nowait((update, users, chats)) elif isinstance(updates, (raw.types.UpdateShortMessage, raw.types.UpdateShortChatMessage)): - diff = await self.send( + diff = await self.invoke( raw.functions.updates.GetDifference( pts=updates.pts - updates.pts_count, date=updates.date, @@ -595,79 +612,6 @@ async def handle_updates(self, updates): elif isinstance(updates, raw.types.UpdatesTooLong): log.info(updates) - def load_config(self): - parser = ConfigParser() - parser.read(str(self.config_file)) - - if self.bot_token: - pass - else: - self.bot_token = parser.get("pyrogram", "bot_token", fallback=None) - - if self.api_id and self.api_hash: - pass - else: - if parser.has_section("pyrogram"): - self.api_id = parser.getint("pyrogram", "api_id") - self.api_hash = parser.get("pyrogram", "api_hash") - else: - raise AttributeError("No API Key found. More info: https://docs.pyrogram.org/intro/setup") - - for option in ["app_version", "device_model", "system_version", "lang_code"]: - if getattr(self, option): - pass - else: - if parser.has_section("pyrogram"): - setattr(self, option, parser.get( - "pyrogram", - option, - fallback=getattr(Client, option.upper()) - )) - else: - setattr(self, option, getattr(Client, option.upper())) - - if self._proxy: - self._proxy["enabled"] = bool(self._proxy.get("enabled", True)) - else: - self._proxy = {} - - if parser.has_section("proxy"): - self._proxy["enabled"] = parser.getboolean("proxy", "enabled", fallback=True) - self._proxy["hostname"] = parser.get("proxy", "hostname") - self._proxy["port"] = parser.getint("proxy", "port") - self._proxy["username"] = parser.get("proxy", "username", fallback=None) or None - self._proxy["password"] = parser.get("proxy", "password", fallback=None) or None - - if self.plugins: - self.plugins = { - "enabled": bool(self.plugins.get("enabled", True)), - "root": self.plugins.get("root", None), - "include": self.plugins.get("include", []), - "exclude": self.plugins.get("exclude", []) - } - else: - try: - section = parser["plugins"] - - self.plugins = { - "enabled": section.getboolean("enabled", True), - "root": section.get("root", None), - "include": section.get("include", []), - "exclude": section.get("exclude", []) - } - - include = self.plugins["include"] - exclude = self.plugins["exclude"] - - if include: - self.plugins["include"] = include.strip().split("\n") - - if exclude: - self.plugins["exclude"] = exclude.strip().split("\n") - - except KeyError: - self.plugins = None - async def load_session(self): await self.storage.open() @@ -679,6 +623,12 @@ async def load_session(self): ]) if session_empty: + if not self.api_id or not self.api_hash: + raise AttributeError("The API key is required for new authorizations. " + "More info: https://docs.pyrogram.org/start/auth") + + await self.storage.api_id(self.api_id) + await self.storage.dc_id(2) await self.storage.date(0) @@ -691,13 +641,34 @@ async def load_session(self): ) await self.storage.user_id(None) await self.storage.is_bot(None) + else: + # Needed for migration from storage v2 to v3 + if not await self.storage.api_id(): + if self.api_id: + await self.storage.api_id(self.api_id) + else: + while True: + try: + value = int(await ainput("Enter the api_id part of the API key: ")) + + if value <= 0: + print("Invalid value") + continue + + confirm = (await ainput(f'Is "{value}" correct? (y/N): ')).lower() + + if confirm == "y": + await self.storage.api_id(value) + break + except Exception as e: + print(e) def load_plugins(self): if self.plugins: plugins = self.plugins.copy() for option in ["include", "exclude"]: - if plugins[option]: + if plugins.get(option, []): plugins[option] = [ (i.split()[0], i.split()[1:] or None) for i in self.plugins[option] @@ -705,10 +676,10 @@ def load_plugins(self): else: return - if plugins.get("enabled", False): + if plugins.get("enabled", True): root = plugins["root"] - include = plugins["include"] - exclude = plugins["exclude"] + include = plugins.get("include", []) + exclude = plugins.get("exclude", []) count = 0 @@ -725,7 +696,7 @@ def load_plugins(self): self.add_handler(handler, group) log.info('[{}] [LOAD] {}("{}") in group {} from "{}"'.format( - self.session_name, type(handler).__name__, name, group, module_path)) + self.name, type(handler).__name__, name, group, module_path)) count += 1 except Exception: @@ -738,11 +709,11 @@ def load_plugins(self): try: module = import_module(module_path) except ImportError: - log.warning(f'[{self.session_name}] [LOAD] Ignoring non-existent module "{module_path}"') + log.warning('[%s] [LOAD] Ignoring non-existent module "%s"', self.name, module_path) continue if "__path__" in dir(module): - log.warning(f'[{self.session_name}] [LOAD] Ignoring namespace "{module_path}"') + log.warning('[%s] [LOAD] Ignoring namespace "%s"', self.name, module_path) continue if handlers is None: @@ -757,13 +728,13 @@ def load_plugins(self): self.add_handler(handler, group) log.info('[{}] [LOAD] {}("{}") in group {} from "{}"'.format( - self.session_name, type(handler).__name__, name, group, module_path)) + self.name, type(handler).__name__, name, group, module_path)) count += 1 except Exception: if warn_non_existent_functions: log.warning('[{}] [LOAD] Ignoring non-existent function "{}" from "{}"'.format( - self.session_name, name, module_path)) + self.name, name, module_path)) if exclude: for path, handlers in exclude: @@ -773,11 +744,11 @@ def load_plugins(self): try: module = import_module(module_path) except ImportError: - log.warning(f'[{self.session_name}] [UNLOAD] Ignoring non-existent module "{module_path}"') + log.warning('[%s] [UNLOAD] Ignoring non-existent module "%s"', self.name, module_path) continue if "__path__" in dir(module): - log.warning(f'[{self.session_name}] [UNLOAD] Ignoring namespace "{module_path}"') + log.warning('[%s] [UNLOAD] Ignoring namespace "%s"', self.name, module_path) continue if handlers is None: @@ -792,143 +763,155 @@ def load_plugins(self): self.remove_handler(handler, group) log.info('[{}] [UNLOAD] {}("{}") from group {} in "{}"'.format( - self.session_name, type(handler).__name__, name, group, module_path)) + self.name, type(handler).__name__, name, group, module_path)) count -= 1 except Exception: if warn_non_existent_functions: log.warning('[{}] [UNLOAD] Ignoring non-existent function "{}" from "{}"'.format( - self.session_name, name, module_path)) + self.name, name, module_path)) if count > 0: log.info('[{}] Successfully loaded {} plugin{} from "{}"'.format( - self.session_name, count, "s" if count > 1 else "", root)) + self.name, count, "s" if count > 1 else "", root)) + else: + log.warning('[%s] No plugin loaded from "%s"', self.name, root) + + async def handle_download(self, packet): + file_id, directory, file_name, in_memory, file_size, progress, progress_args = packet + + os.makedirs(directory, exist_ok=True) if not in_memory else None + temp_file_path = os.path.abspath(re.sub("\\\\", "/", os.path.join(directory, file_name))) + ".temp" + file = BytesIO() if in_memory else open(temp_file_path, "wb") + + try: + async for chunk in self.get_file(file_id, file_size, 0, 0, progress, progress_args): + file.write(chunk) + except BaseException as e: + if not in_memory: + file.close() + os.remove(temp_file_path) + + if isinstance(e, asyncio.CancelledError): + raise e + + return None + else: + if in_memory: + file.name = file_name + return file else: - log.warning(f'[{self.session_name}] No plugin loaded from "{root}"') + file.close() + file_path = os.path.splitext(temp_file_path)[0] + shutil.move(temp_file_path, file_path) + return file_path async def get_file( self, file_id: FileId, - file_size: int, - progress: callable, + file_size: int = 0, + limit: int = 0, + offset: int = 0, + progress: Callable = None, progress_args: tuple = () - ) -> str: - dc_id = file_id.dc_id - - async with self.media_sessions_lock: - session = self.media_sessions.get(dc_id, None) - - if session is None: - if dc_id != await self.storage.dc_id(): - session = Session( - self, dc_id, await Auth(self, dc_id, await self.storage.test_mode()).create(), - await self.storage.test_mode(), is_media=True + ) -> Optional[AsyncGenerator[bytes, None]]: + async with self.get_file_semaphore: + file_type = file_id.file_type + + if file_type == FileType.CHAT_PHOTO: + if file_id.chat_id > 0: + peer = raw.types.InputPeerUser( + user_id=file_id.chat_id, + access_hash=file_id.chat_access_hash ) - await session.start() - - for _ in range(3): - exported_auth = await self.send( - raw.functions.auth.ExportAuthorization( - dc_id=dc_id - ) + else: + if file_id.chat_access_hash == 0: + peer = raw.types.InputPeerChat( + chat_id=-file_id.chat_id ) - - try: - await session.send( - raw.functions.auth.ImportAuthorization( - id=exported_auth.id, - bytes=exported_auth.bytes - ) - ) - except AuthBytesInvalid: - continue - else: - break else: - await session.stop() - raise AuthBytesInvalid - else: - session = Session( - self, dc_id, await self.storage.auth_key(), - await self.storage.test_mode(), is_media=True - ) - await session.start() - - self.media_sessions[dc_id] = session - - file_type = file_id.file_type + peer = raw.types.InputPeerChannel( + channel_id=utils.get_channel_id(file_id.chat_id), + access_hash=file_id.chat_access_hash + ) - if file_type == FileType.CHAT_PHOTO: - if file_id.chat_id > 0: - peer = raw.types.InputPeerUser( - user_id=file_id.chat_id, - access_hash=file_id.chat_access_hash + location = raw.types.InputPeerPhotoFileLocation( + peer=peer, + photo_id=file_id.media_id, + big=file_id.thumbnail_source == ThumbnailSource.CHAT_PHOTO_BIG + ) + elif file_type == FileType.PHOTO: + location = raw.types.InputPhotoFileLocation( + id=file_id.media_id, + access_hash=file_id.access_hash, + file_reference=file_id.file_reference, + thumb_size=file_id.thumbnail_size ) else: - if file_id.chat_access_hash == 0: - peer = raw.types.InputPeerChat( - chat_id=-file_id.chat_id - ) - else: - peer = raw.types.InputPeerChannel( - channel_id=utils.get_channel_id(file_id.chat_id), - access_hash=file_id.chat_access_hash - ) + location = raw.types.InputDocumentFileLocation( + id=file_id.media_id, + access_hash=file_id.access_hash, + file_reference=file_id.file_reference, + thumb_size=file_id.thumbnail_size + ) - location = raw.types.InputPeerPhotoFileLocation( - peer=peer, - photo_id=file_id.media_id, - big=file_id.thumbnail_source == ThumbnailSource.CHAT_PHOTO_BIG - ) - elif file_type == FileType.PHOTO: - location = raw.types.InputPhotoFileLocation( - id=file_id.media_id, - access_hash=file_id.access_hash, - file_reference=file_id.file_reference, - thumb_size=file_id.thumbnail_size - ) - else: - location = raw.types.InputDocumentFileLocation( - id=file_id.media_id, - access_hash=file_id.access_hash, - file_reference=file_id.file_reference, - thumb_size=file_id.thumbnail_size - ) + current = 0 + total = abs(limit) or (1 << 31) - 1 + chunk_size = 1024 * 1024 + offset_bytes = abs(offset) * chunk_size - limit = 1024 * 1024 - offset = 0 - file_name = "" + dc_id = file_id.dc_id - try: - r = await session.send( - raw.functions.upload.GetFile( - location=location, - offset=offset, - limit=limit - ), - sleep_threshold=30 + session = Session( + self, dc_id, + await Auth(self, dc_id, await self.storage.test_mode()).create() + if dc_id != await self.storage.dc_id() + else await self.storage.auth_key(), + await self.storage.test_mode(), + is_media=True ) - if isinstance(r, raw.types.upload.File): - with tempfile.NamedTemporaryFile("wb", delete=False) as f: - file_name = f.name + try: + await session.start() + + if dc_id != await self.storage.dc_id(): + exported_auth = await self.invoke( + raw.functions.auth.ExportAuthorization( + dc_id=dc_id + ) + ) + + await session.invoke( + raw.functions.auth.ImportAuthorization( + id=exported_auth.id, + bytes=exported_auth.bytes + ) + ) + + r = await session.invoke( + raw.functions.upload.GetFile( + location=location, + offset=offset_bytes, + limit=chunk_size + ), + sleep_threshold=30 + ) + if isinstance(r, raw.types.upload.File): while True: chunk = r.bytes - if not chunk: - break - - f.write(chunk) + yield chunk - offset += limit + current += 1 + offset_bytes += chunk_size if progress: func = functools.partial( progress, - min(offset, file_size) + min(offset_bytes, file_size) if file_size != 0 - else offset, + else offset_bytes, file_size, *progress_args ) @@ -938,45 +921,39 @@ async def get_file( else: await self.loop.run_in_executor(self.executor, func) - r = await session.send( + if len(chunk) < chunk_size or current >= total: + break + + r = await session.invoke( raw.functions.upload.GetFile( location=location, - offset=offset, - limit=limit + offset=offset_bytes, + limit=chunk_size ), sleep_threshold=30 ) - elif isinstance(r, raw.types.upload.FileCdnRedirect): - async with self.media_sessions_lock: - cdn_session = self.media_sessions.get(r.dc_id, None) - - if cdn_session is None: - cdn_session = Session( - self, r.dc_id, await Auth(self, r.dc_id, await self.storage.test_mode()).create(), - await self.storage.test_mode(), is_media=True, is_cdn=True - ) + elif isinstance(r, raw.types.upload.FileCdnRedirect): + cdn_session = Session( + self, r.dc_id, await Auth(self, r.dc_id, await self.storage.test_mode()).create(), + await self.storage.test_mode(), is_media=True, is_cdn=True + ) + try: await cdn_session.start() - self.media_sessions[r.dc_id] = cdn_session - - try: - with tempfile.NamedTemporaryFile("wb", delete=False) as f: - file_name = f.name - while True: - r2 = await cdn_session.send( + r2 = await cdn_session.invoke( raw.functions.upload.GetCdnFile( file_token=r.file_token, - offset=offset, - limit=limit + offset=offset_bytes, + limit=chunk_size ) ) if isinstance(r2, raw.types.upload.CdnFileReuploadNeeded): try: - await session.send( + await session.invoke( raw.functions.upload.ReuploadCdnFile( file_token=r.file_token, request_token=r2.request_token @@ -995,30 +972,34 @@ async def get_file( r.encryption_key, bytearray( r.encryption_iv[:-4] - + (offset // 16).to_bytes(4, "big") + + (offset_bytes // 16).to_bytes(4, "big") ) ) - hashes = await session.send( + hashes = await session.invoke( raw.functions.upload.GetCdnFileHashes( file_token=r.file_token, - offset=offset + offset=offset_bytes ) ) # https://core.telegram.org/cdn#verifying-files for i, h in enumerate(hashes): cdn_chunk = decrypted_chunk[h.limit * i: h.limit * (i + 1)] - assert h.hash == sha256(cdn_chunk).digest(), f"Invalid CDN hash part {i}" + CDNFileHashMismatch.check( + h.hash == sha256(cdn_chunk).digest(), + "h.hash == sha256(cdn_chunk).digest()" + ) - f.write(decrypted_chunk) + yield decrypted_chunk - offset += limit + current += 1 + offset_bytes += chunk_size if progress: func = functools.partial( progress, - min(offset, file_size) if file_size != 0 else offset, + min(offset_bytes, file_size) if file_size != 0 else offset_bytes, file_size, *progress_args ) @@ -1028,25 +1009,40 @@ async def get_file( else: await self.loop.run_in_executor(self.executor, func) - if len(chunk) < limit: + if len(chunk) < chunk_size or current >= total: break - except Exception as e: - raise e - except Exception as e: - if not isinstance(e, pyrogram.StopTransmission): - log.error(e, exc_info=True) - - try: - os.remove(file_name) - except OSError: - pass - - return "" - else: - return file_name + except Exception as e: + raise e + finally: + await cdn_session.stop() + except pyrogram.StopTransmission: + raise + except Exception as e: + log.exception(e) + finally: + await session.stop() def guess_mime_type(self, filename: str) -> Optional[str]: return self.mimetypes.guess_type(filename)[0] def guess_extension(self, mime_type: str) -> Optional[str]: return self.mimetypes.guess_extension(mime_type) + + +class Cache: + def __init__(self, capacity: int): + self.capacity = capacity + self.store = {} + + def __getitem__(self, key): + return self.store.get(key, None) + + def __setitem__(self, key, value): + if key in self.store: + del self.store[key] + + self.store[key] = value + + if len(self.store) > self.capacity: + for _ in range(self.capacity // 2 + 1): + del self.store[next(iter(self.store))] diff --git a/pyrogram/connection/__init__.py b/pyrogram/connection/__init__.py index 4b63340ee6..4665ce913f 100644 --- a/pyrogram/connection/__init__.py +++ b/pyrogram/connection/__init__.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2021 Dan +# Copyright (C) 2017-present Dan # # This file is part of Pyrogram. # diff --git a/pyrogram/connection/connection.py b/pyrogram/connection/connection.py index 3ab52d7105..1107673f1a 100644 --- a/pyrogram/connection/connection.py +++ b/pyrogram/connection/connection.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2021 Dan +# Copyright (C) 2017-present Dan # # This file is part of Pyrogram. # @@ -20,67 +20,53 @@ import logging from typing import Optional -from .transport import * +from .transport import TCP, TCPAbridged from ..session.internals import DataCenter log = logging.getLogger(__name__) class Connection: - MAX_RETRIES = 3 + MAX_CONNECTION_ATTEMPTS = 3 - MODES = { - 0: TCPFull, - 1: TCPAbridged, - 2: TCPIntermediate, - 3: TCPAbridgedO, - 4: TCPIntermediateO - } - - def __init__(self, dc_id: int, test_mode: bool, ipv6: bool, proxy: dict, media: bool = False, mode: int = 3): + def __init__(self, dc_id: int, test_mode: bool, ipv6: bool, proxy: dict, media: bool = False): self.dc_id = dc_id self.test_mode = test_mode self.ipv6 = ipv6 self.proxy = proxy self.media = media - self.address = DataCenter(dc_id, test_mode, ipv6, media) - self.mode = self.MODES.get(mode, TCPAbridged) - self.protocol = None # type: TCP + self.address = DataCenter(dc_id, test_mode, ipv6, media) + self.protocol: TCP = None async def connect(self): - for i in range(Connection.MAX_RETRIES): - self.protocol = self.mode(self.ipv6, self.proxy) + for i in range(Connection.MAX_CONNECTION_ATTEMPTS): + self.protocol = TCPAbridged(self.ipv6, self.proxy) try: log.info("Connecting...") await self.protocol.connect(self.address) except OSError as e: - log.warning(f"Unable to connect due to network issues: {e}") - self.protocol.close() + log.warning("Unable to connect due to network issues: %s", e) + await self.protocol.close() await asyncio.sleep(1) else: - log.info("Connected! {} DC{}{} - IPv{} - {}".format( - "Test" if self.test_mode else "Production", - self.dc_id, - " (media)" if self.media else "", - "6" if self.ipv6 else "4", - self.mode.__name__, - )) + log.info("Connected! %s DC%s%s - IPv%s", + "Test" if self.test_mode else "Production", + self.dc_id, + " (media)" if self.media else "", + "6" if self.ipv6 else "4") break else: log.warning("Connection failed! Trying again...") - raise TimeoutError + raise ConnectionError - def close(self): - self.protocol.close() + async def close(self): + await self.protocol.close() log.info("Disconnected") async def send(self, data: bytes): - try: - await self.protocol.send(data) - except Exception: - raise OSError + await self.protocol.send(data) async def recv(self) -> Optional[bytes]: return await self.protocol.recv() diff --git a/pyrogram/connection/transport/__init__.py b/pyrogram/connection/transport/__init__.py index b856583cf0..2d08832a70 100644 --- a/pyrogram/connection/transport/__init__.py +++ b/pyrogram/connection/transport/__init__.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2021 Dan +# Copyright (C) 2017-present Dan # # This file is part of Pyrogram. # diff --git a/pyrogram/connection/transport/tcp/__init__.py b/pyrogram/connection/transport/tcp/__init__.py index 6a5456de3f..3e23a88379 100644 --- a/pyrogram/connection/transport/tcp/__init__.py +++ b/pyrogram/connection/transport/tcp/__init__.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2021 Dan +# Copyright (C) 2017-present Dan # # This file is part of Pyrogram. # diff --git a/pyrogram/connection/transport/tcp/tcp.py b/pyrogram/connection/transport/tcp/tcp.py index 6c3e760315..82ef033be4 100644 --- a/pyrogram/connection/transport/tcp/tcp.py +++ b/pyrogram/connection/transport/tcp/tcp.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2021 Dan +# Copyright (C) 2017-present Dan # # This file is part of Pyrogram. # @@ -20,17 +20,9 @@ import ipaddress import logging import socket -import time +from concurrent.futures import ThreadPoolExecutor -try: - import socks -except ImportError as e: - e.msg = ( - "PySocks is missing and Pyrogram can't run without. " - "Please install it using \"pip3 install pysocks\"." - ) - - raise e +import socks log = logging.getLogger(__name__) @@ -41,15 +33,16 @@ class TCP: def __init__(self, ipv6: bool, proxy: dict): self.socket = None - self.reader = None # type: asyncio.StreamReader - self.writer = None # type: asyncio.StreamWriter + self.reader = None + self.writer = None self.lock = asyncio.Lock() self.loop = asyncio.get_event_loop() - if proxy.get("enabled", False): - hostname = proxy.get("hostname", None) - port = proxy.get("port", None) + self.proxy = proxy + + if proxy: + hostname = proxy.get("hostname") try: ip_address = ipaddress.ip_address(hostname) @@ -62,44 +55,53 @@ def __init__(self, ipv6: bool, proxy: dict): self.socket = socks.socksocket(socket.AF_INET) self.socket.set_proxy( - proxy_type=socks.SOCKS5, + proxy_type=getattr(socks, proxy.get("scheme").upper()), addr=hostname, - port=port, + port=proxy.get("port", None), username=proxy.get("username", None), password=proxy.get("password", None) ) - log.info(f"Using proxy {hostname}:{port}") + self.socket.settimeout(TCP.TIMEOUT) + + log.info("Using proxy %s", hostname) else: - self.socket = socks.socksocket( + self.socket = socket.socket( socket.AF_INET6 if ipv6 else socket.AF_INET ) - self.socket.settimeout(TCP.TIMEOUT) + self.socket.setblocking(False) async def connect(self, address: tuple): - self.socket.connect(address) + if self.proxy: + with ThreadPoolExecutor(1) as executor: + await self.loop.run_in_executor(executor, self.socket.connect, address) + else: + try: + await asyncio.wait_for(asyncio.get_event_loop().sock_connect(self.socket, address), TCP.TIMEOUT) + except asyncio.TimeoutError: # Re-raise as TimeoutError. asyncio.TimeoutError is deprecated in 3.11 + raise TimeoutError("Connection timed out") + self.reader, self.writer = await asyncio.open_connection(sock=self.socket) - def close(self): + async def close(self): try: - self.writer.close() - except AttributeError: - try: - self.socket.shutdown(socket.SHUT_RDWR) - except OSError: - pass - finally: - # A tiny sleep placed here helps avoiding .recv(n) hanging until the timeout. - # This is a workaround that seems to fix the occasional delayed stop of a client. - time.sleep(0.001) - self.socket.close() + if self.writer is not None: + self.writer.close() + await asyncio.wait_for(self.writer.wait_closed(), TCP.TIMEOUT) + except Exception as e: + log.info("Close exception: %s %s", type(e).__name__, e) async def send(self, data: bytes): async with self.lock: - self.writer.write(data) - await self.writer.drain() + try: + if self.writer is not None: + self.writer.write(data) + await self.writer.drain() + except Exception as e: + log.info("Send exception: %s %s", type(e).__name__, e) + raise OSError(e) async def recv(self, length: int = 0): data = b"" diff --git a/pyrogram/connection/transport/tcp/tcp_abridged.py b/pyrogram/connection/transport/tcp/tcp_abridged.py index cee9783121..77d44cf41c 100644 --- a/pyrogram/connection/transport/tcp/tcp_abridged.py +++ b/pyrogram/connection/transport/tcp/tcp_abridged.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2021 Dan +# Copyright (C) 2017-present Dan # # This file is part of Pyrogram. # @@ -17,7 +17,6 @@ # along with Pyrogram. If not, see . import logging - from typing import Optional from .tcp import TCP diff --git a/pyrogram/connection/transport/tcp/tcp_abridged_o.py b/pyrogram/connection/transport/tcp/tcp_abridged_o.py index 9db148bf2f..6f57ab1154 100644 --- a/pyrogram/connection/transport/tcp/tcp_abridged_o.py +++ b/pyrogram/connection/transport/tcp/tcp_abridged_o.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2021 Dan +# Copyright (C) 2017-present Dan # # This file is part of Pyrogram. # @@ -42,7 +42,7 @@ async def connect(self, address: tuple): while True: nonce = bytearray(os.urandom(64)) - if nonce[0] != b"\xef" and nonce[:4] not in self.RESERVED and nonce[4:4] != b"\x00" * 4: + if bytes([nonce[0]]) != b"\xef" and nonce[:4] not in self.RESERVED and nonce[4:8] != b"\x00" * 4: nonce[56] = nonce[57] = nonce[58] = nonce[59] = 0xef break diff --git a/pyrogram/connection/transport/tcp/tcp_full.py b/pyrogram/connection/transport/tcp/tcp_full.py index 7befddb1af..8bd89000c8 100644 --- a/pyrogram/connection/transport/tcp/tcp_full.py +++ b/pyrogram/connection/transport/tcp/tcp_full.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2021 Dan +# Copyright (C) 2017-present Dan # # This file is part of Pyrogram. # @@ -17,9 +17,9 @@ # along with Pyrogram. If not, see . import logging -from typing import Optional from binascii import crc32 from struct import pack, unpack +from typing import Optional from .tcp import TCP diff --git a/pyrogram/connection/transport/tcp/tcp_intermediate.py b/pyrogram/connection/transport/tcp/tcp_intermediate.py index 659c30e4ce..b6aef335a9 100644 --- a/pyrogram/connection/transport/tcp/tcp_intermediate.py +++ b/pyrogram/connection/transport/tcp/tcp_intermediate.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2021 Dan +# Copyright (C) 2017-present Dan # # This file is part of Pyrogram. # @@ -17,8 +17,8 @@ # along with Pyrogram. If not, see . import logging -from typing import Optional from struct import pack, unpack +from typing import Optional from .tcp import TCP diff --git a/pyrogram/connection/transport/tcp/tcp_intermediate_o.py b/pyrogram/connection/transport/tcp/tcp_intermediate_o.py index be783f048f..48b2d44520 100644 --- a/pyrogram/connection/transport/tcp/tcp_intermediate_o.py +++ b/pyrogram/connection/transport/tcp/tcp_intermediate_o.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2021 Dan +# Copyright (C) 2017-present Dan # # This file is part of Pyrogram. # @@ -18,8 +18,8 @@ import logging import os -from typing import Optional from struct import pack, unpack +from typing import Optional from pyrogram.crypto import aes from .tcp import TCP @@ -42,7 +42,7 @@ async def connect(self, address: tuple): while True: nonce = bytearray(os.urandom(64)) - if nonce[0] != b"\xef" and nonce[:4] not in self.RESERVED and nonce[4:4] != b"\x00" * 4: + if bytes([nonce[0]]) != b"\xef" and nonce[:4] not in self.RESERVED and nonce[4:8] != b"\x00" * 4: nonce[56] = nonce[57] = nonce[58] = nonce[59] = 0xee break diff --git a/pyrogram/crypto/__init__.py b/pyrogram/crypto/__init__.py index 4ad4f32b47..46887cb7a5 100644 --- a/pyrogram/crypto/__init__.py +++ b/pyrogram/crypto/__init__.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2021 Dan +# Copyright (C) 2017-present Dan # # This file is part of Pyrogram. # diff --git a/pyrogram/crypto/aes.py b/pyrogram/crypto/aes.py index 05aca1c087..bb937128df 100644 --- a/pyrogram/crypto/aes.py +++ b/pyrogram/crypto/aes.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2021 Dan +# Copyright (C) 2017-present Dan # # This file is part of Pyrogram. # @@ -54,7 +54,7 @@ def xor(a: bytes, b: bytes) -> bytes: log.warning( "TgCrypto is missing! " "Pyrogram will work the same, but at a much slower speed. " - "More info: https://docs.pyrogram.org/topics/tgcrypto" + "More info: https://docs.pyrogram.org/topics/speedups" ) diff --git a/pyrogram/crypto/mtproto.py b/pyrogram/crypto/mtproto.py index 803db29704..6d1521a47b 100644 --- a/pyrogram/crypto/mtproto.py +++ b/pyrogram/crypto/mtproto.py @@ -1,25 +1,26 @@ -# Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2018 Dan Tès +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-present Dan # -# This file is part of Pyrogram. +# This file is part of Pyrogram. # -# Pyrogram is free software: you can redistribute it and/or modify -# it under the terms of the GNU Lesser General Public License as published -# by the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. +# Pyrogram is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License as published +# by the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. # -# Pyrogram is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Lesser General Public License for more details. +# Pyrogram is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser General Public License for more details. # -# You should have received a copy of the GNU Lesser General Public License -# along with Pyrogram. If not, see . +# You should have received a copy of the GNU Lesser General Public License +# along with Pyrogram. If not, see . from hashlib import sha256 from io import BytesIO from os import urandom +from pyrogram.errors import SecurityCheckMismatch from pyrogram.raw.core import Message, Long from . import aes @@ -49,16 +50,21 @@ def pack(message: Message, salt: int, session_id: bytes, auth_key: bytes, auth_k return auth_key_id + msg_key + aes.ige256_encrypt(data + padding, aes_key, aes_iv) -def unpack(b: BytesIO, session_id: bytes, auth_key: bytes, auth_key_id: bytes) -> Message: - assert b.read(8) == auth_key_id, b.getvalue() +def unpack( + b: BytesIO, + session_id: bytes, + auth_key: bytes, + auth_key_id: bytes +) -> Message: + SecurityCheckMismatch.check(b.read(8) == auth_key_id, "b.read(8) == auth_key_id") msg_key = b.read(16) aes_key, aes_iv = kdf(auth_key, msg_key, False) data = BytesIO(aes.ige256_decrypt(b.read(), aes_key, aes_iv)) - data.read(8) + data.read(8) # Salt # https://core.telegram.org/mtproto/security_guidelines#checking-session-id - assert data.read(8) == session_id + SecurityCheckMismatch.check(data.read(8) == session_id, "data.read(8) == session_id") try: message = Message.read(data) @@ -75,11 +81,20 @@ def unpack(b: BytesIO, session_id: bytes, auth_key: bytes, auth_key_id: bytes) - raise ValueError(f"The server sent an unknown constructor: {hex(e.args[0])}\n{left}") # https://core.telegram.org/mtproto/security_guidelines#checking-sha256-hash-value-of-msg-key - # https://core.telegram.org/mtproto/security_guidelines#checking-message-length # 96 = 88 + 8 (incoming message) - assert msg_key == sha256(auth_key[96:96 + 32] + data.getvalue()).digest()[8:24] + SecurityCheckMismatch.check( + msg_key == sha256(auth_key[96:96 + 32] + data.getvalue()).digest()[8:24], + "msg_key == sha256(auth_key[96:96 + 32] + data.getvalue()).digest()[8:24]" + ) + + # https://core.telegram.org/mtproto/security_guidelines#checking-message-length + data.seek(32) # Get to the payload, skip salt (8) + session_id (8) + msg_id (8) + seq_no (4) + length (4) + payload = data.read() + padding = payload[message.length:] + SecurityCheckMismatch.check(12 <= len(padding) <= 1024, "12 <= len(padding) <= 1024") + SecurityCheckMismatch.check(len(payload) % 4 == 0, "len(payload) % 4 == 0") # https://core.telegram.org/mtproto/security_guidelines#checking-msg-id - assert message.msg_id % 2 != 0 + SecurityCheckMismatch.check(message.msg_id % 2 != 0, "message.msg_id % 2 != 0") return message diff --git a/pyrogram/crypto/prime.py b/pyrogram/crypto/prime.py index 82d1df758e..e919e22ce4 100644 --- a/pyrogram/crypto/prime.py +++ b/pyrogram/crypto/prime.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2021 Dan +# Copyright (C) 2017-present Dan # # This file is part of Pyrogram. # diff --git a/pyrogram/crypto/rsa.py b/pyrogram/crypto/rsa.py index 8804f87a60..25c2322957 100644 --- a/pyrogram/crypto/rsa.py +++ b/pyrogram/crypto/rsa.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2021 Dan +# Copyright (C) 2017-present Dan # # This file is part of Pyrogram. # @@ -153,25 +153,25 @@ int("010001", 16) # Exponent ), - # 6427105915145367799 - 0x15931aac70e0d30f7 - (1 << 64): PublicKey( # CDN DC-121 + # -7395192255793472640 + 0x995effd323b5db80 - (1 << 64): PublicKey( # CDN DC-121 # -----BEGIN RSA PUBLIC KEY----- - # MIIBCgKCAQEA+Lf3PvgE1yxbJUCMaEAkV0QySTVpnaDjiednB5RbtNWjCeqSVakY - # HbqqGMIIv5WCGdFdrqOfMNcNSstPtSU6R9UmRw6tquOIykpSuUOje9H+4XVIKquj - # yL2ISdK+4ZOMl4hCMkqauw4bP1Sbr03vZRQbU6qEA04V4j879BAyBVhr3WG9+Zi+ - # t5XfGSTgSExPYEl8rZNHYNV5RB+BuroVH2HLTOpT/mJVfikYpgjfWF5ldezV4Wo9 - # LSH0cZGSFIaeJl8d0A8Eiy5B9gtBO8mL+XfQRKOOmr7a4BM4Ro2de5rr2i2od7hY - # Xd3DO9FRSl4y1zA8Am48Rfd95WHF3N/OmQIDAQAB + # MIIBCgKCAQEA4tWHcGJlElkxuxKQJwFjJaulmVHgdxNA3wgI2E8XbNnA88y51Xog + # V5m8BEYuTSP4llXZY4ZSJW5VlFXnmsJT/hmjyeFqqTajyAW6nb9vwZX291QvqD/1 + # ZCFBy7TLvCM0lbNIEhcLMf33ZV8AetLAd+uRLF6QHosys5w0iJ7x+UbGwDxyfeic + # 8EJJnsKaXrUOwRycMRN+V/zDySa0EYl1u1EB1MDX1/jIV1IQEbLvdBH4vsVTVEdW + # KHlzOcFzT9qX/g8XibCPiHLJvqQb8hVibvs9NaANyClcBEt3mOucG1/46Lilkc/K + # d4nlCcohk0jIHNp8symUzNWRPUGmTs3SPwIDAQAB # -----END RSA PUBLIC KEY----- int( - "F8B7F73EF804D72C5B25408C6840245744324935699DA0E389E76707945BB4D5" - "A309EA9255A9181DBAAA18C208BF958219D15DAEA39F30D70D4ACB4FB5253A47" - "D526470EADAAE388CA4A52B943A37BD1FEE175482AABA3C8BD8849D2BEE1938C" - "978842324A9ABB0E1B3F549BAF4DEF65141B53AA84034E15E23F3BF410320558" - "6BDD61BDF998BEB795DF1924E0484C4F60497CAD934760D579441F81BABA151F" - "61CB4CEA53FE62557E2918A608DF585E6575ECD5E16A3D2D21F471919214869E" - "265F1DD00F048B2E41F60B413BC98BF977D044A38E9ABEDAE01338468D9D7B9A" - "EBDA2DA877B8585DDDC33BD1514A5E32D7303C026E3C45F77DE561C5DCDFCE99", + "E2D587706265125931BB129027016325ABA59951E0771340DF0808D84F176CD9" + "C0F3CCB9D57A205799BC04462E4D23F89655D9638652256E559455E79AC253FE" + "19A3C9E16AA936A3C805BA9DBF6FC195F6F7542FA83FF5642141CBB4CBBC2334" + "95B34812170B31FDF7655F007AD2C077EB912C5E901E8B32B39C34889EF1F946" + "C6C03C727DE89CF042499EC29A5EB50EC11C9C31137E57FCC3C926B4118975BB" + "5101D4C0D7D7F8C857521011B2EF7411F8BEC55354475628797339C1734FDA97" + "FE0F1789B08F8872C9BEA41BF215626EFB3D35A00DC8295C044B7798EB9C1B5F" + "F8E8B8A591CFCA7789E509CA219348C81CDA7CB32994CCD5913D41A64ECDD23F", 16 ), # Modulus int("010001", 16) # Exponent @@ -199,6 +199,54 @@ 16 ), # Modulus int("010001", 16) # Exponent + ), + + # -3997872768018684475 + 0xc884b3e62d09e5c5 - (1 << 64): PublicKey( # CDN DC-201 + # -----BEGIN RSA PUBLIC KEY----- + # MIIBCgKCAQEAug6fETVb7NkXYYu5ueZuM0pqw1heuqUrZNYomQN0lS0o7i6mAWwb + # 1/FiscFK+y4LQSSEx+oUzXAhjmll9fmb4e7PbUiXo8MuXO0Rj3e5416DXfTiOYGW + # XlFRV0aQzu8agy1epKwkFDidnmy7g5rJJV0q1+3eR+Jk2OEc/B6lMAOv3fBU6xhE + # ZByN9gqc6fvkNo13PQ8JYZUSGttzLlYy76uFmvFBhRsJU+LNQ2+bsTHwafSffVYl + # Z2boJOblvqbRWe453CzssaSWywGXOQmWvVbEe7F8q1ki/s7S8BxYWrhSLJ6bsu9V + # ZWnIHD9vB34QF8IABPRE93mhCOHBqJxSBQIDAQAB + # -----END RSA PUBLIC KEY----- + int( + "BA0E9F11355BECD917618BB9B9E66E334A6AC3585EBAA52B64D628990374952D" + "28EE2EA6016C1BD7F162B1C14AFB2E0B412484C7EA14CD70218E6965F5F99BE1" + "EECF6D4897A3C32E5CED118F77B9E35E835DF4E23981965E5151574690CEEF1A" + "832D5EA4AC2414389D9E6CBB839AC9255D2AD7EDDE47E264D8E11CFC1EA53003" + "AFDDF054EB1844641C8DF60A9CE9FBE4368D773D0F096195121ADB732E5632EF" + "AB859AF141851B0953E2CD436F9BB131F069F49F7D56256766E824E6E5BEA6D1" + "59EE39DC2CECB1A496CB0197390996BD56C47BB17CAB5922FECED2F01C585AB8" + "522C9E9BB2EF556569C81C3F6F077E1017C20004F444F779A108E1C1A89C5205", + 16 + ), # Modulus + int("010001", 16) # Exponent + ), + + # -4960899639492471258 + 0xbb27580fd5b01626 - (1 << 64): PublicKey( # CDN DC-203 + # -----BEGIN RSA PUBLIC KEY----- + # MIIBCgKCAQEAv/L6td+mj7Dl81NHfu+Xf1KNtvZPR1tS5xFqkiUson1u7D2ulK05 + # jM8HKvpV1o+1HPPqhaXhasvsX90u3TIHRQ0zuJKJxKAiZo3GK7phHozjAJ9VUFbO + # 7jKAa5BTE9tXgA5ZwJAiQWb3U6ykwRzk3fFRe5WaW7xfVUiepxyWGdr1eecoWCfB + # af1TCXfcS7vcyljNT03pwt2YyS5iXE5IB5wBB5yqSSm4GYtWWR67UjIsXBd77TRp + # foLGpfOdUHxBz4ZSj8D76m1zlpID5J2pF6bH4+ZCz0SUpv3j7bE8WFlvgMfwEPhw + # xMYidRGayq9YlLlYd4D+Yoq0U6jS3MWTRQIDAQAB + # -----END RSA PUBLIC KEY----- + int( + "BFF2FAB5DFA68FB0E5F353477EEF977F528DB6F64F475B52E7116A92252CA27D" + "6EEC3DAE94AD398CCF072AFA55D68FB51CF3EA85A5E16ACBEC5FDD2EDD320745" + "0D33B89289C4A022668DC62BBA611E8CE3009F555056CEEE32806B905313DB57" + "800E59C090224166F753ACA4C11CE4DDF1517B959A5BBC5F55489EA71C9619DA" + "F579E7285827C169FD530977DC4BBBDCCA58CD4F4DE9C2DD98C92E625C4E4807" + "9C01079CAA4929B8198B56591EBB52322C5C177BED34697E82C6A5F39D507C41" + "CF86528FC0FBEA6D73969203E49DA917A6C7E3E642CF4494A6FDE3EDB13C5859" + "6F80C7F010F870C4C62275119ACAAF5894B9587780FE628AB453A8D2DCC59345", + 16 + ), # Modulus + int("010001", 16) # Exponent ) } diff --git a/pyrogram/dispatcher.py b/pyrogram/dispatcher.py index 0f46d64236..6e503cebb3 100644 --- a/pyrogram/dispatcher.py +++ b/pyrogram/dispatcher.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2021 Dan +# Copyright (C) 2017-present Dan # # This file is part of Pyrogram. # @@ -24,9 +24,9 @@ import pyrogram from pyrogram import utils from pyrogram.handlers import ( - CallbackQueryHandler, MessageHandler, DeletedMessagesHandler, + CallbackQueryHandler, MessageHandler, EditedMessageHandler, DeletedMessagesHandler, UserStatusHandler, RawUpdateHandler, InlineQueryHandler, PollHandler, - ChosenInlineResultHandler, ChatMemberUpdatedHandler + ChosenInlineResultHandler, ChatMemberUpdatedHandler, ChatJoinRequestHandler ) from pyrogram.raw.types import ( UpdateNewMessage, UpdateNewChannelMessage, UpdateNewScheduledMessage, @@ -34,40 +34,24 @@ UpdateDeleteMessages, UpdateDeleteChannelMessages, UpdateBotCallbackQuery, UpdateInlineBotCallbackQuery, UpdateUserStatus, UpdateBotInlineQuery, UpdateMessagePoll, - UpdateBotInlineSend, UpdateChatParticipant, UpdateChannelParticipant + UpdateBotInlineSend, UpdateChatParticipant, UpdateChannelParticipant, + UpdateBotChatInviteRequester ) log = logging.getLogger(__name__) class Dispatcher: - NEW_MESSAGE_UPDATES = ( - UpdateNewMessage, - UpdateNewChannelMessage, - UpdateNewScheduledMessage - ) - - EDIT_MESSAGE_UPDATES = ( - UpdateEditMessage, - UpdateEditChannelMessage, - ) - - DELETE_MESSAGES_UPDATES = ( - UpdateDeleteMessages, - UpdateDeleteChannelMessages - ) - - CALLBACK_QUERY_UPDATES = ( - UpdateBotCallbackQuery, - UpdateInlineBotCallbackQuery - ) - - CHAT_MEMBER_UPDATES = ( - UpdateChatParticipant, - UpdateChannelParticipant - ) - - MESSAGE_UPDATES = NEW_MESSAGE_UPDATES + EDIT_MESSAGE_UPDATES + NEW_MESSAGE_UPDATES = (UpdateNewMessage, UpdateNewChannelMessage, UpdateNewScheduledMessage) + EDIT_MESSAGE_UPDATES = (UpdateEditMessage, UpdateEditChannelMessage) + DELETE_MESSAGES_UPDATES = (UpdateDeleteMessages, UpdateDeleteChannelMessages) + CALLBACK_QUERY_UPDATES = (UpdateBotCallbackQuery, UpdateInlineBotCallbackQuery) + CHAT_MEMBER_UPDATES = (UpdateChatParticipant, UpdateChannelParticipant) + USER_STATUS_UPDATES = (UpdateUserStatus,) + BOT_INLINE_QUERY_UPDATES = (UpdateBotInlineQuery,) + POLL_UPDATES = (UpdateMessagePoll,) + CHOSEN_INLINE_RESULT_UPDATES = (UpdateBotInlineSend,) + CHAT_JOIN_REQUEST_UPDATES = (UpdateBotChatInviteRequester,) def __init__(self, client: "pyrogram.Client"): self.client = client @@ -80,41 +64,80 @@ def __init__(self, client: "pyrogram.Client"): self.groups = OrderedDict() async def message_parser(update, users, chats): - return await pyrogram.types.Message._parse( - self.client, update.message, users, chats, - isinstance(update, UpdateNewScheduledMessage) - ), MessageHandler + return ( + await pyrogram.types.Message._parse(self.client, update.message, users, chats, + isinstance(update, UpdateNewScheduledMessage)), + MessageHandler + ) + + async def edited_message_parser(update, users, chats): + # Edited messages are parsed the same way as new messages, but the handler is different + parsed, _ = await message_parser(update, users, chats) + + return ( + parsed, + EditedMessageHandler + ) async def deleted_messages_parser(update, users, chats): - return utils.parse_deleted_messages(self.client, update), DeletedMessagesHandler + return ( + utils.parse_deleted_messages(self.client, update), + DeletedMessagesHandler + ) async def callback_query_parser(update, users, chats): - return await pyrogram.types.CallbackQuery._parse(self.client, update, users), CallbackQueryHandler + return ( + await pyrogram.types.CallbackQuery._parse(self.client, update, users), + CallbackQueryHandler + ) async def user_status_parser(update, users, chats): - return pyrogram.types.User._parse_user_status(self.client, update), UserStatusHandler + return ( + pyrogram.types.User._parse_user_status(self.client, update), + UserStatusHandler + ) async def inline_query_parser(update, users, chats): - return pyrogram.types.InlineQuery._parse(self.client, update, users), InlineQueryHandler + return ( + pyrogram.types.InlineQuery._parse(self.client, update, users), + InlineQueryHandler + ) async def poll_parser(update, users, chats): - return pyrogram.types.Poll._parse_update(self.client, update), PollHandler + return ( + pyrogram.types.Poll._parse_update(self.client, update), + PollHandler + ) async def chosen_inline_result_parser(update, users, chats): - return pyrogram.types.ChosenInlineResult._parse(self.client, update, users), ChosenInlineResultHandler + return ( + pyrogram.types.ChosenInlineResult._parse(self.client, update, users), + ChosenInlineResultHandler + ) async def chat_member_updated_parser(update, users, chats): - return pyrogram.types.ChatMemberUpdated._parse(self.client, update, users, chats), ChatMemberUpdatedHandler + return ( + pyrogram.types.ChatMemberUpdated._parse(self.client, update, users, chats), + ChatMemberUpdatedHandler + ) + + async def chat_join_request_parser(update, users, chats): + return ( + pyrogram.types.ChatJoinRequest._parse(self.client, update, users, chats), + ChatJoinRequestHandler + ) self.update_parsers = { - Dispatcher.MESSAGE_UPDATES: message_parser, + Dispatcher.NEW_MESSAGE_UPDATES: message_parser, + Dispatcher.EDIT_MESSAGE_UPDATES: edited_message_parser, Dispatcher.DELETE_MESSAGES_UPDATES: deleted_messages_parser, Dispatcher.CALLBACK_QUERY_UPDATES: callback_query_parser, - (UpdateUserStatus,): user_status_parser, - (UpdateBotInlineQuery,): inline_query_parser, - (UpdateMessagePoll,): poll_parser, - (UpdateBotInlineSend,): chosen_inline_result_parser, - Dispatcher.CHAT_MEMBER_UPDATES: chat_member_updated_parser + Dispatcher.USER_STATUS_UPDATES: user_status_parser, + Dispatcher.BOT_INLINE_QUERY_UPDATES: inline_query_parser, + Dispatcher.POLL_UPDATES: poll_parser, + Dispatcher.CHOSEN_INLINE_RESULT_UPDATES: chosen_inline_result_parser, + Dispatcher.CHAT_MEMBER_UPDATES: chat_member_updated_parser, + Dispatcher.CHAT_JOIN_REQUEST_UPDATES: chat_join_request_parser } self.update_parsers = {key: value for key_tuple, value in self.update_parsers.items() for key in key_tuple} @@ -128,7 +151,7 @@ async def start(self): self.loop.create_task(self.handler_worker(self.locks_list[-1])) ) - log.info(f"Started {self.client.workers} HandlerTasks") + log.info("Started %s HandlerTasks", self.client.workers) async def stop(self): if not self.client.no_updates: @@ -141,7 +164,7 @@ async def stop(self): self.handler_worker_tasks.clear() self.groups.clear() - log.info(f"Stopped {self.client.workers} HandlerTasks") + log.info("Stopped %s HandlerTasks", self.client.workers) def add_handler(self, handler, group: int): async def fn(): @@ -203,7 +226,7 @@ async def handler_worker(self, lock): if await handler.check(self.client, parsed_update): args = (parsed_update,) except Exception as e: - log.error(e, exc_info=True) + log.exception(e) continue elif isinstance(handler, RawUpdateHandler): @@ -227,10 +250,10 @@ async def handler_worker(self, lock): except pyrogram.ContinuePropagation: continue except Exception as e: - log.error(e, exc_info=True) + log.exception(e) break except pyrogram.StopPropagation: pass except Exception as e: - log.error(e, exc_info=True) + log.exception(e) diff --git a/pyrogram/emoji.py b/pyrogram/emoji.py index 69e44fa574..d135faf74d 100644 --- a/pyrogram/emoji.py +++ b/pyrogram/emoji.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2021 Dan +# Copyright (C) 2017-present Dan # # This file is part of Pyrogram. # @@ -26,6 +26,7 @@ FACE_WITH_TEARS_OF_JOY = "\U0001f602" SLIGHTLY_SMILING_FACE = "\U0001f642" UPSIDE_DOWN_FACE = "\U0001f643" +MELTING_FACE = "\U0001fae0" WINKING_FACE = "\U0001f609" SMILING_FACE_WITH_SMILING_EYES = "\U0001f60a" SMILING_FACE_WITH_HALO = "\U0001f607" @@ -44,19 +45,25 @@ ZANY_FACE = "\U0001f92a" SQUINTING_FACE_WITH_TONGUE = "\U0001f61d" MONEY_MOUTH_FACE = "\U0001f911" -HUGGING_FACE = "\U0001f917" +SMILING_FACE_WITH_OPEN_HANDS = "\U0001f917" FACE_WITH_HAND_OVER_MOUTH = "\U0001f92d" +FACE_WITH_OPEN_EYES_AND_HAND_OVER_MOUTH = "\U0001fae2" +FACE_WITH_PEEKING_EYE = "\U0001fae3" SHUSHING_FACE = "\U0001f92b" THINKING_FACE = "\U0001f914" +SALUTING_FACE = "\U0001fae1" ZIPPER_MOUTH_FACE = "\U0001f910" FACE_WITH_RAISED_EYEBROW = "\U0001f928" NEUTRAL_FACE = "\U0001f610" EXPRESSIONLESS_FACE = "\U0001f611" FACE_WITHOUT_MOUTH = "\U0001f636" +DOTTED_LINE_FACE = "\U0001fae5" +FACE_IN_CLOUDS = "\U0001f636\u200d\U0001f32b\ufe0f" SMIRKING_FACE = "\U0001f60f" UNAMUSED_FACE = "\U0001f612" FACE_WITH_ROLLING_EYES = "\U0001f644" GRIMACING_FACE = "\U0001f62c" +FACE_EXHALING = "\U0001f62e\u200d\U0001f4a8" LYING_FACE = "\U0001f925" RELIEVED_FACE = "\U0001f60c" PENSIVE_FACE = "\U0001f614" @@ -72,7 +79,8 @@ HOT_FACE = "\U0001f975" COLD_FACE = "\U0001f976" WOOZY_FACE = "\U0001f974" -DIZZY_FACE = "\U0001f635" +FACE_WITH_CROSSED_OUT_EYES = "\U0001f635" +FACE_WITH_SPIRAL_EYES = "\U0001f635\u200d\U0001f4ab" EXPLODING_HEAD = "\U0001f92f" COWBOY_HAT_FACE = "\U0001f920" PARTYING_FACE = "\U0001f973" @@ -81,6 +89,7 @@ NERD_FACE = "\U0001f913" FACE_WITH_MONOCLE = "\U0001f9d0" CONFUSED_FACE = "\U0001f615" +FACE_WITH_DIAGONAL_MOUTH = "\U0001fae4" WORRIED_FACE = "\U0001f61f" SLIGHTLY_FROWNING_FACE = "\U0001f641" FROWNING_FACE = "\u2639\ufe0f" @@ -89,6 +98,7 @@ ASTONISHED_FACE = "\U0001f632" FLUSHED_FACE = "\U0001f633" PLEADING_FACE = "\U0001f97a" +FACE_HOLDING_BACK_TEARS = "\U0001f979" FROWNING_FACE_WITH_OPEN_MOUTH = "\U0001f626" ANGUISHED_FACE = "\U0001f627" FEARFUL_FACE = "\U0001f628" @@ -105,7 +115,7 @@ TIRED_FACE = "\U0001f62b" YAWNING_FACE = "\U0001f971" FACE_WITH_STEAM_FROM_NOSE = "\U0001f624" -POUTING_FACE = "\U0001f621" +ENRAGED_FACE = "\U0001f621" ANGRY_FACE = "\U0001f620" FACE_WITH_SYMBOLS_ON_MOUTH = "\U0001f92c" SMILING_FACE_WITH_HORNS = "\U0001f608" @@ -144,6 +154,8 @@ HEART_DECORATION = "\U0001f49f" HEART_EXCLAMATION = "\u2763\ufe0f" BROKEN_HEART = "\U0001f494" +HEART_ON_FIRE = "\u2764\ufe0f\u200d\U0001f525" +MENDING_HEART = "\u2764\ufe0f\u200d\U0001fa79" RED_HEART = "\u2764\ufe0f" ORANGE_HEART = "\U0001f9e1" YELLOW_HEART = "\U0001f49b" @@ -197,6 +209,30 @@ VULCAN_SALUTE_MEDIUM_SKIN_TONE = "\U0001f596\U0001f3fd" VULCAN_SALUTE_MEDIUM_DARK_SKIN_TONE = "\U0001f596\U0001f3fe" VULCAN_SALUTE_DARK_SKIN_TONE = "\U0001f596\U0001f3ff" +RIGHTWARDS_HAND = "\U0001faf1" +RIGHTWARDS_HAND_LIGHT_SKIN_TONE = "\U0001faf1\U0001f3fb" +RIGHTWARDS_HAND_MEDIUM_LIGHT_SKIN_TONE = "\U0001faf1\U0001f3fc" +RIGHTWARDS_HAND_MEDIUM_SKIN_TONE = "\U0001faf1\U0001f3fd" +RIGHTWARDS_HAND_MEDIUM_DARK_SKIN_TONE = "\U0001faf1\U0001f3fe" +RIGHTWARDS_HAND_DARK_SKIN_TONE = "\U0001faf1\U0001f3ff" +LEFTWARDS_HAND = "\U0001faf2" +LEFTWARDS_HAND_LIGHT_SKIN_TONE = "\U0001faf2\U0001f3fb" +LEFTWARDS_HAND_MEDIUM_LIGHT_SKIN_TONE = "\U0001faf2\U0001f3fc" +LEFTWARDS_HAND_MEDIUM_SKIN_TONE = "\U0001faf2\U0001f3fd" +LEFTWARDS_HAND_MEDIUM_DARK_SKIN_TONE = "\U0001faf2\U0001f3fe" +LEFTWARDS_HAND_DARK_SKIN_TONE = "\U0001faf2\U0001f3ff" +PALM_DOWN_HAND = "\U0001faf3" +PALM_DOWN_HAND_LIGHT_SKIN_TONE = "\U0001faf3\U0001f3fb" +PALM_DOWN_HAND_MEDIUM_LIGHT_SKIN_TONE = "\U0001faf3\U0001f3fc" +PALM_DOWN_HAND_MEDIUM_SKIN_TONE = "\U0001faf3\U0001f3fd" +PALM_DOWN_HAND_MEDIUM_DARK_SKIN_TONE = "\U0001faf3\U0001f3fe" +PALM_DOWN_HAND_DARK_SKIN_TONE = "\U0001faf3\U0001f3ff" +PALM_UP_HAND = "\U0001faf4" +PALM_UP_HAND_LIGHT_SKIN_TONE = "\U0001faf4\U0001f3fb" +PALM_UP_HAND_MEDIUM_LIGHT_SKIN_TONE = "\U0001faf4\U0001f3fc" +PALM_UP_HAND_MEDIUM_SKIN_TONE = "\U0001faf4\U0001f3fd" +PALM_UP_HAND_MEDIUM_DARK_SKIN_TONE = "\U0001faf4\U0001f3fe" +PALM_UP_HAND_DARK_SKIN_TONE = "\U0001faf4\U0001f3ff" OK_HAND = "\U0001f44c" OK_HAND_LIGHT_SKIN_TONE = "\U0001f44c\U0001f3fb" OK_HAND_MEDIUM_LIGHT_SKIN_TONE = "\U0001f44c\U0001f3fc" @@ -227,6 +263,12 @@ CROSSED_FINGERS_MEDIUM_SKIN_TONE = "\U0001f91e\U0001f3fd" CROSSED_FINGERS_MEDIUM_DARK_SKIN_TONE = "\U0001f91e\U0001f3fe" CROSSED_FINGERS_DARK_SKIN_TONE = "\U0001f91e\U0001f3ff" +HAND_WITH_INDEX_FINGER_AND_THUMB_CROSSED = "\U0001faf0" +HAND_WITH_INDEX_FINGER_AND_THUMB_CROSSED_LIGHT_SKIN_TONE = "\U0001faf0\U0001f3fb" +HAND_WITH_INDEX_FINGER_AND_THUMB_CROSSED_MEDIUM_LIGHT_SKIN_TONE = "\U0001faf0\U0001f3fc" +HAND_WITH_INDEX_FINGER_AND_THUMB_CROSSED_MEDIUM_SKIN_TONE = "\U0001faf0\U0001f3fd" +HAND_WITH_INDEX_FINGER_AND_THUMB_CROSSED_MEDIUM_DARK_SKIN_TONE = "\U0001faf0\U0001f3fe" +HAND_WITH_INDEX_FINGER_AND_THUMB_CROSSED_DARK_SKIN_TONE = "\U0001faf0\U0001f3ff" LOVE_YOU_GESTURE = "\U0001f91f" LOVE_YOU_GESTURE_LIGHT_SKIN_TONE = "\U0001f91f\U0001f3fb" LOVE_YOU_GESTURE_MEDIUM_LIGHT_SKIN_TONE = "\U0001f91f\U0001f3fc" @@ -281,6 +323,12 @@ INDEX_POINTING_UP_MEDIUM_SKIN_TONE = "\u261d\U0001f3fd" INDEX_POINTING_UP_MEDIUM_DARK_SKIN_TONE = "\u261d\U0001f3fe" INDEX_POINTING_UP_DARK_SKIN_TONE = "\u261d\U0001f3ff" +INDEX_POINTING_AT_THE_VIEWER = "\U0001faf5" +INDEX_POINTING_AT_THE_VIEWER_LIGHT_SKIN_TONE = "\U0001faf5\U0001f3fb" +INDEX_POINTING_AT_THE_VIEWER_MEDIUM_LIGHT_SKIN_TONE = "\U0001faf5\U0001f3fc" +INDEX_POINTING_AT_THE_VIEWER_MEDIUM_SKIN_TONE = "\U0001faf5\U0001f3fd" +INDEX_POINTING_AT_THE_VIEWER_MEDIUM_DARK_SKIN_TONE = "\U0001faf5\U0001f3fe" +INDEX_POINTING_AT_THE_VIEWER_DARK_SKIN_TONE = "\U0001faf5\U0001f3ff" THUMBS_UP = "\U0001f44d" THUMBS_UP_LIGHT_SKIN_TONE = "\U0001f44d\U0001f3fb" THUMBS_UP_MEDIUM_LIGHT_SKIN_TONE = "\U0001f44d\U0001f3fc" @@ -329,6 +377,12 @@ RAISING_HANDS_MEDIUM_SKIN_TONE = "\U0001f64c\U0001f3fd" RAISING_HANDS_MEDIUM_DARK_SKIN_TONE = "\U0001f64c\U0001f3fe" RAISING_HANDS_DARK_SKIN_TONE = "\U0001f64c\U0001f3ff" +HEART_HANDS = "\U0001faf6" +HEART_HANDS_LIGHT_SKIN_TONE = "\U0001faf6\U0001f3fb" +HEART_HANDS_MEDIUM_LIGHT_SKIN_TONE = "\U0001faf6\U0001f3fc" +HEART_HANDS_MEDIUM_SKIN_TONE = "\U0001faf6\U0001f3fd" +HEART_HANDS_MEDIUM_DARK_SKIN_TONE = "\U0001faf6\U0001f3fe" +HEART_HANDS_DARK_SKIN_TONE = "\U0001faf6\U0001f3ff" OPEN_HANDS = "\U0001f450" OPEN_HANDS_LIGHT_SKIN_TONE = "\U0001f450\U0001f3fb" OPEN_HANDS_MEDIUM_LIGHT_SKIN_TONE = "\U0001f450\U0001f3fc" @@ -342,6 +396,31 @@ PALMS_UP_TOGETHER_MEDIUM_DARK_SKIN_TONE = "\U0001f932\U0001f3fe" PALMS_UP_TOGETHER_DARK_SKIN_TONE = "\U0001f932\U0001f3ff" HANDSHAKE = "\U0001f91d" +HANDSHAKE_LIGHT_SKIN_TONE = "\U0001f91d\U0001f3fb" +HANDSHAKE_MEDIUM_LIGHT_SKIN_TONE = "\U0001f91d\U0001f3fc" +HANDSHAKE_MEDIUM_SKIN_TONE = "\U0001f91d\U0001f3fd" +HANDSHAKE_MEDIUM_DARK_SKIN_TONE = "\U0001f91d\U0001f3fe" +HANDSHAKE_DARK_SKIN_TONE = "\U0001f91d\U0001f3ff" +HANDSHAKE_LIGHT_SKIN_TONE_MEDIUM_LIGHT_SKIN_TONE = "\U0001faf1\U0001f3fb\u200d\U0001faf2\U0001f3fc" +HANDSHAKE_LIGHT_SKIN_TONE_MEDIUM_SKIN_TONE = "\U0001faf1\U0001f3fb\u200d\U0001faf2\U0001f3fd" +HANDSHAKE_LIGHT_SKIN_TONE_MEDIUM_DARK_SKIN_TONE = "\U0001faf1\U0001f3fb\u200d\U0001faf2\U0001f3fe" +HANDSHAKE_LIGHT_SKIN_TONE_DARK_SKIN_TONE = "\U0001faf1\U0001f3fb\u200d\U0001faf2\U0001f3ff" +HANDSHAKE_MEDIUM_LIGHT_SKIN_TONE_LIGHT_SKIN_TONE = "\U0001faf1\U0001f3fc\u200d\U0001faf2\U0001f3fb" +HANDSHAKE_MEDIUM_LIGHT_SKIN_TONE_MEDIUM_SKIN_TONE = "\U0001faf1\U0001f3fc\u200d\U0001faf2\U0001f3fd" +HANDSHAKE_MEDIUM_LIGHT_SKIN_TONE_MEDIUM_DARK_SKIN_TONE = "\U0001faf1\U0001f3fc\u200d\U0001faf2\U0001f3fe" +HANDSHAKE_MEDIUM_LIGHT_SKIN_TONE_DARK_SKIN_TONE = "\U0001faf1\U0001f3fc\u200d\U0001faf2\U0001f3ff" +HANDSHAKE_MEDIUM_SKIN_TONE_LIGHT_SKIN_TONE = "\U0001faf1\U0001f3fd\u200d\U0001faf2\U0001f3fb" +HANDSHAKE_MEDIUM_SKIN_TONE_MEDIUM_LIGHT_SKIN_TONE = "\U0001faf1\U0001f3fd\u200d\U0001faf2\U0001f3fc" +HANDSHAKE_MEDIUM_SKIN_TONE_MEDIUM_DARK_SKIN_TONE = "\U0001faf1\U0001f3fd\u200d\U0001faf2\U0001f3fe" +HANDSHAKE_MEDIUM_SKIN_TONE_DARK_SKIN_TONE = "\U0001faf1\U0001f3fd\u200d\U0001faf2\U0001f3ff" +HANDSHAKE_MEDIUM_DARK_SKIN_TONE_LIGHT_SKIN_TONE = "\U0001faf1\U0001f3fe\u200d\U0001faf2\U0001f3fb" +HANDSHAKE_MEDIUM_DARK_SKIN_TONE_MEDIUM_LIGHT_SKIN_TONE = "\U0001faf1\U0001f3fe\u200d\U0001faf2\U0001f3fc" +HANDSHAKE_MEDIUM_DARK_SKIN_TONE_MEDIUM_SKIN_TONE = "\U0001faf1\U0001f3fe\u200d\U0001faf2\U0001f3fd" +HANDSHAKE_MEDIUM_DARK_SKIN_TONE_DARK_SKIN_TONE = "\U0001faf1\U0001f3fe\u200d\U0001faf2\U0001f3ff" +HANDSHAKE_DARK_SKIN_TONE_LIGHT_SKIN_TONE = "\U0001faf1\U0001f3ff\u200d\U0001faf2\U0001f3fb" +HANDSHAKE_DARK_SKIN_TONE_MEDIUM_LIGHT_SKIN_TONE = "\U0001faf1\U0001f3ff\u200d\U0001faf2\U0001f3fc" +HANDSHAKE_DARK_SKIN_TONE_MEDIUM_SKIN_TONE = "\U0001faf1\U0001f3ff\u200d\U0001faf2\U0001f3fd" +HANDSHAKE_DARK_SKIN_TONE_MEDIUM_DARK_SKIN_TONE = "\U0001faf1\U0001f3ff\u200d\U0001faf2\U0001f3fe" FOLDED_HANDS = "\U0001f64f" FOLDED_HANDS_LIGHT_SKIN_TONE = "\U0001f64f\U0001f3fb" FOLDED_HANDS_MEDIUM_LIGHT_SKIN_TONE = "\U0001f64f\U0001f3fc" @@ -413,6 +492,7 @@ EYE = "\U0001f441\ufe0f" TONGUE = "\U0001f445" MOUTH = "\U0001f444" +BITING_LIP = "\U0001fae6" BABY = "\U0001f476" BABY_LIGHT_SKIN_TONE = "\U0001f476\U0001f3fb" BABY_MEDIUM_LIGHT_SKIN_TONE = "\U0001f476\U0001f3fc" @@ -461,6 +541,18 @@ PERSON_MEDIUM_SKIN_TONE_BEARD = "\U0001f9d4\U0001f3fd" PERSON_MEDIUM_DARK_SKIN_TONE_BEARD = "\U0001f9d4\U0001f3fe" PERSON_DARK_SKIN_TONE_BEARD = "\U0001f9d4\U0001f3ff" +MAN_BEARD = "\U0001f9d4\u200d\u2642\ufe0f" +MAN_LIGHT_SKIN_TONE_BEARD = "\U0001f9d4\U0001f3fb\u200d\u2642\ufe0f" +MAN_MEDIUM_LIGHT_SKIN_TONE_BEARD = "\U0001f9d4\U0001f3fc\u200d\u2642\ufe0f" +MAN_MEDIUM_SKIN_TONE_BEARD = "\U0001f9d4\U0001f3fd\u200d\u2642\ufe0f" +MAN_MEDIUM_DARK_SKIN_TONE_BEARD = "\U0001f9d4\U0001f3fe\u200d\u2642\ufe0f" +MAN_DARK_SKIN_TONE_BEARD = "\U0001f9d4\U0001f3ff\u200d\u2642\ufe0f" +WOMAN_BEARD = "\U0001f9d4\u200d\u2640\ufe0f" +WOMAN_LIGHT_SKIN_TONE_BEARD = "\U0001f9d4\U0001f3fb\u200d\u2640\ufe0f" +WOMAN_MEDIUM_LIGHT_SKIN_TONE_BEARD = "\U0001f9d4\U0001f3fc\u200d\u2640\ufe0f" +WOMAN_MEDIUM_SKIN_TONE_BEARD = "\U0001f9d4\U0001f3fd\u200d\u2640\ufe0f" +WOMAN_MEDIUM_DARK_SKIN_TONE_BEARD = "\U0001f9d4\U0001f3fe\u200d\u2640\ufe0f" +WOMAN_DARK_SKIN_TONE_BEARD = "\U0001f9d4\U0001f3ff\u200d\u2640\ufe0f" MAN_RED_HAIR = "\U0001f468\u200d\U0001f9b0" MAN_LIGHT_SKIN_TONE_RED_HAIR = "\U0001f468\U0001f3fb\u200d\U0001f9b0" MAN_MEDIUM_LIGHT_SKIN_TONE_RED_HAIR = "\U0001f468\U0001f3fc\u200d\U0001f9b0" @@ -1115,6 +1207,12 @@ WOMAN_CONSTRUCTION_WORKER_MEDIUM_SKIN_TONE = "\U0001f477\U0001f3fd\u200d\u2640\ufe0f" WOMAN_CONSTRUCTION_WORKER_MEDIUM_DARK_SKIN_TONE = "\U0001f477\U0001f3fe\u200d\u2640\ufe0f" WOMAN_CONSTRUCTION_WORKER_DARK_SKIN_TONE = "\U0001f477\U0001f3ff\u200d\u2640\ufe0f" +PERSON_WITH_CROWN = "\U0001fac5" +PERSON_WITH_CROWN_LIGHT_SKIN_TONE = "\U0001fac5\U0001f3fb" +PERSON_WITH_CROWN_MEDIUM_LIGHT_SKIN_TONE = "\U0001fac5\U0001f3fc" +PERSON_WITH_CROWN_MEDIUM_SKIN_TONE = "\U0001fac5\U0001f3fd" +PERSON_WITH_CROWN_MEDIUM_DARK_SKIN_TONE = "\U0001fac5\U0001f3fe" +PERSON_WITH_CROWN_DARK_SKIN_TONE = "\U0001fac5\U0001f3ff" PRINCE = "\U0001f934" PRINCE_LIGHT_SKIN_TONE = "\U0001f934\U0001f3fb" PRINCE_MEDIUM_LIGHT_SKIN_TONE = "\U0001f934\U0001f3fc" @@ -1199,6 +1297,18 @@ PREGNANT_WOMAN_MEDIUM_SKIN_TONE = "\U0001f930\U0001f3fd" PREGNANT_WOMAN_MEDIUM_DARK_SKIN_TONE = "\U0001f930\U0001f3fe" PREGNANT_WOMAN_DARK_SKIN_TONE = "\U0001f930\U0001f3ff" +PREGNANT_MAN = "\U0001fac3" +PREGNANT_MAN_LIGHT_SKIN_TONE = "\U0001fac3\U0001f3fb" +PREGNANT_MAN_MEDIUM_LIGHT_SKIN_TONE = "\U0001fac3\U0001f3fc" +PREGNANT_MAN_MEDIUM_SKIN_TONE = "\U0001fac3\U0001f3fd" +PREGNANT_MAN_MEDIUM_DARK_SKIN_TONE = "\U0001fac3\U0001f3fe" +PREGNANT_MAN_DARK_SKIN_TONE = "\U0001fac3\U0001f3ff" +PREGNANT_PERSON = "\U0001fac4" +PREGNANT_PERSON_LIGHT_SKIN_TONE = "\U0001fac4\U0001f3fb" +PREGNANT_PERSON_MEDIUM_LIGHT_SKIN_TONE = "\U0001fac4\U0001f3fc" +PREGNANT_PERSON_MEDIUM_SKIN_TONE = "\U0001fac4\U0001f3fd" +PREGNANT_PERSON_MEDIUM_DARK_SKIN_TONE = "\U0001fac4\U0001f3fe" +PREGNANT_PERSON_DARK_SKIN_TONE = "\U0001fac4\U0001f3ff" BREAST_FEEDING = "\U0001f931" BREAST_FEEDING_LIGHT_SKIN_TONE = "\U0001f931\U0001f3fb" BREAST_FEEDING_MEDIUM_LIGHT_SKIN_TONE = "\U0001f931\U0001f3fc" @@ -1379,6 +1489,7 @@ ZOMBIE = "\U0001f9df" MAN_ZOMBIE = "\U0001f9df\u200d\u2642\ufe0f" WOMAN_ZOMBIE = "\U0001f9df\u200d\u2640\ufe0f" +TROLL = "\U0001f9cc" PERSON_GETTING_MASSAGE = "\U0001f486" PERSON_GETTING_MASSAGE_LIGHT_SKIN_TONE = "\U0001f486\U0001f3fb" PERSON_GETTING_MASSAGE_MEDIUM_LIGHT_SKIN_TONE = "\U0001f486\U0001f3fc" @@ -1775,6 +1886,8 @@ WOMAN_CARTWHEELING_MEDIUM_DARK_SKIN_TONE = "\U0001f938\U0001f3fe\u200d\u2640\ufe0f" WOMAN_CARTWHEELING_DARK_SKIN_TONE = "\U0001f938\U0001f3ff\u200d\u2640\ufe0f" PEOPLE_WRESTLING = "\U0001f93c" +MEN_WRESTLING = "\U0001f93c\u200d\u2642\ufe0f" +WOMEN_WRESTLING = "\U0001f93c\u200d\u2640\ufe0f" PERSON_PLAYING_WATER_POLO = "\U0001f93d" PERSON_PLAYING_WATER_POLO_LIGHT_SKIN_TONE = "\U0001f93d\U0001f3fb" PERSON_PLAYING_WATER_POLO_MEDIUM_LIGHT_SKIN_TONE = "\U0001f93d\U0001f3fc" @@ -2028,21 +2141,388 @@ "\U0001f468\U0001f3ff\u200d\U0001f91d\u200d\U0001f468\U0001f3fe" MEN_HOLDING_HANDS_DARK_SKIN_TONE = "\U0001f46c\U0001f3ff" KISS = "\U0001f48f" +KISS_LIGHT_SKIN_TONE = "\U0001f48f\U0001f3fb" +KISS_MEDIUM_LIGHT_SKIN_TONE = "\U0001f48f\U0001f3fc" +KISS_MEDIUM_SKIN_TONE = "\U0001f48f\U0001f3fd" +KISS_MEDIUM_DARK_SKIN_TONE = "\U0001f48f\U0001f3fe" +KISS_DARK_SKIN_TONE = "\U0001f48f\U0001f3ff" +KISS_PERSON_PERSON_LIGHT_SKIN_TONE_MEDIUM_LIGHT_SKIN_TONE = \ + "\U0001f9d1\U0001f3fb\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f9d1\U0001f3fc" +KISS_PERSON_PERSON_LIGHT_SKIN_TONE_MEDIUM_SKIN_TONE = \ + "\U0001f9d1\U0001f3fb\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f9d1\U0001f3fd" +KISS_PERSON_PERSON_LIGHT_SKIN_TONE_MEDIUM_DARK_SKIN_TONE = \ + "\U0001f9d1\U0001f3fb\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f9d1\U0001f3fe" +KISS_PERSON_PERSON_LIGHT_SKIN_TONE_DARK_SKIN_TONE = \ + "\U0001f9d1\U0001f3fb\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f9d1\U0001f3ff" +KISS_PERSON_PERSON_MEDIUM_LIGHT_SKIN_TONE_LIGHT_SKIN_TONE = \ + "\U0001f9d1\U0001f3fc\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f9d1\U0001f3fb" +KISS_PERSON_PERSON_MEDIUM_LIGHT_SKIN_TONE_MEDIUM_SKIN_TONE = \ + "\U0001f9d1\U0001f3fc\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f9d1\U0001f3fd" +KISS_PERSON_PERSON_MEDIUM_LIGHT_SKIN_TONE_MEDIUM_DARK_SKIN_TONE = \ + "\U0001f9d1\U0001f3fc\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f9d1\U0001f3fe" +KISS_PERSON_PERSON_MEDIUM_LIGHT_SKIN_TONE_DARK_SKIN_TONE = \ + "\U0001f9d1\U0001f3fc\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f9d1\U0001f3ff" +KISS_PERSON_PERSON_MEDIUM_SKIN_TONE_LIGHT_SKIN_TONE = \ + "\U0001f9d1\U0001f3fd\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f9d1\U0001f3fb" +KISS_PERSON_PERSON_MEDIUM_SKIN_TONE_MEDIUM_LIGHT_SKIN_TONE = \ + "\U0001f9d1\U0001f3fd\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f9d1\U0001f3fc" +KISS_PERSON_PERSON_MEDIUM_SKIN_TONE_MEDIUM_DARK_SKIN_TONE = \ + "\U0001f9d1\U0001f3fd\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f9d1\U0001f3fe" +KISS_PERSON_PERSON_MEDIUM_SKIN_TONE_DARK_SKIN_TONE = \ + "\U0001f9d1\U0001f3fd\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f9d1\U0001f3ff" +KISS_PERSON_PERSON_MEDIUM_DARK_SKIN_TONE_LIGHT_SKIN_TONE = \ + "\U0001f9d1\U0001f3fe\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f9d1\U0001f3fb" +KISS_PERSON_PERSON_MEDIUM_DARK_SKIN_TONE_MEDIUM_LIGHT_SKIN_TONE = \ + "\U0001f9d1\U0001f3fe\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f9d1\U0001f3fc" +KISS_PERSON_PERSON_MEDIUM_DARK_SKIN_TONE_MEDIUM_SKIN_TONE = \ + "\U0001f9d1\U0001f3fe\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f9d1\U0001f3fd" +KISS_PERSON_PERSON_MEDIUM_DARK_SKIN_TONE_DARK_SKIN_TONE = \ + "\U0001f9d1\U0001f3fe\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f9d1\U0001f3ff" +KISS_PERSON_PERSON_DARK_SKIN_TONE_LIGHT_SKIN_TONE = \ + "\U0001f9d1\U0001f3ff\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f9d1\U0001f3fb" +KISS_PERSON_PERSON_DARK_SKIN_TONE_MEDIUM_LIGHT_SKIN_TONE = \ + "\U0001f9d1\U0001f3ff\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f9d1\U0001f3fc" +KISS_PERSON_PERSON_DARK_SKIN_TONE_MEDIUM_SKIN_TONE = \ + "\U0001f9d1\U0001f3ff\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f9d1\U0001f3fd" +KISS_PERSON_PERSON_DARK_SKIN_TONE_MEDIUM_DARK_SKIN_TONE = \ + "\U0001f9d1\U0001f3ff\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f9d1\U0001f3fe" KISS_WOMAN_MAN = "\U0001f469\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468" +KISS_WOMAN_MAN_LIGHT_SKIN_TONE = "\U0001f469\U0001f3fb\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468\U0001f3fb" +KISS_WOMAN_MAN_LIGHT_SKIN_TONE_MEDIUM_LIGHT_SKIN_TONE = \ + "\U0001f469\U0001f3fb\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468\U0001f3fc" +KISS_WOMAN_MAN_LIGHT_SKIN_TONE_MEDIUM_SKIN_TONE = \ + "\U0001f469\U0001f3fb\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468\U0001f3fd" +KISS_WOMAN_MAN_LIGHT_SKIN_TONE_MEDIUM_DARK_SKIN_TONE = \ + "\U0001f469\U0001f3fb\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468\U0001f3fe" +KISS_WOMAN_MAN_LIGHT_SKIN_TONE_DARK_SKIN_TONE = \ + "\U0001f469\U0001f3fb\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468\U0001f3ff" +KISS_WOMAN_MAN_MEDIUM_LIGHT_SKIN_TONE_LIGHT_SKIN_TONE = \ + "\U0001f469\U0001f3fc\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468\U0001f3fb" +KISS_WOMAN_MAN_MEDIUM_LIGHT_SKIN_TONE = \ + "\U0001f469\U0001f3fc\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468\U0001f3fc" +KISS_WOMAN_MAN_MEDIUM_LIGHT_SKIN_TONE_MEDIUM_SKIN_TONE = \ + "\U0001f469\U0001f3fc\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468\U0001f3fd" +KISS_WOMAN_MAN_MEDIUM_LIGHT_SKIN_TONE_MEDIUM_DARK_SKIN_TONE = \ + "\U0001f469\U0001f3fc\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468\U0001f3fe" +KISS_WOMAN_MAN_MEDIUM_LIGHT_SKIN_TONE_DARK_SKIN_TONE = \ + "\U0001f469\U0001f3fc\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468\U0001f3ff" +KISS_WOMAN_MAN_MEDIUM_SKIN_TONE_LIGHT_SKIN_TONE = \ + "\U0001f469\U0001f3fd\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468\U0001f3fb" +KISS_WOMAN_MAN_MEDIUM_SKIN_TONE_MEDIUM_LIGHT_SKIN_TONE = \ + "\U0001f469\U0001f3fd\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468\U0001f3fc" +KISS_WOMAN_MAN_MEDIUM_SKIN_TONE = "\U0001f469\U0001f3fd\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468\U0001f3fd" +KISS_WOMAN_MAN_MEDIUM_SKIN_TONE_MEDIUM_DARK_SKIN_TONE = \ + "\U0001f469\U0001f3fd\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468\U0001f3fe" +KISS_WOMAN_MAN_MEDIUM_SKIN_TONE_DARK_SKIN_TONE = \ + "\U0001f469\U0001f3fd\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468\U0001f3ff" +KISS_WOMAN_MAN_MEDIUM_DARK_SKIN_TONE_LIGHT_SKIN_TONE = \ + "\U0001f469\U0001f3fe\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468\U0001f3fb" +KISS_WOMAN_MAN_MEDIUM_DARK_SKIN_TONE_MEDIUM_LIGHT_SKIN_TONE = \ + "\U0001f469\U0001f3fe\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468\U0001f3fc" +KISS_WOMAN_MAN_MEDIUM_DARK_SKIN_TONE_MEDIUM_SKIN_TONE = \ + "\U0001f469\U0001f3fe\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468\U0001f3fd" +KISS_WOMAN_MAN_MEDIUM_DARK_SKIN_TONE = \ + "\U0001f469\U0001f3fe\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468\U0001f3fe" +KISS_WOMAN_MAN_MEDIUM_DARK_SKIN_TONE_DARK_SKIN_TONE = \ + "\U0001f469\U0001f3fe\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468\U0001f3ff" +KISS_WOMAN_MAN_DARK_SKIN_TONE_LIGHT_SKIN_TONE = \ + "\U0001f469\U0001f3ff\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468\U0001f3fb" +KISS_WOMAN_MAN_DARK_SKIN_TONE_MEDIUM_LIGHT_SKIN_TONE = \ + "\U0001f469\U0001f3ff\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468\U0001f3fc" +KISS_WOMAN_MAN_DARK_SKIN_TONE_MEDIUM_SKIN_TONE = \ + "\U0001f469\U0001f3ff\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468\U0001f3fd" +KISS_WOMAN_MAN_DARK_SKIN_TONE_MEDIUM_DARK_SKIN_TONE = \ + "\U0001f469\U0001f3ff\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468\U0001f3fe" +KISS_WOMAN_MAN_DARK_SKIN_TONE = "\U0001f469\U0001f3ff\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468\U0001f3ff" KISS_MAN_MAN = "\U0001f468\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468" +KISS_MAN_MAN_LIGHT_SKIN_TONE = "\U0001f468\U0001f3fb\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468\U0001f3fb" +KISS_MAN_MAN_LIGHT_SKIN_TONE_MEDIUM_LIGHT_SKIN_TONE = \ + "\U0001f468\U0001f3fb\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468\U0001f3fc" +KISS_MAN_MAN_LIGHT_SKIN_TONE_MEDIUM_SKIN_TONE = \ + "\U0001f468\U0001f3fb\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468\U0001f3fd" +KISS_MAN_MAN_LIGHT_SKIN_TONE_MEDIUM_DARK_SKIN_TONE = \ + "\U0001f468\U0001f3fb\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468\U0001f3fe" +KISS_MAN_MAN_LIGHT_SKIN_TONE_DARK_SKIN_TONE = \ + "\U0001f468\U0001f3fb\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468\U0001f3ff" +KISS_MAN_MAN_MEDIUM_LIGHT_SKIN_TONE_LIGHT_SKIN_TONE = \ + "\U0001f468\U0001f3fc\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468\U0001f3fb" +KISS_MAN_MAN_MEDIUM_LIGHT_SKIN_TONE = \ + "\U0001f468\U0001f3fc\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468\U0001f3fc" +KISS_MAN_MAN_MEDIUM_LIGHT_SKIN_TONE_MEDIUM_SKIN_TONE = \ + "\U0001f468\U0001f3fc\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468\U0001f3fd" +KISS_MAN_MAN_MEDIUM_LIGHT_SKIN_TONE_MEDIUM_DARK_SKIN_TONE = \ + "\U0001f468\U0001f3fc\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468\U0001f3fe" +KISS_MAN_MAN_MEDIUM_LIGHT_SKIN_TONE_DARK_SKIN_TONE = \ + "\U0001f468\U0001f3fc\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468\U0001f3ff" +KISS_MAN_MAN_MEDIUM_SKIN_TONE_LIGHT_SKIN_TONE = \ + "\U0001f468\U0001f3fd\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468\U0001f3fb" +KISS_MAN_MAN_MEDIUM_SKIN_TONE_MEDIUM_LIGHT_SKIN_TONE = \ + "\U0001f468\U0001f3fd\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468\U0001f3fc" +KISS_MAN_MAN_MEDIUM_SKIN_TONE = "\U0001f468\U0001f3fd\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468\U0001f3fd" +KISS_MAN_MAN_MEDIUM_SKIN_TONE_MEDIUM_DARK_SKIN_TONE = \ + "\U0001f468\U0001f3fd\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468\U0001f3fe" +KISS_MAN_MAN_MEDIUM_SKIN_TONE_DARK_SKIN_TONE = \ + "\U0001f468\U0001f3fd\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468\U0001f3ff" +KISS_MAN_MAN_MEDIUM_DARK_SKIN_TONE_LIGHT_SKIN_TONE = \ + "\U0001f468\U0001f3fe\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468\U0001f3fb" +KISS_MAN_MAN_MEDIUM_DARK_SKIN_TONE_MEDIUM_LIGHT_SKIN_TONE = \ + "\U0001f468\U0001f3fe\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468\U0001f3fc" +KISS_MAN_MAN_MEDIUM_DARK_SKIN_TONE_MEDIUM_SKIN_TONE = \ + "\U0001f468\U0001f3fe\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468\U0001f3fd" +KISS_MAN_MAN_MEDIUM_DARK_SKIN_TONE = "\U0001f468\U0001f3fe\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468\U0001f3fe" +KISS_MAN_MAN_MEDIUM_DARK_SKIN_TONE_DARK_SKIN_TONE = \ + "\U0001f468\U0001f3fe\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468\U0001f3ff" +KISS_MAN_MAN_DARK_SKIN_TONE_LIGHT_SKIN_TONE = \ + "\U0001f468\U0001f3ff\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468\U0001f3fb" +KISS_MAN_MAN_DARK_SKIN_TONE_MEDIUM_LIGHT_SKIN_TONE = \ + "\U0001f468\U0001f3ff\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468\U0001f3fc" +KISS_MAN_MAN_DARK_SKIN_TONE_MEDIUM_SKIN_TONE = \ + "\U0001f468\U0001f3ff\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468\U0001f3fd" +KISS_MAN_MAN_DARK_SKIN_TONE_MEDIUM_DARK_SKIN_TONE = \ + "\U0001f468\U0001f3ff\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468\U0001f3fe" +KISS_MAN_MAN_DARK_SKIN_TONE = "\U0001f468\U0001f3ff\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468\U0001f3ff" KISS_WOMAN_WOMAN = "\U0001f469\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f469" +KISS_WOMAN_WOMAN_LIGHT_SKIN_TONE = "\U0001f469\U0001f3fb\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f469\U0001f3fb" +KISS_WOMAN_WOMAN_LIGHT_SKIN_TONE_MEDIUM_LIGHT_SKIN_TONE = \ + "\U0001f469\U0001f3fb\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f469\U0001f3fc" +KISS_WOMAN_WOMAN_LIGHT_SKIN_TONE_MEDIUM_SKIN_TONE = \ + "\U0001f469\U0001f3fb\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f469\U0001f3fd" +KISS_WOMAN_WOMAN_LIGHT_SKIN_TONE_MEDIUM_DARK_SKIN_TONE = \ + "\U0001f469\U0001f3fb\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f469\U0001f3fe" +KISS_WOMAN_WOMAN_LIGHT_SKIN_TONE_DARK_SKIN_TONE = \ + "\U0001f469\U0001f3fb\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f469\U0001f3ff" +KISS_WOMAN_WOMAN_MEDIUM_LIGHT_SKIN_TONE_LIGHT_SKIN_TONE = \ + "\U0001f469\U0001f3fc\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f469\U0001f3fb" +KISS_WOMAN_WOMAN_MEDIUM_LIGHT_SKIN_TONE = \ + "\U0001f469\U0001f3fc\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f469\U0001f3fc" +KISS_WOMAN_WOMAN_MEDIUM_LIGHT_SKIN_TONE_MEDIUM_SKIN_TONE = \ + "\U0001f469\U0001f3fc\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f469\U0001f3fd" +KISS_WOMAN_WOMAN_MEDIUM_LIGHT_SKIN_TONE_MEDIUM_DARK_SKIN_TONE = \ + "\U0001f469\U0001f3fc\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f469\U0001f3fe" +KISS_WOMAN_WOMAN_MEDIUM_LIGHT_SKIN_TONE_DARK_SKIN_TONE = \ + "\U0001f469\U0001f3fc\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f469\U0001f3ff" +KISS_WOMAN_WOMAN_MEDIUM_SKIN_TONE_LIGHT_SKIN_TONE = \ + "\U0001f469\U0001f3fd\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f469\U0001f3fb" +KISS_WOMAN_WOMAN_MEDIUM_SKIN_TONE_MEDIUM_LIGHT_SKIN_TONE = \ + "\U0001f469\U0001f3fd\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f469\U0001f3fc" +KISS_WOMAN_WOMAN_MEDIUM_SKIN_TONE = "\U0001f469\U0001f3fd\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f469\U0001f3fd" +KISS_WOMAN_WOMAN_MEDIUM_SKIN_TONE_MEDIUM_DARK_SKIN_TONE = \ + "\U0001f469\U0001f3fd\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f469\U0001f3fe" +KISS_WOMAN_WOMAN_MEDIUM_SKIN_TONE_DARK_SKIN_TONE = \ + "\U0001f469\U0001f3fd\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f469\U0001f3ff" +KISS_WOMAN_WOMAN_MEDIUM_DARK_SKIN_TONE_LIGHT_SKIN_TONE = \ + "\U0001f469\U0001f3fe\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f469\U0001f3fb" +KISS_WOMAN_WOMAN_MEDIUM_DARK_SKIN_TONE_MEDIUM_LIGHT_SKIN_TONE = \ + "\U0001f469\U0001f3fe\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f469\U0001f3fc" +KISS_WOMAN_WOMAN_MEDIUM_DARK_SKIN_TONE_MEDIUM_SKIN_TONE = \ + "\U0001f469\U0001f3fe\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f469\U0001f3fd" +KISS_WOMAN_WOMAN_MEDIUM_DARK_SKIN_TONE = \ + "\U0001f469\U0001f3fe\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f469\U0001f3fe" +KISS_WOMAN_WOMAN_MEDIUM_DARK_SKIN_TONE_DARK_SKIN_TONE = \ + "\U0001f469\U0001f3fe\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f469\U0001f3ff" +KISS_WOMAN_WOMAN_DARK_SKIN_TONE_LIGHT_SKIN_TONE = \ + "\U0001f469\U0001f3ff\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f469\U0001f3fb" +KISS_WOMAN_WOMAN_DARK_SKIN_TONE_MEDIUM_LIGHT_SKIN_TONE = \ + "\U0001f469\U0001f3ff\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f469\U0001f3fc" +KISS_WOMAN_WOMAN_DARK_SKIN_TONE_MEDIUM_SKIN_TONE = \ + "\U0001f469\U0001f3ff\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f469\U0001f3fd" +KISS_WOMAN_WOMAN_DARK_SKIN_TONE_MEDIUM_DARK_SKIN_TONE = \ + "\U0001f469\U0001f3ff\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f469\U0001f3fe" +KISS_WOMAN_WOMAN_DARK_SKIN_TONE = "\U0001f469\U0001f3ff\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f469\U0001f3ff" COUPLE_WITH_HEART = "\U0001f491" +COUPLE_WITH_HEART_LIGHT_SKIN_TONE = "\U0001f491\U0001f3fb" +COUPLE_WITH_HEART_MEDIUM_LIGHT_SKIN_TONE = "\U0001f491\U0001f3fc" +COUPLE_WITH_HEART_MEDIUM_SKIN_TONE = "\U0001f491\U0001f3fd" +COUPLE_WITH_HEART_MEDIUM_DARK_SKIN_TONE = "\U0001f491\U0001f3fe" +COUPLE_WITH_HEART_DARK_SKIN_TONE = "\U0001f491\U0001f3ff" +COUPLE_WITH_HEART_PERSON_PERSON_LIGHT_SKIN_TONE_MEDIUM_LIGHT_SKIN_TONE = \ + "\U0001f9d1\U0001f3fb\u200d\u2764\ufe0f\u200d\U0001f9d1\U0001f3fc" +COUPLE_WITH_HEART_PERSON_PERSON_LIGHT_SKIN_TONE_MEDIUM_SKIN_TONE = \ + "\U0001f9d1\U0001f3fb\u200d\u2764\ufe0f\u200d\U0001f9d1\U0001f3fd" +COUPLE_WITH_HEART_PERSON_PERSON_LIGHT_SKIN_TONE_MEDIUM_DARK_SKIN_TONE = \ + "\U0001f9d1\U0001f3fb\u200d\u2764\ufe0f\u200d\U0001f9d1\U0001f3fe" +COUPLE_WITH_HEART_PERSON_PERSON_LIGHT_SKIN_TONE_DARK_SKIN_TONE = \ + "\U0001f9d1\U0001f3fb\u200d\u2764\ufe0f\u200d\U0001f9d1\U0001f3ff" +COUPLE_WITH_HEART_PERSON_PERSON_MEDIUM_LIGHT_SKIN_TONE_LIGHT_SKIN_TONE = \ + "\U0001f9d1\U0001f3fc\u200d\u2764\ufe0f\u200d\U0001f9d1\U0001f3fb" +COUPLE_WITH_HEART_PERSON_PERSON_MEDIUM_LIGHT_SKIN_TONE_MEDIUM_SKIN_TONE = \ + "\U0001f9d1\U0001f3fc\u200d\u2764\ufe0f\u200d\U0001f9d1\U0001f3fd" +COUPLE_WITH_HEART_PERSON_PERSON_MEDIUM_LIGHT_SKIN_TONE_MEDIUM_DARK_SKIN_TONE = \ + "\U0001f9d1\U0001f3fc\u200d\u2764\ufe0f\u200d\U0001f9d1\U0001f3fe" +COUPLE_WITH_HEART_PERSON_PERSON_MEDIUM_LIGHT_SKIN_TONE_DARK_SKIN_TONE = \ + "\U0001f9d1\U0001f3fc\u200d\u2764\ufe0f\u200d\U0001f9d1\U0001f3ff" +COUPLE_WITH_HEART_PERSON_PERSON_MEDIUM_SKIN_TONE_LIGHT_SKIN_TONE = \ + "\U0001f9d1\U0001f3fd\u200d\u2764\ufe0f\u200d\U0001f9d1\U0001f3fb" +COUPLE_WITH_HEART_PERSON_PERSON_MEDIUM_SKIN_TONE_MEDIUM_LIGHT_SKIN_TONE = \ + "\U0001f9d1\U0001f3fd\u200d\u2764\ufe0f\u200d\U0001f9d1\U0001f3fc" +COUPLE_WITH_HEART_PERSON_PERSON_MEDIUM_SKIN_TONE_MEDIUM_DARK_SKIN_TONE = \ + "\U0001f9d1\U0001f3fd\u200d\u2764\ufe0f\u200d\U0001f9d1\U0001f3fe" +COUPLE_WITH_HEART_PERSON_PERSON_MEDIUM_SKIN_TONE_DARK_SKIN_TONE = \ + "\U0001f9d1\U0001f3fd\u200d\u2764\ufe0f\u200d\U0001f9d1\U0001f3ff" +COUPLE_WITH_HEART_PERSON_PERSON_MEDIUM_DARK_SKIN_TONE_LIGHT_SKIN_TONE = \ + "\U0001f9d1\U0001f3fe\u200d\u2764\ufe0f\u200d\U0001f9d1\U0001f3fb" +COUPLE_WITH_HEART_PERSON_PERSON_MEDIUM_DARK_SKIN_TONE_MEDIUM_LIGHT_SKIN_TONE = \ + "\U0001f9d1\U0001f3fe\u200d\u2764\ufe0f\u200d\U0001f9d1\U0001f3fc" +COUPLE_WITH_HEART_PERSON_PERSON_MEDIUM_DARK_SKIN_TONE_MEDIUM_SKIN_TONE = \ + "\U0001f9d1\U0001f3fe\u200d\u2764\ufe0f\u200d\U0001f9d1\U0001f3fd" +COUPLE_WITH_HEART_PERSON_PERSON_MEDIUM_DARK_SKIN_TONE_DARK_SKIN_TONE = \ + "\U0001f9d1\U0001f3fe\u200d\u2764\ufe0f\u200d\U0001f9d1\U0001f3ff" +COUPLE_WITH_HEART_PERSON_PERSON_DARK_SKIN_TONE_LIGHT_SKIN_TONE = \ + "\U0001f9d1\U0001f3ff\u200d\u2764\ufe0f\u200d\U0001f9d1\U0001f3fb" +COUPLE_WITH_HEART_PERSON_PERSON_DARK_SKIN_TONE_MEDIUM_LIGHT_SKIN_TONE = \ + "\U0001f9d1\U0001f3ff\u200d\u2764\ufe0f\u200d\U0001f9d1\U0001f3fc" +COUPLE_WITH_HEART_PERSON_PERSON_DARK_SKIN_TONE_MEDIUM_SKIN_TONE = \ + "\U0001f9d1\U0001f3ff\u200d\u2764\ufe0f\u200d\U0001f9d1\U0001f3fd" +COUPLE_WITH_HEART_PERSON_PERSON_DARK_SKIN_TONE_MEDIUM_DARK_SKIN_TONE = \ + "\U0001f9d1\U0001f3ff\u200d\u2764\ufe0f\u200d\U0001f9d1\U0001f3fe" COUPLE_WITH_HEART_WOMAN_MAN = "\U0001f469\u200d\u2764\ufe0f\u200d\U0001f468" -COUPLE_WITH_HEART_MAN_MAN = "\U0001f468\u200d\u2764\ufe0f\u200d\U0001f468" -COUPLE_WITH_HEART_WOMAN_WOMAN = "\U0001f469\u200d\u2764\ufe0f\u200d\U0001f469" -FAMILY = "\U0001f46a" -FAMILY_MAN_WOMAN_BOY = "\U0001f468\u200d\U0001f469\u200d\U0001f466" -FAMILY_MAN_WOMAN_GIRL = "\U0001f468\u200d\U0001f469\u200d\U0001f467" -FAMILY_MAN_WOMAN_GIRL_BOY = "\U0001f468\u200d\U0001f469\u200d\U0001f467\u200d\U0001f466" -FAMILY_MAN_WOMAN_BOY_BOY = "\U0001f468\u200d\U0001f469\u200d\U0001f466\u200d\U0001f466" -FAMILY_MAN_WOMAN_GIRL_GIRL = "\U0001f468\u200d\U0001f469\u200d\U0001f467\u200d\U0001f467" -FAMILY_MAN_MAN_BOY = "\U0001f468\u200d\U0001f468\u200d\U0001f466" -FAMILY_MAN_MAN_GIRL = "\U0001f468\u200d\U0001f468\u200d\U0001f467" +COUPLE_WITH_HEART_WOMAN_MAN_LIGHT_SKIN_TONE = "\U0001f469\U0001f3fb\u200d\u2764\ufe0f\u200d\U0001f468\U0001f3fb" +COUPLE_WITH_HEART_WOMAN_MAN_LIGHT_SKIN_TONE_MEDIUM_LIGHT_SKIN_TONE = \ + "\U0001f469\U0001f3fb\u200d\u2764\ufe0f\u200d\U0001f468\U0001f3fc" +COUPLE_WITH_HEART_WOMAN_MAN_LIGHT_SKIN_TONE_MEDIUM_SKIN_TONE = \ + "\U0001f469\U0001f3fb\u200d\u2764\ufe0f\u200d\U0001f468\U0001f3fd" +COUPLE_WITH_HEART_WOMAN_MAN_LIGHT_SKIN_TONE_MEDIUM_DARK_SKIN_TONE = \ + "\U0001f469\U0001f3fb\u200d\u2764\ufe0f\u200d\U0001f468\U0001f3fe" +COUPLE_WITH_HEART_WOMAN_MAN_LIGHT_SKIN_TONE_DARK_SKIN_TONE = \ + "\U0001f469\U0001f3fb\u200d\u2764\ufe0f\u200d\U0001f468\U0001f3ff" +COUPLE_WITH_HEART_WOMAN_MAN_MEDIUM_LIGHT_SKIN_TONE_LIGHT_SKIN_TONE = \ + "\U0001f469\U0001f3fc\u200d\u2764\ufe0f\u200d\U0001f468\U0001f3fb" +COUPLE_WITH_HEART_WOMAN_MAN_MEDIUM_LIGHT_SKIN_TONE = "\U0001f469\U0001f3fc\u200d\u2764\ufe0f\u200d\U0001f468\U0001f3fc" +COUPLE_WITH_HEART_WOMAN_MAN_MEDIUM_LIGHT_SKIN_TONE_MEDIUM_SKIN_TONE = \ + "\U0001f469\U0001f3fc\u200d\u2764\ufe0f\u200d\U0001f468\U0001f3fd" +COUPLE_WITH_HEART_WOMAN_MAN_MEDIUM_LIGHT_SKIN_TONE_MEDIUM_DARK_SKIN_TONE = \ + "\U0001f469\U0001f3fc\u200d\u2764\ufe0f\u200d\U0001f468\U0001f3fe" +COUPLE_WITH_HEART_WOMAN_MAN_MEDIUM_LIGHT_SKIN_TONE_DARK_SKIN_TONE = \ + "\U0001f469\U0001f3fc\u200d\u2764\ufe0f\u200d\U0001f468\U0001f3ff" +COUPLE_WITH_HEART_WOMAN_MAN_MEDIUM_SKIN_TONE_LIGHT_SKIN_TONE = \ + "\U0001f469\U0001f3fd\u200d\u2764\ufe0f\u200d\U0001f468\U0001f3fb" +COUPLE_WITH_HEART_WOMAN_MAN_MEDIUM_SKIN_TONE_MEDIUM_LIGHT_SKIN_TONE = \ + "\U0001f469\U0001f3fd\u200d\u2764\ufe0f\u200d\U0001f468\U0001f3fc" +COUPLE_WITH_HEART_WOMAN_MAN_MEDIUM_SKIN_TONE = "\U0001f469\U0001f3fd\u200d\u2764\ufe0f\u200d\U0001f468\U0001f3fd" +COUPLE_WITH_HEART_WOMAN_MAN_MEDIUM_SKIN_TONE_MEDIUM_DARK_SKIN_TONE = \ + "\U0001f469\U0001f3fd\u200d\u2764\ufe0f\u200d\U0001f468\U0001f3fe" +COUPLE_WITH_HEART_WOMAN_MAN_MEDIUM_SKIN_TONE_DARK_SKIN_TONE = \ + "\U0001f469\U0001f3fd\u200d\u2764\ufe0f\u200d\U0001f468\U0001f3ff" +COUPLE_WITH_HEART_WOMAN_MAN_MEDIUM_DARK_SKIN_TONE_LIGHT_SKIN_TONE = \ + "\U0001f469\U0001f3fe\u200d\u2764\ufe0f\u200d\U0001f468\U0001f3fb" +COUPLE_WITH_HEART_WOMAN_MAN_MEDIUM_DARK_SKIN_TONE_MEDIUM_LIGHT_SKIN_TONE = \ + "\U0001f469\U0001f3fe\u200d\u2764\ufe0f\u200d\U0001f468\U0001f3fc" +COUPLE_WITH_HEART_WOMAN_MAN_MEDIUM_DARK_SKIN_TONE_MEDIUM_SKIN_TONE = \ + "\U0001f469\U0001f3fe\u200d\u2764\ufe0f\u200d\U0001f468\U0001f3fd" +COUPLE_WITH_HEART_WOMAN_MAN_MEDIUM_DARK_SKIN_TONE = "\U0001f469\U0001f3fe\u200d\u2764\ufe0f\u200d\U0001f468\U0001f3fe" +COUPLE_WITH_HEART_WOMAN_MAN_MEDIUM_DARK_SKIN_TONE_DARK_SKIN_TONE = \ + "\U0001f469\U0001f3fe\u200d\u2764\ufe0f\u200d\U0001f468\U0001f3ff" +COUPLE_WITH_HEART_WOMAN_MAN_DARK_SKIN_TONE_LIGHT_SKIN_TONE = \ + "\U0001f469\U0001f3ff\u200d\u2764\ufe0f\u200d\U0001f468\U0001f3fb" +COUPLE_WITH_HEART_WOMAN_MAN_DARK_SKIN_TONE_MEDIUM_LIGHT_SKIN_TONE = \ + "\U0001f469\U0001f3ff\u200d\u2764\ufe0f\u200d\U0001f468\U0001f3fc" +COUPLE_WITH_HEART_WOMAN_MAN_DARK_SKIN_TONE_MEDIUM_SKIN_TONE = \ + "\U0001f469\U0001f3ff\u200d\u2764\ufe0f\u200d\U0001f468\U0001f3fd" +COUPLE_WITH_HEART_WOMAN_MAN_DARK_SKIN_TONE_MEDIUM_DARK_SKIN_TONE = \ + "\U0001f469\U0001f3ff\u200d\u2764\ufe0f\u200d\U0001f468\U0001f3fe" +COUPLE_WITH_HEART_WOMAN_MAN_DARK_SKIN_TONE = "\U0001f469\U0001f3ff\u200d\u2764\ufe0f\u200d\U0001f468\U0001f3ff" +COUPLE_WITH_HEART_MAN_MAN = "\U0001f468\u200d\u2764\ufe0f\u200d\U0001f468" +COUPLE_WITH_HEART_MAN_MAN_LIGHT_SKIN_TONE = "\U0001f468\U0001f3fb\u200d\u2764\ufe0f\u200d\U0001f468\U0001f3fb" +COUPLE_WITH_HEART_MAN_MAN_LIGHT_SKIN_TONE_MEDIUM_LIGHT_SKIN_TONE = \ + "\U0001f468\U0001f3fb\u200d\u2764\ufe0f\u200d\U0001f468\U0001f3fc" +COUPLE_WITH_HEART_MAN_MAN_LIGHT_SKIN_TONE_MEDIUM_SKIN_TONE = \ + "\U0001f468\U0001f3fb\u200d\u2764\ufe0f\u200d\U0001f468\U0001f3fd" +COUPLE_WITH_HEART_MAN_MAN_LIGHT_SKIN_TONE_MEDIUM_DARK_SKIN_TONE = \ + "\U0001f468\U0001f3fb\u200d\u2764\ufe0f\u200d\U0001f468\U0001f3fe" +COUPLE_WITH_HEART_MAN_MAN_LIGHT_SKIN_TONE_DARK_SKIN_TONE = \ + "\U0001f468\U0001f3fb\u200d\u2764\ufe0f\u200d\U0001f468\U0001f3ff" +COUPLE_WITH_HEART_MAN_MAN_MEDIUM_LIGHT_SKIN_TONE_LIGHT_SKIN_TONE = \ + "\U0001f468\U0001f3fc\u200d\u2764\ufe0f\u200d\U0001f468\U0001f3fb" +COUPLE_WITH_HEART_MAN_MAN_MEDIUM_LIGHT_SKIN_TONE = "\U0001f468\U0001f3fc\u200d\u2764\ufe0f\u200d\U0001f468\U0001f3fc" +COUPLE_WITH_HEART_MAN_MAN_MEDIUM_LIGHT_SKIN_TONE_MEDIUM_SKIN_TONE = \ + "\U0001f468\U0001f3fc\u200d\u2764\ufe0f\u200d\U0001f468\U0001f3fd" +COUPLE_WITH_HEART_MAN_MAN_MEDIUM_LIGHT_SKIN_TONE_MEDIUM_DARK_SKIN_TONE = \ + "\U0001f468\U0001f3fc\u200d\u2764\ufe0f\u200d\U0001f468\U0001f3fe" +COUPLE_WITH_HEART_MAN_MAN_MEDIUM_LIGHT_SKIN_TONE_DARK_SKIN_TONE = \ + "\U0001f468\U0001f3fc\u200d\u2764\ufe0f\u200d\U0001f468\U0001f3ff" +COUPLE_WITH_HEART_MAN_MAN_MEDIUM_SKIN_TONE_LIGHT_SKIN_TONE = \ + "\U0001f468\U0001f3fd\u200d\u2764\ufe0f\u200d\U0001f468\U0001f3fb" +COUPLE_WITH_HEART_MAN_MAN_MEDIUM_SKIN_TONE_MEDIUM_LIGHT_SKIN_TONE = \ + "\U0001f468\U0001f3fd\u200d\u2764\ufe0f\u200d\U0001f468\U0001f3fc" +COUPLE_WITH_HEART_MAN_MAN_MEDIUM_SKIN_TONE = "\U0001f468\U0001f3fd\u200d\u2764\ufe0f\u200d\U0001f468\U0001f3fd" +COUPLE_WITH_HEART_MAN_MAN_MEDIUM_SKIN_TONE_MEDIUM_DARK_SKIN_TONE = \ + "\U0001f468\U0001f3fd\u200d\u2764\ufe0f\u200d\U0001f468\U0001f3fe" +COUPLE_WITH_HEART_MAN_MAN_MEDIUM_SKIN_TONE_DARK_SKIN_TONE = \ + "\U0001f468\U0001f3fd\u200d\u2764\ufe0f\u200d\U0001f468\U0001f3ff" +COUPLE_WITH_HEART_MAN_MAN_MEDIUM_DARK_SKIN_TONE_LIGHT_SKIN_TONE = \ + "\U0001f468\U0001f3fe\u200d\u2764\ufe0f\u200d\U0001f468\U0001f3fb" +COUPLE_WITH_HEART_MAN_MAN_MEDIUM_DARK_SKIN_TONE_MEDIUM_LIGHT_SKIN_TONE = \ + "\U0001f468\U0001f3fe\u200d\u2764\ufe0f\u200d\U0001f468\U0001f3fc" +COUPLE_WITH_HEART_MAN_MAN_MEDIUM_DARK_SKIN_TONE_MEDIUM_SKIN_TONE = \ + "\U0001f468\U0001f3fe\u200d\u2764\ufe0f\u200d\U0001f468\U0001f3fd" +COUPLE_WITH_HEART_MAN_MAN_MEDIUM_DARK_SKIN_TONE = "\U0001f468\U0001f3fe\u200d\u2764\ufe0f\u200d\U0001f468\U0001f3fe" +COUPLE_WITH_HEART_MAN_MAN_MEDIUM_DARK_SKIN_TONE_DARK_SKIN_TONE = \ + "\U0001f468\U0001f3fe\u200d\u2764\ufe0f\u200d\U0001f468\U0001f3ff" +COUPLE_WITH_HEART_MAN_MAN_DARK_SKIN_TONE_LIGHT_SKIN_TONE = \ + "\U0001f468\U0001f3ff\u200d\u2764\ufe0f\u200d\U0001f468\U0001f3fb" +COUPLE_WITH_HEART_MAN_MAN_DARK_SKIN_TONE_MEDIUM_LIGHT_SKIN_TONE = \ + "\U0001f468\U0001f3ff\u200d\u2764\ufe0f\u200d\U0001f468\U0001f3fc" +COUPLE_WITH_HEART_MAN_MAN_DARK_SKIN_TONE_MEDIUM_SKIN_TONE = \ + "\U0001f468\U0001f3ff\u200d\u2764\ufe0f\u200d\U0001f468\U0001f3fd" +COUPLE_WITH_HEART_MAN_MAN_DARK_SKIN_TONE_MEDIUM_DARK_SKIN_TONE = \ + "\U0001f468\U0001f3ff\u200d\u2764\ufe0f\u200d\U0001f468\U0001f3fe" +COUPLE_WITH_HEART_MAN_MAN_DARK_SKIN_TONE = "\U0001f468\U0001f3ff\u200d\u2764\ufe0f\u200d\U0001f468\U0001f3ff" +COUPLE_WITH_HEART_WOMAN_WOMAN = "\U0001f469\u200d\u2764\ufe0f\u200d\U0001f469" +COUPLE_WITH_HEART_WOMAN_WOMAN_LIGHT_SKIN_TONE = "\U0001f469\U0001f3fb\u200d\u2764\ufe0f\u200d\U0001f469\U0001f3fb" +COUPLE_WITH_HEART_WOMAN_WOMAN_LIGHT_SKIN_TONE_MEDIUM_LIGHT_SKIN_TONE = \ + "\U0001f469\U0001f3fb\u200d\u2764\ufe0f\u200d\U0001f469\U0001f3fc" +COUPLE_WITH_HEART_WOMAN_WOMAN_LIGHT_SKIN_TONE_MEDIUM_SKIN_TONE = \ + "\U0001f469\U0001f3fb\u200d\u2764\ufe0f\u200d\U0001f469\U0001f3fd" +COUPLE_WITH_HEART_WOMAN_WOMAN_LIGHT_SKIN_TONE_MEDIUM_DARK_SKIN_TONE = \ + "\U0001f469\U0001f3fb\u200d\u2764\ufe0f\u200d\U0001f469\U0001f3fe" +COUPLE_WITH_HEART_WOMAN_WOMAN_LIGHT_SKIN_TONE_DARK_SKIN_TONE = \ + "\U0001f469\U0001f3fb\u200d\u2764\ufe0f\u200d\U0001f469\U0001f3ff" +COUPLE_WITH_HEART_WOMAN_WOMAN_MEDIUM_LIGHT_SKIN_TONE_LIGHT_SKIN_TONE = \ + "\U0001f469\U0001f3fc\u200d\u2764\ufe0f\u200d\U0001f469\U0001f3fb" +COUPLE_WITH_HEART_WOMAN_WOMAN_MEDIUM_LIGHT_SKIN_TONE = \ + "\U0001f469\U0001f3fc\u200d\u2764\ufe0f\u200d\U0001f469\U0001f3fc" +COUPLE_WITH_HEART_WOMAN_WOMAN_MEDIUM_LIGHT_SKIN_TONE_MEDIUM_SKIN_TONE = \ + "\U0001f469\U0001f3fc\u200d\u2764\ufe0f\u200d\U0001f469\U0001f3fd" +COUPLE_WITH_HEART_WOMAN_WOMAN_MEDIUM_LIGHT_SKIN_TONE_MEDIUM_DARK_SKIN_TONE = \ + "\U0001f469\U0001f3fc\u200d\u2764\ufe0f\u200d\U0001f469\U0001f3fe" +COUPLE_WITH_HEART_WOMAN_WOMAN_MEDIUM_LIGHT_SKIN_TONE_DARK_SKIN_TONE = \ + "\U0001f469\U0001f3fc\u200d\u2764\ufe0f\u200d\U0001f469\U0001f3ff" +COUPLE_WITH_HEART_WOMAN_WOMAN_MEDIUM_SKIN_TONE_LIGHT_SKIN_TONE = \ + "\U0001f469\U0001f3fd\u200d\u2764\ufe0f\u200d\U0001f469\U0001f3fb" +COUPLE_WITH_HEART_WOMAN_WOMAN_MEDIUM_SKIN_TONE_MEDIUM_LIGHT_SKIN_TONE = \ + "\U0001f469\U0001f3fd\u200d\u2764\ufe0f\u200d\U0001f469\U0001f3fc" +COUPLE_WITH_HEART_WOMAN_WOMAN_MEDIUM_SKIN_TONE = "\U0001f469\U0001f3fd\u200d\u2764\ufe0f\u200d\U0001f469\U0001f3fd" +COUPLE_WITH_HEART_WOMAN_WOMAN_MEDIUM_SKIN_TONE_MEDIUM_DARK_SKIN_TONE = \ + "\U0001f469\U0001f3fd\u200d\u2764\ufe0f\u200d\U0001f469\U0001f3fe" +COUPLE_WITH_HEART_WOMAN_WOMAN_MEDIUM_SKIN_TONE_DARK_SKIN_TONE = \ + "\U0001f469\U0001f3fd\u200d\u2764\ufe0f\u200d\U0001f469\U0001f3ff" +COUPLE_WITH_HEART_WOMAN_WOMAN_MEDIUM_DARK_SKIN_TONE_LIGHT_SKIN_TONE = \ + "\U0001f469\U0001f3fe\u200d\u2764\ufe0f\u200d\U0001f469\U0001f3fb" +COUPLE_WITH_HEART_WOMAN_WOMAN_MEDIUM_DARK_SKIN_TONE_MEDIUM_LIGHT_SKIN_TONE = \ + "\U0001f469\U0001f3fe\u200d\u2764\ufe0f\u200d\U0001f469\U0001f3fc" +COUPLE_WITH_HEART_WOMAN_WOMAN_MEDIUM_DARK_SKIN_TONE_MEDIUM_SKIN_TONE = \ + "\U0001f469\U0001f3fe\u200d\u2764\ufe0f\u200d\U0001f469\U0001f3fd" +COUPLE_WITH_HEART_WOMAN_WOMAN_MEDIUM_DARK_SKIN_TONE = \ + "\U0001f469\U0001f3fe\u200d\u2764\ufe0f\u200d\U0001f469\U0001f3fe" +COUPLE_WITH_HEART_WOMAN_WOMAN_MEDIUM_DARK_SKIN_TONE_DARK_SKIN_TONE = \ + "\U0001f469\U0001f3fe\u200d\u2764\ufe0f\u200d\U0001f469\U0001f3ff" +COUPLE_WITH_HEART_WOMAN_WOMAN_DARK_SKIN_TONE_LIGHT_SKIN_TONE = \ + "\U0001f469\U0001f3ff\u200d\u2764\ufe0f\u200d\U0001f469\U0001f3fb" +COUPLE_WITH_HEART_WOMAN_WOMAN_DARK_SKIN_TONE_MEDIUM_LIGHT_SKIN_TONE = \ + "\U0001f469\U0001f3ff\u200d\u2764\ufe0f\u200d\U0001f469\U0001f3fc" +COUPLE_WITH_HEART_WOMAN_WOMAN_DARK_SKIN_TONE_MEDIUM_SKIN_TONE = \ + "\U0001f469\U0001f3ff\u200d\u2764\ufe0f\u200d\U0001f469\U0001f3fd" +COUPLE_WITH_HEART_WOMAN_WOMAN_DARK_SKIN_TONE_MEDIUM_DARK_SKIN_TONE = \ + "\U0001f469\U0001f3ff\u200d\u2764\ufe0f\u200d\U0001f469\U0001f3fe" +COUPLE_WITH_HEART_WOMAN_WOMAN_DARK_SKIN_TONE = "\U0001f469\U0001f3ff\u200d\u2764\ufe0f\u200d\U0001f469\U0001f3ff" +FAMILY = "\U0001f46a" +FAMILY_MAN_WOMAN_BOY = "\U0001f468\u200d\U0001f469\u200d\U0001f466" +FAMILY_MAN_WOMAN_GIRL = "\U0001f468\u200d\U0001f469\u200d\U0001f467" +FAMILY_MAN_WOMAN_GIRL_BOY = "\U0001f468\u200d\U0001f469\u200d\U0001f467\u200d\U0001f466" +FAMILY_MAN_WOMAN_BOY_BOY = "\U0001f468\u200d\U0001f469\u200d\U0001f466\u200d\U0001f466" +FAMILY_MAN_WOMAN_GIRL_GIRL = "\U0001f468\u200d\U0001f469\u200d\U0001f467\u200d\U0001f467" +FAMILY_MAN_MAN_BOY = "\U0001f468\u200d\U0001f468\u200d\U0001f466" +FAMILY_MAN_MAN_GIRL = "\U0001f468\u200d\U0001f468\u200d\U0001f467" FAMILY_MAN_MAN_GIRL_BOY = "\U0001f468\u200d\U0001f468\u200d\U0001f467\u200d\U0001f466" FAMILY_MAN_MAN_BOY_BOY = "\U0001f468\u200d\U0001f468\u200d\U0001f466\u200d\U0001f466" FAMILY_MAN_MAN_GIRL_GIRL = "\U0001f468\u200d\U0001f468\u200d\U0001f467\u200d\U0001f467" @@ -2176,6 +2656,7 @@ SHARK = "\U0001f988" OCTOPUS = "\U0001f419" SPIRAL_SHELL = "\U0001f41a" +CORAL = "\U0001fab8" SNAIL = "\U0001f40c" BUTTERFLY = "\U0001f98b" BUG = "\U0001f41b" @@ -2195,6 +2676,7 @@ BOUQUET = "\U0001f490" CHERRY_BLOSSOM = "\U0001f338" WHITE_FLOWER = "\U0001f4ae" +LOTUS = "\U0001fab7" ROSETTE = "\U0001f3f5\ufe0f" ROSE = "\U0001f339" WILTED_FLOWER = "\U0001f940" @@ -2215,6 +2697,8 @@ MAPLE_LEAF = "\U0001f341" FALLEN_LEAF = "\U0001f342" LEAF_FLUTTERING_IN_WIND = "\U0001f343" +EMPTY_NEST = "\U0001fab9" +NEST_WITH_EGGS = "\U0001faba" GRAPES = "\U0001f347" MELON = "\U0001f348" WATERMELON = "\U0001f349" @@ -2248,6 +2732,7 @@ ONION = "\U0001f9c5" MUSHROOM = "\U0001f344" PEANUTS = "\U0001f95c" +BEANS = "\U0001fad8" CHESTNUT = "\U0001f330" BREAD = "\U0001f35e" CROISSANT = "\U0001f950" @@ -2333,6 +2818,7 @@ CLINKING_BEER_MUGS = "\U0001f37b" CLINKING_GLASSES = "\U0001f942" TUMBLER_GLASS = "\U0001f943" +POURING_LIQUID = "\U0001fad7" CUP_WITH_STRAW = "\U0001f964" BUBBLE_TEA = "\U0001f9cb" BEVERAGE_BOX = "\U0001f9c3" @@ -2343,6 +2829,7 @@ FORK_AND_KNIFE = "\U0001f374" SPOON = "\U0001f944" KITCHEN_KNIFE = "\U0001f52a" +JAR = "\U0001fad9" AMPHORA = "\U0001f3fa" GLOBE_SHOWING_EUROPE_AFRICA = "\U0001f30d" GLOBE_SHOWING_AMERICAS = "\U0001f30e" @@ -2405,6 +2892,7 @@ BRIDGE_AT_NIGHT = "\U0001f309" HOT_SPRINGS = "\u2668\ufe0f" CAROUSEL_HORSE = "\U0001f3a0" +PLAYGROUND_SLIDE = "\U0001f6dd" FERRIS_WHEEL = "\U0001f3a1" ROLLER_COASTER = "\U0001f3a2" BARBER_POLE = "\U0001f488" @@ -2453,12 +2941,14 @@ RAILWAY_TRACK = "\U0001f6e4\ufe0f" OIL_DRUM = "\U0001f6e2\ufe0f" FUEL_PUMP = "\u26fd" +WHEEL = "\U0001f6de" POLICE_CAR_LIGHT = "\U0001f6a8" HORIZONTAL_TRAFFIC_LIGHT = "\U0001f6a5" VERTICAL_TRAFFIC_LIGHT = "\U0001f6a6" STOP_SIGN = "\U0001f6d1" CONSTRUCTION = "\U0001f6a7" ANCHOR = "\u2693" +RING_BUOY = "\U0001f6df" SAILBOAT = "\u26f5" CANOE = "\U0001f6f6" SPEEDBOAT = "\U0001f6a4" @@ -2613,13 +3103,14 @@ SKIS = "\U0001f3bf" SLED = "\U0001f6f7" CURLING_STONE = "\U0001f94c" -DIRECT_HIT = "\U0001f3af" +BULLSEYE = "\U0001f3af" YO_YO = "\U0001fa80" KITE = "\U0001fa81" POOL_8_BALL = "\U0001f3b1" CRYSTAL_BALL = "\U0001f52e" MAGIC_WAND = "\U0001fa84" NAZAR_AMULET = "\U0001f9ff" +HAMSA = "\U0001faac" VIDEO_GAME = "\U0001f3ae" JOYSTICK = "\U0001f579\ufe0f" SLOT_MACHINE = "\U0001f3b0" @@ -2627,6 +3118,7 @@ PUZZLE_PIECE = "\U0001f9e9" TEDDY_BEAR = "\U0001f9f8" PINATA = "\U0001fa85" +MIRROR_BALL = "\U0001faa9" NESTING_DOLLS = "\U0001fa86" SPADE_SUIT = "\u2660\ufe0f" HEART_SUIT = "\u2665\ufe0f" @@ -2722,6 +3214,7 @@ PAGER = "\U0001f4df" FAX_MACHINE = "\U0001f4e0" BATTERY = "\U0001f50b" +LOW_BATTERY = "\U0001faab" ELECTRIC_PLUG = "\U0001f50c" LAPTOP = "\U0001f4bb" DESKTOP_COMPUTER = "\U0001f5a5\ufe0f" @@ -2833,7 +3326,7 @@ HAMMER_AND_WRENCH = "\U0001f6e0\ufe0f" DAGGER = "\U0001f5e1\ufe0f" CROSSED_SWORDS = "\u2694\ufe0f" -PISTOL = "\U0001f52b" +WATER_PISTOL = "\U0001f52b" BOOMERANG = "\U0001fa83" BOW_AND_ARROW = "\U0001f3f9" SHIELD = "\U0001f6e1\ufe0f" @@ -2862,7 +3355,9 @@ DROP_OF_BLOOD = "\U0001fa78" PILL = "\U0001f48a" ADHESIVE_BANDAGE = "\U0001fa79" +CRUTCH = "\U0001fa7c" STETHOSCOPE = "\U0001fa7a" +X_RAY = "\U0001fa7b" DOOR = "\U0001f6aa" ELEVATOR = "\U0001f6d7" MIRROR = "\U0001fa9e" @@ -2883,6 +3378,7 @@ ROLL_OF_PAPER = "\U0001f9fb" BUCKET = "\U0001faa3" SOAP = "\U0001f9fc" +BUBBLES = "\U0001fae7" TOOTHBRUSH = "\U0001faa5" SPONGE = "\U0001f9fd" FIRE_EXTINGUISHER = "\U0001f9ef" @@ -2893,6 +3389,7 @@ FUNERAL_URN = "\u26b1\ufe0f" MOAI = "\U0001f5ff" PLACARD = "\U0001faa7" +IDENTIFICATION_CARD = "\U0001faaa" ATM_SIGN = "\U0001f3e7" LITTER_IN_BIN_SIGN = "\U0001f6ae" POTABLE_WATER = "\U0001f6b0" @@ -2996,13 +3493,14 @@ PLUS = "\u2795" MINUS = "\u2796" DIVIDE = "\u2797" +HEAVY_EQUALS_SIGN = "\U0001f7f0" INFINITY = "\u267e\ufe0f" DOUBLE_EXCLAMATION_MARK = "\u203c\ufe0f" EXCLAMATION_QUESTION_MARK = "\u2049\ufe0f" -QUESTION_MARK = "\u2753" +RED_QUESTION_MARK = "\u2753" WHITE_QUESTION_MARK = "\u2754" WHITE_EXCLAMATION_MARK = "\u2755" -EXCLAMATION_MARK = "\u2757" +RED_EXCLAMATION_MARK = "\u2757" WAVY_DASH = "\u3030\ufe0f" CURRENCY_EXCHANGE = "\U0001f4b1" HEAVY_DOLLAR_SIGN = "\U0001f4b2" @@ -3408,498 +3906,114 @@ REGIONAL_INDICATOR_SYMBOL_LETTER_X = "\U0001f1fd" REGIONAL_INDICATOR_SYMBOL_LETTER_Y = "\U0001f1fe" REGIONAL_INDICATOR_SYMBOL_LETTER_Z = "\U0001f1ff" -FACE_EXHALING = "\U0001f62e\u200d\U0001f4a8" -FACE_WITH_SPIRAL_EYES = "\U0001f635\u200d\U0001f4ab" -FACE_IN_CLOUDS = "\U0001f636\u200d\U0001f32b" -HEART_ON_FIRE = "\u2764\ufe0f\u200d\U0001f525" -MENDING_HEART = "\u2764\ufe0f\u200d\U0001fa79" -WOMAN_BEARD = "\U0001f9d4\u200d\u2640\ufe0f" -WOMAN_LIGHT_SKIN_TONE_BEARD = "\U0001f9d4\U0001f3fb\u200d\u2640\ufe0f" -WOMAN_MEDIUM_LIGHT_SKIN_TONE_BEARD = "\U0001f9d4\U0001f3fc\u200d\u2640\ufe0f" -WOMAN_MEDIUM_SKIN_TONE_BEARD = "\U0001f9d4\U0001f3fd\u200d\u2640\ufe0f" -WOMAN_MEDIUM_DARK_SKIN_TONE_BEARD = "\U0001f9d4\U0001f3fe\u200d\u2640\ufe0f" -WOMAN_DARK_SKIN_TONE_BEARD = "\U0001f9d4\U0001f3ff\u200d\u2640\ufe0f" -MAN_BEARD = "\U0001f9d4\u200d\u2642\ufe0f" -MAN_LIGHT_SKIN_TONE_BEARD = "\U0001f9d4\U0001f3fb\u200d\u2642\ufe0f" -MAN_MEDIUM_LIGHT_SKIN_TONE_BEARD = "\U0001f9d4\U0001f3fc\u200d\u2642\ufe0f" -MAN_MEDIUM_SKIN_TONE_BEARD = "\U0001f9d4\U0001f3fd\u200d\u2642\ufe0f" -MAN_MEDIUM_DARK_SKIN_TONE_BEARD = "\U0001f9d4\U0001f3fe\u200d\u2642\ufe0f" -MAN_DARK_SKIN_TONE_BEARD = "\U0001f9d4\U0001f3ff\u200d\u2642\ufe0f" -COUPLE_WITH_HEART_LIGHT_SKIN_TONE = "\U0001f491\U0001f3fb" -COUPLE_WITH_HEART_MEDIUM_LIGHT_SKIN_TONE = "\U0001f491\U0001f3fc" -COUPLE_WITH_HEART_MEDIUM_SKIN_TONE = "\U0001f491\U0001f3fd" -COUPLE_WITH_HEART_MEDIUM_DARK_SKIN_TONE = "\U0001f491\U0001f3fe" -COUPLE_WITH_HEART_DARK_SKIN_TONE = "\U0001f491\U0001f3ff" -KISS_LIGHT_SKIN_TONE = "\U0001f48f\U0001f3fb" -KISS_MEDIUM_LIGHT_SKIN_TONE = "\U0001f48f\U0001f3fc" -KISS_MEDIUM_SKIN_TONE = "\U0001f48f\U0001f3fd" -KISS_MEDIUM_DARK_SKIN_TONE = "\U0001f48f\U0001f3fe" -KISS_DARK_SKIN_TONE = "\U0001f48f\U0001f3ff" -COUPLE_WITH_HEART_MAN_MAN_LIGHT_SKIN_TONE = "\U0001f468\U0001f3fb\u200d\u2764\ufe0f\u200d\U0001f468\U0001f3fb" -COUPLE_WITH_HEART_MAN_MAN_LIGHT_SKIN_TONE_MEDIUM_LIGHT_SKIN_TONE = \ - "\U0001f468\U0001f3fb\u200d\u2764\ufe0f\u200d\U0001f468\U0001f3fc" -COUPLE_WITH_HEART_MAN_MAN_LIGHT_SKIN_TONE_MEDIUM_SKIN_TONE = \ - "\U0001f468\U0001f3fb\u200d\u2764\ufe0f\u200d\U0001f468\U0001f3fd" -COUPLE_WITH_HEART_MAN_MAN_LIGHT_SKIN_TONE_MEDIUM_DARK_SKIN_TONE = \ - "\U0001f468\U0001f3fb\u200d\u2764\ufe0f\u200d\U0001f468\U0001f3fe" -COUPLE_WITH_HEART_MAN_MAN_LIGHT_SKIN_TONE_DARK_SKIN_TONE = \ - "\U0001f468\U0001f3fb\u200d\u2764\ufe0f\u200d\U0001f468\U0001f3ff" -COUPLE_WITH_HEART_MAN_MAN_MEDIUM_LIGHT_SKIN_TONE_LIGHT_SKIN_TONE = \ - "\U0001f468\U0001f3fc\u200d\u2764\ufe0f\u200d\U0001f468\U0001f3fb" -COUPLE_WITH_HEART_MAN_MAN_MEDIUM_LIGHT_SKIN_TONE = "\U0001f468\U0001f3fc\u200d\u2764\ufe0f\u200d\U0001f468\U0001f3fc" -COUPLE_WITH_HEART_MAN_MAN_MEDIUM_LIGHT_SKIN_TONE_MEDIUM_SKIN_TONE = \ - "\U0001f468\U0001f3fc\u200d\u2764\ufe0f\u200d\U0001f468\U0001f3fd" -COUPLE_WITH_HEART_MAN_MAN_MEDIUM_LIGHT_SKIN_TONE_MEDIUM_DARK_SKIN_TONE = \ - "\U0001f468\U0001f3fc\u200d\u2764\ufe0f\u200d\U0001f468\U0001f3fe" -COUPLE_WITH_HEART_MAN_MAN_MEDIUM_LIGHT_SKIN_TONE_DARK_SKIN_TONE = \ - "\U0001f468\U0001f3fc\u200d\u2764\ufe0f\u200d\U0001f468\U0001f3ff" -COUPLE_WITH_HEART_MAN_MAN_MEDIUM_SKIN_TONE_LIGHT_SKIN_TONE = \ - "\U0001f468\U0001f3fd\u200d\u2764\ufe0f\u200d\U0001f468\U0001f3fb" -COUPLE_WITH_HEART_MAN_MAN_MEDIUM_SKIN_TONE_MEDIUM_LIGHT_SKIN_TONE = \ - "\U0001f468\U0001f3fd\u200d\u2764\ufe0f\u200d\U0001f468\U0001f3fc" -COUPLE_WITH_HEART_MAN_MAN_MEDIUM_SKIN_TONE = "\U0001f468\U0001f3fd\u200d\u2764\ufe0f\u200d\U0001f468\U0001f3fd" -COUPLE_WITH_HEART_MAN_MAN_MEDIUM_SKIN_TONE_MEDIUM_DARK_SKIN_TONE = \ - "\U0001f468\U0001f3fd\u200d\u2764\ufe0f\u200d\U0001f468\U0001f3fe" -COUPLE_WITH_HEART_MAN_MAN_MEDIUM_SKIN_TONE_DARK_SKIN_TONE = \ - "\U0001f468\U0001f3fd\u200d\u2764\ufe0f\u200d\U0001f468\U0001f3ff" -COUPLE_WITH_HEART_MAN_MAN_MEDIUM_DARK_SKIN_TONE_LIGHT_SKIN_TONE = \ - "\U0001f468\U0001f3fe\u200d\u2764\ufe0f\u200d\U0001f468\U0001f3fb" -COUPLE_WITH_HEART_MAN_MAN_MEDIUM_DARK_SKIN_TONE_MEDIUM_LIGHT_SKIN_TONE = \ - "\U0001f468\U0001f3fe\u200d\u2764\ufe0f\u200d\U0001f468\U0001f3fc" -COUPLE_WITH_HEART_MAN_MAN_MEDIUM_DARK_SKIN_TONE_MEDIUM_SKIN_TONE = \ - "\U0001f468\U0001f3fe\u200d\u2764\ufe0f\u200d\U0001f468\U0001f3fd" -COUPLE_WITH_HEART_MAN_MAN_MEDIUM_DARK_SKIN_TONE = "\U0001f468\U0001f3fe\u200d\u2764\ufe0f\u200d\U0001f468\U0001f3fe" -COUPLE_WITH_HEART_MAN_MAN_MEDIUM_DARK_SKIN_TONE_DARK_SKIN_TONE = \ - "\U0001f468\U0001f3fe\u200d\u2764\ufe0f\u200d\U0001f468\U0001f3ff" -COUPLE_WITH_HEART_MAN_MAN_DARK_SKIN_TONE_LIGHT_SKIN_TONE = \ - "\U0001f468\U0001f3ff\u200d\u2764\ufe0f\u200d\U0001f468\U0001f3fb" -COUPLE_WITH_HEART_MAN_MAN_DARK_SKIN_TONE_MEDIUM_LIGHT_SKIN_TONE = \ - "\U0001f468\U0001f3ff\u200d\u2764\ufe0f\u200d\U0001f468\U0001f3fc" -COUPLE_WITH_HEART_MAN_MAN_DARK_SKIN_TONE_MEDIUM_SKIN_TONE = \ - "\U0001f468\U0001f3ff\u200d\u2764\ufe0f\u200d\U0001f468\U0001f3fd" -COUPLE_WITH_HEART_MAN_MAN_DARK_SKIN_TONE_MEDIUM_DARK_SKIN_TONE = \ - "\U0001f468\U0001f3ff\u200d\u2764\ufe0f\u200d\U0001f468\U0001f3fe" -COUPLE_WITH_HEART_MAN_MAN_DARK_SKIN_TONE = "\U0001f468\U0001f3ff\u200d\u2764\ufe0f\u200d\U0001f468\U0001f3ff" -COUPLE_WITH_HEART_WOMAN_MAN_LIGHT_SKIN_TONE = "\U0001f469\U0001f3fb\u200d\u2764\ufe0f\u200d\U0001f468\U0001f3fb" -COUPLE_WITH_HEART_WOMAN_MAN_LIGHT_SKIN_TONE_MEDIUM_LIGHT_SKIN_TONE = \ - "\U0001f469\U0001f3fb\u200d\u2764\ufe0f\u200d\U0001f468\U0001f3fc" -COUPLE_WITH_HEART_WOMAN_MAN_LIGHT_SKIN_TONE_MEDIUM_SKIN_TONE = \ - "\U0001f469\U0001f3fb\u200d\u2764\ufe0f\u200d\U0001f468\U0001f3fd" -COUPLE_WITH_HEART_WOMAN_MAN_LIGHT_SKIN_TONE_MEDIUM_DARK_SKIN_TONE = \ - "\U0001f469\U0001f3fb\u200d\u2764\ufe0f\u200d\U0001f468\U0001f3fe" -COUPLE_WITH_HEART_WOMAN_MAN_LIGHT_SKIN_TONE_DARK_SKIN_TONE = \ - "\U0001f469\U0001f3fb\u200d\u2764\ufe0f\u200d\U0001f468\U0001f3ff" -COUPLE_WITH_HEART_WOMAN_WOMAN_LIGHT_SKIN_TONE = "\U0001f469\U0001f3fb\u200d\u2764\ufe0f\u200d\U0001f469\U0001f3fb" -COUPLE_WITH_HEART_WOMAN_WOMAN_LIGHT_SKIN_TONE_MEDIUM_LIGHT_SKIN_TONE = \ - "\U0001f469\U0001f3fb\u200d\u2764\ufe0f\u200d\U0001f469\U0001f3fc" -COUPLE_WITH_HEART_WOMAN_WOMAN_LIGHT_SKIN_TONE_MEDIUM_SKIN_TONE = \ - "\U0001f469\U0001f3fb\u200d\u2764\ufe0f\u200d\U0001f469\U0001f3fd" -COUPLE_WITH_HEART_WOMAN_WOMAN_LIGHT_SKIN_TONE_MEDIUM_DARK_SKIN_TONE = \ - "\U0001f469\U0001f3fb\u200d\u2764\ufe0f\u200d\U0001f469\U0001f3fe" -COUPLE_WITH_HEART_WOMAN_WOMAN_LIGHT_SKIN_TONE_DARK_SKIN_TONE = \ - "\U0001f469\U0001f3fb\u200d\u2764\ufe0f\u200d\U0001f469\U0001f3ff" -COUPLE_WITH_HEART_WOMAN_MAN_MEDIUM_LIGHT_SKIN_TONE_LIGHT_SKIN_TONE = \ - "\U0001f469\U0001f3fc\u200d\u2764\ufe0f\u200d\U0001f468\U0001f3fb" -COUPLE_WITH_HEART_WOMAN_MAN_MEDIUM_LIGHT_SKIN_TONE = "\U0001f469\U0001f3fc\u200d\u2764\ufe0f\u200d\U0001f468\U0001f3fc" -COUPLE_WITH_HEART_WOMAN_MAN_MEDIUM_LIGHT_SKIN_TONE_MEDIUM_SKIN_TONE = \ - "\U0001f469\U0001f3fc\u200d\u2764\ufe0f\u200d\U0001f468\U0001f3fd" -COUPLE_WITH_HEART_WOMAN_MAN_MEDIUM_LIGHT_SKIN_TONE_MEDIUM_DARK_SKIN_TONE = \ - "\U0001f469\U0001f3fc\u200d\u2764\ufe0f\u200d\U0001f468\U0001f3fe" -COUPLE_WITH_HEART_WOMAN_MAN_MEDIUM_LIGHT_SKIN_TONE_DARK_SKIN_TONE = \ - "\U0001f469\U0001f3fc\u200d\u2764\ufe0f\u200d\U0001f468\U0001f3ff" -COUPLE_WITH_HEART_WOMAN_WOMAN_MEDIUM_LIGHT_SKIN_TONE_LIGHT_SKIN_TONE = \ - "\U0001f469\U0001f3fc\u200d\u2764\ufe0f\u200d\U0001f469\U0001f3fb" -COUPLE_WITH_HEART_WOMAN_WOMAN_MEDIUM_LIGHT_SKIN_TONE = \ - "\U0001f469\U0001f3fc\u200d\u2764\ufe0f\u200d\U0001f469\U0001f3fc" -COUPLE_WITH_HEART_WOMAN_WOMAN_MEDIUM_LIGHT_SKIN_TONE_MEDIUM_SKIN_TONE = \ - "\U0001f469\U0001f3fc\u200d\u2764\ufe0f\u200d\U0001f469\U0001f3fd" -COUPLE_WITH_HEART_WOMAN_WOMAN_MEDIUM_LIGHT_SKIN_TONE_MEDIUM_DARK_SKIN_TONE = \ - "\U0001f469\U0001f3fc\u200d\u2764\ufe0f\u200d\U0001f469\U0001f3fe" -COUPLE_WITH_HEART_WOMAN_WOMAN_MEDIUM_LIGHT_SKIN_TONE_DARK_SKIN_TONE = \ - "\U0001f469\U0001f3fc\u200d\u2764\ufe0f\u200d\U0001f469\U0001f3ff" -COUPLE_WITH_HEART_WOMAN_MAN_MEDIUM_SKIN_TONE_LIGHT_SKIN_TONE = \ - "\U0001f469\U0001f3fd\u200d\u2764\ufe0f\u200d\U0001f468\U0001f3fb" -COUPLE_WITH_HEART_WOMAN_MAN_MEDIUM_SKIN_TONE_MEDIUM_LIGHT_SKIN_TONE = \ - "\U0001f469\U0001f3fd\u200d\u2764\ufe0f\u200d\U0001f468\U0001f3fc" -COUPLE_WITH_HEART_WOMAN_MAN_MEDIUM_SKIN_TONE = "\U0001f469\U0001f3fd\u200d\u2764\ufe0f\u200d\U0001f468\U0001f3fd" -COUPLE_WITH_HEART_WOMAN_MAN_MEDIUM_SKIN_TONE_MEDIUM_DARK_SKIN_TONE = \ - "\U0001f469\U0001f3fd\u200d\u2764\ufe0f\u200d\U0001f468\U0001f3fe" -COUPLE_WITH_HEART_WOMAN_MAN_MEDIUM_SKIN_TONE_DARK_SKIN_TONE = \ - "\U0001f469\U0001f3fd\u200d\u2764\ufe0f\u200d\U0001f468\U0001f3ff" -COUPLE_WITH_HEART_WOMAN_WOMAN_MEDIUM_SKIN_TONE_LIGHT_SKIN_TONE = \ - "\U0001f469\U0001f3fd\u200d\u2764\ufe0f\u200d\U0001f469\U0001f3fb" -COUPLE_WITH_HEART_WOMAN_WOMAN_MEDIUM_SKIN_TONE_MEDIUM_LIGHT_SKIN_TONE = \ - "\U0001f469\U0001f3fd\u200d\u2764\ufe0f\u200d\U0001f469\U0001f3fc" -COUPLE_WITH_HEART_WOMAN_WOMAN_MEDIUM_SKIN_TONE = "\U0001f469\U0001f3fd\u200d\u2764\ufe0f\u200d\U0001f469\U0001f3fd" -COUPLE_WITH_HEART_WOMAN_WOMAN_MEDIUM_SKIN_TONE_MEDIUM_DARK_SKIN_TONE = \ - "\U0001f469\U0001f3fd\u200d\u2764\ufe0f\u200d\U0001f469\U0001f3fe" -COUPLE_WITH_HEART_WOMAN_WOMAN_MEDIUM_SKIN_TONE_DARK_SKIN_TONE = \ - "\U0001f469\U0001f3fd\u200d\u2764\ufe0f\u200d\U0001f469\U0001f3ff" -COUPLE_WITH_HEART_WOMAN_MAN_MEDIUM_DARK_SKIN_TONE_LIGHT_SKIN_TONE = \ - "\U0001f469\U0001f3fe\u200d\u2764\ufe0f\u200d\U0001f468\U0001f3fb" -COUPLE_WITH_HEART_WOMAN_MAN_MEDIUM_DARK_SKIN_TONE_MEDIUM_LIGHT_SKIN_TONE = \ - "\U0001f469\U0001f3fe\u200d\u2764\ufe0f\u200d\U0001f468\U0001f3fc" -COUPLE_WITH_HEART_WOMAN_MAN_MEDIUM_DARK_SKIN_TONE_MEDIUM_SKIN_TONE = \ - "\U0001f469\U0001f3fe\u200d\u2764\ufe0f\u200d\U0001f468\U0001f3fd" -COUPLE_WITH_HEART_WOMAN_MAN_MEDIUM_DARK_SKIN_TONE = "\U0001f469\U0001f3fe\u200d\u2764\ufe0f\u200d\U0001f468\U0001f3fe" -COUPLE_WITH_HEART_WOMAN_MAN_MEDIUM_DARK_SKIN_TONE_DARK_SKIN_TONE = \ - "\U0001f469\U0001f3fe\u200d\u2764\ufe0f\u200d\U0001f468\U0001f3ff" -COUPLE_WITH_HEART_WOMAN_WOMAN_MEDIUM_DARK_SKIN_TONE_LIGHT_SKIN_TONE = \ - "\U0001f469\U0001f3fe\u200d\u2764\ufe0f\u200d\U0001f469\U0001f3fb" -COUPLE_WITH_HEART_WOMAN_WOMAN_MEDIUM_DARK_SKIN_TONE_MEDIUM_LIGHT_SKIN_TONE = \ - "\U0001f469\U0001f3fe\u200d\u2764\ufe0f\u200d\U0001f469\U0001f3fc" -COUPLE_WITH_HEART_WOMAN_WOMAN_MEDIUM_DARK_SKIN_TONE_MEDIUM_SKIN_TONE = \ - "\U0001f469\U0001f3fe\u200d\u2764\ufe0f\u200d\U0001f469\U0001f3fd" -COUPLE_WITH_HEART_WOMAN_WOMAN_MEDIUM_DARK_SKIN_TONE = \ - "\U0001f469\U0001f3fe\u200d\u2764\ufe0f\u200d\U0001f469\U0001f3fe" -COUPLE_WITH_HEART_WOMAN_WOMAN_MEDIUM_DARK_SKIN_TONE_DARK_SKIN_TONE = \ - "\U0001f469\U0001f3fe\u200d\u2764\ufe0f\u200d\U0001f469\U0001f3ff" -COUPLE_WITH_HEART_WOMAN_MAN_DARK_SKIN_TONE_LIGHT_SKIN_TONE = \ - "\U0001f469\U0001f3ff\u200d\u2764\ufe0f\u200d\U0001f468\U0001f3fb" -COUPLE_WITH_HEART_WOMAN_MAN_DARK_SKIN_TONE_MEDIUM_LIGHT_SKIN_TONE = \ - "\U0001f469\U0001f3ff\u200d\u2764\ufe0f\u200d\U0001f468\U0001f3fc" -COUPLE_WITH_HEART_WOMAN_MAN_DARK_SKIN_TONE_MEDIUM_SKIN_TONE = \ - "\U0001f469\U0001f3ff\u200d\u2764\ufe0f\u200d\U0001f468\U0001f3fd" -COUPLE_WITH_HEART_WOMAN_MAN_DARK_SKIN_TONE_MEDIUM_DARK_SKIN_TONE = \ - "\U0001f469\U0001f3ff\u200d\u2764\ufe0f\u200d\U0001f468\U0001f3fe" -COUPLE_WITH_HEART_WOMAN_MAN_DARK_SKIN_TONE = "\U0001f469\U0001f3ff\u200d\u2764\ufe0f\u200d\U0001f468\U0001f3ff" -COUPLE_WITH_HEART_WOMAN_WOMAN_DARK_SKIN_TONE_LIGHT_SKIN_TONE = \ - "\U0001f469\U0001f3ff\u200d\u2764\ufe0f\u200d\U0001f469\U0001f3fb" -COUPLE_WITH_HEART_WOMAN_WOMAN_DARK_SKIN_TONE_MEDIUM_LIGHT_SKIN_TONE = \ - "\U0001f469\U0001f3ff\u200d\u2764\ufe0f\u200d\U0001f469\U0001f3fc" -COUPLE_WITH_HEART_WOMAN_WOMAN_DARK_SKIN_TONE_MEDIUM_SKIN_TONE = \ - "\U0001f469\U0001f3ff\u200d\u2764\ufe0f\u200d\U0001f469\U0001f3fd" -COUPLE_WITH_HEART_WOMAN_WOMAN_DARK_SKIN_TONE_MEDIUM_DARK_SKIN_TONE = \ - "\U0001f469\U0001f3ff\u200d\u2764\ufe0f\u200d\U0001f469\U0001f3fe" -COUPLE_WITH_HEART_WOMAN_WOMAN_DARK_SKIN_TONE = "\U0001f469\U0001f3ff\u200d\u2764\ufe0f\u200d\U0001f469\U0001f3ff" -COUPLE_WITH_HEART_PERSON_PERSON_LIGHT_SKIN_TONE_MEDIUM_LIGHT_SKIN_TONE = \ - "\U0001f9d1\U0001f3fb\u200d\u2764\ufe0f\u200d\U0001f9d1\U0001f3fc" -COUPLE_WITH_HEART_PERSON_PERSON_LIGHT_SKIN_TONE_MEDIUM_SKIN_TONE = \ - "\U0001f9d1\U0001f3fb\u200d\u2764\ufe0f\u200d\U0001f9d1\U0001f3fd" -COUPLE_WITH_HEART_PERSON_PERSON_LIGHT_SKIN_TONE_MEDIUM_DARK_SKIN_TONE = \ - "\U0001f9d1\U0001f3fb\u200d\u2764\ufe0f\u200d\U0001f9d1\U0001f3fe" -COUPLE_WITH_HEART_PERSON_PERSON_LIGHT_SKIN_TONE_DARK_SKIN_TONE = \ - "\U0001f9d1\U0001f3fb\u200d\u2764\ufe0f\u200d\U0001f9d1\U0001f3ff" -COUPLE_WITH_HEART_PERSON_PERSON_MEDIUM_LIGHT_SKIN_TONE_LIGHT_SKIN_TONE = \ - "\U0001f9d1\U0001f3fc\u200d\u2764\ufe0f\u200d\U0001f9d1\U0001f3fb" -COUPLE_WITH_HEART_PERSON_PERSON_MEDIUM_LIGHT_SKIN_TONE_MEDIUM_SKIN_TONE = \ - "\U0001f9d1\U0001f3fc\u200d\u2764\ufe0f\u200d\U0001f9d1\U0001f3fd" -COUPLE_WITH_HEART_PERSON_PERSON_MEDIUM_LIGHT_SKIN_TONE_MEDIUM_DARK_SKIN_TONE = \ - "\U0001f9d1\U0001f3fc\u200d\u2764\ufe0f\u200d\U0001f9d1\U0001f3fe" -COUPLE_WITH_HEART_PERSON_PERSON_MEDIUM_LIGHT_SKIN_TONE_DARK_SKIN_TONE = \ - "\U0001f9d1\U0001f3fc\u200d\u2764\ufe0f\u200d\U0001f9d1\U0001f3ff" -COUPLE_WITH_HEART_PERSON_PERSON_MEDIUM_SKIN_TONE_LIGHT_SKIN_TONE = \ - "\U0001f9d1\U0001f3fd\u200d\u2764\ufe0f\u200d\U0001f9d1\U0001f3fb" -COUPLE_WITH_HEART_PERSON_PERSON_MEDIUM_SKIN_TONE_MEDIUM_LIGHT_SKIN_TONE = \ - "\U0001f9d1\U0001f3fd\u200d\u2764\ufe0f\u200d\U0001f9d1\U0001f3fc" -COUPLE_WITH_HEART_PERSON_PERSON_MEDIUM_SKIN_TONE_MEDIUM_DARK_SKIN_TONE = \ - "\U0001f9d1\U0001f3fd\u200d\u2764\ufe0f\u200d\U0001f9d1\U0001f3fe" -COUPLE_WITH_HEART_PERSON_PERSON_MEDIUM_SKIN_TONE_DARK_SKIN_TONE = \ - "\U0001f9d1\U0001f3fd\u200d\u2764\ufe0f\u200d\U0001f9d1\U0001f3ff" -COUPLE_WITH_HEART_PERSON_PERSON_MEDIUM_DARK_SKIN_TONE_LIGHT_SKIN_TONE = \ - "\U0001f9d1\U0001f3fe\u200d\u2764\ufe0f\u200d\U0001f9d1\U0001f3fb" -COUPLE_WITH_HEART_PERSON_PERSON_MEDIUM_DARK_SKIN_TONE_MEDIUM_LIGHT_SKIN_TONE = \ - "\U0001f9d1\U0001f3fe\u200d\u2764\ufe0f\u200d\U0001f9d1\U0001f3fc" -COUPLE_WITH_HEART_PERSON_PERSON_MEDIUM_DARK_SKIN_TONE_MEDIUM_SKIN_TONE = \ - "\U0001f9d1\U0001f3fe\u200d\u2764\ufe0f\u200d\U0001f9d1\U0001f3fd" -COUPLE_WITH_HEART_PERSON_PERSON_MEDIUM_DARK_SKIN_TONE_DARK_SKIN_TONE = \ - "\U0001f9d1\U0001f3fe\u200d\u2764\ufe0f\u200d\U0001f9d1\U0001f3ff" -COUPLE_WITH_HEART_PERSON_PERSON_DARK_SKIN_TONE_LIGHT_SKIN_TONE = \ - "\U0001f9d1\U0001f3ff\u200d\u2764\ufe0f\u200d\U0001f9d1\U0001f3fb" -COUPLE_WITH_HEART_PERSON_PERSON_DARK_SKIN_TONE_MEDIUM_LIGHT_SKIN_TONE = \ - "\U0001f9d1\U0001f3ff\u200d\u2764\ufe0f\u200d\U0001f9d1\U0001f3fc" -COUPLE_WITH_HEART_PERSON_PERSON_DARK_SKIN_TONE_MEDIUM_SKIN_TONE = \ - "\U0001f9d1\U0001f3ff\u200d\u2764\ufe0f\u200d\U0001f9d1\U0001f3fd" -COUPLE_WITH_HEART_PERSON_PERSON_DARK_SKIN_TONE_MEDIUM_DARK_SKIN_TONE = \ - "\U0001f9d1\U0001f3ff\u200d\u2764\ufe0f\u200d\U0001f9d1\U0001f3fe" -KISS_MAN_MAN_LIGHT_SKIN_TONE = "\U0001f468\U0001f3fb\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468\U0001f3fb" -KISS_MAN_MAN_LIGHT_SKIN_TONE_MEDIUM_LIGHT_SKIN_TONE = \ - "\U0001f468\U0001f3fb\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468\U0001f3fc" -KISS_MAN_MAN_LIGHT_SKIN_TONE_MEDIUM_SKIN_TONE = \ - "\U0001f468\U0001f3fb\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468\U0001f3fd" -KISS_MAN_MAN_LIGHT_SKIN_TONE_MEDIUM_DARK_SKIN_TONE = \ - "\U0001f468\U0001f3fb\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468\U0001f3fe" -KISS_MAN_MAN_LIGHT_SKIN_TONE_DARK_SKIN_TONE = \ - "\U0001f468\U0001f3fb\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468\U0001f3ff" -KISS_MAN_MAN_MEDIUM_LIGHT_SKIN_TONE_LIGHT_SKIN_TONE = \ - "\U0001f468\U0001f3fc\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468\U0001f3fb" -KISS_MAN_MAN_MEDIUM_LIGHT_SKIN_TONE = \ - "\U0001f468\U0001f3fc\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468\U0001f3fc" -KISS_MAN_MAN_MEDIUM_LIGHT_SKIN_TONE_MEDIUM_SKIN_TONE = \ - "\U0001f468\U0001f3fc\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468\U0001f3fd" -KISS_MAN_MAN_MEDIUM_LIGHT_SKIN_TONE_MEDIUM_DARK_SKIN_TONE = \ - "\U0001f468\U0001f3fc\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468\U0001f3fe" -KISS_MAN_MAN_MEDIUM_LIGHT_SKIN_TONE_DARK_SKIN_TONE = \ - "\U0001f468\U0001f3fc\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468\U0001f3ff" -KISS_MAN_MAN_MEDIUM_SKIN_TONE_LIGHT_SKIN_TONE = \ - "\U0001f468\U0001f3fd\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468\U0001f3fb" -KISS_MAN_MAN_MEDIUM_SKIN_TONE_MEDIUM_LIGHT_SKIN_TONE = \ - "\U0001f468\U0001f3fd\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468\U0001f3fc" -KISS_MAN_MAN_MEDIUM_SKIN_TONE = "\U0001f468\U0001f3fd\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468\U0001f3fd" -KISS_MAN_MAN_MEDIUM_SKIN_TONE_MEDIUM_DARK_SKIN_TONE = \ - "\U0001f468\U0001f3fd\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468\U0001f3fe" -KISS_MAN_MAN_MEDIUM_SKIN_TONE_DARK_SKIN_TONE = \ - "\U0001f468\U0001f3fd\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468\U0001f3ff" -KISS_MAN_MAN_MEDIUM_DARK_SKIN_TONE_LIGHT_SKIN_TONE = \ - "\U0001f468\U0001f3fe\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468\U0001f3fb" -KISS_MAN_MAN_MEDIUM_DARK_SKIN_TONE_MEDIUM_LIGHT_SKIN_TONE = \ - "\U0001f468\U0001f3fe\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468\U0001f3fc" -KISS_MAN_MAN_MEDIUM_DARK_SKIN_TONE_MEDIUM_SKIN_TONE = \ - "\U0001f468\U0001f3fe\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468\U0001f3fd" -KISS_MAN_MAN_MEDIUM_DARK_SKIN_TONE = "\U0001f468\U0001f3fe\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468\U0001f3fe" -KISS_MAN_MAN_MEDIUM_DARK_SKIN_TONE_DARK_SKIN_TONE = \ - "\U0001f468\U0001f3fe\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468\U0001f3ff" -KISS_MAN_MAN_DARK_SKIN_TONE_LIGHT_SKIN_TONE = \ - "\U0001f468\U0001f3ff\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468\U0001f3fb" -KISS_MAN_MAN_DARK_SKIN_TONE_MEDIUM_LIGHT_SKIN_TONE = \ - "\U0001f468\U0001f3ff\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468\U0001f3fc" -KISS_MAN_MAN_DARK_SKIN_TONE_MEDIUM_SKIN_TONE = \ - "\U0001f468\U0001f3ff\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468\U0001f3fd" -KISS_MAN_MAN_DARK_SKIN_TONE_MEDIUM_DARK_SKIN_TONE = \ - "\U0001f468\U0001f3ff\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468\U0001f3fe" -KISS_MAN_MAN_DARK_SKIN_TONE = "\U0001f468\U0001f3ff\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468\U0001f3ff" -KISS_WOMAN_MAN_LIGHT_SKIN_TONE = "\U0001f469\U0001f3fb\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468\U0001f3fb" -KISS_WOMAN_MAN_LIGHT_SKIN_TONE_MEDIUM_LIGHT_SKIN_TONE = \ - "\U0001f469\U0001f3fb\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468\U0001f3fc" -KISS_WOMAN_MAN_LIGHT_SKIN_TONE_MEDIUM_SKIN_TONE = \ - "\U0001f469\U0001f3fb\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468\U0001f3fd" -KISS_WOMAN_MAN_LIGHT_SKIN_TONE_MEDIUM_DARK_SKIN_TONE = \ - "\U0001f469\U0001f3fb\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468\U0001f3fe" -KISS_WOMAN_MAN_LIGHT_SKIN_TONE_DARK_SKIN_TONE = \ - "\U0001f469\U0001f3fb\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468\U0001f3ff" -KISS_WOMAN_WOMAN_LIGHT_SKIN_TONE = "\U0001f469\U0001f3fb\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f469\U0001f3fb" -KISS_WOMAN_WOMAN_LIGHT_SKIN_TONE_MEDIUM_LIGHT_SKIN_TONE = \ - "\U0001f469\U0001f3fb\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f469\U0001f3fc" -KISS_WOMAN_WOMAN_LIGHT_SKIN_TONE_MEDIUM_SKIN_TONE = \ - "\U0001f469\U0001f3fb\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f469\U0001f3fd" -KISS_WOMAN_WOMAN_LIGHT_SKIN_TONE_MEDIUM_DARK_SKIN_TONE = \ - "\U0001f469\U0001f3fb\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f469\U0001f3fe" -KISS_WOMAN_WOMAN_LIGHT_SKIN_TONE_DARK_SKIN_TONE = \ - "\U0001f469\U0001f3fb\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f469\U0001f3ff" -KISS_WOMAN_MAN_MEDIUM_LIGHT_SKIN_TONE_LIGHT_SKIN_TONE = \ - "\U0001f469\U0001f3fc\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468\U0001f3fb" -KISS_WOMAN_MAN_MEDIUM_LIGHT_SKIN_TONE = \ - "\U0001f469\U0001f3fc\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468\U0001f3fc" -KISS_WOMAN_MAN_MEDIUM_LIGHT_SKIN_TONE_MEDIUM_SKIN_TONE = \ - "\U0001f469\U0001f3fc\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468\U0001f3fd" -KISS_WOMAN_MAN_MEDIUM_LIGHT_SKIN_TONE_MEDIUM_DARK_SKIN_TONE = \ - "\U0001f469\U0001f3fc\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468\U0001f3fe" -KISS_WOMAN_MAN_MEDIUM_LIGHT_SKIN_TONE_DARK_SKIN_TONE = \ - "\U0001f469\U0001f3fc\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468\U0001f3ff" -KISS_WOMAN_WOMAN_MEDIUM_LIGHT_SKIN_TONE_LIGHT_SKIN_TONE = \ - "\U0001f469\U0001f3fc\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f469\U0001f3fb" -KISS_WOMAN_WOMAN_MEDIUM_LIGHT_SKIN_TONE = \ - "\U0001f469\U0001f3fc\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f469\U0001f3fc" -KISS_WOMAN_WOMAN_MEDIUM_LIGHT_SKIN_TONE_MEDIUM_SKIN_TONE = \ - "\U0001f469\U0001f3fc\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f469\U0001f3fd" -KISS_WOMAN_WOMAN_MEDIUM_LIGHT_SKIN_TONE_MEDIUM_DARK_SKIN_TONE = \ - "\U0001f469\U0001f3fc\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f469\U0001f3fe" -KISS_WOMAN_WOMAN_MEDIUM_LIGHT_SKIN_TONE_DARK_SKIN_TONE = \ - "\U0001f469\U0001f3fc\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f469\U0001f3ff" -KISS_WOMAN_MAN_MEDIUM_SKIN_TONE_LIGHT_SKIN_TONE = \ - "\U0001f469\U0001f3fd\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468\U0001f3fb" -KISS_WOMAN_MAN_MEDIUM_SKIN_TONE_MEDIUM_LIGHT_SKIN_TONE = \ - "\U0001f469\U0001f3fd\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468\U0001f3fc" -KISS_WOMAN_MAN_MEDIUM_SKIN_TONE = "\U0001f469\U0001f3fd\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468\U0001f3fd" -KISS_WOMAN_MAN_MEDIUM_SKIN_TONE_MEDIUM_DARK_SKIN_TONE = \ - "\U0001f469\U0001f3fd\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468\U0001f3fe" -KISS_WOMAN_MAN_MEDIUM_SKIN_TONE_DARK_SKIN_TONE = \ - "\U0001f469\U0001f3fd\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468\U0001f3ff" -KISS_WOMAN_WOMAN_MEDIUM_SKIN_TONE_LIGHT_SKIN_TONE = \ - "\U0001f469\U0001f3fd\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f469\U0001f3fb" -KISS_WOMAN_WOMAN_MEDIUM_SKIN_TONE_MEDIUM_LIGHT_SKIN_TONE = \ - "\U0001f469\U0001f3fd\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f469\U0001f3fc" -KISS_WOMAN_WOMAN_MEDIUM_SKIN_TONE = "\U0001f469\U0001f3fd\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f469\U0001f3fd" -KISS_WOMAN_WOMAN_MEDIUM_SKIN_TONE_MEDIUM_DARK_SKIN_TONE = \ - "\U0001f469\U0001f3fd\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f469\U0001f3fe" -KISS_WOMAN_WOMAN_MEDIUM_SKIN_TONE_DARK_SKIN_TONE = \ - "\U0001f469\U0001f3fd\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f469\U0001f3ff" -KISS_WOMAN_MAN_MEDIUM_DARK_SKIN_TONE_LIGHT_SKIN_TONE = \ - "\U0001f469\U0001f3fe\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468\U0001f3fb" -KISS_WOMAN_MAN_MEDIUM_DARK_SKIN_TONE_MEDIUM_LIGHT_SKIN_TONE = \ - "\U0001f469\U0001f3fe\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468\U0001f3fc" -KISS_WOMAN_MAN_MEDIUM_DARK_SKIN_TONE_MEDIUM_SKIN_TONE = \ - "\U0001f469\U0001f3fe\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468\U0001f3fd" -KISS_WOMAN_MAN_MEDIUM_DARK_SKIN_TONE = \ - "\U0001f469\U0001f3fe\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468\U0001f3fe" -KISS_WOMAN_MAN_MEDIUM_DARK_SKIN_TONE_DARK_SKIN_TONE = \ - "\U0001f469\U0001f3fe\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468\U0001f3ff" -KISS_WOMAN_WOMAN_MEDIUM_DARK_SKIN_TONE_LIGHT_SKIN_TONE = \ - "\U0001f469\U0001f3fe\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f469\U0001f3fb" -KISS_WOMAN_WOMAN_MEDIUM_DARK_SKIN_TONE_MEDIUM_LIGHT_SKIN_TONE = \ - "\U0001f469\U0001f3fe\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f469\U0001f3fc" -KISS_WOMAN_WOMAN_MEDIUM_DARK_SKIN_TONE_MEDIUM_SKIN_TONE = \ - "\U0001f469\U0001f3fe\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f469\U0001f3fd" -KISS_WOMAN_WOMAN_MEDIUM_DARK_SKIN_TONE = \ - "\U0001f469\U0001f3fe\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f469\U0001f3fe" -KISS_WOMAN_WOMAN_MEDIUM_DARK_SKIN_TONE_DARK_SKIN_TONE = \ - "\U0001f469\U0001f3fe\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f469\U0001f3ff" -KISS_WOMAN_MAN_DARK_SKIN_TONE_LIGHT_SKIN_TONE = \ - "\U0001f469\U0001f3ff\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468\U0001f3fb" -KISS_WOMAN_MAN_DARK_SKIN_TONE_MEDIUM_LIGHT_SKIN_TONE = \ - "\U0001f469\U0001f3ff\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468\U0001f3fc" -KISS_WOMAN_MAN_DARK_SKIN_TONE_MEDIUM_SKIN_TONE = \ - "\U0001f469\U0001f3ff\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468\U0001f3fd" -KISS_WOMAN_MAN_DARK_SKIN_TONE_MEDIUM_DARK_SKIN_TONE = \ - "\U0001f469\U0001f3ff\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468\U0001f3fe" -KISS_WOMAN_MAN_DARK_SKIN_TONE = "\U0001f469\U0001f3ff\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468\U0001f3ff" -KISS_WOMAN_WOMAN_DARK_SKIN_TONE_LIGHT_SKIN_TONE = \ - "\U0001f469\U0001f3ff\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f469\U0001f3fb" -KISS_WOMAN_WOMAN_DARK_SKIN_TONE_MEDIUM_LIGHT_SKIN_TONE = \ - "\U0001f469\U0001f3ff\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f469\U0001f3fc" -KISS_WOMAN_WOMAN_DARK_SKIN_TONE_MEDIUM_SKIN_TONE = \ - "\U0001f469\U0001f3ff\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f469\U0001f3fd" -KISS_WOMAN_WOMAN_DARK_SKIN_TONE_MEDIUM_DARK_SKIN_TONE = \ - "\U0001f469\U0001f3ff\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f469\U0001f3fe" -KISS_WOMAN_WOMAN_DARK_SKIN_TONE = "\U0001f469\U0001f3ff\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f469\U0001f3ff" -KISS_PERSON_PERSON_LIGHT_SKIN_TONE_MEDIUM_LIGHT_SKIN_TONE = \ - "\U0001f9d1\U0001f3fb\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f9d1\U0001f3fc" -KISS_PERSON_PERSON_LIGHT_SKIN_TONE_MEDIUM_SKIN_TONE = \ - "\U0001f9d1\U0001f3fb\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f9d1\U0001f3fd" -KISS_PERSON_PERSON_LIGHT_SKIN_TONE_MEDIUM_DARK_SKIN_TONE = \ - "\U0001f9d1\U0001f3fb\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f9d1\U0001f3fe" -KISS_PERSON_PERSON_LIGHT_SKIN_TONE_DARK_SKIN_TONE = \ - "\U0001f9d1\U0001f3fb\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f9d1\U0001f3ff" -KISS_PERSON_PERSON_MEDIUM_LIGHT_SKIN_TONE_LIGHT_SKIN_TONE = \ - "\U0001f9d1\U0001f3fc\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f9d1\U0001f3fb" -KISS_PERSON_PERSON_MEDIUM_LIGHT_SKIN_TONE_MEDIUM_SKIN_TONE = \ - "\U0001f9d1\U0001f3fc\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f9d1\U0001f3fd" -KISS_PERSON_PERSON_MEDIUM_LIGHT_SKIN_TONE_MEDIUM_DARK_SKIN_TONE = \ - "\U0001f9d1\U0001f3fc\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f9d1\U0001f3fe" -KISS_PERSON_PERSON_MEDIUM_LIGHT_SKIN_TONE_DARK_SKIN_TONE = \ - "\U0001f9d1\U0001f3fc\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f9d1\U0001f3ff" -KISS_PERSON_PERSON_MEDIUM_SKIN_TONE_LIGHT_SKIN_TONE = \ - "\U0001f9d1\U0001f3fd\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f9d1\U0001f3fb" -KISS_PERSON_PERSON_MEDIUM_SKIN_TONE_MEDIUM_LIGHT_SKIN_TONE = \ - "\U0001f9d1\U0001f3fd\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f9d1\U0001f3fc" -KISS_PERSON_PERSON_MEDIUM_SKIN_TONE_MEDIUM_DARK_SKIN_TONE = \ - "\U0001f9d1\U0001f3fd\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f9d1\U0001f3fe" -KISS_PERSON_PERSON_MEDIUM_SKIN_TONE_DARK_SKIN_TONE = \ - "\U0001f9d1\U0001f3fd\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f9d1\U0001f3ff" -KISS_PERSON_PERSON_MEDIUM_DARK_SKIN_TONE_LIGHT_SKIN_TONE = \ - "\U0001f9d1\U0001f3fe\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f9d1\U0001f3fb" -KISS_PERSON_PERSON_MEDIUM_DARK_SKIN_TONE_MEDIUM_LIGHT_SKIN_TONE = \ - "\U0001f9d1\U0001f3fe\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f9d1\U0001f3fc" -KISS_PERSON_PERSON_MEDIUM_DARK_SKIN_TONE_MEDIUM_SKIN_TONE = \ - "\U0001f9d1\U0001f3fe\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f9d1\U0001f3fd" -KISS_PERSON_PERSON_MEDIUM_DARK_SKIN_TONE_DARK_SKIN_TONE = \ - "\U0001f9d1\U0001f3fe\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f9d1\U0001f3ff" -KISS_PERSON_PERSON_DARK_SKIN_TONE_LIGHT_SKIN_TONE = \ - "\U0001f9d1\U0001f3ff\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f9d1\U0001f3fb" -KISS_PERSON_PERSON_DARK_SKIN_TONE_MEDIUM_LIGHT_SKIN_TONE = \ - "\U0001f9d1\U0001f3ff\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f9d1\U0001f3fc" -KISS_PERSON_PERSON_DARK_SKIN_TONE_MEDIUM_SKIN_TONE = \ - "\U0001f9d1\U0001f3ff\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f9d1\U0001f3fd" -KISS_PERSON_PERSON_DARK_SKIN_TONE_MEDIUM_DARK_SKIN_TONE = \ - "\U0001f9d1\U0001f3ff\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f9d1\U0001f3fe" -TAG_TILDE = "\U000e007e" -TAG_PERCENT_SIGN = "\U000e0025" -ZERO_WIDTH_JOINER = "\u200d" -TAG_LATIN_SMALL_LETTER_E = "\U000e0065" -TAG_LATIN_CAPITAL_LETTER_I = "\U000e0049" -TAG_LATIN_SMALL_LETTER_N = "\U000e006e" -TAG_LATIN_CAPITAL_LETTER_S = "\U000e0053" TAG_RIGHT_CURLY_BRACKET = "\U000e007d" -TAG_DIGIT_FIVE = "\U000e0035" -TAG_LATIN_CAPITAL_LETTER_V = "\U000e0056" -TAG_LATIN_CAPITAL_LETTER_Z = "\U000e005a" -TAG_LATIN_CAPITAL_LETTER_M = "\U000e004d" -TAG_LATIN_CAPITAL_LETTER_W = "\U000e0057" -TAG_LATIN_SMALL_LETTER_D = "\U000e0064" -TAG_LATIN_CAPITAL_LETTER_F = "\U000e0046" -TAG_LATIN_SMALL_LETTER_U = "\U000e0075" -TAG_LATIN_SMALL_LETTER_B = "\U000e0062" -VARIATION_SELECTOR_16 = "\ufe0f" +DIGIT_FIVE = "5\ufe0f" +TAG_LATIN_CAPITAL_LETTER_U = "\U000e0055" +TAG_LATIN_CAPITAL_LETTER_Q = "\U000e0051" +TAG_LATIN_CAPITAL_LETTER_K = "\U000e004b" +COMBINING_ENCLOSING_KEYCAP = "\u20e3" +TAG_LATIN_CAPITAL_LETTER_C = "\U000e0043" +TAG_ASTERISK = "\U000e002a" TAG_FULL_STOP = "\U000e002e" -DIGIT_FOUR = "4\ufe0f" -TAG_LATIN_SMALL_LETTER_F = "\U000e0066" +TAG_CIRCUMFLEX_ACCENT = "\U000e005e" DIGIT_ONE = "1\ufe0f" -TAG_LATIN_CAPITAL_LETTER_B = "\U000e0042" -TAG_DIGIT_SEVEN = "\U000e0037" -TAG_DIGIT_ONE = "\U000e0031" -TAG_COMMERCIAL_AT = "\U000e0040" -TAG_LATIN_SMALL_LETTER_Z = "\U000e007a" -TAG_LATIN_SMALL_LETTER_K = "\U000e006b" -TAG_LATIN_SMALL_LETTER_V = "\U000e0076" -TAG_LATIN_CAPITAL_LETTER_C = "\U000e0043" -TAG_LATIN_SMALL_LETTER_X = "\U000e0078" -TAG_LATIN_SMALL_LETTER_C = "\U000e0063" -TAG_SOLIDUS = "\U000e002f" TAG_COMMA = "\U000e002c" -TAG_LATIN_CAPITAL_LETTER_P = "\U000e0050" -TAG_LATIN_SMALL_LETTER_M = "\U000e006d" -TAG_LATIN_CAPITAL_LETTER_J = "\U000e004a" -TAG_LATIN_SMALL_LETTER_P = "\U000e0070" -TAG_LATIN_SMALL_LETTER_I = "\U000e0069" -TAG_COLON = "\U000e003a" -TAG_LATIN_SMALL_LETTER_A = "\U000e0061" +DIGIT_ZERO = "0\ufe0f" +TAG_EQUALS_SIGN = "\U000e003d" +TAG_LATIN_CAPITAL_LETTER_O = "\U000e004f" +TAG_COMMERCIAL_AT = "\U000e0040" +DIGIT_EIGHT = "8\ufe0f" TAG_NUMBER_SIGN = "\U000e0023" -TAG_LATIN_CAPITAL_LETTER_X = "\U000e0058" TAG_LATIN_CAPITAL_LETTER_T = "\U000e0054" -NUMBER_SIGN = "#\ufe0f" +TAG_LATIN_CAPITAL_LETTER_N = "\U000e004e" +DIGIT_SIX = "6\ufe0f" +TAG_PERCENT_SIGN = "\U000e0025" +VARIATION_SELECTOR_16 = "\ufe0f" +TAG_LATIN_CAPITAL_LETTER_W = "\U000e0057" TAG_DOLLAR_SIGN = "\U000e0024" -TAG_LATIN_CAPITAL_LETTER_Y = "\U000e0059" -TAG_LATIN_CAPITAL_LETTER_E = "\U000e0045" -TAG_LATIN_SMALL_LETTER_O = "\U000e006f" -TAG_DIGIT_FOUR = "\U000e0034" -TAG_HYPHEN_MINUS = "\U000e002d" -TAG_RIGHT_PARENTHESIS = "\U000e0029" -TAG_CIRCUMFLEX_ACCENT = "\U000e005e" -TAG_LATIN_CAPITAL_LETTER_Q = "\U000e0051" +TAG_LOW_LINE = "\U000e005f" +TAG_DIGIT_EIGHT = "\U000e0038" +TAG_LATIN_CAPITAL_LETTER_M = "\U000e004d" +TAG_LATIN_CAPITAL_LETTER_A = "\U000e0041" TAG_REVERSE_SOLIDUS = "\U000e005c" -TAG_LATIN_CAPITAL_LETTER_R = "\U000e0052" -TAG_QUOTATION_MARK = "\U000e0022" +TAG_SOLIDUS = "\U000e002f" +TAG_LATIN_CAPITAL_LETTER_H = "\U000e0048" TAG_DIGIT_NINE = "\U000e0039" -CANCEL_TAG = "\U000e007f" -TAG_ASTERISK = "\U000e002a" +TAG_LEFT_CURLY_BRACKET = "\U000e007b" +TAG_LATIN_CAPITAL_LETTER_E = "\U000e0045" +TAG_LATIN_SMALL_LETTER_W = "\U000e0077" +TAG_DIGIT_ZERO = "\U000e0030" +TAG_LATIN_CAPITAL_LETTER_B = "\U000e0042" +TAG_LATIN_CAPITAL_LETTER_F = "\U000e0046" +TAG_LATIN_CAPITAL_LETTER_Y = "\U000e0059" +TAG_TILDE = "\U000e007e" +TAG_LATIN_SMALL_LETTER_P = "\U000e0070" +TAG_LATIN_CAPITAL_LETTER_Z = "\U000e005a" +TAG_GREATER_THAN_SIGN = "\U000e003e" +TAG_LATIN_SMALL_LETTER_S = "\U000e0073" +TAG_LATIN_SMALL_LETTER_G = "\U000e0067" +TAG_APOSTROPHE = "\U000e0027" +TAG_RIGHT_PARENTHESIS = "\U000e0029" +TAG_DIGIT_THREE = "\U000e0033" TAG_LEFT_PARENTHESIS = "\U000e0028" +TAG_DIGIT_SEVEN = "\U000e0037" +TAG_LATIN_SMALL_LETTER_O = "\U000e006f" +TAG_DIGIT_SIX = "\U000e0036" +TAG_DIGIT_TWO = "\U000e0032" +TAG_LATIN_SMALL_LETTER_F = "\U000e0066" +TAG_LATIN_SMALL_LETTER_K = "\U000e006b" +TAG_LATIN_SMALL_LETTER_Y = "\U000e0079" +TAG_SPACE = "\U000e0020" +TAG_LATIN_SMALL_LETTER_I = "\U000e0069" +DIGIT_TWO = "2\ufe0f" +TAG_DIGIT_ONE = "\U000e0031" TAG_RIGHT_SQUARE_BRACKET = "\U000e005d" -DIGIT_SIX = "6\ufe0f" -DIGIT_FIVE = "5\ufe0f" +TAG_LATIN_SMALL_LETTER_R = "\U000e0072" +HASH_SIGN = "#\ufe0f" +TAG_SEMICOLON = "\U000e003b" +TAG_LATIN_CAPITAL_LETTER_L = "\U000e004c" +TAG_HYPHEN_MINUS = "\U000e002d" ASTERISK = "*\ufe0f" +TAG_LATIN_SMALL_LETTER_A = "\U000e0061" +TAG_EXCLAMATION_MARK = "\U000e0021" +TAG_LATIN_CAPITAL_LETTER_V = "\U000e0056" +TAG_LATIN_SMALL_LETTER_C = "\U000e0063" +TAG_GRAVE_ACCENT = "\U000e0060" +ZERO_WIDTH_JOINER = "\u200d" TAG_LATIN_CAPITAL_LETTER_G = "\U000e0047" -DIGIT_ZERO = "0\ufe0f" +DIGIT_NINE = "9\ufe0f" TAG_VERTICAL_LINE = "\U000e007c" -TAG_PLUS_SIGN = "\U000e002b" -TAG_LEFT_SQUARE_BRACKET = "\U000e005b" -TAG_DIGIT_EIGHT = "\U000e0038" -TAG_SPACE = "\U000e0020" +TAG_LATIN_SMALL_LETTER_Z = "\U000e007a" +TAG_LATIN_CAPITAL_LETTER_X = "\U000e0058" TAG_LATIN_SMALL_LETTER_J = "\U000e006a" -TAG_LATIN_CAPITAL_LETTER_H = "\U000e0048" -DIGIT_THREE = "3\ufe0f" -TAG_LATIN_CAPITAL_LETTER_L = "\U000e004c" -TAG_LATIN_CAPITAL_LETTER_D = "\U000e0044" -TAG_LATIN_CAPITAL_LETTER_U = "\U000e0055" -TAG_LESS_THAN_SIGN = "\U000e003c" -TAG_EXCLAMATION_MARK = "\U000e0021" -TAG_APOSTROPHE = "\U000e0027" -TAG_GREATER_THAN_SIGN = "\U000e003e" -TAG_LATIN_SMALL_LETTER_T = "\U000e0074" -DIGIT_NINE = "9\ufe0f" -TAG_LATIN_SMALL_LETTER_S = "\U000e0073" -TAG_LATIN_SMALL_LETTER_Q = "\U000e0071" -DIGIT_TWO = "2\ufe0f" +TAG_LATIN_CAPITAL_LETTER_P = "\U000e0050" TAG_AMPERSAND = "\U000e0026" -COMBINING_ENCLOSING_KEYCAP = "\u20e3" -TAG_LATIN_SMALL_LETTER_H = "\U000e0068" -TAG_LATIN_SMALL_LETTER_R = "\U000e0072" -TAG_SEMICOLON = "\U000e003b" -TAG_LATIN_CAPITAL_LETTER_O = "\U000e004f" -TAG_LOW_LINE = "\U000e005f" +TAG_LATIN_SMALL_LETTER_L = "\U000e006c" +TAG_LATIN_SMALL_LETTER_X = "\U000e0078" DIGIT_SEVEN = "7\ufe0f" -TAG_GRAVE_ACCENT = "\U000e0060" -TAG_LATIN_SMALL_LETTER_W = "\U000e0077" -TAG_EQUALS_SIGN = "\U000e003d" -TAG_LATIN_SMALL_LETTER_G = "\U000e0067" -TAG_LATIN_CAPITAL_LETTER_A = "\U000e0041" -TAG_DIGIT_THREE = "\U000e0033" -TAG_LEFT_CURLY_BRACKET = "\U000e007b" +TAG_LATIN_CAPITAL_LETTER_J = "\U000e004a" +TAG_LATIN_SMALL_LETTER_T = "\U000e0074" TAG_QUESTION_MARK = "\U000e003f" -TAG_LATIN_SMALL_LETTER_L = "\U000e006c" -TAG_DIGIT_SIX = "\U000e0036" -TAG_LATIN_CAPITAL_LETTER_N = "\U000e004e" -TAG_DIGIT_TWO = "\U000e0032" -TAG_LATIN_CAPITAL_LETTER_K = "\U000e004b" -TAG_DIGIT_ZERO = "\U000e0030" -DIGIT_EIGHT = "8\ufe0f" -TAG_LATIN_SMALL_LETTER_Y = "\U000e0079" +TAG_LATIN_SMALL_LETTER_B = "\U000e0062" +TAG_LEFT_SQUARE_BRACKET = "\U000e005b" +TAG_LATIN_SMALL_LETTER_D = "\U000e0064" +TAG_LATIN_SMALL_LETTER_E = "\U000e0065" +TAG_LATIN_SMALL_LETTER_M = "\U000e006d" +TAG_LESS_THAN_SIGN = "\U000e003c" +TAG_DIGIT_FIVE = "\U000e0035" +TAG_LATIN_CAPITAL_LETTER_D = "\U000e0044" +TAG_LATIN_SMALL_LETTER_N = "\U000e006e" +TAG_PLUS_SIGN = "\U000e002b" +TAG_COLON = "\U000e003a" +DIGIT_THREE = "3\ufe0f" +TAG_LATIN_SMALL_LETTER_Q = "\U000e0071" +TAG_LATIN_CAPITAL_LETTER_R = "\U000e0052" +TAG_LATIN_CAPITAL_LETTER_S = "\U000e0053" +DIGIT_FOUR = "4\ufe0f" +TAG_LATIN_CAPITAL_LETTER_I = "\U000e0049" +TAG_QUOTATION_MARK = "\U000e0022" +CANCEL_TAG = "\U000e007f" +TAG_LATIN_SMALL_LETTER_V = "\U000e0076" +TAG_LATIN_SMALL_LETTER_H = "\U000e0068" +TAG_LATIN_SMALL_LETTER_U = "\U000e0075" +TAG_DIGIT_FOUR = "\U000e0034" diff --git a/pyrogram/enums/__init__.py b/pyrogram/enums/__init__.py new file mode 100644 index 0000000000..d19c7044ff --- /dev/null +++ b/pyrogram/enums/__init__.py @@ -0,0 +1,49 @@ +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-present Dan +# +# This file is part of Pyrogram. +# +# Pyrogram is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License as published +# by the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Pyrogram is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with Pyrogram. If not, see . + +from .chat_action import ChatAction +from .chat_event_action import ChatEventAction +from .chat_member_status import ChatMemberStatus +from .chat_members_filter import ChatMembersFilter +from .chat_type import ChatType +from .message_entity_type import MessageEntityType +from .message_media_type import MessageMediaType +from .message_service_type import MessageServiceType +from .messages_filter import MessagesFilter +from .next_code_type import NextCodeType +from .parse_mode import ParseMode +from .poll_type import PollType +from .sent_code_type import SentCodeType +from .user_status import UserStatus + +__all__ = [ + 'ChatAction', + 'ChatEventAction', + 'ChatMemberStatus', + 'ChatMembersFilter', + 'ChatType', + 'MessageEntityType', + 'MessageMediaType', + 'MessageServiceType', + 'MessagesFilter', + 'NextCodeType', + 'ParseMode', + 'PollType', + 'SentCodeType', + 'UserStatus' +] diff --git a/pyrogram/enums/auto_name.py b/pyrogram/enums/auto_name.py new file mode 100644 index 0000000000..b43fa74180 --- /dev/null +++ b/pyrogram/enums/auto_name.py @@ -0,0 +1,27 @@ +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-present Dan +# +# This file is part of Pyrogram. +# +# Pyrogram is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License as published +# by the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Pyrogram is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with Pyrogram. If not, see . + +from enum import Enum + + +class AutoName(Enum): + def _generate_next_value_(self, *args): + return self.lower() + + def __repr__(self): + return f"pyrogram.enums.{self}" diff --git a/pyrogram/enums/chat_action.py b/pyrogram/enums/chat_action.py new file mode 100644 index 0000000000..167937e002 --- /dev/null +++ b/pyrogram/enums/chat_action.py @@ -0,0 +1,72 @@ +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-present Dan +# +# This file is part of Pyrogram. +# +# Pyrogram is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License as published +# by the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Pyrogram is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with Pyrogram. If not, see . + +from pyrogram import raw +from .auto_name import AutoName + + +class ChatAction(AutoName): + """Chat action enumeration used in :obj:`~pyrogram.types.ChatEvent`.""" + + TYPING = raw.types.SendMessageTypingAction + "Typing text message" + + UPLOAD_PHOTO = raw.types.SendMessageUploadPhotoAction + "Uploading photo" + + RECORD_VIDEO = raw.types.SendMessageRecordVideoAction + "Recording video" + + UPLOAD_VIDEO = raw.types.SendMessageUploadVideoAction + "Uploading video" + + RECORD_AUDIO = raw.types.SendMessageRecordAudioAction + "Recording audio" + + UPLOAD_AUDIO = raw.types.SendMessageUploadAudioAction + "Uploading audio" + + UPLOAD_DOCUMENT = raw.types.SendMessageUploadDocumentAction + "Uploading document" + + FIND_LOCATION = raw.types.SendMessageGeoLocationAction + "Finding location" + + RECORD_VIDEO_NOTE = raw.types.SendMessageRecordRoundAction + "Recording video note" + + UPLOAD_VIDEO_NOTE = raw.types.SendMessageUploadRoundAction + "Uploading video note" + + PLAYING = raw.types.SendMessageGamePlayAction + "Playing game" + + CHOOSE_CONTACT = raw.types.SendMessageChooseContactAction + "Choosing contact" + + SPEAKING = raw.types.SpeakingInGroupCallAction + "Speaking in group call" + + IMPORT_HISTORY = raw.types.SendMessageHistoryImportAction + "Importing history" + + CHOOSE_STICKER = raw.types.SendMessageChooseStickerAction + "Choosing sticker" + + CANCEL = raw.types.SendMessageCancelAction + "Cancel ongoing chat action" diff --git a/pyrogram/enums/chat_event_action.py b/pyrogram/enums/chat_event_action.py new file mode 100644 index 0000000000..7a5954720f --- /dev/null +++ b/pyrogram/enums/chat_event_action.py @@ -0,0 +1,127 @@ +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-present Dan +# +# This file is part of Pyrogram. +# +# Pyrogram is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License as published +# by the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Pyrogram is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with Pyrogram. If not, see . + +from enum import auto + +from .auto_name import AutoName + + +class ChatEventAction(AutoName): + """Chat event action enumeration used in :meth:`~pyrogram.Client.get_chat_event_log`.""" + + DESCRIPTION_CHANGED = auto() + "The chat description has been changed (see ``old_description`` and ``new_description``)" + + HISTORY_TTL_CHANGED = auto() + "The history time-to-live has been changed (see ``old_history_ttl`` and ``new_history_ttl``)" + + LINKED_CHAT_CHANGED = auto() + "The linked chat has been changed (see ``old_linked_chat`` and ``new_linked_chat``)" + + # LOCATION_CHANGED = auto() + "" + + PHOTO_CHANGED = auto() + "The chat photo has been changed (see ``old_photo`` and ``new_photo``)" + + # STICKER_SET_CHANGED = auto() + "" + + TITLE_CHANGED = auto() + "the chat title has been changed (see ``old_title`` and ``new_title``)" + + USERNAME_CHANGED = auto() + "the chat username has been changed (see ``old_username`` and ``new_username``)" + + CHAT_PERMISSIONS_CHANGED = auto() + "the default chat permissions has been changed (see ``old_chat_permissions`` and ``new_chat_permissions``)" + + MESSAGE_DELETED = auto() + "a message has been deleted (see ``deleted_message``)" + + # VOICE_CHAT_DISCARDED = auto() + "" + + MESSAGE_EDITED = auto() + "a message has been edited (see ``old_message`` and ``new_message``)" + + INVITE_LINK_EDITED = auto() + "An invite link has been edited (see ``old_invite_link`` and ``new_invite`` link)" + + INVITE_LINK_REVOKED = auto() + "An invite link has been revoked (see ``revoked_invite_link``)" + + INVITE_LINK_DELETED = auto() + "An invite link has been deleted (see ``deleted_invite_link``)" + + MEMBER_INVITED = auto() + "a member has been invited by someone (see ``invited_member``)" + + MEMBER_JOINED = auto() + "a member joined by themselves. (see ``user``)" + + # MEMBER_JOINED_BY_LINK = auto() + "" + + MEMBER_LEFT = auto() + "a member left by themselves. (see ``user``)" + + # MEMBER_MUTED = auto() + "" + + ADMINISTRATOR_PRIVILEGES_CHANGED = auto() + "a chat member has been promoted/demoted or their administrator privileges has changed (see ``old_administrator_privileges`` and ``new_administrator_privileges``)" + + MEMBER_PERMISSIONS_CHANGED = auto() + "a chat member has been restricted/unrestricted or banned/unbanned, or their permissions has changed (see ``old_member_permissions`` and ``new_member_permissions``)" + + # MEMBER_UNMUTED = auto() + "" + + # MEMBER_VOLUME_CHANGED = auto() + "" + + # VIDEO_CHAT_STARTED = auto() + "" + + POLL_STOPPED = auto() + "a poll has been stopped (see ``stopped_poll``)" + + # VOICE_CHAT_SETTINGS_CHANGED = auto() + "" + + INVITES_ENABLED = auto() + "the chat invitation has been enabled or disabled (see ``invites_enabled``)" + + HISTORY_HIDDEN = auto() + "the chat history has been hidden or unhidden (see ``history_hidden``)" + + SIGNATURES_ENABLED = auto() + "the message signatures have been enabled or disabled (see ``signatures_enabled``)" + + SLOW_MODE_CHANGED = auto() + "the slow mode has been changes (see ``old_slow_mode`` and ``new_slow_mode``)" + + MESSAGE_PINNED = auto() + "a message has been pinned (see ``pinned_message``)" + + MESSAGE_UNPINNED = auto() + "a message has been unpinned (see ``unpinned_message``)" + + UNKNOWN = auto() + "Unknown chat event action" diff --git a/pyrogram/enums/chat_member_status.py b/pyrogram/enums/chat_member_status.py new file mode 100644 index 0000000000..7fa9e8eca8 --- /dev/null +++ b/pyrogram/enums/chat_member_status.py @@ -0,0 +1,43 @@ +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-present Dan +# +# This file is part of Pyrogram. +# +# Pyrogram is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License as published +# by the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Pyrogram is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with Pyrogram. If not, see . + +from enum import auto + +from .auto_name import AutoName + + +class ChatMemberStatus(AutoName): + """Chat member status enumeration used in :obj:`~pyrogram.types.ChatMember`.""" + + OWNER = auto() + "Chat owner" + + ADMINISTRATOR = auto() + "Chat administrator" + + MEMBER = auto() + "Chat member" + + RESTRICTED = auto() + "Restricted chat member" + + LEFT = auto() + "Left chat member" + + BANNED = auto() + "Banned chat member" diff --git a/pyrogram/enums/chat_members_filter.py b/pyrogram/enums/chat_members_filter.py new file mode 100644 index 0000000000..bf0e7ef358 --- /dev/null +++ b/pyrogram/enums/chat_members_filter.py @@ -0,0 +1,42 @@ +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-present Dan +# +# This file is part of Pyrogram. +# +# Pyrogram is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License as published +# by the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Pyrogram is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with Pyrogram. If not, see . + +from pyrogram import raw +from .auto_name import AutoName + + +class ChatMembersFilter(AutoName): + """Chat members filter enumeration used in :meth:`~pyrogram.Client.get_chat_members`""" + + SEARCH = raw.types.ChannelParticipantsSearch + "Search for members" + + BANNED = raw.types.ChannelParticipantsKicked + "Banned members" + + RESTRICTED = raw.types.ChannelParticipantsBanned + "Restricted members" + + BOTS = raw.types.ChannelParticipantsBots + "Bots" + + RECENT = raw.types.ChannelParticipantsRecent + "Recently active members" + + ADMINISTRATORS = raw.types.ChannelParticipantsAdmins + "Administrators" diff --git a/pyrogram/enums/chat_type.py b/pyrogram/enums/chat_type.py new file mode 100644 index 0000000000..5750e16d7c --- /dev/null +++ b/pyrogram/enums/chat_type.py @@ -0,0 +1,40 @@ +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-present Dan +# +# This file is part of Pyrogram. +# +# Pyrogram is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License as published +# by the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Pyrogram is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with Pyrogram. If not, see . + +from enum import auto + +from .auto_name import AutoName + + +class ChatType(AutoName): + """Chat type enumeration used in :obj:`~pyrogram.types.Chat`.""" + + PRIVATE = auto() + "Chat is a private chat with a user" + + BOT = auto() + "Chat is a private chat with a bot" + + GROUP = auto() + "Chat is a basic group" + + SUPERGROUP = auto() + "Chat is a supergroup" + + CHANNEL = auto() + "Chat is a channel" diff --git a/pyrogram/enums/message_entity_type.py b/pyrogram/enums/message_entity_type.py new file mode 100644 index 0000000000..4db75f93f7 --- /dev/null +++ b/pyrogram/enums/message_entity_type.py @@ -0,0 +1,84 @@ +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-present Dan +# +# This file is part of Pyrogram. +# +# Pyrogram is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License as published +# by the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Pyrogram is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with Pyrogram. If not, see . + +from pyrogram import raw +from .auto_name import AutoName + + +class MessageEntityType(AutoName): + """Message entity type enumeration used in :obj:`~pyrogram.types.MessageEntity`.""" + + MENTION = raw.types.MessageEntityMention + "``@username``" + + HASHTAG = raw.types.MessageEntityHashtag + "``#hashtag``" + + CASHTAG = raw.types.MessageEntityCashtag + "``$USD``" + + BOT_COMMAND = raw.types.MessageEntityBotCommand + "``/start@pyrogrambot``" + + URL = raw.types.MessageEntityUrl + "``https://pyrogram.org`` (see ``url``)" + + EMAIL = raw.types.MessageEntityEmail + "``do-not-reply@pyrogram.org``" + + PHONE_NUMBER = raw.types.MessageEntityPhone + "``+1-123-456-7890``" + + BOLD = raw.types.MessageEntityBold + "Bold text" + + ITALIC = raw.types.MessageEntityItalic + "Italic text" + + UNDERLINE = raw.types.MessageEntityUnderline + "Underlined text" + + STRIKETHROUGH = raw.types.MessageEntityStrike + "Strikethrough text" + + SPOILER = raw.types.MessageEntitySpoiler + "Spoiler text" + + CODE = raw.types.MessageEntityCode + "Monowidth string" + + PRE = raw.types.MessageEntityPre + "Monowidth block (see ``language``)" + + BLOCKQUOTE = raw.types.MessageEntityBlockquote + "Blockquote text" + + TEXT_LINK = raw.types.MessageEntityTextUrl + "For clickable text URLs" + + TEXT_MENTION = raw.types.MessageEntityMentionName + "for users without usernames (see ``user``)" + + BANK_CARD = raw.types.MessageEntityBankCard + "Bank card text" + + CUSTOM_EMOJI = raw.types.MessageEntityCustomEmoji + "Custom emoji" + + UNKNOWN = raw.types.MessageEntityUnknown + "Unknown message entity type" diff --git a/pyrogram/enums/message_media_type.py b/pyrogram/enums/message_media_type.py new file mode 100644 index 0000000000..5887811405 --- /dev/null +++ b/pyrogram/enums/message_media_type.py @@ -0,0 +1,70 @@ +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-present Dan +# +# This file is part of Pyrogram. +# +# Pyrogram is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License as published +# by the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Pyrogram is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with Pyrogram. If not, see . + +from enum import auto + +from .auto_name import AutoName + + +class MessageMediaType(AutoName): + """Message media type enumeration used in :obj:`~pyrogram.types.Message`.""" + + AUDIO = auto() + "Audio media" + + DOCUMENT = auto() + "Document media" + + PHOTO = auto() + "Photo media" + + STICKER = auto() + "Sticker media" + + VIDEO = auto() + "Video media" + + ANIMATION = auto() + "Animation media" + + VOICE = auto() + "Voice media" + + VIDEO_NOTE = auto() + "Video note media" + + CONTACT = auto() + "Contact media" + + LOCATION = auto() + "Location media" + + VENUE = auto() + "Venue media" + + POLL = auto() + "Poll media" + + WEB_PAGE = auto() + "Web page media" + + DICE = auto() + "Dice media" + + GAME = auto() + "Game media" diff --git a/pyrogram/enums/message_service_type.py b/pyrogram/enums/message_service_type.py new file mode 100644 index 0000000000..8a60e29e6e --- /dev/null +++ b/pyrogram/enums/message_service_type.py @@ -0,0 +1,73 @@ +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-present Dan +# +# This file is part of Pyrogram. +# +# Pyrogram is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License as published +# by the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Pyrogram is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with Pyrogram. If not, see . + +from enum import auto + +from .auto_name import AutoName + + +class MessageServiceType(AutoName): + """Message service type enumeration used in :obj:`~pyrogram.types.Message`.""" + + NEW_CHAT_MEMBERS = auto() + "New members join" + + LEFT_CHAT_MEMBERS = auto() + "Left chat members" + + NEW_CHAT_TITLE = auto() + "New chat title" + + NEW_CHAT_PHOTO = auto() + "New chat photo" + + DELETE_CHAT_PHOTO = auto() + "Deleted chat photo" + + GROUP_CHAT_CREATED = auto() + "Group chat created" + + CHANNEL_CHAT_CREATED = auto() + "Channel chat created" + + MIGRATE_TO_CHAT_ID = auto() + "Migrated to chat id" + + MIGRATE_FROM_CHAT_ID = auto() + "Migrated from chat id" + + PINNED_MESSAGE = auto() + "Pinned message" + + GAME_HIGH_SCORE = auto() + "Game high score" + + VIDEO_CHAT_STARTED = auto() + "Video chat started" + + VIDEO_CHAT_ENDED = auto() + "Video chat ended" + + VIDEO_CHAT_SCHEDULED = auto() + "Video chat scheduled" + + VIDEO_CHAT_MEMBERS_INVITED = auto() + "Video chat members invited" + + WEB_APP_DATA = auto() + "Web app data" diff --git a/pyrogram/enums/messages_filter.py b/pyrogram/enums/messages_filter.py new file mode 100644 index 0000000000..67bfac63ee --- /dev/null +++ b/pyrogram/enums/messages_filter.py @@ -0,0 +1,75 @@ +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-present Dan +# +# This file is part of Pyrogram. +# +# Pyrogram is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License as published +# by the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Pyrogram is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with Pyrogram. If not, see . + +from pyrogram import raw +from .auto_name import AutoName + + +class MessagesFilter(AutoName): + """Messages filter enumeration used in :meth:`~pyrogram.Client.search_messages` and :meth:`~pyrogram.Client.search_global`""" + + EMPTY = raw.types.InputMessagesFilterEmpty + "Empty filter (any kind of messages)" + + PHOTO = raw.types.InputMessagesFilterPhotos + "Photo messages" + + VIDEO = raw.types.InputMessagesFilterVideo + "Video messages" + + PHOTO_VIDEO = raw.types.InputMessagesFilterPhotoVideo + "Photo and video messages" + + DOCUMENT = raw.types.InputMessagesFilterDocument + "Document messages" + + URL = raw.types.InputMessagesFilterUrl + "Messages containing URLs" + + ANIMATION = raw.types.InputMessagesFilterGif + "Animation messages" + + VOICE_NOTE = raw.types.InputMessagesFilterVoice + "Voice note messages" + + VIDEO_NOTE = raw.types.InputMessagesFilterRoundVideo + "Video note messages" + + AUDIO_VIDEO_NOTE = raw.types.InputMessagesFilterRoundVideo + "Audio and video note messages" + + AUDIO = raw.types.InputMessagesFilterMusic + "Audio messages (music)" + + CHAT_PHOTO = raw.types.InputMessagesFilterChatPhotos + "Chat photo messages" + + PHONE_CALL = raw.types.InputMessagesFilterPhoneCalls + "Phone call messages" + + MENTION = raw.types.InputMessagesFilterMyMentions + "Messages containing mentions" + + LOCATION = raw.types.InputMessagesFilterGeo + "Location messages" + + CONTACT = raw.types.InputMessagesFilterContacts + "Contact messages" + + PINNED = raw.types.InputMessagesFilterPinned + "Pinned messages" diff --git a/pyrogram/enums/next_code_type.py b/pyrogram/enums/next_code_type.py new file mode 100644 index 0000000000..7b3137a7ad --- /dev/null +++ b/pyrogram/enums/next_code_type.py @@ -0,0 +1,39 @@ +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-present Dan +# +# This file is part of Pyrogram. +# +# Pyrogram is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License as published +# by the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Pyrogram is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with Pyrogram. If not, see . + +from pyrogram import raw +from .auto_name import AutoName + + +class NextCodeType(AutoName): + """Next code type enumeration used in :obj:`~pyrogram.types.SentCode`.""" + + CALL = raw.types.auth.CodeTypeCall + "The code will be sent via a phone call. A synthesized voice will tell the user which verification code to input." + + FLASH_CALL = raw.types.auth.CodeTypeFlashCall + "The code will be sent via a flash phone call, that will be closed immediately." + + MISSED_CALL = raw.types.auth.CodeTypeMissedCall + "Missed call." + + SMS = raw.types.auth.CodeTypeSms + "The code was sent via SMS." + + FRAGMENT_SMS = raw.types.auth.CodeTypeFragmentSms + "The code was sent via Fragment SMS." diff --git a/pyrogram/enums/parse_mode.py b/pyrogram/enums/parse_mode.py new file mode 100644 index 0000000000..26dc165a25 --- /dev/null +++ b/pyrogram/enums/parse_mode.py @@ -0,0 +1,37 @@ +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-present Dan +# +# This file is part of Pyrogram. +# +# Pyrogram is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License as published +# by the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Pyrogram is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with Pyrogram. If not, see . + +from enum import auto + +from .auto_name import AutoName + + +class ParseMode(AutoName): + """Parse mode enumeration used in various places to set a specific parse mode""" + + DEFAULT = auto() + "Default mode. Markdown and HTML combined" + + MARKDOWN = auto() + "Markdown only mode" + + HTML = auto() + "HTML only mode" + + DISABLED = auto() + "Disabled mode" diff --git a/pyrogram/enums/poll_type.py b/pyrogram/enums/poll_type.py new file mode 100644 index 0000000000..384592dee3 --- /dev/null +++ b/pyrogram/enums/poll_type.py @@ -0,0 +1,31 @@ +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-present Dan +# +# This file is part of Pyrogram. +# +# Pyrogram is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License as published +# by the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Pyrogram is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with Pyrogram. If not, see . + +from enum import auto + +from .auto_name import AutoName + + +class PollType(AutoName): + """Poll type enumeration used in :obj:`~pyrogram.types.Poll`.""" + + QUIZ = auto() + "Quiz poll" + + REGULAR = auto() + "Regular poll" diff --git a/pyrogram/enums/sent_code_type.py b/pyrogram/enums/sent_code_type.py new file mode 100644 index 0000000000..474ed6b0f3 --- /dev/null +++ b/pyrogram/enums/sent_code_type.py @@ -0,0 +1,45 @@ +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-present Dan +# +# This file is part of Pyrogram. +# +# Pyrogram is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License as published +# by the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Pyrogram is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with Pyrogram. If not, see . + +from pyrogram import raw +from .auto_name import AutoName + + +class SentCodeType(AutoName): + """Sent code type enumeration used in :obj:`~pyrogram.types.SentCode`.""" + + APP = raw.types.auth.SentCodeTypeApp + "The code was sent through the telegram app." + + CALL = raw.types.auth.SentCodeTypeCall + "The code will be sent via a phone call. A synthesized voice will tell the user which verification code to input." + + FLASH_CALL = raw.types.auth.SentCodeTypeFlashCall + "The code will be sent via a flash phone call, that will be closed immediately." + + MISSED_CALL = raw.types.auth.SentCodeTypeMissedCall + "Missed call." + + SMS = raw.types.auth.SentCodeTypeSms + "The code was sent via SMS." + + FRAGMENT_SMS = raw.types.auth.SentCodeTypeFragmentSms + "The code was sent via Fragment SMS." + + EMAIL_CODE = raw.types.auth.SentCodeTypeEmailCode + "The code was sent via email." diff --git a/pyrogram/enums/user_status.py b/pyrogram/enums/user_status.py new file mode 100644 index 0000000000..c7c25234cb --- /dev/null +++ b/pyrogram/enums/user_status.py @@ -0,0 +1,43 @@ +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-present Dan +# +# This file is part of Pyrogram. +# +# Pyrogram is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License as published +# by the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Pyrogram is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with Pyrogram. If not, see . + +from enum import auto + +from .auto_name import AutoName + + +class UserStatus(AutoName): + """User status enumeration used in :obj:`~pyrogram.types.User`.""" + + ONLINE = auto() + """User is online""" + + OFFLINE = auto() + """User is offline""" + + RECENTLY = auto() + """User was seen recently""" + + LAST_WEEK = auto() + """User was seen last week""" + + LAST_MONTH = auto() + """User was seen last month""" + + LONG_AGO = auto() + """User was seen long ago""" diff --git a/pyrogram/errors/__init__.py b/pyrogram/errors/__init__.py index 1b94700faa..aa3a042c54 100644 --- a/pyrogram/errors/__init__.py +++ b/pyrogram/errors/__init__.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2021 Dan +# Copyright (C) 2017-present Dan # # This file is part of Pyrogram. # @@ -18,3 +18,48 @@ from .exceptions import * from .rpc_error import UnknownError + + +class BadMsgNotification(Exception): + descriptions = { + 16: "The msg_id is too low, the client time has to be synchronized.", + 17: "The msg_id is too high, the client time has to be synchronized.", + 18: "Incorrect two lower order of the msg_id bits, the server expects the client message " + "msg_id to be divisible by 4.", + 19: "The container msg_id is the same as the msg_id of a previously received message.", + 20: "The message is too old, it cannot be verified by the server.", + 32: "The msg_seqno is too low.", + 33: "The msg_seqno is too high.", + 34: "An even msg_seqno was expected, but an odd one was received.", + 35: "An odd msg_seqno was expected, but an even one was received.", + 48: "Incorrect server salt.", + 64: "Invalid container." + } + + def __init__(self, code): + description = self.descriptions.get(code, "Unknown error code") + super().__init__(f"[{code}] {description}") + + +class SecurityError(Exception): + """Generic security error.""" + + @classmethod + def check(cls, cond: bool, msg: str): + """Raises this exception if the condition is false""" + if not cond: + raise cls(f"Check failed: {msg}") + + +class SecurityCheckMismatch(SecurityError): + """Raised when a security check mismatch occurs.""" + + def __init__(self, msg: str = None): + super().__init__("A security check mismatch has occurred." if msg is None else msg) + + +class CDNFileHashMismatch(SecurityError): + """Raised when a CDN file hash mismatch occurs.""" + + def __init__(self, msg: str = None): + super().__init__("A CDN file hash mismatch has occurred." if msg is None else msg) diff --git a/pyrogram/errors/rpc_error.py b/pyrogram/errors/rpc_error.py index 71a7249f66..63375f199e 100644 --- a/pyrogram/errors/rpc_error.py +++ b/pyrogram/errors/rpc_error.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2021 Dan +# Copyright (C) 2017-present Dan # # This file is part of Pyrogram. # @@ -30,31 +30,31 @@ class RPCError(Exception): ID = None CODE = None NAME = None - MESSAGE = "{x}" + MESSAGE = "{value}" def __init__( self, - x: Union[int, str, raw.types.RpcError] = None, + value: Union[int, str, raw.types.RpcError] = None, rpc_name: str = None, is_unknown: bool = False, is_signed: bool = False ): - super().__init__("[{}{} {}]: {} {}".format( + super().__init__("Telegram says: [{}{} {}] - {} {}".format( "-" if is_signed else "", self.CODE, self.ID or self.NAME, - self.MESSAGE.format(x=x), + self.MESSAGE.format(value=value), f'(caused by "{rpc_name}")' if rpc_name else "" )) try: - self.x = int(x) + self.value = int(value) except (ValueError, TypeError): - self.x = x + self.value = value if is_unknown: with open("unknown_errors.txt", "a", encoding="utf-8") as f: - f.write(f"{datetime.now()}\t{x}\t{rpc_name}\n") + f.write(f"{datetime.now()}\t{value}\t{rpc_name}\n") @staticmethod def raise_it(rpc_error: "raw.types.RpcError", rpc_type: Type[TLObject]): @@ -68,7 +68,7 @@ def raise_it(rpc_error: "raw.types.RpcError", rpc_type: Type[TLObject]): if error_code not in exceptions: raise UnknownError( - x=f"[{error_code} {error_message}]", + value=f"[{error_code} {error_message}]", rpc_name=rpc_name, is_unknown=True, is_signed=is_signed @@ -80,18 +80,18 @@ def raise_it(rpc_error: "raw.types.RpcError", rpc_type: Type[TLObject]): raise getattr( import_module("pyrogram.errors"), exceptions[error_code]["_"] - )(x=f"[{error_code} {error_message}]", + )(value=f"[{error_code} {error_message}]", rpc_name=rpc_name, is_unknown=True, is_signed=is_signed) - x = re.search(r"_(\d+)", error_message) - x = x.group(1) if x is not None else x + value = re.search(r"_(\d+)", error_message) + value = value.group(1) if value is not None else value raise getattr( import_module("pyrogram.errors"), exceptions[error_code][error_id] - )(x=x, + )(value=value, rpc_name=rpc_name, is_unknown=False, is_signed=is_signed) diff --git a/pyrogram/file_id.py b/pyrogram/file_id.py index ef1319ecae..1a54a49d61 100644 --- a/pyrogram/file_id.py +++ b/pyrogram/file_id.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2021 Dan +# Copyright (C) 2017-present Dan # # This file is part of Pyrogram. # @@ -21,6 +21,7 @@ import struct from enum import IntEnum from io import BytesIO +from typing import List from pyrogram.raw.core import Bytes, String @@ -63,23 +64,23 @@ def rle_encode(s: bytes) -> bytes: Returns: ``bytes``: The encoded bytes """ - r = b"" - n = 0 + r: List[int] = [] + n: int = 0 for b in s: - if b == 0: + if not b: n += 1 else: - if n > 0: - r += bytes([0, n]) + if n: + r.extend((0, n)) n = 0 - r += bytes([b]) + r.append(b) - if n > 0: - r += bytes([0, n]) + if n: + r.extend((0, n)) - return r + return bytes(r) def rle_decode(s: bytes) -> bytes: @@ -87,24 +88,26 @@ def rle_decode(s: bytes) -> bytes: Parameters: s (``bytes``): - Bytes to encode + Bytes to decode Returns: - ``bytes``: The encoded bytes + ``bytes``: The decoded bytes """ - r = b"" - i = 0 + r: List[int] = [] + z: bool = False - while i < len(s): - if s[i] != 0: - r += bytes([s[i]]) - else: - r += b"\x00" * s[i + 1] - i += 1 + for b in s: + if not b: + z = True + continue - i += 1 + if z: + r.extend((0,) * b) + z = False + else: + r.append(b) - return r + return bytes(r) class FileType(IntEnum): diff --git a/pyrogram/filters.py b/pyrogram/filters.py index b6d27087e7..b52dfe601d 100644 --- a/pyrogram/filters.py +++ b/pyrogram/filters.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2021 Dan +# Copyright (C) 2017-present Dan # # This file is part of Pyrogram. # @@ -21,6 +21,7 @@ from typing import Callable, Union, List, Pattern import pyrogram +from pyrogram import enums from pyrogram.types import Message, CallbackQuery, InlineQuery, InlineKeyboardMarkup, ReplyKeyboardMarkup, Update @@ -126,7 +127,7 @@ def create(func: Callable, name: str = None, **kwargs) -> Filter: Custom filters give you extra control over which updates are allowed or not to be processed by your handlers. Parameters: - func (``callable``): + func (``Callable``): A function that accepts three positional arguments *(filter, client, update)* and returns a boolean: True if the update should be handled, False otherwise. The *filter* argument refers to the filter itself and can be used to access keyword arguments (read below). @@ -163,7 +164,7 @@ async def all_filter(_, __, ___): # region me_filter async def me_filter(_, __, m: Message): - return bool(m.from_user and m.from_user.is_self or m.outgoing) + return bool(m.from_user and m.from_user.is_self or getattr(m, "outgoing", False)) me = create(me_filter) @@ -218,7 +219,7 @@ async def text_filter(_, __, m: Message): # region reply_filter async def reply_filter(_, __, m: Message): - return bool(m.reply_to_message) + return bool(m.reply_to_message_id) reply = create(reply_filter) @@ -249,16 +250,6 @@ async def caption_filter(_, __, m: Message): # endregion -# region edited_filter -async def edited_filter(_, __, m: Message): - return bool(m.edit_date) - - -edited = create(edited_filter) -"""Filter edited messages.""" - - -# endregion # region audio_filter async def audio_filter(_, __, m: Message): @@ -434,11 +425,22 @@ async def dice_filter(_, __, m: Message): """Filter messages that contain :obj:`~pyrogram.types.Dice` objects.""" +# endregion + +# region media_spoiler +async def media_spoiler_filter(_, __, m: Message): + return bool(m.has_media_spoiler) + + +media_spoiler = create(media_spoiler_filter) +"""Filter media messages that contain a spoiler.""" + + # endregion # region private_filter async def private_filter(_, __, m: Message): - return bool(m.chat and m.chat.type in {"private", "bot"}) + return bool(m.chat and m.chat.type in {enums.ChatType.PRIVATE, enums.ChatType.BOT}) private = create(private_filter) @@ -449,7 +451,7 @@ async def private_filter(_, __, m: Message): # region group_filter async def group_filter(_, __, m: Message): - return bool(m.chat and m.chat.type in {"group", "supergroup"}) + return bool(m.chat and m.chat.type in {enums.ChatType.GROUP, enums.ChatType.SUPERGROUP}) group = create(group_filter) @@ -460,7 +462,7 @@ async def group_filter(_, __, m: Message): # region channel_filter async def channel_filter(_, __, m: Message): - return bool(m.chat and m.chat.type == "channel") + return bool(m.chat and m.chat.type == enums.ChatType.CHANNEL) channel = create(channel_filter) @@ -645,34 +647,34 @@ async def via_bot_filter(_, __, m: Message): # endregion -# region voice_chat_started_filter -async def voice_chat_started_filter(_, __, m: Message): - return bool(m.voice_chat_started) +# region video_chat_started_filter +async def video_chat_started_filter(_, __, m: Message): + return bool(m.video_chat_started) -voice_chat_started = create(voice_chat_started_filter) -"""Filter messages for started voice chats""" +video_chat_started = create(video_chat_started_filter) +"""Filter messages for started video chats""" # endregion -# region voice_chat_ended_filter -async def voice_chat_ended_filter(_, __, m: Message): - return bool(m.voice_chat_ended) +# region video_chat_ended_filter +async def video_chat_ended_filter(_, __, m: Message): + return bool(m.video_chat_ended) -voice_chat_ended = create(voice_chat_ended_filter) -"""Filter messages for ended voice chats""" +video_chat_ended = create(video_chat_ended_filter) +"""Filter messages for ended video chats""" # endregion -# region voice_chat_members_invited_filter -async def voice_chat_members_invited_filter(_, __, m: Message): - return bool(m.voice_chat_members_invited) +# region video_chat_members_invited_filter +async def video_chat_members_invited_filter(_, __, m: Message): + return bool(m.video_chat_members_invited) -voice_chat_members_invited = create(voice_chat_members_invited_filter) +video_chat_members_invited = create(video_chat_members_invited_filter) """Filter messages for voice chat invited members""" @@ -689,7 +691,7 @@ async def service_filter(_, __, m: Message): A service message contains any of the following fields set: *left_chat_member*, *new_chat_title*, *new_chat_photo*, *delete_chat_photo*, *group_chat_created*, *supergroup_chat_created*, *channel_chat_created*, *migrate_to_chat_id*, *migrate_from_chat_id*, *pinned_message*, *game_score*, -*voice_chat_started*, *voice_chat_ended*, *voice_chat_members_invited*. +*video_chat_started*, *video_chat_ended*, *video_chat_members_invited*. """ @@ -740,15 +742,11 @@ async def linked_channel_filter(_, __, m: Message): linked_channel = create(linked_channel_filter) """Filter messages that are automatically forwarded from the linked channel to the group chat.""" + # endregion # region command_filter - -# Used by the command filter below -username = None - - def command(commands: Union[str, List[str]], prefixes: Union[str, List[str]] = "/", case_sensitive: bool = False): """Filter commands, i.e.: text messages starting with "/" or any other custom prefix. @@ -771,12 +769,7 @@ def command(commands: Union[str, List[str]], prefixes: Union[str, List[str]] = " command_re = re.compile(r"([\"'])(.*?)(? +# Copyright (C) 2017-present Dan # # This file is part of Pyrogram. # @@ -17,10 +17,12 @@ # along with Pyrogram. If not, see . from .callback_query_handler import CallbackQueryHandler +from .chat_join_request_handler import ChatJoinRequestHandler from .chat_member_updated_handler import ChatMemberUpdatedHandler from .chosen_inline_result_handler import ChosenInlineResultHandler from .deleted_messages_handler import DeletedMessagesHandler from .disconnect_handler import DisconnectHandler +from .edited_message_handler import EditedMessageHandler from .inline_query_handler import InlineQueryHandler from .message_handler import MessageHandler from .poll_handler import PollHandler diff --git a/pyrogram/handlers/callback_query_handler.py b/pyrogram/handlers/callback_query_handler.py index e28e7216a6..b924fffa26 100644 --- a/pyrogram/handlers/callback_query_handler.py +++ b/pyrogram/handlers/callback_query_handler.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2021 Dan +# Copyright (C) 2017-present Dan # # This file is part of Pyrogram. # @@ -16,6 +16,8 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . +from typing import Callable + from .handler import Handler @@ -27,7 +29,7 @@ class CallbackQueryHandler(Handler): :meth:`~pyrogram.Client.on_callback_query` decorator. Parameters: - callback (``callable``): + callback (``Callable``): Pass a function that will be called when a new CallbackQuery arrives. It takes *(client, callback_query)* as positional arguments (look at the section below for a detailed description). @@ -43,5 +45,5 @@ class CallbackQueryHandler(Handler): The received callback query. """ - def __init__(self, callback: callable, filters=None): + def __init__(self, callback: Callable, filters=None): super().__init__(callback, filters) diff --git a/pyrogram/handlers/chat_join_request_handler.py b/pyrogram/handlers/chat_join_request_handler.py new file mode 100644 index 0000000000..54b8b86abb --- /dev/null +++ b/pyrogram/handlers/chat_join_request_handler.py @@ -0,0 +1,49 @@ +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-present Dan +# +# This file is part of Pyrogram. +# +# Pyrogram is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License as published +# by the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Pyrogram is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with Pyrogram. If not, see . + +from typing import Callable + +from .handler import Handler + + +class ChatJoinRequestHandler(Handler): + """The ChatJoinRequest handler class. Used to handle join chat requests. + It is intended to be used with :meth:`~pyrogram.Client.add_handler`. + + For a nicer way to register this handler, have a look at the + :meth:`~pyrogram.Client.on_chat_join_request` decorator. + + Parameters: + callback (``Callable``): + Pass a function that will be called when a new ChatJoinRequest event arrives. It takes + *(client, chat_join_request)* as positional arguments (look at the section below for a detailed + description). + + filters (:obj:`Filters`): + Pass one or more filters to allow only a subset of updates to be passed in your callback function. + + Other parameters: + client (:obj:`~pyrogram.Client`): + The Client itself, useful when you want to call other API methods inside the handler. + + chat_join_request (:obj:`~pyrogram.types.ChatJoinRequest`): + The received chat join request. + """ + + def __init__(self, callback: Callable, filters=None): + super().__init__(callback, filters) diff --git a/pyrogram/handlers/chat_member_updated_handler.py b/pyrogram/handlers/chat_member_updated_handler.py index d03eae11f3..a89e7e2b57 100644 --- a/pyrogram/handlers/chat_member_updated_handler.py +++ b/pyrogram/handlers/chat_member_updated_handler.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2021 Dan +# Copyright (C) 2017-present Dan # # This file is part of Pyrogram. # @@ -16,6 +16,8 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . +from typing import Callable + from .handler import Handler @@ -27,7 +29,7 @@ class ChatMemberUpdatedHandler(Handler): :meth:`~pyrogram.Client.on_chat_member_updated` decorator. Parameters: - callback (``callable``): + callback (``Callable``): Pass a function that will be called when a new ChatMemberUpdated event arrives. It takes *(client, chat_member_updated)* as positional arguments (look at the section below for a detailed description). @@ -43,5 +45,5 @@ class ChatMemberUpdatedHandler(Handler): The received chat member update. """ - def __init__(self, callback: callable, filters=None): + def __init__(self, callback: Callable, filters=None): super().__init__(callback, filters) diff --git a/pyrogram/handlers/chosen_inline_result_handler.py b/pyrogram/handlers/chosen_inline_result_handler.py index 42b5bb79eb..1c04f8f44b 100644 --- a/pyrogram/handlers/chosen_inline_result_handler.py +++ b/pyrogram/handlers/chosen_inline_result_handler.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2021 Dan +# Copyright (C) 2017-present Dan # # This file is part of Pyrogram. # @@ -16,6 +16,8 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . +from typing import Callable + from .handler import Handler @@ -27,7 +29,7 @@ class ChosenInlineResultHandler(Handler): :meth:`~pyrogram.Client.on_chosen_inline_result` decorator. Parameters: - callback (``callable``): + callback (``Callable``): Pass a function that will be called when a new chosen inline result arrives. It takes *(client, chosen_inline_result)* as positional arguments (look at the section below for a detailed description). @@ -44,5 +46,5 @@ class ChosenInlineResultHandler(Handler): The received chosen inline result. """ - def __init__(self, callback: callable, filters=None): + def __init__(self, callback: Callable, filters=None): super().__init__(callback, filters) diff --git a/pyrogram/handlers/deleted_messages_handler.py b/pyrogram/handlers/deleted_messages_handler.py index e41158f93b..ab9f834705 100644 --- a/pyrogram/handlers/deleted_messages_handler.py +++ b/pyrogram/handlers/deleted_messages_handler.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2021 Dan +# Copyright (C) 2017-present Dan # # This file is part of Pyrogram. # @@ -32,7 +32,7 @@ class DeletedMessagesHandler(Handler): :meth:`~pyrogram.Client.on_deleted_messages` decorator. Parameters: - callback (``callable``): + callback (``Callable``): Pass a function that will be called when one or more messages have been deleted. It takes *(client, messages)* as positional arguments (look at the section below for a detailed description). diff --git a/pyrogram/handlers/disconnect_handler.py b/pyrogram/handlers/disconnect_handler.py index 2302db5821..7420afd67a 100644 --- a/pyrogram/handlers/disconnect_handler.py +++ b/pyrogram/handlers/disconnect_handler.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2021 Dan +# Copyright (C) 2017-present Dan # # This file is part of Pyrogram. # @@ -16,6 +16,8 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . +from typing import Callable + from .handler import Handler @@ -27,7 +29,7 @@ class DisconnectHandler(Handler): :meth:`~pyrogram.Client.on_disconnect` decorator. Parameters: - callback (``callable``): + callback (``Callable``): Pass a function that will be called when a disconnection occurs. It takes *(client)* as positional argument (look at the section below for a detailed description). @@ -37,5 +39,5 @@ class DisconnectHandler(Handler): is established. """ - def __init__(self, callback: callable): + def __init__(self, callback: Callable): super().__init__(callback) diff --git a/pyrogram/handlers/edited_message_handler.py b/pyrogram/handlers/edited_message_handler.py new file mode 100644 index 0000000000..78deaf0fa9 --- /dev/null +++ b/pyrogram/handlers/edited_message_handler.py @@ -0,0 +1,49 @@ +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-present Dan +# +# This file is part of Pyrogram. +# +# Pyrogram is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License as published +# by the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Pyrogram is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with Pyrogram. If not, see . + +from typing import Callable + +from .handler import Handler + + +class EditedMessageHandler(Handler): + """The EditedMessage handler class. Used to handle edited messages. + It is intended to be used with :meth:`~pyrogram.Client.add_handler` + + For a nicer way to register this handler, have a look at the + :meth:`~pyrogram.Client.on_edited_message` decorator. + + Parameters: + callback (``Callable``): + Pass a function that will be called when a new edited message arrives. It takes *(client, message)* + as positional arguments (look at the section below for a detailed description). + + filters (:obj:`Filters`): + Pass one or more filters to allow only a subset of messages to be passed + in your callback function. + + Other parameters: + client (:obj:`~pyrogram.Client`): + The Client itself, useful when you want to call other API methods inside the message handler. + + edited_message (:obj:`~pyrogram.types.Message`): + The received edited message. + """ + + def __init__(self, callback: Callable, filters=None): + super().__init__(callback, filters) diff --git a/pyrogram/handlers/handler.py b/pyrogram/handlers/handler.py index 78786ab797..c666d042e2 100644 --- a/pyrogram/handlers/handler.py +++ b/pyrogram/handlers/handler.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2021 Dan +# Copyright (C) 2017-present Dan # # This file is part of Pyrogram. # diff --git a/pyrogram/handlers/inline_query_handler.py b/pyrogram/handlers/inline_query_handler.py index 31f085b35f..f5ea23bc57 100644 --- a/pyrogram/handlers/inline_query_handler.py +++ b/pyrogram/handlers/inline_query_handler.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2021 Dan +# Copyright (C) 2017-present Dan # # This file is part of Pyrogram. # @@ -16,6 +16,8 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . +from typing import Callable + from .handler import Handler @@ -27,7 +29,7 @@ class InlineQueryHandler(Handler): :meth:`~pyrogram.Client.on_inline_query` decorator. Parameters: - callback (``callable``): + callback (``Callable``): Pass a function that will be called when a new InlineQuery arrives. It takes *(client, inline_query)* as positional arguments (look at the section below for a detailed description). @@ -43,5 +45,5 @@ class InlineQueryHandler(Handler): The received inline query. """ - def __init__(self, callback: callable, filters=None): + def __init__(self, callback: Callable, filters=None): super().__init__(callback, filters) diff --git a/pyrogram/handlers/message_handler.py b/pyrogram/handlers/message_handler.py index f20669afc8..f5a35b5531 100644 --- a/pyrogram/handlers/message_handler.py +++ b/pyrogram/handlers/message_handler.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2021 Dan +# Copyright (C) 2017-present Dan # # This file is part of Pyrogram. # @@ -16,18 +16,20 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . +from typing import Callable + from .handler import Handler class MessageHandler(Handler): - """The Message handler class. Used to handle text, media and service messages coming from - any chat (private, group, channel). It is intended to be used with :meth:`~pyrogram.Client.add_handler` + """The Message handler class. Used to handle new messages. + It is intended to be used with :meth:`~pyrogram.Client.add_handler` For a nicer way to register this handler, have a look at the :meth:`~pyrogram.Client.on_message` decorator. Parameters: - callback (``callable``): + callback (``Callable``): Pass a function that will be called when a new Message arrives. It takes *(client, message)* as positional arguments (look at the section below for a detailed description). @@ -43,5 +45,5 @@ class MessageHandler(Handler): The received message. """ - def __init__(self, callback: callable, filters=None): + def __init__(self, callback: Callable, filters=None): super().__init__(callback, filters) diff --git a/pyrogram/handlers/poll_handler.py b/pyrogram/handlers/poll_handler.py index 09940c1252..332ca7ea28 100644 --- a/pyrogram/handlers/poll_handler.py +++ b/pyrogram/handlers/poll_handler.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2021 Dan +# Copyright (C) 2017-present Dan # # This file is part of Pyrogram. # @@ -16,6 +16,8 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . +from typing import Callable + from .handler import Handler @@ -28,7 +30,7 @@ class PollHandler(Handler): :meth:`~pyrogram.Client.on_poll` decorator. Parameters: - callback (``callable``): + callback (``Callable``): Pass a function that will be called when a new poll update arrives. It takes *(client, poll)* as positional arguments (look at the section below for a detailed description). @@ -44,5 +46,5 @@ class PollHandler(Handler): The received poll. """ - def __init__(self, callback: callable, filters=None): + def __init__(self, callback: Callable, filters=None): super().__init__(callback, filters) diff --git a/pyrogram/handlers/raw_update_handler.py b/pyrogram/handlers/raw_update_handler.py index 53413c3144..d957083b57 100644 --- a/pyrogram/handlers/raw_update_handler.py +++ b/pyrogram/handlers/raw_update_handler.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2021 Dan +# Copyright (C) 2017-present Dan # # This file is part of Pyrogram. # @@ -16,6 +16,8 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . +from typing import Callable + from .handler import Handler @@ -27,7 +29,7 @@ class RawUpdateHandler(Handler): :meth:`~pyrogram.Client.on_raw_update` decorator. Parameters: - callback (``callable``): + callback (``Callable``): A function that will be called when a new update is received from the server. It takes *(client, update, users, chats)* as positional arguments (look at the section below for a detailed description). @@ -61,5 +63,5 @@ class RawUpdateHandler(Handler): - :obj:`~pyrogram.raw.types.ChannelForbidden` """ - def __init__(self, callback: callable): + def __init__(self, callback: Callable): super().__init__(callback) diff --git a/pyrogram/handlers/user_status_handler.py b/pyrogram/handlers/user_status_handler.py index 23b9af2f97..f10871e874 100644 --- a/pyrogram/handlers/user_status_handler.py +++ b/pyrogram/handlers/user_status_handler.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2021 Dan +# Copyright (C) 2017-present Dan # # This file is part of Pyrogram. # @@ -16,6 +16,8 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . +from typing import Callable + from .handler import Handler @@ -26,7 +28,7 @@ class UserStatusHandler(Handler): For a nicer way to register this handler, have a look at the :meth:`~pyrogram.Client.on_user_status` decorator. Parameters: - callback (``callable``): + callback (``Callable``): Pass a function that will be called when a new user status update arrives. It takes *(client, user)* as positional arguments (look at the section below for a detailed description). @@ -41,5 +43,5 @@ class UserStatusHandler(Handler): The user containing the updated status. """ - def __init__(self, callback: callable, filters=None): + def __init__(self, callback: Callable, filters=None): super().__init__(callback, filters) diff --git a/pyrogram/methods/__init__.py b/pyrogram/methods/__init__.py index ee453ce3bb..ea71f6b178 100644 --- a/pyrogram/methods/__init__.py +++ b/pyrogram/methods/__init__.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2021 Dan +# Copyright (C) 2017-present Dan # # This file is part of Pyrogram. # diff --git a/pyrogram/methods/advanced/__init__.py b/pyrogram/methods/advanced/__init__.py index 934b0b9f21..bf19658a3c 100644 --- a/pyrogram/methods/advanced/__init__.py +++ b/pyrogram/methods/advanced/__init__.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2021 Dan +# Copyright (C) 2017-present Dan # # This file is part of Pyrogram. # @@ -16,14 +16,14 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . +from .invoke import Invoke from .resolve_peer import ResolvePeer from .save_file import SaveFile -from .send import Send class Advanced( + Invoke, ResolvePeer, - SaveFile, - Send + SaveFile ): pass diff --git a/pyrogram/methods/advanced/send.py b/pyrogram/methods/advanced/invoke.py similarity index 82% rename from pyrogram/methods/advanced/send.py rename to pyrogram/methods/advanced/invoke.py index 9ac7ec24d6..0af771adc8 100644 --- a/pyrogram/methods/advanced/send.py +++ b/pyrogram/methods/advanced/invoke.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2021 Dan +# Copyright (C) 2017-present Dan # # This file is part of Pyrogram. # @@ -18,23 +18,23 @@ import logging +import pyrogram from pyrogram import raw from pyrogram.raw.core import TLObject -from pyrogram.scaffold import Scaffold from pyrogram.session import Session log = logging.getLogger(__name__) -class Send(Scaffold): - async def send( - self, - data: TLObject, +class Invoke: + async def invoke( + self: "pyrogram.Client", + query: TLObject, retries: int = Session.MAX_RETRIES, timeout: float = Session.WAIT_TIMEOUT, sleep_threshold: float = None ): - """Send raw Telegram queries. + """Invoke raw Telegram functions. This method makes it possible to manually call every single Telegram API method in a low-level manner. Available functions are listed in the :obj:`functions ` package and may accept compound @@ -46,8 +46,10 @@ async def send( :obj:`functions ` (i.e: a Telegram API method you wish to use which is not available yet in the Client class as an easy-to-use method). + .. include:: /_includes/usable-by/users-bots.rst + Parameters: - data (``RawFunction``): + query (``RawFunction``): The API Schema function filled with proper arguments. retries (``int``): @@ -69,13 +71,13 @@ async def send( raise ConnectionError("Client has not been started yet") if self.no_updates: - data = raw.functions.InvokeWithoutUpdates(query=data) + query = raw.functions.InvokeWithoutUpdates(query=query) if self.takeout_id: - data = raw.functions.InvokeWithTakeout(takeout_id=self.takeout_id, query=data) + query = raw.functions.InvokeWithTakeout(takeout_id=self.takeout_id, query=query) - r = await self.session.send( - data, retries, timeout, + r = await self.session.invoke( + query, retries, timeout, (sleep_threshold if sleep_threshold is not None else self.sleep_threshold) diff --git a/pyrogram/methods/advanced/resolve_peer.py b/pyrogram/methods/advanced/resolve_peer.py index 3b3915aca6..ebb8ff4475 100644 --- a/pyrogram/methods/advanced/resolve_peer.py +++ b/pyrogram/methods/advanced/resolve_peer.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2021 Dan +# Copyright (C) 2017-present Dan # # This file is part of Pyrogram. # @@ -20,17 +20,17 @@ import re from typing import Union +import pyrogram from pyrogram import raw from pyrogram import utils from pyrogram.errors import PeerIdInvalid -from pyrogram.scaffold import Scaffold log = logging.getLogger(__name__) -class ResolvePeer(Scaffold): +class ResolvePeer: async def resolve_peer( - self, + self: "pyrogram.Client", peer_id: Union[int, str] ) -> Union[raw.base.InputPeer, raw.base.InputUser, raw.base.InputChannel]: """Get the InputPeer of a known peer id. @@ -42,6 +42,8 @@ async def resolve_peer( :obj:`functions ` (i.e: a Telegram API method you wish to use which is not available yet in the Client class as an easy-to-use method). + .. include:: /_includes/usable-by/users-bots.rst + Parameters: peer_id (``int`` | ``str``): The peer id you want to extract the InputPeer from. @@ -71,7 +73,7 @@ async def resolve_peer( try: return await self.storage.get_peer_by_username(peer_id) except KeyError: - await self.send( + await self.invoke( raw.functions.contacts.ResolveUsername( username=peer_id ) @@ -88,7 +90,7 @@ async def resolve_peer( if peer_type == "user": await self.fetch_peers( - await self.send( + await self.invoke( raw.functions.users.GetUsers( id=[ raw.types.InputUser( @@ -100,13 +102,13 @@ async def resolve_peer( ) ) elif peer_type == "chat": - await self.send( + await self.invoke( raw.functions.messages.GetChats( id=[-peer_id] ) ) else: - await self.send( + await self.invoke( raw.functions.channels.GetChannels( id=[ raw.types.InputChannel( diff --git a/pyrogram/methods/advanced/save_file.py b/pyrogram/methods/advanced/save_file.py index 8436156584..453a62af17 100644 --- a/pyrogram/methods/advanced/save_file.py +++ b/pyrogram/methods/advanced/save_file.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2021 Dan +# Copyright (C) 2017-present Dan # # This file is part of Pyrogram. # @@ -25,23 +25,23 @@ import os from hashlib import md5 from pathlib import PurePath -from typing import Union, BinaryIO +from typing import Union, BinaryIO, Callable +import pyrogram from pyrogram import StopTransmission from pyrogram import raw -from pyrogram.scaffold import Scaffold from pyrogram.session import Session log = logging.getLogger(__name__) -class SaveFile(Scaffold): +class SaveFile: async def save_file( - self, + self: "pyrogram.Client", path: Union[str, BinaryIO], file_id: int = None, file_part: int = 0, - progress: callable = None, + progress: Callable = None, progress_args: tuple = () ): """Upload a file onto Telegram servers, without actually sending the message to anyone. @@ -53,6 +53,8 @@ async def save_file( :obj:`functions ` (i.e: a Telegram API method you wish to use which is not available yet in the Client class as an easy-to-use method). + .. include:: /_includes/usable-by/users-bots.rst + Parameters: path (``str`` | ``BinaryIO``): The path of the file you want to upload that exists on your local machine or a binary file-like object @@ -64,7 +66,7 @@ async def save_file( file_part (``int``, *optional*): In case a file part expired, pass the file_id and the file_part to retry uploading that specific chunk. - progress (``callable``, *optional*): + progress (``Callable``, *optional*): Pass a callback function to view the file transmission progress. The function must take *(current, total)* as positional arguments (look at Other Parameters below for a detailed description) and will be called back each time a new file chunk has been successfully @@ -92,135 +94,133 @@ async def save_file( Raises: RPCError: In case of a Telegram RPC error. """ - if path is None: - return None - - async def worker(session): - while True: - data = await queue.get() - - if data is None: - return - - try: - await self.loop.create_task(session.send(data)) - except Exception as e: - log.error(e) - - part_size = 512 * 1024 - - if isinstance(path, (str, PurePath)): - fp = open(path, "rb") - elif isinstance(path, io.IOBase): - fp = path - else: - raise ValueError("Invalid file. Expected a file path as string or a binary (not text) file pointer") - - file_name = getattr(fp, "name", "file.jpg") - - fp.seek(0, os.SEEK_END) - file_size = fp.tell() - fp.seek(0) - - if file_size == 0: - raise ValueError("File size equals to 0 B") - - if file_size > 2000 * 1024 * 1024: - raise ValueError("Telegram doesn't support uploading files bigger than 2000 MiB") - - file_total_parts = int(math.ceil(file_size / part_size)) - is_big = file_size > 10 * 1024 * 1024 - pool_size = 3 if is_big else 1 - workers_count = 4 if is_big else 1 - is_missing_part = file_id is not None - file_id = file_id or self.rnd_id() - md5_sum = md5() if not is_big and not is_missing_part else None - pool = [ - Session( + async with self.save_file_semaphore: + if path is None: + return None + + async def worker(session): + while True: + data = await queue.get() + + if data is None: + return + + try: + await session.invoke(data) + except Exception as e: + log.exception(e) + + part_size = 512 * 1024 + + if isinstance(path, (str, PurePath)): + fp = open(path, "rb") + elif isinstance(path, io.IOBase): + fp = path + else: + raise ValueError("Invalid file. Expected a file path as string or a binary (not text) file pointer") + + file_name = getattr(fp, "name", "file.jpg") + + fp.seek(0, os.SEEK_END) + file_size = fp.tell() + fp.seek(0) + + if file_size == 0: + raise ValueError("File size equals to 0 B") + + file_size_limit_mib = 4000 if self.me.is_premium else 2000 + + if file_size > file_size_limit_mib * 1024 * 1024: + raise ValueError(f"Can't upload files bigger than {file_size_limit_mib} MiB") + + file_total_parts = int(math.ceil(file_size / part_size)) + is_big = file_size > 10 * 1024 * 1024 + workers_count = 4 if is_big else 1 + is_missing_part = file_id is not None + file_id = file_id or self.rnd_id() + md5_sum = md5() if not is_big and not is_missing_part else None + session = Session( self, await self.storage.dc_id(), await self.storage.auth_key(), await self.storage.test_mode(), is_media=True - ) for _ in range(pool_size) - ] - workers = [self.loop.create_task(worker(session)) for session in pool for _ in range(workers_count)] - queue = asyncio.Queue(16) + ) + workers = [self.loop.create_task(worker(session)) for _ in range(workers_count)] + queue = asyncio.Queue(1) - try: - for session in pool: + try: await session.start() - fp.seek(part_size * file_part) - - while True: - chunk = fp.read(part_size) + fp.seek(part_size * file_part) - if not chunk: - if not is_big and not is_missing_part: - md5_sum = "".join([hex(i)[2:].zfill(2) for i in md5_sum.digest()]) - break + while True: + chunk = fp.read(part_size) - if is_big: - rpc = raw.functions.upload.SaveBigFilePart( - file_id=file_id, - file_part=file_part, - file_total_parts=file_total_parts, - bytes=chunk - ) - else: - rpc = raw.functions.upload.SaveFilePart( - file_id=file_id, - file_part=file_part, - bytes=chunk - ) + if not chunk: + if not is_big and not is_missing_part: + md5_sum = "".join([hex(i)[2:].zfill(2) for i in md5_sum.digest()]) + break - await queue.put(rpc) + if is_big: + rpc = raw.functions.upload.SaveBigFilePart( + file_id=file_id, + file_part=file_part, + file_total_parts=file_total_parts, + bytes=chunk + ) + else: + rpc = raw.functions.upload.SaveFilePart( + file_id=file_id, + file_part=file_part, + bytes=chunk + ) - if is_missing_part: - return + await queue.put(rpc) - if not is_big and not is_missing_part: - md5_sum.update(chunk) + if is_missing_part: + return - file_part += 1 + if not is_big and not is_missing_part: + md5_sum.update(chunk) + + file_part += 1 + + if progress: + func = functools.partial( + progress, + min(file_part * part_size, file_size), + file_size, + *progress_args + ) + + if inspect.iscoroutinefunction(progress): + await func() + else: + await self.loop.run_in_executor(self.executor, func) + except StopTransmission: + raise + except Exception as e: + log.exception(e) + else: + if is_big: + return raw.types.InputFileBig( + id=file_id, + parts=file_total_parts, + name=file_name, - if progress: - func = functools.partial( - progress, - min(file_part * part_size, file_size), - file_size, - *progress_args ) + else: + return raw.types.InputFile( + id=file_id, + parts=file_total_parts, + name=file_name, + md5_checksum=md5_sum + ) + finally: + for _ in workers: + await queue.put(None) + + await asyncio.gather(*workers) - if inspect.iscoroutinefunction(progress): - await func() - else: - await self.loop.run_in_executor(self.executor, func) - except StopTransmission: - raise - except Exception as e: - log.error(e, exc_info=True) - else: - if is_big: - return raw.types.InputFileBig( - id=file_id, - parts=file_total_parts, - name=file_name, - - ) - else: - return raw.types.InputFile( - id=file_id, - parts=file_total_parts, - name=file_name, - md5_checksum=md5_sum - ) - finally: - for _ in workers: - await queue.put(None) - - await asyncio.gather(*workers) - - for session in pool: await session.stop() - if isinstance(path, (str, PurePath)): - fp.close() + if isinstance(path, (str, PurePath)): + fp.close() diff --git a/pyrogram/methods/auth/__init__.py b/pyrogram/methods/auth/__init__.py index 2227be8102..ce585648da 100644 --- a/pyrogram/methods/auth/__init__.py +++ b/pyrogram/methods/auth/__init__.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2021 Dan +# Copyright (C) 2017-present Dan # # This file is part of Pyrogram. # diff --git a/pyrogram/methods/auth/accept_terms_of_service.py b/pyrogram/methods/auth/accept_terms_of_service.py index b5abab8613..cc1fcf5453 100644 --- a/pyrogram/methods/auth/accept_terms_of_service.py +++ b/pyrogram/methods/auth/accept_terms_of_service.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2021 Dan +# Copyright (C) 2017-present Dan # # This file is part of Pyrogram. # @@ -16,19 +16,24 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . +import pyrogram from pyrogram import raw -from pyrogram.scaffold import Scaffold -class AcceptTermsOfService(Scaffold): - async def accept_terms_of_service(self, terms_of_service_id: str) -> bool: +class AcceptTermsOfService: + async def accept_terms_of_service( + self: "pyrogram.Client", + terms_of_service_id: str + ) -> bool: """Accept the given terms of service. + .. include:: /_includes/usable-by/users.rst + Parameters: terms_of_service_id (``str``): The terms of service identifier. """ - r = await self.send( + r = await self.invoke( raw.functions.help.AcceptTermsOfService( id=raw.types.DataJSON( data=terms_of_service_id @@ -36,6 +41,4 @@ async def accept_terms_of_service(self, terms_of_service_id: str) -> bool: ) ) - assert r - - return True + return bool(r) diff --git a/pyrogram/methods/auth/check_password.py b/pyrogram/methods/auth/check_password.py index 1dd60f7ebf..39aa82fcc0 100644 --- a/pyrogram/methods/auth/check_password.py +++ b/pyrogram/methods/auth/check_password.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2021 Dan +# Copyright (C) 2017-present Dan # # This file is part of Pyrogram. # @@ -18,18 +18,23 @@ import logging +import pyrogram from pyrogram import raw from pyrogram import types -from pyrogram.scaffold import Scaffold from pyrogram.utils import compute_password_check log = logging.getLogger(__name__) -class CheckPassword(Scaffold): - async def check_password(self, password: str) -> "types.User": +class CheckPassword: + async def check_password( + self: "pyrogram.Client", + password: str + ) -> "types.User": """Check your Two-Step Verification password and log in. + .. include:: /_includes/usable-by/users.rst + Parameters: password (``str``): Your Two-Step Verification password. @@ -40,10 +45,10 @@ async def check_password(self, password: str) -> "types.User": Raises: BadRequest: In case the password is invalid. """ - r = await self.send( + r = await self.invoke( raw.functions.auth.CheckPassword( password=compute_password_check( - await self.send(raw.functions.account.GetPassword()), + await self.invoke(raw.functions.account.GetPassword()), password ) ) diff --git a/pyrogram/methods/auth/connect.py b/pyrogram/methods/auth/connect.py index 7376ddf60e..612e064bb4 100644 --- a/pyrogram/methods/auth/connect.py +++ b/pyrogram/methods/auth/connect.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2021 Dan +# Copyright (C) 2017-present Dan # # This file is part of Pyrogram. # @@ -16,12 +16,14 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . -from pyrogram.scaffold import Scaffold +import pyrogram from pyrogram.session import Session -class Connect(Scaffold): - async def connect(self) -> bool: +class Connect: + async def connect( + self: "pyrogram.Client", + ) -> bool: """ Connect the client to Telegram servers. @@ -35,7 +37,6 @@ async def connect(self) -> bool: if self.is_connected: raise ConnectionError("Client is already connected") - self.load_config() await self.load_session() self.session = Session( diff --git a/pyrogram/methods/auth/disconnect.py b/pyrogram/methods/auth/disconnect.py index 4d516a119e..daa07b8353 100644 --- a/pyrogram/methods/auth/disconnect.py +++ b/pyrogram/methods/auth/disconnect.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2021 Dan +# Copyright (C) 2017-present Dan # # This file is part of Pyrogram. # @@ -16,11 +16,13 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . -from pyrogram.scaffold import Scaffold +import pyrogram -class Disconnect(Scaffold): - async def disconnect(self): +class Disconnect: + async def disconnect( + self: "pyrogram.Client", + ): """Disconnect the client from Telegram servers. Raises: diff --git a/pyrogram/methods/auth/get_password_hint.py b/pyrogram/methods/auth/get_password_hint.py index 44f1439c84..a57f7c8075 100644 --- a/pyrogram/methods/auth/get_password_hint.py +++ b/pyrogram/methods/auth/get_password_hint.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2021 Dan +# Copyright (C) 2017-present Dan # # This file is part of Pyrogram. # @@ -18,17 +18,21 @@ import logging +import pyrogram from pyrogram import raw -from pyrogram.scaffold import Scaffold log = logging.getLogger(__name__) -class GetPasswordHint(Scaffold): - async def get_password_hint(self) -> str: +class GetPasswordHint: + async def get_password_hint( + self: "pyrogram.Client", + ) -> str: """Get your Two-Step Verification password hint. + .. include:: /_includes/usable-by/users.rst + Returns: ``str``: On success, the password hint as string is returned. """ - return (await self.send(raw.functions.account.GetPassword())).hint + return (await self.invoke(raw.functions.account.GetPassword())).hint diff --git a/pyrogram/methods/auth/initialize.py b/pyrogram/methods/auth/initialize.py index 0fb276f7fd..7188b66817 100644 --- a/pyrogram/methods/auth/initialize.py +++ b/pyrogram/methods/auth/initialize.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2021 Dan +# Copyright (C) 2017-present Dan # # This file is part of Pyrogram. # @@ -16,16 +16,18 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . +import asyncio import logging -from pyrogram.scaffold import Scaffold -from pyrogram.syncer import Syncer +import pyrogram log = logging.getLogger(__name__) -class Initialize(Scaffold): - async def initialize(self): +class Initialize: + async def initialize( + self: "pyrogram.Client", + ): """Initialize the client by starting up workers. This method will start updates and download workers. @@ -44,6 +46,7 @@ async def initialize(self): self.load_plugins() await self.dispatcher.start() - await Syncer.add(self) + + self.updates_watchdog_task = asyncio.create_task(self.updates_watchdog()) self.is_initialized = True diff --git a/pyrogram/methods/auth/log_out.py b/pyrogram/methods/auth/log_out.py index 06c9824bbd..84b7db64cd 100644 --- a/pyrogram/methods/auth/log_out.py +++ b/pyrogram/methods/auth/log_out.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2021 Dan +# Copyright (C) 2017-present Dan # # This file is part of Pyrogram. # @@ -18,19 +18,23 @@ import logging +import pyrogram from pyrogram import raw -from pyrogram.scaffold import Scaffold log = logging.getLogger(__name__) -class LogOut(Scaffold): - async def log_out(self): +class LogOut: + async def log_out( + self: "pyrogram.Client", + ): """Log out from Telegram and delete the *\\*.session* file. When you log out, the current client is stopped and the storage session deleted. No more API calls can be made until you start the client and re-authorize again. + .. include:: /_includes/usable-by/users-bots.rst + Returns: ``bool``: On success, True is returned. @@ -40,7 +44,7 @@ async def log_out(self): # Log out. app.log_out() """ - await self.send(raw.functions.auth.LogOut()) + await self.invoke(raw.functions.auth.LogOut()) await self.stop() await self.storage.delete() diff --git a/pyrogram/methods/auth/recover_password.py b/pyrogram/methods/auth/recover_password.py index 03887f4ed8..0a34750752 100644 --- a/pyrogram/methods/auth/recover_password.py +++ b/pyrogram/methods/auth/recover_password.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2021 Dan +# Copyright (C) 2017-present Dan # # This file is part of Pyrogram. # @@ -18,17 +18,22 @@ import logging +import pyrogram from pyrogram import raw from pyrogram import types -from pyrogram.scaffold import Scaffold log = logging.getLogger(__name__) -class RecoverPassword(Scaffold): - async def recover_password(self, recovery_code: str) -> "types.User": +class RecoverPassword: + async def recover_password( + self: "pyrogram.Client", + recovery_code: str + ) -> "types.User": """Recover your password with a recovery code and log in. + .. include:: /_includes/usable-by/users.rst + Parameters: recovery_code (``str``): The recovery code sent via email. @@ -40,7 +45,7 @@ async def recover_password(self, recovery_code: str) -> "types.User": Raises: BadRequest: In case the recovery code is invalid. """ - r = await self.send( + r = await self.invoke( raw.functions.auth.RecoverPassword( code=recovery_code ) diff --git a/pyrogram/methods/auth/resend_code.py b/pyrogram/methods/auth/resend_code.py index cf8ce3be6d..9644ac4f54 100644 --- a/pyrogram/methods/auth/resend_code.py +++ b/pyrogram/methods/auth/resend_code.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2021 Dan +# Copyright (C) 2017-present Dan # # This file is part of Pyrogram. # @@ -18,20 +18,26 @@ import logging +import pyrogram from pyrogram import raw from pyrogram import types -from pyrogram.scaffold import Scaffold log = logging.getLogger(__name__) -class ResendCode(Scaffold): - async def resend_code(self, phone_number: str, phone_code_hash: str) -> "types.SentCode": +class ResendCode: + async def resend_code( + self: "pyrogram.Client", + phone_number: str, + phone_code_hash: str + ) -> "types.SentCode": """Re-send the confirmation code using a different type. The type of the code to be re-sent is specified in the *next_type* attribute of the :obj:`~pyrogram.types.SentCode` object returned by :meth:`send_code`. + .. include:: /_includes/usable-by/users.rst + Parameters: phone_number (``str``): Phone number in international format (includes the country prefix). @@ -48,7 +54,7 @@ async def resend_code(self, phone_number: str, phone_code_hash: str) -> "types.S """ phone_number = phone_number.strip(" +") - r = await self.send( + r = await self.invoke( raw.functions.auth.ResendCode( phone_number=phone_number, phone_code_hash=phone_code_hash diff --git a/pyrogram/methods/auth/send_code.py b/pyrogram/methods/auth/send_code.py index 62fb256bc6..92ffc99996 100644 --- a/pyrogram/methods/auth/send_code.py +++ b/pyrogram/methods/auth/send_code.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2021 Dan +# Copyright (C) 2017-present Dan # # This file is part of Pyrogram. # @@ -18,19 +18,24 @@ import logging +import pyrogram from pyrogram import raw from pyrogram import types from pyrogram.errors import PhoneMigrate, NetworkMigrate -from pyrogram.scaffold import Scaffold from pyrogram.session import Session, Auth log = logging.getLogger(__name__) -class SendCode(Scaffold): - async def send_code(self, phone_number: str) -> "types.SentCode": +class SendCode: + async def send_code( + self: "pyrogram.Client", + phone_number: str + ) -> "types.SentCode": """Send the confirmation code to the given phone number. + .. include:: /_includes/usable-by/users.rst + Parameters: phone_number (``str``): Phone number in international format (includes the country prefix). @@ -46,7 +51,7 @@ async def send_code(self, phone_number: str) -> "types.SentCode": while True: try: - r = await self.send( + r = await self.invoke( raw.functions.auth.SendCode( phone_number=phone_number, api_id=self.api_id, @@ -57,7 +62,7 @@ async def send_code(self, phone_number: str) -> "types.SentCode": except (PhoneMigrate, NetworkMigrate) as e: await self.session.stop() - await self.storage.dc_id(e.x) + await self.storage.dc_id(e.value) await self.storage.auth_key( await Auth( self, await self.storage.dc_id(), diff --git a/pyrogram/methods/auth/send_recovery_code.py b/pyrogram/methods/auth/send_recovery_code.py index 4c28f1cf0b..9a25f3c68f 100644 --- a/pyrogram/methods/auth/send_recovery_code.py +++ b/pyrogram/methods/auth/send_recovery_code.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2021 Dan +# Copyright (C) 2017-present Dan # # This file is part of Pyrogram. # @@ -18,22 +18,26 @@ import logging +import pyrogram from pyrogram import raw -from pyrogram.scaffold import Scaffold log = logging.getLogger(__name__) -class SendRecoveryCode(Scaffold): - async def send_recovery_code(self) -> str: +class SendRecoveryCode: + async def send_recovery_code( + self: "pyrogram.Client", + ) -> str: """Send a code to your email to recover your password. + .. include:: /_includes/usable-by/users.rst + Returns: ``str``: On success, the hidden email pattern is returned and a recovery code is sent to that email. Raises: BadRequest: In case no recovery email was set up. """ - return (await self.send( + return (await self.invoke( raw.functions.auth.RequestPasswordRecovery() )).email_pattern diff --git a/pyrogram/methods/auth/sign_in.py b/pyrogram/methods/auth/sign_in.py index 6e5e7c46fe..9d77f1cd37 100644 --- a/pyrogram/methods/auth/sign_in.py +++ b/pyrogram/methods/auth/sign_in.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2021 Dan +# Copyright (C) 2017-present Dan # # This file is part of Pyrogram. # @@ -19,22 +19,24 @@ import logging from typing import Union +import pyrogram from pyrogram import raw from pyrogram import types -from pyrogram.scaffold import Scaffold log = logging.getLogger(__name__) -class SignIn(Scaffold): +class SignIn: async def sign_in( - self, + self: "pyrogram.Client", phone_number: str, phone_code_hash: str, phone_code: str ) -> Union["types.User", "types.TermsOfService", bool]: """Authorize a user in Telegram with a valid confirmation code. + .. include:: /_includes/usable-by/users.rst + Parameters: phone_number (``str``): Phone number in international format (includes the country prefix). @@ -58,7 +60,7 @@ async def sign_in( """ phone_number = phone_number.strip(" +") - r = await self.send( + r = await self.invoke( raw.functions.auth.SignIn( phone_number=phone_number, phone_code_hash=phone_code_hash, diff --git a/pyrogram/methods/auth/sign_in_bot.py b/pyrogram/methods/auth/sign_in_bot.py index 490ceced27..09a2f28379 100644 --- a/pyrogram/methods/auth/sign_in_bot.py +++ b/pyrogram/methods/auth/sign_in_bot.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2021 Dan +# Copyright (C) 2017-present Dan # # This file is part of Pyrogram. # @@ -18,19 +18,24 @@ import logging +import pyrogram from pyrogram import raw from pyrogram import types from pyrogram.errors import UserMigrate -from pyrogram.scaffold import Scaffold from pyrogram.session import Session, Auth log = logging.getLogger(__name__) -class SignInBot(Scaffold): - async def sign_in_bot(self, bot_token: str) -> "types.User": +class SignInBot: + async def sign_in_bot( + self: "pyrogram.Client", + bot_token: str + ) -> "types.User": """Authorize a bot using its bot token generated by BotFather. + .. include:: /_includes/usable-by/bots.rst + Parameters: bot_token (``str``): The bot token generated by BotFather @@ -43,7 +48,7 @@ async def sign_in_bot(self, bot_token: str) -> "types.User": """ while True: try: - r = await self.send( + r = await self.invoke( raw.functions.auth.ImportBotAuthorization( flags=0, api_id=self.api_id, @@ -54,7 +59,7 @@ async def sign_in_bot(self, bot_token: str) -> "types.User": except UserMigrate as e: await self.session.stop() - await self.storage.dc_id(e.x) + await self.storage.dc_id(e.value) await self.storage.auth_key( await Auth( self, await self.storage.dc_id(), diff --git a/pyrogram/methods/auth/sign_up.py b/pyrogram/methods/auth/sign_up.py index bed07f8509..f09a779d8a 100644 --- a/pyrogram/methods/auth/sign_up.py +++ b/pyrogram/methods/auth/sign_up.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2021 Dan +# Copyright (C) 2017-present Dan # # This file is part of Pyrogram. # @@ -18,16 +18,16 @@ import logging +import pyrogram from pyrogram import raw from pyrogram import types -from pyrogram.scaffold import Scaffold log = logging.getLogger(__name__) -class SignUp(Scaffold): +class SignUp: async def sign_up( - self, + self: "pyrogram.Client", phone_number: str, phone_code_hash: str, first_name: str, @@ -35,6 +35,8 @@ async def sign_up( ) -> "types.User": """Register a new user in Telegram. + .. include:: /_includes/usable-by/users.rst + Parameters: phone_number (``str``): Phone number in international format (includes the country prefix). @@ -56,7 +58,7 @@ async def sign_up( """ phone_number = phone_number.strip(" +") - r = await self.send( + r = await self.invoke( raw.functions.auth.SignUp( phone_number=phone_number, first_name=first_name, diff --git a/pyrogram/methods/auth/terminate.py b/pyrogram/methods/auth/terminate.py index 70166fe428..d5fd949cba 100644 --- a/pyrogram/methods/auth/terminate.py +++ b/pyrogram/methods/auth/terminate.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2021 Dan +# Copyright (C) 2017-present Dan # # This file is part of Pyrogram. # @@ -18,15 +18,16 @@ import logging +import pyrogram from pyrogram import raw -from pyrogram.scaffold import Scaffold -from pyrogram.syncer import Syncer log = logging.getLogger(__name__) -class Terminate(Scaffold): - async def terminate(self): +class Terminate: + async def terminate( + self: "pyrogram.Client", + ): """Terminate the client by shutting down workers. This method does the opposite of :meth:`~pyrogram.Client.initialize`. @@ -39,10 +40,10 @@ async def terminate(self): raise ConnectionError("Client is already terminated") if self.takeout_id: - await self.send(raw.functions.account.FinishTakeoutSession()) - log.warning(f"Takeout session {self.takeout_id} finished") + await self.invoke(raw.functions.account.FinishTakeoutSession()) + log.info("Takeout session %s finished", self.takeout_id) - await Syncer.remove(self) + await self.storage.save() await self.dispatcher.stop() for media_session in self.media_sessions.values(): @@ -50,4 +51,11 @@ async def terminate(self): self.media_sessions.clear() + self.updates_watchdog_event.set() + + if self.updates_watchdog_task is not None: + await self.updates_watchdog_task + + self.updates_watchdog_event.clear() + self.is_initialized = False diff --git a/pyrogram/methods/bots/__init__.py b/pyrogram/methods/bots/__init__.py index 8cc7dd3066..da52fcfb00 100644 --- a/pyrogram/methods/bots/__init__.py +++ b/pyrogram/methods/bots/__init__.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2021 Dan +# Copyright (C) 2017-present Dan # # This file is part of Pyrogram. # @@ -18,13 +18,20 @@ from .answer_callback_query import AnswerCallbackQuery from .answer_inline_query import AnswerInlineQuery +from .answer_web_app_query import AnswerWebAppQuery +from .delete_bot_commands import DeleteBotCommands +from .get_bot_commands import GetBotCommands +from .get_bot_default_privileges import GetBotDefaultPrivileges +from .get_chat_menu_button import GetChatMenuButton from .get_game_high_scores import GetGameHighScores from .get_inline_bot_results import GetInlineBotResults from .request_callback_answer import RequestCallbackAnswer from .send_game import SendGame from .send_inline_bot_result import SendInlineBotResult -from .set_game_score import SetGameScore from .set_bot_commands import SetBotCommands +from .set_bot_default_privileges import SetBotDefaultPrivileges +from .set_chat_menu_button import SetChatMenuButton +from .set_game_score import SetGameScore class Bots( @@ -36,6 +43,13 @@ class Bots( SendGame, SetGameScore, GetGameHighScores, - SetBotCommands + SetBotCommands, + GetBotCommands, + DeleteBotCommands, + SetBotDefaultPrivileges, + GetBotDefaultPrivileges, + SetChatMenuButton, + GetChatMenuButton, + AnswerWebAppQuery ): pass diff --git a/pyrogram/methods/bots/answer_callback_query.py b/pyrogram/methods/bots/answer_callback_query.py index ccb1f4449a..a6d8747cd5 100644 --- a/pyrogram/methods/bots/answer_callback_query.py +++ b/pyrogram/methods/bots/answer_callback_query.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2021 Dan +# Copyright (C) 2017-present Dan # # This file is part of Pyrogram. # @@ -16,13 +16,13 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . +import pyrogram from pyrogram import raw -from pyrogram.scaffold import Scaffold -class AnswerCallbackQuery(Scaffold): +class AnswerCallbackQuery: async def answer_callback_query( - self, + self: "pyrogram.Client", callback_query_id: str, text: str = None, show_alert: bool = None, @@ -32,6 +32,8 @@ async def answer_callback_query( """Send answers to callback queries sent from inline keyboards. The answer will be displayed to the user as a notification at the top of the chat screen or as an alert. + .. include:: /_includes/usable-by/bots.rst + Parameters: callback_query_id (``str``): Unique identifier for the query to be answered. @@ -60,15 +62,15 @@ async def answer_callback_query( .. code-block:: python # Answer only (remove the spinning circles) - app.answer_callback_query(query_id) + await app.answer_callback_query(query_id) # Answer without alert - app.answer_callback_query(query_id, text=text) + await app.answer_callback_query(query_id, text=text) # Answer with alert - app.answer_callback_query(query_id, text=text, show_alert=True) + await app.answer_callback_query(query_id, text=text, show_alert=True) """ - return await self.send( + return await self.invoke( raw.functions.messages.SetBotCallbackAnswer( query_id=int(callback_query_id), cache_time=cache_time, diff --git a/pyrogram/methods/bots/answer_inline_query.py b/pyrogram/methods/bots/answer_inline_query.py index 66bd739ffb..c3a450a015 100644 --- a/pyrogram/methods/bots/answer_inline_query.py +++ b/pyrogram/methods/bots/answer_inline_query.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2021 Dan +# Copyright (C) 2017-present Dan # # This file is part of Pyrogram. # @@ -16,18 +16,18 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . -from typing import List +from typing import Iterable +import pyrogram from pyrogram import raw from pyrogram import types -from pyrogram.scaffold import Scaffold -class AnswerInlineQuery(Scaffold): +class AnswerInlineQuery: async def answer_inline_query( - self, + self: "pyrogram.Client", inline_query_id: str, - results: List["types.InlineQueryResult"], + results: Iterable["types.InlineQueryResult"], cache_time: int = 300, is_gallery: bool = False, is_personal: bool = False, @@ -39,6 +39,8 @@ async def answer_inline_query( A maximum of 50 results per query is allowed. + .. include:: /_includes/usable-by/bots.rst + Parameters: inline_query_id (``str``): Unique identifier for the answered query. @@ -86,7 +88,7 @@ async def answer_inline_query( from pyrogram.types import InlineQueryResultArticle, InputTextMessageContent - app.answer_inline_query( + await app.answer_inline_query( inline_query_id, results=[ InlineQueryResultArticle( @@ -94,7 +96,7 @@ async def answer_inline_query( InputTextMessageContent("Message content"))]) """ - return await self.send( + return await self.invoke( raw.functions.messages.SetInlineBotResults( query_id=int(inline_query_id), results=[await r.write(self) for r in results], diff --git a/pyrogram/methods/bots/answer_web_app_query.py b/pyrogram/methods/bots/answer_web_app_query.py new file mode 100644 index 0000000000..74f56079bb --- /dev/null +++ b/pyrogram/methods/bots/answer_web_app_query.py @@ -0,0 +1,53 @@ +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-present Dan +# +# This file is part of Pyrogram. +# +# Pyrogram is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License as published +# by the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Pyrogram is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with Pyrogram. If not, see . + +import pyrogram +from pyrogram import raw +from pyrogram import types + + +class AnswerWebAppQuery: + async def answer_web_app_query( + self: "pyrogram.Client", + web_app_query_id: str, + result: "types.InlineQueryResult" + ) -> "types.SentWebAppMessage": + """Set the result of an interaction with a `Web App `_ and send a + corresponding message on behalf of the user to the chat from which the query originated. + + .. include:: /_includes/usable-by/bots.rst + + Parameters: + web_app_query_id (``str``): + Unique identifier for the answered query. + + result (:obj:`~pyrogram.types.InlineQueryResult`): + A list of results for the inline query. + + Returns: + :obj:`~pyrogram.types.SentWebAppMessage`: On success the sent web app message is returned. + """ + + r = await self.invoke( + raw.functions.messages.SendWebViewResultMessage( + bot_query_id=web_app_query_id, + result=await result.write(self) + ) + ) + + return types.SentWebAppMessage._parse(r) diff --git a/pyrogram/methods/bots/delete_bot_commands.py b/pyrogram/methods/bots/delete_bot_commands.py new file mode 100644 index 0000000000..e8173d32d9 --- /dev/null +++ b/pyrogram/methods/bots/delete_bot_commands.py @@ -0,0 +1,62 @@ +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-present Dan +# +# This file is part of Pyrogram. +# +# Pyrogram is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License as published +# by the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Pyrogram is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with Pyrogram. If not, see . + +import pyrogram +from pyrogram import raw, types + + +class DeleteBotCommands: + async def delete_bot_commands( + self: "pyrogram.Client", + scope: "types.BotCommandScope" = types.BotCommandScopeDefault(), + language_code: str = "", + ) -> bool: + """Delete the list of the bot's commands for the given scope and user language. + After deletion, higher level commands will be shown to affected users. + + The commands passed will overwrite any command set previously. + This method can be used by the own bot only. + + .. include:: /_includes/usable-by/bots.rst + + Parameters: + scope (:obj:`~pyrogram.types.BotCommandScope`, *optional*): + An object describing the scope of users for which the commands are relevant. + Defaults to :obj:`~pyrogram.types.BotCommandScopeDefault`. + + language_code (``str``, *optional*): + A two-letter ISO 639-1 language code. + If empty, commands will be applied to all users from the given scope, for whose language there are no + dedicated commands. + + Returns: + ``bool``: On success, True is returned. + + Example: + .. code-block:: python + + # Delete commands + await app.delete_bot_commands() + """ + + return await self.invoke( + raw.functions.bots.ResetBotCommands( + scope=await scope.write(self), + lang_code=language_code, + ) + ) diff --git a/pyrogram/methods/bots/get_bot_commands.py b/pyrogram/methods/bots/get_bot_commands.py new file mode 100644 index 0000000000..78deff30fd --- /dev/null +++ b/pyrogram/methods/bots/get_bot_commands.py @@ -0,0 +1,67 @@ +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-present Dan +# +# This file is part of Pyrogram. +# +# Pyrogram is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License as published +# by the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Pyrogram is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with Pyrogram. If not, see . + +from typing import List + +import pyrogram +from pyrogram import raw, types + + +class GetBotCommands: + async def get_bot_commands( + self: "pyrogram.Client", + scope: "types.BotCommandScope" = types.BotCommandScopeDefault(), + language_code: str = "", + ) -> List["types.BotCommand"]: + """Get the current list of the bot's commands for the given scope and user language. + Returns Array of BotCommand on success. If commands aren't set, an empty list is returned. + + The commands passed will overwrite any command set previously. + This method can be used by the own bot only. + + .. include:: /_includes/usable-by/bots.rst + + Parameters: + scope (:obj:`~pyrogram.types.BotCommandScope`, *optional*): + An object describing the scope of users for which the commands are relevant. + Defaults to :obj:`~pyrogram.types.BotCommandScopeDefault`. + + language_code (``str``, *optional*): + A two-letter ISO 639-1 language code. + If empty, commands will be applied to all users from the given scope, for whose language there are no + dedicated commands. + + Returns: + List of :obj:`~pyrogram.types.BotCommand`: On success, the list of bot commands is returned. + + Example: + .. code-block:: python + + # Get commands + commands = await app.get_bot_commands() + print(commands) + """ + + r = await self.invoke( + raw.functions.bots.GetBotCommands( + scope=await scope.write(self), + lang_code=language_code, + ) + ) + + return types.List(types.BotCommand.read(c) for c in r) diff --git a/pyrogram/methods/bots/get_bot_default_privileges.py b/pyrogram/methods/bots/get_bot_default_privileges.py new file mode 100644 index 0000000000..217d9b4384 --- /dev/null +++ b/pyrogram/methods/bots/get_bot_default_privileges.py @@ -0,0 +1,59 @@ +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-present Dan +# +# This file is part of Pyrogram. +# +# Pyrogram is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License as published +# by the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Pyrogram is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with Pyrogram. If not, see . + +from typing import Optional + +import pyrogram +from pyrogram import raw +from pyrogram import types + + +class GetBotDefaultPrivileges: + async def get_bot_default_privileges( + self: "pyrogram.Client", + for_channels: bool = None + ) -> Optional["types.ChatPrivileges"]: + """Get the current default privileges of the bot. + + .. include:: /_includes/usable-by/bots.rst + + Parameters: + for_channels (``bool``, *optional*): + Pass True to get default privileges of the bot in channels. Otherwise, default privileges of the bot + for groups and supergroups will be returned. + + Returns: + ``bool``: On success, True is returned. + + Example: + .. code-block:: python + + privileges = await app.get_bot_default_privileges() + """ + + bot_info = await self.invoke( + raw.functions.users.GetFullUser( + id=raw.types.InputUserSelf() + ) + ) + + field = "bot_broadcast_admin_rights" if for_channels else "bot_group_admin_rights" + + admin_rights = getattr(bot_info.full_user, field) + + return types.ChatPrivileges._parse(admin_rights) if admin_rights else None diff --git a/pyrogram/methods/bots/get_chat_menu_button.py b/pyrogram/methods/bots/get_chat_menu_button.py new file mode 100644 index 0000000000..9d143d5819 --- /dev/null +++ b/pyrogram/methods/bots/get_chat_menu_button.py @@ -0,0 +1,66 @@ +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-present Dan +# +# This file is part of Pyrogram. +# +# Pyrogram is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License as published +# by the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Pyrogram is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with Pyrogram. If not, see . + +from typing import Union + +import pyrogram +from pyrogram import raw +from pyrogram import types + + +class GetChatMenuButton: + async def get_chat_menu_button( + self: "pyrogram.Client", + chat_id: Union[int, str] = None, + ) -> "types.MenuButton": + """Get the current value of the bot's menu button in a private chat, or the default menu button. + + .. include:: /_includes/usable-by/bots.rst + + Parameters: + chat_id (``int`` | ``str``): + Unique identifier (int) or username (str) of the target chat. + If not specified, default bot's menu button will be returned. + """ + + if chat_id: + r = await self.invoke( + raw.functions.bots.GetBotMenuButton( + user_id=await self.resolve_peer(chat_id), + ) + ) + else: + r = (await self.invoke( + raw.functions.users.GetFullUser( + id=raw.types.InputUserSelf() + ) + )).full_user.bot_info.menu_button + + if isinstance(r, raw.types.BotMenuButtonCommands): + return types.MenuButtonCommands() + + if isinstance(r, raw.types.BotMenuButtonDefault): + return types.MenuButtonDefault() + + if isinstance(r, raw.types.BotMenuButton): + return types.MenuButtonWebApp( + text=r.text, + web_app=types.WebAppInfo( + url=r.url + ) + ) diff --git a/pyrogram/methods/bots/get_game_high_scores.py b/pyrogram/methods/bots/get_game_high_scores.py index 59ddb67ec5..068ecab889 100644 --- a/pyrogram/methods/bots/get_game_high_scores.py +++ b/pyrogram/methods/bots/get_game_high_scores.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2021 Dan +# Copyright (C) 2017-present Dan # # This file is part of Pyrogram. # @@ -18,20 +18,22 @@ from typing import Union, List +import pyrogram from pyrogram import raw from pyrogram import types -from pyrogram.scaffold import Scaffold -class GetGameHighScores(Scaffold): +class GetGameHighScores: async def get_game_high_scores( - self, + self: "pyrogram.Client", user_id: Union[int, str], chat_id: Union[int, str], message_id: int = None ) -> List["types.GameHighScore"]: """Get data for high score tables. + .. include:: /_includes/usable-by/bots.rst + Parameters: user_id (``int`` | ``str``): Unique identifier (int) or username (str) of the target chat. @@ -54,12 +56,12 @@ async def get_game_high_scores( Example: .. code-block:: python - scores = app.get_game_high_scores(user_id, chat_id, message_id) + scores = await app.get_game_high_scores(user_id, chat_id, message_id) print(scores) """ # TODO: inline_message_id - r = await self.send( + r = await self.invoke( raw.functions.messages.GetGameHighScores( peer=await self.resolve_peer(chat_id), id=message_id, diff --git a/pyrogram/methods/bots/get_inline_bot_results.py b/pyrogram/methods/bots/get_inline_bot_results.py index 73b4c5d786..7cb0aa127d 100644 --- a/pyrogram/methods/bots/get_inline_bot_results.py +++ b/pyrogram/methods/bots/get_inline_bot_results.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2021 Dan +# Copyright (C) 2017-present Dan # # This file is part of Pyrogram. # @@ -18,14 +18,14 @@ from typing import Union +import pyrogram from pyrogram import raw from pyrogram.errors import UnknownError -from pyrogram.scaffold import Scaffold -class GetInlineBotResults(Scaffold): +class GetInlineBotResults: async def get_inline_bot_results( - self, + self: "pyrogram.Client", bot: Union[int, str], query: str = "", offset: str = "", @@ -35,6 +35,8 @@ async def get_inline_bot_results( """Get bot results via inline queries. You can then send a result using :meth:`~pyrogram.Client.send_inline_bot_result` + .. include:: /_includes/usable-by/users.rst + Parameters: bot (``int`` | ``str``): Unique identifier of the inline bot you want to get results from. You can specify @@ -64,13 +66,13 @@ async def get_inline_bot_results( Example: .. code-block:: python - results = app.get_inline_bot_results("pyrogrambot") + results = await app.get_inline_bot_results("pyrogrambot") print(results) """ # TODO: Don't return the raw type try: - return await self.send( + return await self.invoke( raw.functions.messages.GetInlineBotResults( bot=await self.resolve_peer(bot), peer=raw.types.InputPeerSelf(), @@ -84,7 +86,7 @@ async def get_inline_bot_results( ) except UnknownError as e: # TODO: Add this -503 Timeout error into the Error DB - if e.x.error_code == -503 and e.x.error_message == "Timeout": + if e.value.error_code == -503 and e.value.error_message == "Timeout": raise TimeoutError("The inline bot didn't answer in time") from None else: raise e diff --git a/pyrogram/methods/bots/request_callback_answer.py b/pyrogram/methods/bots/request_callback_answer.py index 9d32e60f07..22debcbe21 100644 --- a/pyrogram/methods/bots/request_callback_answer.py +++ b/pyrogram/methods/bots/request_callback_answer.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2021 Dan +# Copyright (C) 2017-present Dan # # This file is part of Pyrogram. # @@ -18,13 +18,13 @@ from typing import Union +import pyrogram from pyrogram import raw -from pyrogram.scaffold import Scaffold -class RequestCallbackAnswer(Scaffold): +class RequestCallbackAnswer: async def request_callback_answer( - self, + self: "pyrogram.Client", chat_id: Union[int, str], message_id: int, callback_data: Union[str, bytes], @@ -33,6 +33,8 @@ async def request_callback_answer( """Request a callback answer from bots. This is the equivalent of clicking an inline button containing callback data. + .. include:: /_includes/usable-by/users.rst + Parameters: chat_id (``int`` | ``str``): Unique identifier (int) or username (str) of the target chat. @@ -58,13 +60,13 @@ async def request_callback_answer( Example: .. code-block:: python - app.request_callback_answer(chat_id, message_id, "callback_data") + await app.request_callback_answer(chat_id, message_id, "callback_data") """ # Telegram only wants bytes, but we are allowed to pass strings too. data = bytes(callback_data, "utf-8") if isinstance(callback_data, str) else callback_data - return await self.send( + return await self.invoke( raw.functions.messages.GetBotCallbackAnswer( peer=await self.resolve_peer(chat_id), msg_id=message_id, diff --git a/pyrogram/methods/bots/send_game.py b/pyrogram/methods/bots/send_game.py index 8f6d10ab82..60155a6fe6 100644 --- a/pyrogram/methods/bots/send_game.py +++ b/pyrogram/methods/bots/send_game.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2021 Dan +# Copyright (C) 2017-present Dan # # This file is part of Pyrogram. # @@ -18,18 +18,19 @@ from typing import Union +import pyrogram from pyrogram import raw from pyrogram import types -from pyrogram.scaffold import Scaffold -class SendGame(Scaffold): +class SendGame: async def send_game( - self, + self: "pyrogram.Client", chat_id: Union[int, str], game_short_name: str, disable_notification: bool = None, reply_to_message_id: int = None, + protect_content: bool = None, reply_markup: Union[ "types.InlineKeyboardMarkup", "types.ReplyKeyboardMarkup", @@ -39,6 +40,8 @@ async def send_game( ) -> "types.Message": """Send a game. + .. include:: /_includes/usable-by/bots.rst + Parameters: chat_id (``int`` | ``str``): Unique identifier (int) or username (str) of the target chat. @@ -55,6 +58,9 @@ async def send_game( reply_to_message_id (``int``, *optional*): If the message is a reply, ID of the original message. + protect_content (``bool``, *optional*): + Protects the contents of the sent message from forwarding and saving. + reply_markup (:obj:`~pyrogram.types.InlineKeyboardMarkup`, *optional*): An object for an inline keyboard. If empty, one ‘Play game_title’ button will be shown automatically. If not empty, the first button must launch the game. @@ -65,9 +71,9 @@ async def send_game( Example: .. code-block:: python - app.send_game(chat_id, "gamename") + await app.send_game(chat_id, "gamename") """ - r = await self.send( + r = await self.invoke( raw.functions.messages.SendMedia( peer=await self.resolve_peer(chat_id), media=raw.types.InputMediaGame( @@ -80,6 +86,7 @@ async def send_game( silent=disable_notification or None, reply_to_msg_id=reply_to_message_id, random_id=self.rnd_id(), + noforwards=protect_content, reply_markup=await reply_markup.write(self) if reply_markup else None ) ) diff --git a/pyrogram/methods/bots/send_inline_bot_result.py b/pyrogram/methods/bots/send_inline_bot_result.py index e91aac9789..f29d8eb9cd 100644 --- a/pyrogram/methods/bots/send_inline_bot_result.py +++ b/pyrogram/methods/bots/send_inline_bot_result.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2021 Dan +# Copyright (C) 2017-present Dan # # This file is part of Pyrogram. # @@ -18,23 +18,24 @@ from typing import Union +import pyrogram from pyrogram import raw -from pyrogram.scaffold import Scaffold -class SendInlineBotResult(Scaffold): +class SendInlineBotResult: async def send_inline_bot_result( - self, + self: "pyrogram.Client", chat_id: Union[int, str], query_id: int, result_id: str, disable_notification: bool = None, - reply_to_message_id: int = None, - hide_via: bool = None - ): + reply_to_message_id: int = None + ) -> "raw.base.Updates": """Send an inline bot result. Bot results can be retrieved using :meth:`~pyrogram.Client.get_inline_bot_results` + .. include:: /_includes/usable-by/users.rst + Parameters: chat_id (``int`` | ``str``): Unique identifier (int) or username (str) of the target chat. @@ -54,25 +55,21 @@ async def send_inline_bot_result( reply_to_message_id (``bool``, *optional*): If the message is a reply, ID of the original message. - hide_via (``bool``): - Sends the message with *via @bot* hidden. - Returns: - :obj:`~pyrogram.types.Message`: On success, the sent inline result message is returned. + :obj:`~pyrogram.raw.base.Updates`: Currently, on success, a raw result is returned. Example: .. code-block:: python - app.send_inline_bot_result(chat_id, query_id, result_id) + await app.send_inline_bot_result(chat_id, query_id, result_id) """ - return await self.send( + return await self.invoke( raw.functions.messages.SendInlineBotResult( peer=await self.resolve_peer(chat_id), query_id=query_id, id=result_id, random_id=self.rnd_id(), silent=disable_notification or None, - reply_to_msg_id=reply_to_message_id, - hide_via=hide_via or None + reply_to_msg_id=reply_to_message_id ) ) diff --git a/pyrogram/methods/bots/set_bot_commands.py b/pyrogram/methods/bots/set_bot_commands.py index 3a3c8f54a8..f6f7e6c992 100644 --- a/pyrogram/methods/bots/set_bot_commands.py +++ b/pyrogram/methods/bots/set_bot_commands.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2021 Dan +# Copyright (C) 2017-present Dan # # This file is part of Pyrogram. # @@ -16,44 +16,58 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . -from typing import Optional, List +from typing import List +import pyrogram from pyrogram import raw from pyrogram import types -from pyrogram.scaffold import Scaffold -class SetBotCommands(Scaffold): - async def set_bot_commands(self, commands: Optional[List[types.BotCommand]]): - """Set the bot commands list. - +class SetBotCommands: + async def set_bot_commands( + self: "pyrogram.Client", + commands: List["types.BotCommand"], + scope: "types.BotCommandScope" = types.BotCommandScopeDefault(), + language_code: str = "", + ) -> bool: + """Set the list of the bot's commands. The commands passed will overwrite any command set previously. This method can be used by the own bot only. + .. include:: /_includes/usable-by/bots.rst + Parameters: commands (List of :obj:`~pyrogram.types.BotCommand`): A list of bot commands. - Pass None to remove all commands. + At most 100 commands can be specified. + + scope (:obj:`~pyrogram.types.BotCommandScope`, *optional*): + An object describing the scope of users for which the commands are relevant. + Defaults to :obj:`~pyrogram.types.BotCommandScopeDefault`. + + language_code (``str``, *optional*): + A two-letter ISO 639-1 language code. + If empty, commands will be applied to all users from the given scope, for whose language there are no + dedicated commands. Returns: - ``bool``: True on success, False otherwise. + ``bool``: On success, True is returned. Example: .. code-block:: python - + from pyrogram.types import BotCommand - + # Set new commands - app.set_bot_commands([ + await app.set_bot_commands([ BotCommand("start", "Start the bot"), BotCommand("settings", "Bot settings")]) - - # Remove commands - app.set_bot_commands(None) """ - return await self.send( + return await self.invoke( raw.functions.bots.SetBotCommands( - commands=[c.write() for c in commands or []] + commands=[c.write() for c in commands], + scope=await scope.write(self), + lang_code=language_code, ) ) diff --git a/pyrogram/methods/bots/set_bot_default_privileges.py b/pyrogram/methods/bots/set_bot_default_privileges.py new file mode 100644 index 0000000000..2890ee1ae1 --- /dev/null +++ b/pyrogram/methods/bots/set_bot_default_privileges.py @@ -0,0 +1,81 @@ +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-present Dan +# +# This file is part of Pyrogram. +# +# Pyrogram is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License as published +# by the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Pyrogram is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with Pyrogram. If not, see . + +import pyrogram +from pyrogram import raw +from pyrogram import types + + +class SetBotDefaultPrivileges: + async def set_bot_default_privileges( + self: "pyrogram.Client", + privileges: "types.ChatPrivileges" = None, + for_channels: bool = None + ) -> bool: + """Change the default privileges requested by the bot when it's added as an administrator to groups or channels. + + These privileges will be suggested to users, but they are are free to modify the list before adding the bot. + + .. include:: /_includes/usable-by/bots.rst + + Parameters: + privileges (:obj:`~pyrogram.types.ChatPrivileges`): + New default privileges. None to clear. + Defaults to None. + + for_channels (``bool``, *optional*): + Pass True to change the default privileges of the bot in channels. Otherwise, the default privileges of + the bot for groups and supergroups will be changed. + + Returns: + ``bool``: On success, True is returned. + + Example: + .. code-block:: python + + from pyrogram.types import ChatPrivileges + + await app.set_bot_default_privileges( + ChatPrivileges( + can_delete_messages=True, + can_restrict_members=True + ) + ) + """ + + function = ( + raw.functions.bots.SetBotBroadcastDefaultAdminRights + if for_channels + else raw.functions.bots.SetBotGroupDefaultAdminRights + ) + + admin_rights = raw.types.ChatAdminRights( + change_info=privileges.can_change_info, + post_messages=privileges.can_post_messages, + edit_messages=privileges.can_edit_messages, + delete_messages=privileges.can_delete_messages, + ban_users=privileges.can_restrict_members, + invite_users=privileges.can_invite_users, + pin_messages=privileges.can_pin_messages, + add_admins=privileges.can_promote_members, + anonymous=privileges.is_anonymous, + manage_call=privileges.can_manage_video_chats, + other=privileges.can_manage_chat + ) if privileges else raw.types.ChatAdminRights() + + return await self.invoke(function(admin_rights=admin_rights)) diff --git a/pyrogram/methods/bots/set_chat_menu_button.py b/pyrogram/methods/bots/set_chat_menu_button.py new file mode 100644 index 0000000000..fa5af85ceb --- /dev/null +++ b/pyrogram/methods/bots/set_chat_menu_button.py @@ -0,0 +1,56 @@ +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-present Dan +# +# This file is part of Pyrogram. +# +# Pyrogram is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License as published +# by the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Pyrogram is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with Pyrogram. If not, see . + +from typing import Union + +import pyrogram +from pyrogram import raw +from pyrogram import types + + +class SetChatMenuButton: + async def set_chat_menu_button( + self: "pyrogram.Client", + chat_id: Union[int, str] = None, + menu_button: "types.MenuButton" = None + ) -> bool: + """Change the bot's menu button in a private chat, or the default menu button. + + .. include:: /_includes/usable-by/bots.rst + + Parameters: + chat_id (``int`` | ``str``, *optional*): + Unique identifier (int) or username (str) of the target chat. + If not specified, default bot's menu button will be changed. + + menu_button (:obj:`~pyrogram.types.MenuButton`, *optional*): + The new bot's menu button. + Defaults to :obj:`~pyrogram.types.MenuButtonDefault`. + """ + + await self.invoke( + raw.functions.bots.SetBotMenuButton( + user_id=await self.resolve_peer(chat_id or "me"), + button=( + (await menu_button.write(self)) if menu_button + else (await types.MenuButtonDefault().write(self)) + ) + ) + ) + + return True diff --git a/pyrogram/methods/bots/set_game_score.py b/pyrogram/methods/bots/set_game_score.py index e8af3125ad..b3b58b2362 100644 --- a/pyrogram/methods/bots/set_game_score.py +++ b/pyrogram/methods/bots/set_game_score.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2021 Dan +# Copyright (C) 2017-present Dan # # This file is part of Pyrogram. # @@ -18,14 +18,14 @@ from typing import Union +import pyrogram from pyrogram import raw from pyrogram import types -from pyrogram.scaffold import Scaffold -class SetGameScore(Scaffold): +class SetGameScore: async def set_game_score( - self, + self: "pyrogram.Client", user_id: Union[int, str], score: int, force: bool = None, @@ -36,6 +36,8 @@ async def set_game_score( # inline_message_id: str = None): TODO Add inline_message_id """Set the score of the specified user in a game. + .. include:: /_includes/usable-by/bots.rst + Parameters: user_id (``int`` | ``str``): Unique identifier (int) or username (str) of the target chat. @@ -70,12 +72,12 @@ async def set_game_score( .. code-block:: python # Set new score - app.set_game_score(user_id, 1000) + await app.set_game_score(user_id, 1000) # Force set new score - app.set_game_score(user_id, 25, force=True) + await app.set_game_score(user_id, 25, force=True) """ - r = await self.send( + r = await self.invoke( raw.functions.messages.SetGameScore( peer=await self.resolve_peer(chat_id), score=score, diff --git a/pyrogram/methods/chats/__init__.py b/pyrogram/methods/chats/__init__.py index 31ffe4fd2f..a2bdde08a4 100644 --- a/pyrogram/methods/chats/__init__.py +++ b/pyrogram/methods/chats/__init__.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2021 Dan +# Copyright (C) 2017-present Dan # # This file is part of Pyrogram. # @@ -18,6 +18,7 @@ from .add_chat_members import AddChatMembers from .archive_chats import ArchiveChats +from .ban_chat_member import BanChatMember from .create_channel import CreateChannel from .create_group import CreateGroup from .create_supergroup import CreateSupergroup @@ -30,13 +31,12 @@ from .get_chat_member import GetChatMember from .get_chat_members import GetChatMembers from .get_chat_members_count import GetChatMembersCount +from .get_chat_online_count import GetChatOnlineCount from .get_dialogs import GetDialogs from .get_dialogs_count import GetDialogsCount from .get_nearby_chats import GetNearbyChats -from .iter_chat_members import IterChatMembers -from .iter_dialogs import IterDialogs +from .get_send_as_chats import GetSendAsChats from .join_chat import JoinChat -from .kick_chat_member import KickChatMember from .leave_chat import LeaveChat from .mark_chat_unread import MarkChatUnread from .pin_chat_message import PinChatMessage @@ -46,21 +46,22 @@ from .set_chat_description import SetChatDescription from .set_chat_permissions import SetChatPermissions from .set_chat_photo import SetChatPhoto +from .set_chat_protected_content import SetChatProtectedContent from .set_chat_title import SetChatTitle +from .set_chat_username import SetChatUsername +from .set_send_as_chat import SetSendAsChat from .set_slow_mode import SetSlowMode from .unarchive_chats import UnarchiveChats from .unban_chat_member import UnbanChatMember from .unpin_all_chat_messages import UnpinAllChatMessages from .unpin_chat_message import UnpinChatMessage -from .update_chat_username import UpdateChatUsername -from .get_chat_online_count import GetChatOnlineCount class Chats( GetChat, LeaveChat, JoinChat, - KickChatMember, + BanChatMember, UnbanChatMember, RestrictChatMember, PromoteChatMember, @@ -74,9 +75,7 @@ class Chats( UnpinChatMessage, GetDialogs, GetChatMembersCount, - IterDialogs, - IterChatMembers, - UpdateChatUsername, + SetChatUsername, SetChatPermissions, GetDialogsCount, ArchiveChats, @@ -94,6 +93,9 @@ class Chats( UnpinAllChatMessages, MarkChatUnread, GetChatEventLog, - GetChatOnlineCount + GetChatOnlineCount, + GetSendAsChats, + SetSendAsChat, + SetChatProtectedContent ): pass diff --git a/pyrogram/methods/chats/add_chat_members.py b/pyrogram/methods/chats/add_chat_members.py index 154f1ee1e4..b560a6affc 100644 --- a/pyrogram/methods/chats/add_chat_members.py +++ b/pyrogram/methods/chats/add_chat_members.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2021 Dan +# Copyright (C) 2017-present Dan # # This file is part of Pyrogram. # @@ -18,19 +18,21 @@ from typing import Union, List +import pyrogram from pyrogram import raw -from pyrogram.scaffold import Scaffold -class AddChatMembers(Scaffold): +class AddChatMembers: async def add_chat_members( - self, + self: "pyrogram.Client", chat_id: Union[int, str], user_ids: Union[Union[int, str], List[Union[int, str]]], forward_limit: int = 100 ) -> bool: """Add new chat members to a group, supergroup or channel + .. include:: /_includes/usable-by/users.rst + Parameters: chat_id (``int`` | ``str``): The group, supergroup or channel id @@ -52,13 +54,13 @@ async def add_chat_members( .. code-block:: python # Add one member to a group or channel - app.add_chat_members(chat_id, user_id) + await app.add_chat_members(chat_id, user_id) # Add multiple members to a group or channel - app.add_chat_members(chat_id, [user_id1, user_id2, user_id3]) + await app.add_chat_members(chat_id, [user_id1, user_id2, user_id3]) # Change forward_limit (for basic groups only) - app.add_chat_members(chat_id, user_id, forward_limit=25) + await app.add_chat_members(chat_id, user_id, forward_limit=25) """ peer = await self.resolve_peer(chat_id) @@ -67,7 +69,7 @@ async def add_chat_members( if isinstance(peer, raw.types.InputPeerChat): for user_id in user_ids: - await self.send( + await self.invoke( raw.functions.messages.AddChatUser( chat_id=peer.chat_id, user_id=await self.resolve_peer(user_id), @@ -75,7 +77,7 @@ async def add_chat_members( ) ) else: - await self.send( + await self.invoke( raw.functions.channels.InviteToChannel( channel=peer, users=[ diff --git a/pyrogram/methods/chats/archive_chats.py b/pyrogram/methods/chats/archive_chats.py index df3f69c103..661b450cff 100644 --- a/pyrogram/methods/chats/archive_chats.py +++ b/pyrogram/methods/chats/archive_chats.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2021 Dan +# Copyright (C) 2017-present Dan # # This file is part of Pyrogram. # @@ -18,17 +18,19 @@ from typing import Union, List +import pyrogram from pyrogram import raw -from pyrogram.scaffold import Scaffold -class ArchiveChats(Scaffold): +class ArchiveChats: async def archive_chats( - self, + self: "pyrogram.Client", chat_ids: Union[int, str, List[Union[int, str]]], ) -> bool: """Archive one or more chats. + .. include:: /_includes/usable-by/users.rst + Parameters: chat_ids (``int`` | ``str`` | List[``int``, ``str``]): Unique identifier (int) or username (str) of the target chat. @@ -41,10 +43,10 @@ async def archive_chats( .. code-block:: python # Archive chat - app.archive_chats(chat_id) + await app.archive_chats(chat_id) # Archive multiple chats at once - app.archive_chats([chat_id1, chat_id2, chat_id3]) + await app.archive_chats([chat_id1, chat_id2, chat_id3]) """ if not isinstance(chat_ids, list): @@ -60,7 +62,7 @@ async def archive_chats( ) ) - await self.send( + await self.invoke( raw.functions.folders.EditPeerFolders( folder_peers=folder_peers ) diff --git a/pyrogram/methods/chats/kick_chat_member.py b/pyrogram/methods/chats/ban_chat_member.py similarity index 77% rename from pyrogram/methods/chats/kick_chat_member.py rename to pyrogram/methods/chats/ban_chat_member.py index 495eac9402..635c48b5da 100644 --- a/pyrogram/methods/chats/kick_chat_member.py +++ b/pyrogram/methods/chats/ban_chat_member.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2021 Dan +# Copyright (C) 2017-present Dan # # This file is part of Pyrogram. # @@ -16,21 +16,22 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . +from datetime import datetime from typing import Union -from pyrogram import raw +import pyrogram +from pyrogram import raw, utils from pyrogram import types -from pyrogram.scaffold import Scaffold -class KickChatMember(Scaffold): - async def kick_chat_member( - self, +class BanChatMember: + async def ban_chat_member( + self: "pyrogram.Client", chat_id: Union[int, str], user_id: Union[int, str], - until_date: int = 0 + until_date: datetime = utils.zero_datetime() ) -> Union["types.Message", bool]: - """Kick a user from a group, a supergroup or a channel. + """Ban a user from a group, a supergroup or a channel. In the case of supergroups and channels, the user will not be able to return to the group on their own using invite links, etc., unless unbanned first. You must be an administrator in the chat for this to work and must have the appropriate admin rights. @@ -40,6 +41,8 @@ async def kick_chat_member( off in the target group. Otherwise members may only be removed by the group's creator or by the member that added them. + .. include:: /_includes/usable-by/users-bots.rst + Parameters: chat_id (``int`` | ``str``): Unique identifier (int) or username (str) of the target chat. @@ -48,10 +51,10 @@ async def kick_chat_member( Unique identifier (int) or username (str) of the target user. For a contact that exists in your Telegram address book you can use his phone number (str). - until_date (``int``, *optional*): - Date when the user will be unbanned, unix time. + until_date (:py:obj:`~datetime.datetime`, *optional*): + Date when the user will be unbanned. If user is banned for more than 366 days or less than 30 seconds from the current time they are - considered to be banned forever. Defaults to 0 (ban forever). + considered to be banned forever. Defaults to epoch (ban forever). Returns: :obj:`~pyrogram.types.Message` | ``bool``: On success, a service message will be returned (when applicable), @@ -60,24 +63,24 @@ async def kick_chat_member( Example: .. code-block:: python - from time import time + from datetime import datetime, timedelta # Ban chat member forever - app.kick_chat_member(chat_id, user_id) + await app.ban_chat_member(chat_id, user_id) - # Kick chat member and automatically unban after 24h - app.kick_chat_member(chat_id, user_id, int(time.time() + 86400)) + # Ban chat member and automatically unban after 24h + await app.ban_chat_member(chat_id, user_id, datetime.now() + timedelta(days=1)) """ chat_peer = await self.resolve_peer(chat_id) user_peer = await self.resolve_peer(user_id) if isinstance(chat_peer, raw.types.InputPeerChannel): - r = await self.send( + r = await self.invoke( raw.functions.channels.EditBanned( channel=chat_peer, participant=user_peer, banned_rights=raw.types.ChatBannedRights( - until_date=until_date, + until_date=utils.datetime_to_timestamp(until_date), view_messages=True, send_messages=True, send_media=True, @@ -90,7 +93,7 @@ async def kick_chat_member( ) ) else: - r = await self.send( + r = await self.invoke( raw.functions.messages.DeleteChatUser( chat_id=abs(chat_id), user_id=user_peer diff --git a/pyrogram/methods/chats/create_channel.py b/pyrogram/methods/chats/create_channel.py index 3e1fab82a6..292fa97dd7 100644 --- a/pyrogram/methods/chats/create_channel.py +++ b/pyrogram/methods/chats/create_channel.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2021 Dan +# Copyright (C) 2017-present Dan # # This file is part of Pyrogram. # @@ -15,20 +15,21 @@ # # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . - +import pyrogram from pyrogram import raw from pyrogram import types -from pyrogram.scaffold import Scaffold -class CreateChannel(Scaffold): +class CreateChannel: async def create_channel( - self, + self: "pyrogram.Client", title: str, description: str = "" ) -> "types.Chat": """Create a new broadcast channel. + .. include:: /_includes/usable-by/users.rst + Parameters: title (``str``): The channel title. @@ -42,9 +43,9 @@ async def create_channel( Example: .. code-block:: python - app.create_channel("Channel Title", "Channel Description") + await app.create_channel("Channel Title", "Channel Description") """ - r = await self.send( + r = await self.invoke( raw.functions.channels.CreateChannel( title=title, about=description, diff --git a/pyrogram/methods/chats/create_group.py b/pyrogram/methods/chats/create_group.py index f1833d80e6..027315de2a 100644 --- a/pyrogram/methods/chats/create_group.py +++ b/pyrogram/methods/chats/create_group.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2021 Dan +# Copyright (C) 2017-present Dan # # This file is part of Pyrogram. # @@ -18,14 +18,14 @@ from typing import Union, List +import pyrogram from pyrogram import raw from pyrogram import types -from pyrogram.scaffold import Scaffold -class CreateGroup(Scaffold): +class CreateGroup: async def create_group( - self, + self: "pyrogram.Client", title: str, users: Union[Union[int, str], List[Union[int, str]]] ) -> "types.Chat": @@ -35,6 +35,8 @@ async def create_group( If you want to create a new supergroup, use :meth:`~pyrogram.Client.create_supergroup` instead. + .. include:: /_includes/usable-by/users.rst + Parameters: title (``str``): The group title. @@ -50,12 +52,12 @@ async def create_group( Example: .. code-block:: python - app.create_group("Group Title", user_id) + await app.create_group("Group Title", user_id) """ if not isinstance(users, list): users = [users] - r = await self.send( + r = await self.invoke( raw.functions.messages.CreateChat( title=title, users=[await self.resolve_peer(u) for u in users] diff --git a/pyrogram/methods/chats/create_supergroup.py b/pyrogram/methods/chats/create_supergroup.py index 866a732b56..d1cbbbd890 100644 --- a/pyrogram/methods/chats/create_supergroup.py +++ b/pyrogram/methods/chats/create_supergroup.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2021 Dan +# Copyright (C) 2017-present Dan # # This file is part of Pyrogram. # @@ -15,15 +15,14 @@ # # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . - +import pyrogram from pyrogram import raw from pyrogram import types -from pyrogram.scaffold import Scaffold -class CreateSupergroup(Scaffold): +class CreateSupergroup: async def create_supergroup( - self, + self: "pyrogram.Client", title: str, description: str = "" ) -> "types.Chat": @@ -33,6 +32,8 @@ async def create_supergroup( If you want to create a new basic group, use :meth:`~pyrogram.Client.create_group` instead. + .. include:: /_includes/usable-by/users.rst + Parameters: title (``str``): The supergroup title. @@ -46,9 +47,9 @@ async def create_supergroup( Example: .. code-block:: python - app.create_supergroup("Supergroup Title", "Supergroup Description") + await app.create_supergroup("Supergroup Title", "Supergroup Description") """ - r = await self.send( + r = await self.invoke( raw.functions.channels.CreateChannel( title=title, about=description, diff --git a/pyrogram/methods/chats/delete_channel.py b/pyrogram/methods/chats/delete_channel.py index 0a47fb43aa..374d89a6f4 100644 --- a/pyrogram/methods/chats/delete_channel.py +++ b/pyrogram/methods/chats/delete_channel.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2021 Dan +# Copyright (C) 2017-present Dan # # This file is part of Pyrogram. # @@ -18,14 +18,19 @@ from typing import Union +import pyrogram from pyrogram import raw -from pyrogram.scaffold import Scaffold -class DeleteChannel(Scaffold): - async def delete_channel(self, chat_id: Union[int, str]) -> bool: +class DeleteChannel: + async def delete_channel( + self: "pyrogram.Client", + chat_id: Union[int, str] + ) -> bool: """Delete a channel. + .. include:: /_includes/usable-by/users.rst + Parameters: chat_id (``int`` | ``str``): The id of the channel to be deleted. @@ -36,9 +41,9 @@ async def delete_channel(self, chat_id: Union[int, str]) -> bool: Example: .. code-block:: python - app.delete_channel(channel_id) + await app.delete_channel(channel_id) """ - await self.send( + await self.invoke( raw.functions.channels.DeleteChannel( channel=await self.resolve_peer(chat_id) ) diff --git a/pyrogram/methods/chats/delete_chat_photo.py b/pyrogram/methods/chats/delete_chat_photo.py index 7bd82337a9..1984bc0cc2 100644 --- a/pyrogram/methods/chats/delete_chat_photo.py +++ b/pyrogram/methods/chats/delete_chat_photo.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2021 Dan +# Copyright (C) 2017-present Dan # # This file is part of Pyrogram. # @@ -18,19 +18,21 @@ from typing import Union +import pyrogram from pyrogram import raw -from pyrogram.scaffold import Scaffold -class DeleteChatPhoto(Scaffold): +class DeleteChatPhoto: async def delete_chat_photo( - self, + self: "pyrogram.Client", chat_id: Union[int, str] ) -> bool: """Delete a chat photo. You must be an administrator in the chat for this to work and must have the appropriate admin rights. + .. include:: /_includes/usable-by/users-bots.rst + Parameters: chat_id (``int`` | ``str``): Unique identifier (int) or username (str) of the target chat. @@ -44,19 +46,19 @@ async def delete_chat_photo( Example: .. code-block:: python - app.delete_chat_photo(chat_id) + await app.delete_chat_photo(chat_id) """ peer = await self.resolve_peer(chat_id) if isinstance(peer, raw.types.InputPeerChat): - await self.send( + await self.invoke( raw.functions.messages.EditChatPhoto( chat_id=peer.chat_id, photo=raw.types.InputChatPhotoEmpty() ) ) elif isinstance(peer, raw.types.InputPeerChannel): - await self.send( + await self.invoke( raw.functions.channels.EditPhoto( channel=peer, photo=raw.types.InputChatPhotoEmpty() diff --git a/pyrogram/methods/chats/delete_supergroup.py b/pyrogram/methods/chats/delete_supergroup.py index 016be5463f..ebd60befa8 100644 --- a/pyrogram/methods/chats/delete_supergroup.py +++ b/pyrogram/methods/chats/delete_supergroup.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2021 Dan +# Copyright (C) 2017-present Dan # # This file is part of Pyrogram. # @@ -18,14 +18,19 @@ from typing import Union +import pyrogram from pyrogram import raw -from pyrogram.scaffold import Scaffold -class DeleteSupergroup(Scaffold): - async def delete_supergroup(self, chat_id: Union[int, str]) -> bool: +class DeleteSupergroup: + async def delete_supergroup( + self: "pyrogram.Client", + chat_id: Union[int, str] + ) -> bool: """Delete a supergroup. + .. include:: /_includes/usable-by/users.rst + Parameters: chat_id (``int`` | ``str``): The id of the supergroup to be deleted. @@ -36,9 +41,9 @@ async def delete_supergroup(self, chat_id: Union[int, str]) -> bool: Example: .. code-block:: python - app.delete_supergroup(supergroup_id) + await app.delete_supergroup(supergroup_id) """ - await self.send( + await self.invoke( raw.functions.channels.DeleteChannel( channel=await self.resolve_peer(chat_id) ) diff --git a/pyrogram/methods/chats/delete_user_history.py b/pyrogram/methods/chats/delete_user_history.py index e2f780f9fc..7769c7c3c0 100644 --- a/pyrogram/methods/chats/delete_user_history.py +++ b/pyrogram/methods/chats/delete_user_history.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2021 Dan +# Copyright (C) 2017-present Dan # # This file is part of Pyrogram. # @@ -18,18 +18,20 @@ from typing import Union +import pyrogram from pyrogram import raw -from pyrogram.scaffold import Scaffold -class DeleteUserHistory(Scaffold): +class DeleteUserHistory: async def delete_user_history( - self, + self: "pyrogram.Client", chat_id: Union[int, str], user_id: Union[int, str], ) -> bool: """Delete all messages sent by a certain user in a supergroup. + .. include:: /_includes/usable-by/users.rst + Parameters: chat_id (``int`` | ``str``): Unique identifier (int) or username (str) of the target chat. @@ -41,10 +43,10 @@ async def delete_user_history( ``bool``: True on success, False otherwise. """ - r = await self.send( - raw.functions.channels.DeleteUserHistory( + r = await self.invoke( + raw.functions.channels.DeleteParticipantHistory( channel=await self.resolve_peer(chat_id), - user_id=await self.resolve_peer(user_id) + participant=await self.resolve_peer(user_id) ) ) diff --git a/pyrogram/methods/chats/get_chat.py b/pyrogram/methods/chats/get_chat.py index 4420b5feb7..e2289935a9 100644 --- a/pyrogram/methods/chats/get_chat.py +++ b/pyrogram/methods/chats/get_chat.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2021 Dan +# Copyright (C) 2017-present Dan # # This file is part of Pyrogram. # @@ -18,15 +18,15 @@ from typing import Union +import pyrogram from pyrogram import raw from pyrogram import types from pyrogram import utils -from pyrogram.scaffold import Scaffold -class GetChat(Scaffold): +class GetChat: async def get_chat( - self, + self: "pyrogram.Client", chat_id: Union[int, str] ) -> Union["types.Chat", "types.ChatPreview"]: """Get up to date information about a chat. @@ -34,6 +34,8 @@ async def get_chat( Information include current name of the user for one-on-one conversations, current username of a user, group or channel, etc. + .. include:: /_includes/usable-by/users-bots.rst + Parameters: chat_id (``int`` | ``str``): Unique identifier (int) or username (str) of the target chat. @@ -50,13 +52,13 @@ async def get_chat( Example: .. code-block:: python - chat = app.get_chat("pyrogram") + chat = await app.get_chat("pyrogram") print(chat) """ match = self.INVITE_LINK_RE.match(str(chat_id)) if match: - r = await self.send( + r = await self.invoke( raw.functions.messages.CheckChatInvite( hash=match.group(1) ) @@ -76,10 +78,10 @@ async def get_chat( peer = await self.resolve_peer(chat_id) if isinstance(peer, raw.types.InputPeerChannel): - r = await self.send(raw.functions.channels.GetFullChannel(channel=peer)) + r = await self.invoke(raw.functions.channels.GetFullChannel(channel=peer)) elif isinstance(peer, (raw.types.InputPeerUser, raw.types.InputPeerSelf)): - r = await self.send(raw.functions.users.GetFullUser(id=peer)) + r = await self.invoke(raw.functions.users.GetFullUser(id=peer)) else: - r = await self.send(raw.functions.messages.GetFullChat(chat_id=peer.chat_id)) + r = await self.invoke(raw.functions.messages.GetFullChat(chat_id=peer.chat_id)) return await types.Chat._parse_full(self, r) diff --git a/pyrogram/methods/chats/get_chat_event_log.py b/pyrogram/methods/chats/get_chat_event_log.py index 46c031edb8..06fa01c849 100644 --- a/pyrogram/methods/chats/get_chat_event_log.py +++ b/pyrogram/methods/chats/get_chat_event_log.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2021 Dan +# Copyright (C) 2017-present Dan # # This file is part of Pyrogram. # @@ -18,14 +18,14 @@ from typing import Union, List, AsyncGenerator, Optional +import pyrogram from pyrogram import raw from pyrogram import types -from pyrogram.scaffold import Scaffold -class GetChatEventLog(Scaffold): +class GetChatEventLog: async def get_chat_event_log( - self, + self: "pyrogram.Client", chat_id: Union[int, str], query: str = "", offset_id: int = 0, @@ -38,7 +38,9 @@ async def get_chat_event_log( Only available for supergroups and channels. Requires administrator rights. Results are returned in reverse chronological order (i.e., newest first). - Args: + .. include:: /_includes/usable-by/users.rst + + Parameters: chat_id (``int`` | ``str``): Unique identifier (int) or username (str) of the target chat. @@ -64,13 +66,19 @@ async def get_chat_event_log( Yields: :obj:`~pyrogram.types.ChatEvent` objects. + + Example: + .. code-block:: python + + async for event in app.get_chat_event_log(chat_id): + print(event) """ current = 0 total = abs(limit) or (1 << 31) limit = min(100, total) while True: - r: raw.base.channels.AdminLogResults = await self.send( + r: raw.base.channels.AdminLogResults = await self.invoke( raw.functions.channels.GetAdminLog( channel=await self.resolve_peer(chat_id), q=query, diff --git a/pyrogram/methods/chats/get_chat_member.py b/pyrogram/methods/chats/get_chat_member.py index 74aff5daa6..53d2faad83 100644 --- a/pyrogram/methods/chats/get_chat_member.py +++ b/pyrogram/methods/chats/get_chat_member.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2021 Dan +# Copyright (C) 2017-present Dan # # This file is part of Pyrogram. # @@ -18,20 +18,22 @@ from typing import Union +import pyrogram from pyrogram import raw from pyrogram import types from pyrogram.errors import UserNotParticipant -from pyrogram.scaffold import Scaffold -class GetChatMember(Scaffold): +class GetChatMember: async def get_chat_member( - self, + self: "pyrogram.Client", chat_id: Union[int, str], user_id: Union[int, str] ) -> "types.ChatMember": """Get information about one member of a chat. + .. include:: /_includes/usable-by/users-bots.rst + Parameters: chat_id (``int`` | ``str``): Unique identifier (int) or username (str) of the target chat. @@ -47,14 +49,14 @@ async def get_chat_member( Example: .. code-block:: python - dan = app.get_chat_member("pyrogramchat", "haskell") - print(dan) + member = await app.get_chat_member(chat_id, "me") + print(member) """ chat = await self.resolve_peer(chat_id) user = await self.resolve_peer(user_id) if isinstance(chat, raw.types.InputPeerChat): - r = await self.send( + r = await self.invoke( raw.functions.messages.GetFullChat( chat_id=chat.chat_id ) @@ -75,7 +77,7 @@ async def get_chat_member( else: raise UserNotParticipant elif isinstance(chat, raw.types.InputPeerChannel): - r = await self.send( + r = await self.invoke( raw.functions.channels.GetParticipant( channel=chat, participant=user diff --git a/pyrogram/methods/chats/get_chat_members.py b/pyrogram/methods/chats/get_chat_members.py index 64abf1e5e2..49fb0a094d 100644 --- a/pyrogram/methods/chats/get_chat_members.py +++ b/pyrogram/methods/chats/get_chat_members.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2021 Dan +# Copyright (C) 2017-present Dan # # This file is part of Pyrogram. # @@ -17,96 +17,105 @@ # along with Pyrogram. If not, see . import logging -from typing import Union, List +from typing import Union, Optional, AsyncGenerator -from pyrogram import raw -from pyrogram import types -from pyrogram.scaffold import Scaffold +import pyrogram +from pyrogram import raw, types, enums log = logging.getLogger(__name__) -class Filters: - ALL = "all" - KICKED = "kicked" - RESTRICTED = "restricted" - BOTS = "bots" - RECENT = "recent" - ADMINISTRATORS = "administrators" +async def get_chunk( + client: "pyrogram.Client", + chat_id: Union[int, str], + offset: int, + filter: "enums.ChatMembersFilter", + limit: int, + query: str, +): + is_queryable = filter in [enums.ChatMembersFilter.SEARCH, + enums.ChatMembersFilter.BANNED, + enums.ChatMembersFilter.RESTRICTED] + filter = filter.value(q=query) if is_queryable else filter.value() -class GetChatMembers(Scaffold): + r = await client.invoke( + raw.functions.channels.GetParticipants( + channel=await client.resolve_peer(chat_id), + filter=filter, + offset=offset, + limit=limit, + hash=0 + ), + sleep_threshold=60 + ) + + members = r.participants + users = {u.id: u for u in r.users} + chats = {c.id: c for c in r.chats} + + return [types.ChatMember._parse(client, member, users, chats) for member in members] + + +class GetChatMembers: async def get_chat_members( - self, + self: "pyrogram.Client", chat_id: Union[int, str], - offset: int = 0, - limit: int = 200, query: str = "", - filter: str = Filters.RECENT - ) -> List["types.ChatMember"]: - """Get a chunk of the members list of a chat. + limit: int = 0, + filter: "enums.ChatMembersFilter" = enums.ChatMembersFilter.SEARCH + ) -> Optional[AsyncGenerator["types.ChatMember", None]]: + """Get the members list of a chat. - You can get up to 200 chat members at once. A chat can be either a basic group, a supergroup or a channel. - You must be admin to retrieve the members list of a channel (also known as "subscribers"). - For a more convenient way of getting chat members see :meth:`~pyrogram.Client.iter_chat_members`. + Requires administrator rights in channels. + + .. include:: /_includes/usable-by/users-bots.rst Parameters: chat_id (``int`` | ``str``): Unique identifier (int) or username (str) of the target chat. - offset (``int``, *optional*): - Sequential number of the first member to be returned. - Only applicable to supergroups and channels. Defaults to 0 [1]_. + query (``str``, *optional*): + Query string to filter members based on their display names and usernames. + Only applicable to supergroups and channels. Defaults to "" (empty string). + A query string is applicable only for :obj:`~pyrogram.enums.ChatMembersFilter.SEARCH`, + :obj:`~pyrogram.enums.ChatMembersFilter.BANNED` and :obj:`~pyrogram.enums.ChatMembersFilter.RESTRICTED` + filters only. limit (``int``, *optional*): Limits the number of members to be retrieved. - Only applicable to supergroups and channels. - Defaults to 200, which is also the maximum server limit allowed per method call. - query (``str``, *optional*): - Query string to filter members based on their display names and usernames. - Only applicable to supergroups and channels. Defaults to "" (empty string) [2]_. - - filter (``str``, *optional*): + filter (:obj:`~pyrogram.enums.ChatMembersFilter`, *optional*): Filter used to select the kind of members you want to retrieve. Only applicable for supergroups - and channels. It can be any of the followings: - *"all"* - all kind of members, - *"kicked"* - kicked (banned) members only, - *"restricted"* - restricted members only, - *"bots"* - bots only, - *"recent"* - recent members only, - *"administrators"* - chat administrators only. - Only applicable to supergroups and channels. - Defaults to *"recent"*. - - .. [1] Server limit: on supergroups, you can get up to 10,000 members for a single query and up to 200 members - on channels. - - .. [2] A query string is applicable only for *"all"*, *"kicked"* and *"restricted"* filters only. + and channels. Returns: - List of :obj:`~pyrogram.types.ChatMember`: On success, a list of chat members is returned. - - Raises: - ValueError: In case you used an invalid filter or a chat id that belongs to a user. + ``Generator``: On success, a generator yielding :obj:`~pyrogram.types.ChatMember` objects is returned. Example: .. code-block:: python - # Get first 200 recent members - app.get_chat_members("pyrogramchat") + from pyrogram import enums + + # Get members + async for member in app.get_chat_members(chat_id): + print(member) - # Get all administrators - app.get_chat_members("pyrogramchat", filter="administrators") + # Get administrators + administrators = [] + async for m in app.get_chat_members(chat_id, filter=enums.ChatMembersFilter.ADMINISTRATORS): + administrators.append(m) - # Get all bots - app.get_chat_members("pyrogramchat", filter="bots") + # Get bots + bots = [] + async for m in app.get_chat_members(chat_id, filter=enums.ChatMembersFilter.BOTS): + bots.append(m) """ peer = await self.resolve_peer(chat_id) if isinstance(peer, raw.types.InputPeerChat): - r = await self.send( + r = await self.invoke( raw.functions.messages.GetFullChat( chat_id=peer.chat_id ) @@ -115,40 +124,35 @@ async def get_chat_members( members = getattr(r.full_chat.participants, "participants", []) users = {i.id: i for i in r.users} - return types.List(types.ChatMember._parse(self, member, users, {}) for member in members) - elif isinstance(peer, raw.types.InputPeerChannel): - filter = filter.lower() - - if filter == Filters.ALL: - filter = raw.types.ChannelParticipantsSearch(q=query) - elif filter == Filters.KICKED: - filter = raw.types.ChannelParticipantsKicked(q=query) - elif filter == Filters.RESTRICTED: - filter = raw.types.ChannelParticipantsBanned(q=query) - elif filter == Filters.BOTS: - filter = raw.types.ChannelParticipantsBots() - elif filter == Filters.RECENT: - filter = raw.types.ChannelParticipantsRecent() - elif filter == Filters.ADMINISTRATORS: - filter = raw.types.ChannelParticipantsAdmins() - else: - raise ValueError(f'Invalid filter "{filter}"') - - r = await self.send( - raw.functions.channels.GetParticipants( - channel=peer, - filter=filter, - offset=offset, - limit=limit, - hash=0 - ), - sleep_threshold=60 + for member in members: + yield types.ChatMember._parse(self, member, users, {}) + + return + + current = 0 + offset = 0 + total = abs(limit) or (1 << 31) - 1 + limit = min(200, total) + + while True: + members = await get_chunk( + client=self, + chat_id=chat_id, + offset=offset, + filter=filter, + limit=limit, + query=query ) - members = r.participants - users = {i.id: i for i in r.users} - chats = {i.id: i for i in r.chats} + if not members: + return + + offset += len(members) + + for member in members: + yield member + + current += 1 - return types.List(types.ChatMember._parse(self, member, users, chats) for member in members) - else: - raise ValueError(f'The chat_id "{chat_id}" belongs to a user') + if current >= total: + return diff --git a/pyrogram/methods/chats/get_chat_members_count.py b/pyrogram/methods/chats/get_chat_members_count.py index 3fc80e5dd7..2680702833 100644 --- a/pyrogram/methods/chats/get_chat_members_count.py +++ b/pyrogram/methods/chats/get_chat_members_count.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2021 Dan +# Copyright (C) 2017-present Dan # # This file is part of Pyrogram. # @@ -18,17 +18,19 @@ from typing import Union +import pyrogram from pyrogram import raw -from pyrogram.scaffold import Scaffold -class GetChatMembersCount(Scaffold): +class GetChatMembersCount: async def get_chat_members_count( - self, + self: "pyrogram.Client", chat_id: Union[int, str] ) -> int: """Get the number of members in a chat. + .. include:: /_includes/usable-by/users-bots.rst + Parameters: chat_id (``int`` | ``str``): Unique identifier (int) or username (str) of the target chat. @@ -42,13 +44,13 @@ async def get_chat_members_count( Example: .. code-block:: python - count = app.get_chat_members_count("pyrogramchat") + count = await app.get_chat_members_count(chat_id) print(count) """ peer = await self.resolve_peer(chat_id) if isinstance(peer, raw.types.InputPeerChat): - r = await self.send( + r = await self.invoke( raw.functions.messages.GetChats( id=[peer.chat_id] ) @@ -56,7 +58,7 @@ async def get_chat_members_count( return r.chats[0].participants_count elif isinstance(peer, raw.types.InputPeerChannel): - r = await self.send( + r = await self.invoke( raw.functions.channels.GetFullChannel( channel=peer ) diff --git a/pyrogram/methods/chats/get_chat_online_count.py b/pyrogram/methods/chats/get_chat_online_count.py index 5d3d861f87..6ecd5727dd 100644 --- a/pyrogram/methods/chats/get_chat_online_count.py +++ b/pyrogram/methods/chats/get_chat_online_count.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2021 Dan +# Copyright (C) 2017-present Dan # # This file is part of Pyrogram. # @@ -18,14 +18,19 @@ from typing import Union +import pyrogram from pyrogram import raw -from pyrogram.scaffold import Scaffold -class GetChatOnlineCount(Scaffold): - async def get_chat_online_count(self, chat_id: Union[int, str]) -> int: +class GetChatOnlineCount: + async def get_chat_online_count( + self: "pyrogram.Client", + chat_id: Union[int, str] + ) -> int: """Get the number of members that are currently online in a chat. + .. include:: /_includes/usable-by/users.rst + Parameters: chat_id (``int`` | ``str``): Unique identifier (int) or username (str) of the target chat. @@ -36,10 +41,10 @@ async def get_chat_online_count(self, chat_id: Union[int, str]) -> int: Example: .. code-block:: python - online = app.get_chat_online_count(chat_id) + online = await app.get_chat_online_count(chat_id) print(online) """ - return (await self.send( + return (await self.invoke( raw.functions.messages.GetOnlines( peer=await self.resolve_peer(chat_id) ) diff --git a/pyrogram/methods/chats/get_dialogs.py b/pyrogram/methods/chats/get_dialogs.py index 4e3b11113e..3509ccbcca 100644 --- a/pyrogram/methods/chats/get_dialogs.py +++ b/pyrogram/methods/chats/get_dialogs.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2021 Dan +# Copyright (C) 2017-present Dan # # This file is part of Pyrogram. # @@ -16,91 +16,89 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . -import logging -from typing import List +from typing import AsyncGenerator, Optional -from pyrogram import raw -from pyrogram import types -from pyrogram import utils -from pyrogram.scaffold import Scaffold +import pyrogram +from pyrogram import types, raw, utils -log = logging.getLogger(__name__) - -class GetDialogs(Scaffold): +class GetDialogs: async def get_dialogs( - self, - offset_date: int = 0, - limit: int = 100, - pinned_only: bool = False - ) -> List["types.Dialog"]: - """Get a chunk of the user's dialogs. + self: "pyrogram.Client", + limit: int = 0 + ) -> Optional[AsyncGenerator["types.Dialog", None]]: + """Get a user's dialogs sequentially. - You can get up to 100 dialogs at once. - For a more convenient way of getting a user's dialogs see :meth:`~pyrogram.Client.iter_dialogs`. + .. include:: /_includes/usable-by/users.rst Parameters: - offset_date (``int``): - The offset date in Unix time taken from the top message of a :obj:`~pyrogram.types.Dialog`. - Defaults to 0. Valid for non-pinned dialogs only. - - limit (``str``, *optional*): + limit (``int``, *optional*): Limits the number of dialogs to be retrieved. - Defaults to 100. Valid for non-pinned dialogs only. - - pinned_only (``bool``, *optional*): - Pass True if you want to get only pinned dialogs. - Defaults to False. + By default, no limit is applied and all dialogs are returned. Returns: - List of :obj:`~pyrogram.types.Dialog`: On success, a list of dialogs is returned. + ``Generator``: A generator yielding :obj:`~pyrogram.types.Dialog` objects. Example: .. code-block:: python - # Get first 100 dialogs - app.get_dialogs() - - # Get pinned dialogs - app.get_dialogs(pinned_only=True) + # Iterate through all dialogs + async for dialog in app.get_dialogs(): + print(dialog.chat.first_name or dialog.chat.title) """ + current = 0 + total = limit or (1 << 31) - 1 + limit = min(100, total) - if pinned_only: - r = await self.send( - raw.functions.messages.GetPinnedDialogs(folder_id=0), - sleep_threshold=60 - ) - else: - r = await self.send( + offset_date = 0 + offset_id = 0 + offset_peer = raw.types.InputPeerEmpty() + + while True: + r = await self.invoke( raw.functions.messages.GetDialogs( offset_date=offset_date, - offset_id=0, - offset_peer=raw.types.InputPeerEmpty(), + offset_id=offset_id, + offset_peer=offset_peer, limit=limit, - hash=0, - exclude_pinned=True + hash=0 ), sleep_threshold=60 ) - users = {i.id: i for i in r.users} - chats = {i.id: i for i in r.chats} + users = {i.id: i for i in r.users} + chats = {i.id: i for i in r.chats} + + messages = {} + + for message in r.messages: + if isinstance(message, raw.types.MessageEmpty): + continue + + chat_id = utils.get_peer_id(message.peer_id) + messages[chat_id] = await types.Message._parse(self, message, users, chats) + + dialogs = [] + + for dialog in r.dialogs: + if not isinstance(dialog, raw.types.Dialog): + continue - messages = {} + dialogs.append(types.Dialog._parse(self, dialog, messages, users, chats)) - for message in r.messages: - if isinstance(message, raw.types.MessageEmpty): - continue + if not dialogs: + return - chat_id = utils.get_peer_id(message.peer_id) - messages[chat_id] = await types.Message._parse(self, message, users, chats) + last = dialogs[-1] - parsed_dialogs = [] + offset_id = last.top_message.id + offset_date = utils.datetime_to_timestamp(last.top_message.date) + offset_peer = await self.resolve_peer(last.chat.id) - for dialog in r.dialogs: - if not isinstance(dialog, raw.types.Dialog): - continue + for dialog in dialogs: + yield dialog - parsed_dialogs.append(types.Dialog._parse(self, dialog, messages, users, chats)) + current += 1 - return types.List(parsed_dialogs) + if current >= total: + return diff --git a/pyrogram/methods/chats/get_dialogs_count.py b/pyrogram/methods/chats/get_dialogs_count.py index 260cc4f6ba..ae22eb5cff 100644 --- a/pyrogram/methods/chats/get_dialogs_count.py +++ b/pyrogram/methods/chats/get_dialogs_count.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2021 Dan +# Copyright (C) 2017-present Dan # # This file is part of Pyrogram. # @@ -16,17 +16,23 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . +import pyrogram from pyrogram import raw -from pyrogram.scaffold import Scaffold -class GetDialogsCount(Scaffold): - async def get_dialogs_count(self, pinned_only: bool = False) -> int: +class GetDialogsCount: + async def get_dialogs_count( + self: "pyrogram.Client", + pinned_only: bool = False + ) -> int: """Get the total count of your dialogs. - pinned_only (``bool``, *optional*): - Pass True if you want to count only pinned dialogs. - Defaults to False. + .. include:: /_includes/usable-by/users.rst + + Parameters: + pinned_only (``bool``, *optional*): + Pass True if you want to count only pinned dialogs. + Defaults to False. Returns: ``int``: On success, the dialogs count is returned. @@ -34,14 +40,14 @@ async def get_dialogs_count(self, pinned_only: bool = False) -> int: Example: .. code-block:: python - count = app.get_dialogs_count() + count = await app.get_dialogs_count() print(count) """ if pinned_only: - return len((await self.send(raw.functions.messages.GetPinnedDialogs(folder_id=0))).dialogs) + return len((await self.invoke(raw.functions.messages.GetPinnedDialogs(folder_id=0))).dialogs) else: - r = await self.send( + r = await self.invoke( raw.functions.messages.GetDialogs( offset_date=0, offset_id=0, diff --git a/pyrogram/methods/chats/get_nearby_chats.py b/pyrogram/methods/chats/get_nearby_chats.py index 22ff4fc07f..8d1163ac9d 100644 --- a/pyrogram/methods/chats/get_nearby_chats.py +++ b/pyrogram/methods/chats/get_nearby_chats.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2021 Dan +# Copyright (C) 2017-present Dan # # This file is part of Pyrogram. # @@ -18,20 +18,22 @@ from typing import List +import pyrogram from pyrogram import raw from pyrogram import types from pyrogram import utils -from pyrogram.scaffold import Scaffold -class GetNearbyChats(Scaffold): +class GetNearbyChats: async def get_nearby_chats( - self, + self: "pyrogram.Client", latitude: float, longitude: float ) -> List["types.Chat"]: """Get nearby chats. + .. include:: /_includes/usable-by/users.rst + Parameters: latitude (``float``): Latitude of the location. @@ -45,11 +47,11 @@ async def get_nearby_chats( Example: .. code-block:: python - chats = app.get_nearby_chats(51.500729, -0.124583) + chats = await app.get_nearby_chats(latitude, longitude) print(chats) """ - r = await self.send( + r = await self.invoke( raw.functions.contacts.GetLocated( geo_point=raw.types.InputGeoPoint( lat=latitude, diff --git a/pyrogram/methods/chats/get_send_as_chats.py b/pyrogram/methods/chats/get_send_as_chats.py new file mode 100644 index 0000000000..c9a358c398 --- /dev/null +++ b/pyrogram/methods/chats/get_send_as_chats.py @@ -0,0 +1,65 @@ +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-present Dan +# +# This file is part of Pyrogram. +# +# Pyrogram is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License as published +# by the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Pyrogram is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with Pyrogram. If not, see . + +from typing import List, Union + +import pyrogram +from pyrogram import raw +from pyrogram import types + + +class GetSendAsChats: + async def get_send_as_chats( + self: "pyrogram.Client", + chat_id: Union[int, str] + ) -> List["types.Chat"]: + """Get the list of "send_as" chats available. + + .. include:: /_includes/usable-by/users.rst + + Parameters: + chat_id (``int`` | ``str``): + Unique identifier (int) or username (str) of the target chat. + + Returns: + List[:obj:`~pyrogram.types.Chat`]: The list of chats. + + Example: + .. code-block:: python + + chats = await app.get_send_as_chats(chat_id) + print(chats) + """ + r = await self.invoke( + raw.functions.channels.GetSendAs( + peer=await self.resolve_peer(chat_id) + ) + ) + + users = {u.id: u for u in r.users} + chats = {c.id: c for c in r.chats} + + send_as_chats = types.List() + + for p in r.peers: + if isinstance(p, raw.types.PeerUser): + send_as_chats.append(types.Chat._parse_chat(self, users[p.user_id])) + else: + send_as_chats.append(types.Chat._parse_chat(self, chats[p.channel_id])) + + return send_as_chats diff --git a/pyrogram/methods/chats/iter_chat_members.py b/pyrogram/methods/chats/iter_chat_members.py deleted file mode 100644 index 86ef457c12..0000000000 --- a/pyrogram/methods/chats/iter_chat_members.py +++ /dev/null @@ -1,143 +0,0 @@ -# Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2021 Dan -# -# This file is part of Pyrogram. -# -# Pyrogram is free software: you can redistribute it and/or modify -# it under the terms of the GNU Lesser General Public License as published -# by the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# Pyrogram is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public License -# along with Pyrogram. If not, see . - -from string import ascii_lowercase -from typing import Union, AsyncGenerator, Optional - -from pyrogram import raw -from pyrogram import types -from pyrogram.scaffold import Scaffold - - -class Filters: - ALL = "all" - KICKED = "kicked" - RESTRICTED = "restricted" - BOTS = "bots" - RECENT = "recent" - ADMINISTRATORS = "administrators" - - -QUERIES = [""] + [str(i) for i in range(10)] + list(ascii_lowercase) -QUERYABLE_FILTERS = (Filters.ALL, Filters.KICKED, Filters.RESTRICTED) - - -class IterChatMembers(Scaffold): - async def iter_chat_members( - self, - chat_id: Union[int, str], - limit: int = 0, - query: str = "", - filter: str = Filters.RECENT - ) -> Optional[AsyncGenerator["types.ChatMember", None]]: - """Iterate through the members of a chat sequentially. - - This convenience method does the same as repeatedly calling :meth:`~pyrogram.Client.get_chat_members` in a loop, - thus saving you from the hassle of setting up boilerplate code. It is useful for getting the whole members list - of a chat with a single call. - - Parameters: - chat_id (``int`` | ``str``): - Unique identifier (int) or username (str) of the target chat. - - limit (``int``, *optional*): - Limits the number of members to be retrieved. - By default, no limit is applied and all members are returned [1]_. - - query (``str``, *optional*): - Query string to filter members based on their display names and usernames. - Defaults to "" (empty string) [2]_. - - filter (``str``, *optional*): - Filter used to select the kind of members you want to retrieve. Only applicable for supergroups - and channels. It can be any of the followings: - *"all"* - all kind of members, - *"kicked"* - kicked (banned) members only, - *"restricted"* - restricted members only, - *"bots"* - bots only, - *"recent"* - recent members only, - *"administrators"* - chat administrators only. - Defaults to *"recent"*. - - .. [1] Server limit: on supergroups, you can get up to 10,000 members for a single query and up to 200 members - on channels. - - .. [2] A query string is applicable only for *"all"*, *"kicked"* and *"restricted"* filters only. - - Returns: - ``Generator``: A generator yielding :obj:`~pyrogram.types.ChatMember` objects. - - Example: - .. code-block:: python - - # Iterate though all chat members - for member in app.iter_chat_members("pyrogramchat"): - print(member.user.first_name) - - # Iterate though all administrators - for member in app.iter_chat_members("pyrogramchat", filter="administrators"): - print(member.user.first_name) - - # Iterate though all bots - for member in app.iter_chat_members("pyrogramchat", filter="bots"): - print(member.user.first_name) - """ - current = 0 - yielded = set() - queries = [query] if query else QUERIES - total = limit or (1 << 31) - 1 - limit = min(200, total) - resolved_chat_id = await self.resolve_peer(chat_id) - - if filter not in QUERYABLE_FILTERS: - queries = [""] - - for q in queries: - offset = 0 - - while True: - chat_members = await self.get_chat_members( - chat_id=chat_id, - offset=offset, - limit=limit, - query=q, - filter=filter - ) - - if not chat_members: - break - - if isinstance(resolved_chat_id, raw.types.InputPeerChat): - total = len(chat_members) - - offset += len(chat_members) - - for chat_member in chat_members: - user_id = chat_member.user.id - - if user_id in yielded: - continue - - yield chat_member - - yielded.add(chat_member.user.id) - - current += 1 - - if current >= total: - return diff --git a/pyrogram/methods/chats/iter_dialogs.py b/pyrogram/methods/chats/iter_dialogs.py deleted file mode 100644 index 124eff52f1..0000000000 --- a/pyrogram/methods/chats/iter_dialogs.py +++ /dev/null @@ -1,106 +0,0 @@ -# Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2021 Dan -# -# This file is part of Pyrogram. -# -# Pyrogram is free software: you can redistribute it and/or modify -# it under the terms of the GNU Lesser General Public License as published -# by the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# Pyrogram is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public License -# along with Pyrogram. If not, see . - -from typing import AsyncGenerator, Optional - -from pyrogram import types, raw, utils -from pyrogram.scaffold import Scaffold - - -class IterDialogs(Scaffold): - async def iter_dialogs( - self, - limit: int = 0 - ) -> Optional[AsyncGenerator["types.Dialog", None]]: - """Iterate through a user's dialogs sequentially. - - This convenience method does the same as repeatedly calling :meth:`~pyrogram.Client.get_dialogs` in a loop, - thus saving you from the hassle of setting up boilerplate code. It is useful for getting the whole dialogs list - with a single call. - - Parameters: - limit (``int``, *optional*): - Limits the number of dialogs to be retrieved. - By default, no limit is applied and all dialogs are returned. - - Returns: - ``Generator``: A generator yielding :obj:`~pyrogram.types.Dialog` objects. - - Example: - .. code-block:: python - - # Iterate through all dialogs - for dialog in app.iter_dialogs(): - print(dialog.chat.first_name or dialog.chat.title) - """ - current = 0 - total = limit or (1 << 31) - 1 - limit = min(100, total) - - offset_date = 0 - offset_id = 0 - offset_peer = raw.types.InputPeerEmpty() - - while True: - r = (await self.send( - raw.functions.messages.GetDialogs( - offset_date=offset_date, - offset_id=offset_id, - offset_peer=offset_peer, - limit=limit, - hash=0 - ), - sleep_threshold=60 - )) - - users = {i.id: i for i in r.users} - chats = {i.id: i for i in r.chats} - - messages = {} - - for message in r.messages: - if isinstance(message, raw.types.MessageEmpty): - continue - - chat_id = utils.get_peer_id(message.peer_id) - messages[chat_id] = await types.Message._parse(self, message, users, chats) - - dialogs = [] - - for dialog in r.dialogs: - if not isinstance(dialog, raw.types.Dialog): - continue - - dialogs.append(types.Dialog._parse(self, dialog, messages, users, chats)) - - if not dialogs: - return - - last = dialogs[-1] - - offset_id = last.top_message.message_id - offset_date = last.top_message.date - offset_peer = await self.resolve_peer(last.chat.id) - - for dialog in dialogs: - yield dialog - - current += 1 - - if current >= total: - return diff --git a/pyrogram/methods/chats/join_chat.py b/pyrogram/methods/chats/join_chat.py index 41d0aa9313..2ef82cff31 100644 --- a/pyrogram/methods/chats/join_chat.py +++ b/pyrogram/methods/chats/join_chat.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2021 Dan +# Copyright (C) 2017-present Dan # # This file is part of Pyrogram. # @@ -18,18 +18,20 @@ from typing import Union +import pyrogram from pyrogram import raw from pyrogram import types -from pyrogram.scaffold import Scaffold -class JoinChat(Scaffold): +class JoinChat: async def join_chat( - self, + self: "pyrogram.Client", chat_id: Union[int, str] - ): + ) -> "types.Chat": """Join a group chat or channel. + .. include:: /_includes/usable-by/users.rst + Parameters: chat_id (``int`` | ``str``): Unique identifier for the target chat in form of a *t.me/joinchat/* link, a username of the target @@ -41,19 +43,19 @@ async def join_chat( Example: .. code-block:: python - # Join chat via username - app.join_chat("pyrogram") - # Join chat via invite link - app.join_chat("https://t.me/joinchat/AAAAAE0QmSW3IUmm3UFR7A") + await app.join_chat("https://t.me/+AbCdEf0123456789") + + # Join chat via username + await app.join_chat("pyrogram") # Join a linked chat - app.join_chat(app.get_chat("pyrogram").linked_chat.id) + await app.join_chat(app.get_chat("pyrogram").linked_chat.id) """ match = self.INVITE_LINK_RE.match(str(chat_id)) if match: - chat = await self.send( + chat = await self.invoke( raw.functions.messages.ImportChatInvite( hash=match.group(1) ) @@ -63,7 +65,7 @@ async def join_chat( elif isinstance(chat.chats[0], raw.types.Channel): return types.Chat._parse_channel_chat(self, chat.chats[0]) else: - chat = await self.send( + chat = await self.invoke( raw.functions.channels.JoinChannel( channel=await self.resolve_peer(chat_id) ) diff --git a/pyrogram/methods/chats/leave_chat.py b/pyrogram/methods/chats/leave_chat.py index 84ab4ebc04..b974e38771 100644 --- a/pyrogram/methods/chats/leave_chat.py +++ b/pyrogram/methods/chats/leave_chat.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2021 Dan +# Copyright (C) 2017-present Dan # # This file is part of Pyrogram. # @@ -18,18 +18,20 @@ from typing import Union +import pyrogram from pyrogram import raw -from pyrogram.scaffold import Scaffold -class LeaveChat(Scaffold): +class LeaveChat: async def leave_chat( - self, + self: "pyrogram.Client", chat_id: Union[int, str], delete: bool = False ): """Leave a group chat or channel. + .. include:: /_includes/usable-by/users-bots.rst + Parameters: chat_id (``int`` | ``str``): Unique identifier for the target chat or username of the target channel/supergroup @@ -43,21 +45,21 @@ async def leave_chat( .. code-block:: python # Leave chat or channel - app.leave_chat(chat_id) + await app.leave_chat(chat_id) # Leave basic chat and also delete the dialog - app.leave_chat(chat_id, delete=True) + await app.leave_chat(chat_id, delete=True) """ peer = await self.resolve_peer(chat_id) if isinstance(peer, raw.types.InputPeerChannel): - return await self.send( + return await self.invoke( raw.functions.channels.LeaveChannel( channel=await self.resolve_peer(chat_id) ) ) elif isinstance(peer, raw.types.InputPeerChat): - r = await self.send( + r = await self.invoke( raw.functions.messages.DeleteChatUser( chat_id=peer.chat_id, user_id=raw.types.InputUserSelf() @@ -65,7 +67,7 @@ async def leave_chat( ) if delete: - await self.send( + await self.invoke( raw.functions.messages.DeleteHistory( peer=peer, max_id=0 diff --git a/pyrogram/methods/chats/mark_chat_unread.py b/pyrogram/methods/chats/mark_chat_unread.py index a85cfccbcd..45aec2f878 100644 --- a/pyrogram/methods/chats/mark_chat_unread.py +++ b/pyrogram/methods/chats/mark_chat_unread.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2021 Dan +# Copyright (C) 2017-present Dan # # This file is part of Pyrogram. # @@ -18,17 +18,19 @@ from typing import Union -from pyrogram.raw import functions -from pyrogram.scaffold import Scaffold +import pyrogram +from pyrogram import raw -class MarkChatUnread(Scaffold): +class MarkChatUnread: async def mark_chat_unread( - self, + self: "pyrogram.Client", chat_id: Union[int, str], ) -> bool: """Mark a chat as unread. + .. include:: /_includes/usable-by/users.rst + Parameters: chat_id (``int`` | ``str``): Unique identifier (int) or username (str) of the target chat. @@ -37,8 +39,8 @@ async def mark_chat_unread( ``bool``: On success, True is returned. """ - return await self.send( - functions.messages.MarkDialogUnread( + return await self.invoke( + raw.functions.messages.MarkDialogUnread( peer=await self.resolve_peer(chat_id), unread=True ) diff --git a/pyrogram/methods/chats/pin_chat_message.py b/pyrogram/methods/chats/pin_chat_message.py index 11983f5727..8ec06e7b13 100644 --- a/pyrogram/methods/chats/pin_chat_message.py +++ b/pyrogram/methods/chats/pin_chat_message.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2021 Dan +# Copyright (C) 2017-present Dan # # This file is part of Pyrogram. # @@ -18,22 +18,24 @@ from typing import Union -from pyrogram import raw -from pyrogram.scaffold import Scaffold +import pyrogram +from pyrogram import raw, types -class PinChatMessage(Scaffold): +class PinChatMessage: async def pin_chat_message( - self, + self: "pyrogram.Client", chat_id: Union[int, str], message_id: int, disable_notification: bool = False, both_sides: bool = False, - ) -> bool: + ) -> "types.Message": """Pin a message in a group, channel or your own chat. You must be an administrator in the chat for this to work and must have the "can_pin_messages" admin right in the supergroup or "can_edit_messages" admin right in the channel. + .. include:: /_includes/usable-by/users-bots.rst + Parameters: chat_id (``int`` | ``str``): Unique identifier (int) or username (str) of the target chat. @@ -50,18 +52,18 @@ async def pin_chat_message( Applicable to private chats only. Defaults to False. Returns: - ``bool``: True on success. + :obj:`~pyrogram.types.Message`: On success, the service message is returned. Example: .. code-block:: python # Pin with notification - app.pin_chat_message(chat_id, message_id) + await app.pin_chat_message(chat_id, message_id) # Pin without notification - app.pin_chat_message(chat_id, message_id, disable_notification=True) + await app.pin_chat_message(chat_id, message_id, disable_notification=True) """ - await self.send( + r = await self.invoke( raw.functions.messages.UpdatePinnedMessage( peer=await self.resolve_peer(chat_id), id=message_id, @@ -70,4 +72,10 @@ async def pin_chat_message( ) ) - return True + users = {u.id: u for u in r.users} + chats = {c.id: c for c in r.chats} + + for i in r.updates: + if isinstance(i, (raw.types.UpdateNewMessage, + raw.types.UpdateNewChannelMessage)): + return await types.Message._parse(self, i.message, users, chats) diff --git a/pyrogram/methods/chats/promote_chat_member.py b/pyrogram/methods/chats/promote_chat_member.py index e45b58764c..e453903773 100644 --- a/pyrogram/methods/chats/promote_chat_member.py +++ b/pyrogram/methods/chats/promote_chat_member.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2021 Dan +# Copyright (C) 2017-present Dan # # This file is part of Pyrogram. # @@ -18,32 +18,24 @@ from typing import Union -from pyrogram import raw -from pyrogram.scaffold import Scaffold +import pyrogram +from pyrogram import raw, types, errors -class PromoteChatMember(Scaffold): +class PromoteChatMember: async def promote_chat_member( - self, + self: "pyrogram.Client", chat_id: Union[int, str], user_id: Union[int, str], - is_anonymous: bool = False, - can_manage_chat: bool = True, - can_change_info: bool = False, - can_post_messages: bool = False, - can_edit_messages: bool = False, - can_delete_messages: bool = False, - can_restrict_members: bool = False, - can_invite_users: bool = False, - can_pin_messages: bool = False, - can_promote_members: bool = False, - can_manage_voice_chats: bool = False + privileges: "types.ChatPrivileges" = None, ) -> bool: """Promote or demote a user in a supergroup or a channel. You must be an administrator in the chat for this to work and must have the appropriate admin rights. Pass False for all boolean parameters to demote a user. + .. include:: /_includes/usable-by/users-bots.rst + Parameters: chat_id (``int`` | ``str``): Unique identifier (int) or username (str) of the target chat. @@ -52,42 +44,8 @@ async def promote_chat_member( Unique identifier (int) or username (str) of the target user. For a contact that exists in your Telegram address book you can use his phone number (str). - is_anonymous (``bool``, *optional*): - Pass True, if the administrator's presence in the chat is hidden. - - can_manage_chat (``bool``, *optional*): - Pass True, if the administrator can access the chat event log, chat statistics, message statistics - in channels, see channel members, see anonymous administrators in supergroups and ignore slow mode. - Implied by any other administrator privilege. - - can_change_info (``bool``, *optional*): - Pass True, if the administrator can change chat title, photo and other settings. - - can_post_messages (``bool``, *optional*): - Pass True, if the administrator can create channel posts, channels only. - - can_edit_messages (``bool``, *optional*): - Pass True, if the administrator can edit messages of other users and can pin messages, channels only. - - can_delete_messages (``bool``, *optional*): - Pass True, if the administrator can delete messages of other users. - - can_restrict_members (``bool``, *optional*): - Pass True, if the administrator can restrict, ban or unban chat members. - - can_invite_users (``bool``, *optional*): - Pass True, if the administrator can invite new users to the chat. - - can_pin_messages (``bool``, *optional*): - Pass True, if the administrator can pin messages, supergroups only. - - can_promote_members (``bool``, *optional*): - Pass True, if the administrator can add new administrators with a subset of his own privileges or - demote administrators that he has promoted, directly or indirectly (promoted by administrators that - were appointed by him). - - can_manage_voice_chats (``bool``, *optional*): - Pass True, if the administration can manage voice chats (also called group calls). + privileges (:obj:`~pyrogram.types.ChatPrivileges`, *optional*): + New user privileges. Returns: ``bool``: True on success. @@ -95,27 +53,48 @@ async def promote_chat_member( Example: .. code-block:: python - # Promote chat member to supergroup admin - app.promote_chat_member(chat_id, user_id) + # Promote chat member to admin + await app.promote_chat_member(chat_id, user_id) """ - await self.send( + chat_id = await self.resolve_peer(chat_id) + user_id = await self.resolve_peer(user_id) + + # See Chat.promote_member for the reason of this (instead of setting types.ChatPrivileges() as default arg). + if privileges is None: + privileges = types.ChatPrivileges() + + try: + raw_chat_member = (await self.invoke( + raw.functions.channels.GetParticipant( + channel=chat_id, + participant=user_id + ) + )).participant + except errors.RPCError: + raw_chat_member = None + + rank = None + if isinstance(raw_chat_member, raw.types.ChannelParticipantAdmin): + rank = raw_chat_member.rank + + await self.invoke( raw.functions.channels.EditAdmin( - channel=await self.resolve_peer(chat_id), - user_id=await self.resolve_peer(user_id), + channel=chat_id, + user_id=user_id, admin_rights=raw.types.ChatAdminRights( - anonymous=is_anonymous or None, - change_info=can_change_info or None, - post_messages=can_post_messages or None, - edit_messages=can_edit_messages or None, - delete_messages=can_delete_messages or None, - ban_users=can_restrict_members or None, - invite_users=can_invite_users or None, - pin_messages=can_pin_messages or None, - add_admins=can_promote_members or None, - manage_call=can_manage_voice_chats or None, - other=can_manage_chat or None + anonymous=privileges.is_anonymous, + change_info=privileges.can_change_info, + post_messages=privileges.can_post_messages, + edit_messages=privileges.can_edit_messages, + delete_messages=privileges.can_delete_messages, + ban_users=privileges.can_restrict_members, + invite_users=privileges.can_invite_users, + pin_messages=privileges.can_pin_messages, + add_admins=privileges.can_promote_members, + manage_call=privileges.can_manage_video_chats, + other=privileges.can_manage_chat ), - rank="" + rank=rank or "" ) ) diff --git a/pyrogram/methods/chats/restrict_chat_member.py b/pyrogram/methods/chats/restrict_chat_member.py index d0cc933831..6e42b364ee 100644 --- a/pyrogram/methods/chats/restrict_chat_member.py +++ b/pyrogram/methods/chats/restrict_chat_member.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2021 Dan +# Copyright (C) 2017-present Dan # # This file is part of Pyrogram. # @@ -16,26 +16,29 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . +from datetime import datetime from typing import Union -from pyrogram import raw +import pyrogram +from pyrogram import raw, utils from pyrogram import types -from pyrogram.scaffold import Scaffold -class RestrictChatMember(Scaffold): +class RestrictChatMember: async def restrict_chat_member( - self, + self: "pyrogram.Client", chat_id: Union[int, str], user_id: Union[int, str], permissions: "types.ChatPermissions", - until_date: int = 0 + until_date: datetime = utils.zero_datetime() ) -> "types.Chat": """Restrict a user in a supergroup. You must be an administrator in the supergroup for this to work and must have the appropriate admin rights. Pass True for all permissions to lift restrictions from a user. + .. include:: /_includes/usable-by/users-bots.rst + Parameters: chat_id (``int`` | ``str``): Unique identifier (int) or username (str) of the target chat. @@ -47,10 +50,10 @@ async def restrict_chat_member( permissions (:obj:`~pyrogram.types.ChatPermissions`): New user permissions. - until_date (``int``, *optional*): - Date when the user will be unbanned, unix time. + until_date (:py:obj:`~datetime.datetime`, *optional*): + Date when the user will be unbanned. If user is banned for more than 366 days or less than 30 seconds from the current time they are - considered to be banned forever. Defaults to 0 (ban forever). + considered to be banned forever. Defaults to epoch (ban forever). Returns: :obj:`~pyrogram.types.Chat`: On success, a chat object is returned. @@ -58,36 +61,37 @@ async def restrict_chat_member( Example: .. code-block:: python - from time import time - + from datetime import datetime, timedelta from pyrogram.types import ChatPermissions # Completely restrict chat member (mute) forever - app.restrict_chat_member(chat_id, user_id, ChatPermissions()) + await app.restrict_chat_member(chat_id, user_id, ChatPermissions()) # Chat member muted for 24h - app.restrict_chat_member(chat_id, user_id, ChatPermissions(), int(time() + 86400)) + await app.restrict_chat_member(chat_id, user_id, ChatPermissions(), + datetime.now() + timedelta(days=1)) # Chat member can only send text messages - app.restrict_chat_member(chat_id, user_id, ChatPermissions(can_send_messages=True)) + await app.restrict_chat_member(chat_id, user_id, + ChatPermissions(can_send_messages=True)) """ - r = await self.send( + r = await self.invoke( raw.functions.channels.EditBanned( channel=await self.resolve_peer(chat_id), participant=await self.resolve_peer(user_id), banned_rights=raw.types.ChatBannedRights( - until_date=until_date, - send_messages=True if not permissions.can_send_messages else None, - send_media=True if not permissions.can_send_media_messages else None, - send_stickers=True if not permissions.can_send_other_messages else None, - send_gifs=True if not permissions.can_send_other_messages else None, - send_games=True if not permissions.can_send_other_messages else None, - send_inline=True if not permissions.can_send_other_messages else None, - embed_links=True if not permissions.can_add_web_page_previews else None, - send_polls=True if not permissions.can_send_polls else None, - change_info=True if not permissions.can_change_info else None, - invite_users=True if not permissions.can_invite_users else None, - pin_messages=True if not permissions.can_pin_messages else None, + until_date=utils.datetime_to_timestamp(until_date), + send_messages=not permissions.can_send_messages, + send_media=not permissions.can_send_media_messages, + send_stickers=not permissions.can_send_other_messages, + send_gifs=not permissions.can_send_other_messages, + send_games=not permissions.can_send_other_messages, + send_inline=not permissions.can_send_other_messages, + embed_links=not permissions.can_add_web_page_previews, + send_polls=not permissions.can_send_polls, + change_info=not permissions.can_change_info, + invite_users=not permissions.can_invite_users, + pin_messages=not permissions.can_pin_messages, ) ) ) diff --git a/pyrogram/methods/chats/set_administrator_title.py b/pyrogram/methods/chats/set_administrator_title.py index fff4d61731..2c77066ed7 100644 --- a/pyrogram/methods/chats/set_administrator_title.py +++ b/pyrogram/methods/chats/set_administrator_title.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2021 Dan +# Copyright (C) 2017-present Dan # # This file is part of Pyrogram. # @@ -18,13 +18,13 @@ from typing import Union +import pyrogram from pyrogram import raw -from pyrogram.scaffold import Scaffold -class SetAdministratorTitle(Scaffold): +class SetAdministratorTitle: async def set_administrator_title( - self, + self: "pyrogram.Client", chat_id: Union[int, str], user_id: Union[int, str], title: str, @@ -34,6 +34,8 @@ async def set_administrator_title( If you are an administrator of a supergroup (i.e. not the owner), you can only set the title of other administrators who have been promoted by you. If you are the owner, you can change every administrator's title. + .. include:: /_includes/usable-by/users-bots.rst + Parameters: chat_id (``int`` | ``str``): Unique identifier (int) or username (str) of the target chat. @@ -52,12 +54,12 @@ async def set_administrator_title( Example: .. code-block:: python - app.set_administrator_title(chat_id, user_id, "ฅ^•ﻌ•^ฅ") + await app.set_administrator_title(chat_id, user_id, "Admin Title") """ chat_id = await self.resolve_peer(chat_id) user_id = await self.resolve_peer(user_id) - r = (await self.send( + r = (await self.invoke( raw.functions.channels.GetParticipant( channel=chat_id, participant=user_id @@ -65,46 +67,13 @@ async def set_administrator_title( )).participant if isinstance(r, raw.types.ChannelParticipantCreator): - admin_rights = raw.types.ChatAdminRights( - change_info=True, - post_messages=True, - edit_messages=True, - delete_messages=True, - ban_users=True, - invite_users=True, - pin_messages=True, - add_admins=True, - ) + admin_rights = raw.types.ChatAdminRights() elif isinstance(r, raw.types.ChannelParticipantAdmin): admin_rights = r.admin_rights else: raise ValueError("Custom titles can only be applied to owners or administrators of supergroups") - if not admin_rights.change_info: - admin_rights.change_info = None - - if not admin_rights.post_messages: - admin_rights.post_messages = None - - if not admin_rights.edit_messages: - admin_rights.edit_messages = None - - if not admin_rights.delete_messages: - admin_rights.delete_messages = None - - if not admin_rights.ban_users: - admin_rights.ban_users = None - - if not admin_rights.invite_users: - admin_rights.invite_users = None - - if not admin_rights.pin_messages: - admin_rights.pin_messages = None - - if not admin_rights.add_admins: - admin_rights.add_admins = None - - await self.send( + await self.invoke( raw.functions.channels.EditAdmin( channel=chat_id, user_id=user_id, diff --git a/pyrogram/methods/chats/set_chat_description.py b/pyrogram/methods/chats/set_chat_description.py index 0a7bdf7f87..563e990c4c 100644 --- a/pyrogram/methods/chats/set_chat_description.py +++ b/pyrogram/methods/chats/set_chat_description.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2021 Dan +# Copyright (C) 2017-present Dan # # This file is part of Pyrogram. # @@ -18,19 +18,21 @@ from typing import Union +import pyrogram from pyrogram import raw -from pyrogram.scaffold import Scaffold -class SetChatDescription(Scaffold): +class SetChatDescription: async def set_chat_description( - self, + self: "pyrogram.Client", chat_id: Union[int, str], description: str ) -> bool: """Change the description of a supergroup or a channel. You must be an administrator in the chat for this to work and must have the appropriate admin rights. + .. include:: /_includes/usable-by/users-bots.rst + Parameters: chat_id (``int`` | ``str``): Unique identifier (int) or username (str) of the target chat. @@ -47,12 +49,12 @@ async def set_chat_description( Example: .. code-block:: python - app.set_chat_description(chat_id, "New Description") + await app.set_chat_description(chat_id, "New Description") """ peer = await self.resolve_peer(chat_id) if isinstance(peer, (raw.types.InputPeerChannel, raw.types.InputPeerChat)): - await self.send( + await self.invoke( raw.functions.messages.EditChatAbout( peer=peer, about=description diff --git a/pyrogram/methods/chats/set_chat_permissions.py b/pyrogram/methods/chats/set_chat_permissions.py index fe208818b1..d8ec0cf02b 100644 --- a/pyrogram/methods/chats/set_chat_permissions.py +++ b/pyrogram/methods/chats/set_chat_permissions.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2021 Dan +# Copyright (C) 2017-present Dan # # This file is part of Pyrogram. # @@ -18,14 +18,14 @@ from typing import Union +import pyrogram from pyrogram import raw from pyrogram import types -from pyrogram.scaffold import Scaffold -class SetChatPermissions(Scaffold): +class SetChatPermissions: async def set_chat_permissions( - self, + self: "pyrogram.Client", chat_id: Union[int, str], permissions: "types.ChatPermissions", ) -> "types.Chat": @@ -34,6 +34,8 @@ async def set_chat_permissions( You must be an administrator in the group or a supergroup for this to work and must have the *can_restrict_members* admin rights. + .. include:: /_includes/usable-by/users-bots.rst + Parameters: chat_id (``int`` | ``str``): Unique identifier (int) or username (str) of the target chat. @@ -50,10 +52,10 @@ async def set_chat_permissions( from pyrogram.types import ChatPermissions # Completely restrict chat - app.set_chat_permissions(chat_id, ChatPermissions()) + await app.set_chat_permissions(chat_id, ChatPermissions()) # Chat members can only send text messages and media messages - app.set_chat_permissions( + await app.set_chat_permissions( chat_id, ChatPermissions( can_send_messages=True, @@ -62,22 +64,22 @@ async def set_chat_permissions( ) """ - r = await self.send( + r = await self.invoke( raw.functions.messages.EditChatDefaultBannedRights( peer=await self.resolve_peer(chat_id), banned_rights=raw.types.ChatBannedRights( until_date=0, - send_messages=True if not permissions.can_send_messages else None, - send_media=True if not permissions.can_send_media_messages else None, - send_stickers=permissions.can_send_other_messages, - send_gifs=permissions.can_send_other_messages, - send_games=permissions.can_send_other_messages, - send_inline=permissions.can_send_other_messages, - embed_links=True if not permissions.can_add_web_page_previews else None, - send_polls=True if not permissions.can_send_polls else None, - change_info=True if not permissions.can_change_info else None, - invite_users=True if not permissions.can_invite_users else None, - pin_messages=True if not permissions.can_pin_messages else None, + send_messages=not permissions.can_send_messages, + send_media=not permissions.can_send_media_messages, + send_stickers=not permissions.can_send_other_messages, + send_gifs=not permissions.can_send_other_messages, + send_games=not permissions.can_send_other_messages, + send_inline=not permissions.can_send_other_messages, + embed_links=not permissions.can_add_web_page_previews, + send_polls=not permissions.can_send_polls, + change_info=not permissions.can_change_info, + invite_users=not permissions.can_invite_users, + pin_messages=not permissions.can_pin_messages, ) ) ) diff --git a/pyrogram/methods/chats/set_chat_photo.py b/pyrogram/methods/chats/set_chat_photo.py index e111449040..f3db532991 100644 --- a/pyrogram/methods/chats/set_chat_photo.py +++ b/pyrogram/methods/chats/set_chat_photo.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2021 Dan +# Copyright (C) 2017-present Dan # # This file is part of Pyrogram. # @@ -19,19 +19,20 @@ import os from typing import Union, BinaryIO +import pyrogram from pyrogram import raw from pyrogram import utils from pyrogram.file_id import FileType -from pyrogram.scaffold import Scaffold -class SetChatPhoto(Scaffold): +class SetChatPhoto: async def set_chat_photo( - self, + self: "pyrogram.Client", chat_id: Union[int, str], *, photo: Union[str, BinaryIO] = None, - video: Union[str, BinaryIO] = None + video: Union[str, BinaryIO] = None, + video_start_ts: float = None, ) -> bool: """Set a new chat photo or video (H.264/MPEG-4 AVC video, max 5 seconds). @@ -40,6 +41,8 @@ async def set_chat_photo( You must be an administrator in the chat for this to work and must have the appropriate admin rights. + .. include:: /_includes/usable-by/users-bots.rst + Parameters: chat_id (``int`` | ``str``): Unique identifier (int) or username (str) of the target chat. @@ -54,6 +57,9 @@ async def set_chat_photo( from your local machine or a binary file-like object with its attribute ".name" set for in-memory uploads. + video_start_ts (``float``, *optional*): + The timestamp in seconds of the video frame to use as photo profile preview. + Returns: ``bool``: True on success. @@ -64,17 +70,17 @@ async def set_chat_photo( .. code-block:: python # Set chat photo using a local file - app.set_chat_photo(chat_id, photo="photo.jpg") + await app.set_chat_photo(chat_id, photo="photo.jpg") - # Set chat photo using an exiting Photo file_id - app.set_chat_photo(chat_id, photo=photo.file_id) + # Set chat photo using an existing Photo file_id + await app.set_chat_photo(chat_id, photo=photo.file_id) # Set chat video using a local file - app.set_chat_photo(chat_id, video="video.mp4") + await app.set_chat_photo(chat_id, video="video.mp4") - # Set chat photo using an exiting Video file_id - app.set_chat_photo(chat_id, video=video.file_id) + # Set chat photo using an existing Video file_id + await app.set_chat_photo(chat_id, video=video.file_id) """ peer = await self.resolve_peer(chat_id) @@ -82,7 +88,8 @@ async def set_chat_photo( if os.path.isfile(photo): photo = raw.types.InputChatUploadedPhoto( file=await self.save_file(photo), - video=await self.save_file(video) + video=await self.save_file(video), + video_start_ts=video_start_ts, ) else: photo = utils.get_input_media_from_file_id(photo, FileType.PHOTO) @@ -90,18 +97,19 @@ async def set_chat_photo( else: photo = raw.types.InputChatUploadedPhoto( file=await self.save_file(photo), - video=await self.save_file(video) + video=await self.save_file(video), + video_start_ts=video_start_ts, ) if isinstance(peer, raw.types.InputPeerChat): - await self.send( + await self.invoke( raw.functions.messages.EditChatPhoto( chat_id=peer.chat_id, - photo=photo + photo=photo, ) ) elif isinstance(peer, raw.types.InputPeerChannel): - await self.send( + await self.invoke( raw.functions.channels.EditPhoto( channel=peer, photo=photo diff --git a/pyrogram/methods/chats/set_chat_protected_content.py b/pyrogram/methods/chats/set_chat_protected_content.py new file mode 100644 index 0000000000..b6a89c1190 --- /dev/null +++ b/pyrogram/methods/chats/set_chat_protected_content.py @@ -0,0 +1,53 @@ +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-present Dan +# +# This file is part of Pyrogram. +# +# Pyrogram is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License as published +# by the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Pyrogram is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with Pyrogram. If not, see . + +from typing import Union + +import pyrogram +from pyrogram import raw + + +class SetChatProtectedContent: + async def set_chat_protected_content( + self: "pyrogram.Client", + chat_id: Union[int, str], + enabled: bool + ) -> bool: + """Set the chat protected content setting. + + .. include:: /_includes/usable-by/users-bots.rst + + Parameters: + chat_id (``int`` | ``str``): + Unique identifier (int) or username (str) of the target chat. + + enabled (``bool``): + Pass True to enable the protected content setting, False to disable. + + Returns: + ``bool``: On success, True is returned. + """ + + await self.invoke( + raw.functions.messages.ToggleNoForwards( + peer=await self.resolve_peer(chat_id), + enabled=enabled + ) + ) + + return True diff --git a/pyrogram/methods/chats/set_chat_title.py b/pyrogram/methods/chats/set_chat_title.py index 0cc4c6d2e4..9a963571c0 100644 --- a/pyrogram/methods/chats/set_chat_title.py +++ b/pyrogram/methods/chats/set_chat_title.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2021 Dan +# Copyright (C) 2017-present Dan # # This file is part of Pyrogram. # @@ -18,13 +18,13 @@ from typing import Union +import pyrogram from pyrogram import raw -from pyrogram.scaffold import Scaffold -class SetChatTitle(Scaffold): +class SetChatTitle: async def set_chat_title( - self, + self: "pyrogram.Client", chat_id: Union[int, str], title: str ) -> bool: @@ -36,6 +36,8 @@ async def set_chat_title( In regular groups (non-supergroups), this method will only work if the "All Members Are Admins" setting is off. + .. include:: /_includes/usable-by/users-bots.rst + Parameters: chat_id (``int`` | ``str``): Unique identifier (int) or username (str) of the target chat. @@ -52,19 +54,19 @@ async def set_chat_title( Example: .. code-block:: python - app.set_chat_title(chat_id, "New Title") + await app.set_chat_title(chat_id, "New Title") """ peer = await self.resolve_peer(chat_id) if isinstance(peer, raw.types.InputPeerChat): - await self.send( + await self.invoke( raw.functions.messages.EditChatTitle( chat_id=peer.chat_id, title=title ) ) elif isinstance(peer, raw.types.InputPeerChannel): - await self.send( + await self.invoke( raw.functions.channels.EditTitle( channel=peer, title=title diff --git a/pyrogram/methods/chats/update_chat_username.py b/pyrogram/methods/chats/set_chat_username.py similarity index 78% rename from pyrogram/methods/chats/update_chat_username.py rename to pyrogram/methods/chats/set_chat_username.py index 297d83a24c..e6d64e98c1 100644 --- a/pyrogram/methods/chats/update_chat_username.py +++ b/pyrogram/methods/chats/set_chat_username.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2021 Dan +# Copyright (C) 2017-present Dan # # This file is part of Pyrogram. # @@ -18,23 +18,26 @@ from typing import Union, Optional +import pyrogram from pyrogram import raw -from pyrogram.scaffold import Scaffold -class UpdateChatUsername(Scaffold): - async def update_chat_username( - self, +class SetChatUsername: + async def set_chat_username( + self: "pyrogram.Client", chat_id: Union[int, str], username: Optional[str] ) -> bool: - """Update a channel or a supergroup username. + """Set a channel or a supergroup username. - To update your own username (for users only, not bots) you can use :meth:`~pyrogram.Client.update_username`. + To set your own username (for users only, not bots) you can use :meth:`~pyrogram.Client.set_username`. + + .. include:: /_includes/usable-by/users.rst Parameters: chat_id (``int`` | ``str``) Unique identifier (int) or username (str) of the target chat. + username (``str`` | ``None``): Username to set. Pass "" (empty string) or None to remove the username. @@ -47,14 +50,14 @@ async def update_chat_username( Example: .. code-block:: python - app.update_chat_username(chat_id, "new_username") + await app.set_chat_username(chat_id, "new_username") """ peer = await self.resolve_peer(chat_id) if isinstance(peer, raw.types.InputPeerChannel): return bool( - await self.send( + await self.invoke( raw.functions.channels.UpdateUsername( channel=peer, username=username or "" diff --git a/pyrogram/methods/chats/set_send_as_chat.py b/pyrogram/methods/chats/set_send_as_chat.py new file mode 100644 index 0000000000..15201f3bea --- /dev/null +++ b/pyrogram/methods/chats/set_send_as_chat.py @@ -0,0 +1,57 @@ +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-present Dan +# +# This file is part of Pyrogram. +# +# Pyrogram is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License as published +# by the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Pyrogram is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with Pyrogram. If not, see . + +from typing import Union + +import pyrogram +from pyrogram import raw + + +class SetSendAsChat: + async def set_send_as_chat( + self: "pyrogram.Client", + chat_id: Union[int, str], + send_as_chat_id: Union[int, str] + ) -> bool: + """Set the default "send_as" chat for a chat. + + Use :meth:`~pyrogram.Client.get_send_as_chats` to get all the "send_as" chats available for use. + + .. include:: /_includes/usable-by/users.rst + + Parameters: + chat_id (``int`` | ``str``): + Unique identifier (int) or username (str) of the target chat. + + send_as_chat_id (``int`` | ``str``): + Unique identifier (int) or username (str) of the send_as chat. + + Returns: + ``bool``: On success, true is returned + + Example: + .. code-block:: python + + await app.set_send_as_chat(chat_id, send_as_chat_id) + """ + return await self.invoke( + raw.functions.messages.SaveDefaultSendAs( + peer=await self.resolve_peer(chat_id), + send_as=await self.resolve_peer(send_as_chat_id) + ) + ) diff --git a/pyrogram/methods/chats/set_slow_mode.py b/pyrogram/methods/chats/set_slow_mode.py index 19e64925a8..60e88e6de5 100644 --- a/pyrogram/methods/chats/set_slow_mode.py +++ b/pyrogram/methods/chats/set_slow_mode.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2021 Dan +# Copyright (C) 2017-present Dan # # This file is part of Pyrogram. # @@ -18,18 +18,20 @@ from typing import Union, Optional +import pyrogram from pyrogram import raw -from pyrogram.scaffold import Scaffold -class SetSlowMode(Scaffold): +class SetSlowMode: async def set_slow_mode( - self, + self: "pyrogram.Client", chat_id: Union[int, str], seconds: Optional[int] ) -> bool: """Set the slow mode interval for a chat. + .. include:: /_includes/usable-by/users-bots.rst + Parameters: chat_id (``int`` | ``str``): Unique identifier (int) or username (str) of the target chat. @@ -45,13 +47,13 @@ async def set_slow_mode( .. code-block:: python # Set slow mode to 60 seconds - app.set_slow_mode("pyrogramchat", 60) + await app.set_slow_mode(chat_id, 60) # Disable slow mode - app.set_slow_mode("pyrogramchat", None) + await app.set_slow_mode(chat_id, None) """ - await self.send( + await self.invoke( raw.functions.channels.ToggleSlowMode( channel=await self.resolve_peer(chat_id), seconds=seconds or 0 diff --git a/pyrogram/methods/chats/unarchive_chats.py b/pyrogram/methods/chats/unarchive_chats.py index e49c7afdb3..4d2cb0e411 100644 --- a/pyrogram/methods/chats/unarchive_chats.py +++ b/pyrogram/methods/chats/unarchive_chats.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2021 Dan +# Copyright (C) 2017-present Dan # # This file is part of Pyrogram. # @@ -18,17 +18,19 @@ from typing import Union, List +import pyrogram from pyrogram import raw -from pyrogram.scaffold import Scaffold -class UnarchiveChats(Scaffold): +class UnarchiveChats: async def unarchive_chats( - self, + self: "pyrogram.Client", chat_ids: Union[int, str, List[Union[int, str]]], ) -> bool: """Unarchive one or more chats. + .. include:: /_includes/usable-by/users.rst + Parameters: chat_ids (``int`` | ``str`` | List[``int``, ``str``]): Unique identifier (int) or username (str) of the target chat. @@ -41,10 +43,10 @@ async def unarchive_chats( .. code-block:: python # Unarchive chat - app.unarchive_chats(chat_id) + await app.unarchive_chats(chat_id) # Unarchive multiple chats at once - app.unarchive_chats([chat_id1, chat_id2, chat_id3]) + await app.unarchive_chats([chat_id1, chat_id2, chat_id3]) """ if not isinstance(chat_ids, list): @@ -60,7 +62,7 @@ async def unarchive_chats( ) ) - await self.send( + await self.invoke( raw.functions.folders.EditPeerFolders( folder_peers=folder_peers ) diff --git a/pyrogram/methods/chats/unban_chat_member.py b/pyrogram/methods/chats/unban_chat_member.py index 44c19286a6..bbe7b45492 100644 --- a/pyrogram/methods/chats/unban_chat_member.py +++ b/pyrogram/methods/chats/unban_chat_member.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2021 Dan +# Copyright (C) 2017-present Dan # # This file is part of Pyrogram. # @@ -18,20 +18,22 @@ from typing import Union +import pyrogram from pyrogram import raw -from pyrogram.scaffold import Scaffold -class UnbanChatMember(Scaffold): +class UnbanChatMember: async def unban_chat_member( - self, + self: "pyrogram.Client", chat_id: Union[int, str], user_id: Union[int, str] ) -> bool: - """Unban a previously kicked user in a supergroup or channel. + """Unban a previously banned user in a supergroup or channel. The user will **not** return to the group or channel automatically, but will be able to join via link, etc. You must be an administrator for this to work. + .. include:: /_includes/usable-by/users-bots.rst + Parameters: chat_id (``int`` | ``str``): Unique identifier (int) or username (str) of the target chat. @@ -47,9 +49,9 @@ async def unban_chat_member( .. code-block:: python # Unban chat member right now - app.unban_chat_member(chat_id, user_id) + await app.unban_chat_member(chat_id, user_id) """ - await self.send( + await self.invoke( raw.functions.channels.EditBanned( channel=await self.resolve_peer(chat_id), participant=await self.resolve_peer(user_id), diff --git a/pyrogram/methods/chats/unpin_all_chat_messages.py b/pyrogram/methods/chats/unpin_all_chat_messages.py index 2996f511de..25a53caf62 100644 --- a/pyrogram/methods/chats/unpin_all_chat_messages.py +++ b/pyrogram/methods/chats/unpin_all_chat_messages.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2021 Dan +# Copyright (C) 2017-present Dan # # This file is part of Pyrogram. # @@ -18,19 +18,21 @@ from typing import Union +import pyrogram from pyrogram import raw -from pyrogram.scaffold import Scaffold -class UnpinAllChatMessages(Scaffold): +class UnpinAllChatMessages: async def unpin_all_chat_messages( - self, + self: "pyrogram.Client", chat_id: Union[int, str], ) -> bool: """Use this method to clear the list of pinned messages in a chat. If the chat is not a private chat, the bot must be an administrator in the chat for this to work and must have the 'can_pin_messages' admin right in a supergroup or 'can_edit_messages' admin right in a channel. + .. include:: /_includes/usable-by/users-bots.rst + Parameters: chat_id (``int`` | ``str``): Unique identifier (int) or username (str) of the target chat. @@ -42,9 +44,9 @@ async def unpin_all_chat_messages( .. code-block:: python # Unpin all chat messages - app.unpin_all_chat_messages(chat_id) + await app.unpin_all_chat_messages(chat_id) """ - await self.send( + await self.invoke( raw.functions.messages.UnpinAllMessages( peer=await self.resolve_peer(chat_id) ) diff --git a/pyrogram/methods/chats/unpin_chat_message.py b/pyrogram/methods/chats/unpin_chat_message.py index fe4b71a244..6c8e036e0d 100644 --- a/pyrogram/methods/chats/unpin_chat_message.py +++ b/pyrogram/methods/chats/unpin_chat_message.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2021 Dan +# Copyright (C) 2017-present Dan # # This file is part of Pyrogram. # @@ -18,13 +18,13 @@ from typing import Union +import pyrogram from pyrogram import raw -from pyrogram.scaffold import Scaffold -class UnpinChatMessage(Scaffold): +class UnpinChatMessage: async def unpin_chat_message( - self, + self: "pyrogram.Client", chat_id: Union[int, str], message_id: int = 0 ) -> bool: @@ -32,6 +32,8 @@ async def unpin_chat_message( You must be an administrator in the chat for this to work and must have the "can_pin_messages" admin right in the supergroup or "can_edit_messages" admin right in the channel. + .. include:: /_includes/usable-by/users-bots.rst + Parameters: chat_id (``int`` | ``str``): Unique identifier (int) or username (str) of the target chat. @@ -46,9 +48,9 @@ async def unpin_chat_message( Example: .. code-block:: python - app.unpin_chat_message(chat_id, message_id) + await app.unpin_chat_message(chat_id, message_id) """ - await self.send( + await self.invoke( raw.functions.messages.UpdatePinnedMessage( peer=await self.resolve_peer(chat_id), id=message_id, diff --git a/pyrogram/methods/contacts/__init__.py b/pyrogram/methods/contacts/__init__.py index b542b665ea..5849ce4389 100644 --- a/pyrogram/methods/contacts/__init__.py +++ b/pyrogram/methods/contacts/__init__.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2021 Dan +# Copyright (C) 2017-present Dan # # This file is part of Pyrogram. # diff --git a/pyrogram/methods/contacts/add_contact.py b/pyrogram/methods/contacts/add_contact.py index 5d00253bcb..9c4faa7e48 100644 --- a/pyrogram/methods/contacts/add_contact.py +++ b/pyrogram/methods/contacts/add_contact.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2021 Dan +# Copyright (C) 2017-present Dan # # This file is part of Pyrogram. # @@ -18,14 +18,14 @@ from typing import Union +import pyrogram from pyrogram import raw from pyrogram import types -from pyrogram.scaffold import Scaffold -class AddContact(Scaffold): +class AddContact: async def add_contact( - self, + self: "pyrogram.Client", user_id: Union[int, str], first_name: str, last_name: str = "", @@ -34,11 +34,13 @@ async def add_contact( ): """Add an existing Telegram user as contact, even without a phone number. + .. include:: /_includes/usable-by/users.rst + Parameters: user_id (``int`` | ``str``): Unique identifier (int) or username (str) of the target user. - first_name (``str``, *optional*): + first_name (``str``): User's first name. last_name (``str``, *optional*): @@ -57,10 +59,13 @@ async def add_contact( Example: .. code-block:: python - app.add_contact(12345678, "Foo") - app.add_contact("username", "Bar") + # Add contact by id + await app.add_contact(12345678, "Foo") + + # Add contact by username + await app.add_contact("username", "Bar") """ - r = await self.send( + r = await self.invoke( raw.functions.contacts.AddContact( id=await self.resolve_peer(user_id), first_name=first_name, diff --git a/pyrogram/methods/contacts/delete_contacts.py b/pyrogram/methods/contacts/delete_contacts.py index 6b73fa7434..7f08f29730 100644 --- a/pyrogram/methods/contacts/delete_contacts.py +++ b/pyrogram/methods/contacts/delete_contacts.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2021 Dan +# Copyright (C) 2017-present Dan # # This file is part of Pyrogram. # @@ -18,20 +18,22 @@ from typing import List, Union +import pyrogram from pyrogram import raw, types -from pyrogram.scaffold import Scaffold -class DeleteContacts(Scaffold): +class DeleteContacts: async def delete_contacts( - self, + self: "pyrogram.Client", user_ids: Union[int, str, List[Union[int, str]]] ) -> Union["types.User", List["types.User"], None]: """Delete contacts from your Telegram address book. + .. include:: /_includes/usable-by/users.rst + Parameters: user_ids (``int`` | ``str`` | List of ``int`` or ``str``): - A single user id/username o a list of user identifiers (id or username). + A single user id/username or a list of user identifiers (id or username). Returns: :obj:`~pyrogram.types.User` | List of :obj:`~pyrogram.types.User` | ``None``: In case *user_ids* was an @@ -42,15 +44,15 @@ async def delete_contacts( Example: .. code-block:: python - app.delete_contacts(user_id) - app.delete_contacts([user_id1, user_id2, user_id3]) + await app.delete_contacts(user_id) + await app.delete_contacts([user_id1, user_id2, user_id3]) """ - is_user_ids_list = isinstance(user_ids, list) + is_list = isinstance(user_ids, list) - if not is_user_ids_list: + if not is_list: user_ids = [user_ids] - r = await self.send( + r = await self.invoke( raw.functions.contacts.DeleteContacts( id=[await self.resolve_peer(i) for i in user_ids] ) @@ -61,7 +63,4 @@ async def delete_contacts( users = types.List([types.User._parse(self, i) for i in r.users]) - if is_user_ids_list: - return users - else: - return users[0] + return users if is_list else users[0] diff --git a/pyrogram/methods/contacts/get_contacts.py b/pyrogram/methods/contacts/get_contacts.py index 9b546a16f2..763f9a3058 100644 --- a/pyrogram/methods/contacts/get_contacts.py +++ b/pyrogram/methods/contacts/get_contacts.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2021 Dan +# Copyright (C) 2017-present Dan # # This file is part of Pyrogram. # @@ -19,25 +19,29 @@ import logging from typing import List +import pyrogram from pyrogram import raw from pyrogram import types -from pyrogram.scaffold import Scaffold log = logging.getLogger(__name__) -class GetContacts(Scaffold): - async def get_contacts(self) -> List["types.User"]: +class GetContacts: + async def get_contacts( + self: "pyrogram.Client" + ) -> List["types.User"]: """Get contacts from your Telegram address book. + .. include:: /_includes/usable-by/users.rst + Returns: List of :obj:`~pyrogram.types.User`: On success, a list of users is returned. Example: .. code-block:: python - contacts = app.get_contacts() + contacts = await app.get_contacts() print(contacts) """ - contacts = await self.send(raw.functions.contacts.GetContacts(hash=0)) + contacts = await self.invoke(raw.functions.contacts.GetContacts(hash=0)) return types.List(types.User._parse(self, user) for user in contacts.users) diff --git a/pyrogram/methods/contacts/get_contacts_count.py b/pyrogram/methods/contacts/get_contacts_count.py index f58fb96c63..56120bac71 100644 --- a/pyrogram/methods/contacts/get_contacts_count.py +++ b/pyrogram/methods/contacts/get_contacts_count.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2021 Dan +# Copyright (C) 2017-present Dan # # This file is part of Pyrogram. # @@ -16,22 +16,26 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . +import pyrogram from pyrogram import raw -from pyrogram.scaffold import Scaffold -class GetContactsCount(Scaffold): - async def get_contacts_count(self) -> int: +class GetContactsCount: + async def get_contacts_count( + self: "pyrogram.Client" + ) -> int: """Get the total count of contacts from your Telegram address book. + .. include:: /_includes/usable-by/users.rst + Returns: ``int``: On success, the contacts count is returned. Example: .. code-block:: python - count = app.get_contacts_count() + count = await app.get_contacts_count() print(count) """ - return len((await self.send(raw.functions.contacts.GetContacts(hash=0))).contacts) + return len((await self.invoke(raw.functions.contacts.GetContacts(hash=0))).contacts) diff --git a/pyrogram/methods/contacts/import_contacts.py b/pyrogram/methods/contacts/import_contacts.py index 4039cbe500..aa8fd4ae37 100644 --- a/pyrogram/methods/contacts/import_contacts.py +++ b/pyrogram/methods/contacts/import_contacts.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2021 Dan +# Copyright (C) 2017-present Dan # # This file is part of Pyrogram. # @@ -18,18 +18,20 @@ from typing import List +import pyrogram from pyrogram import raw from pyrogram import types -from pyrogram.scaffold import Scaffold -class ImportContacts(Scaffold): +class ImportContacts: async def import_contacts( - self, + self: "pyrogram.Client", contacts: List["types.InputPhoneContact"] ): """Import contacts to your Telegram address book. + .. include:: /_includes/usable-by/users.rst + Parameters: contacts (List of :obj:`~pyrogram.types.InputPhoneContact`): The contact list to be added @@ -42,12 +44,12 @@ async def import_contacts( from pyrogram.types import InputPhoneContact - app.import_contacts([ - InputPhoneContact("39123456789", "Foo"), - InputPhoneContact("38987654321", "Bar"), - InputPhoneContact("01234567891", "Baz")]) + await app.import_contacts([ + InputPhoneContact("+1-123-456-7890", "Foo"), + InputPhoneContact("+1-456-789-0123", "Bar"), + InputPhoneContact("+1-789-012-3456", "Baz")]) """ - imported_contacts = await self.send( + imported_contacts = await self.invoke( raw.functions.contacts.ImportContacts( contacts=contacts ) diff --git a/pyrogram/methods/decorators/__init__.py b/pyrogram/methods/decorators/__init__.py index 01ddffcee6..1fc61185c9 100644 --- a/pyrogram/methods/decorators/__init__.py +++ b/pyrogram/methods/decorators/__init__.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2021 Dan +# Copyright (C) 2017-present Dan # # This file is part of Pyrogram. # @@ -17,10 +17,12 @@ # along with Pyrogram. If not, see . from .on_callback_query import OnCallbackQuery +from .on_chat_join_request import OnChatJoinRequest from .on_chat_member_updated import OnChatMemberUpdated from .on_chosen_inline_result import OnChosenInlineResult from .on_deleted_messages import OnDeletedMessages from .on_disconnect import OnDisconnect +from .on_edited_message import OnEditedMessage from .on_inline_query import OnInlineQuery from .on_message import OnMessage from .on_poll import OnPoll @@ -30,6 +32,7 @@ class Decorators( OnMessage, + OnEditedMessage, OnDeletedMessages, OnCallbackQuery, OnRawUpdate, @@ -38,6 +41,7 @@ class Decorators( OnInlineQuery, OnPoll, OnChosenInlineResult, - OnChatMemberUpdated + OnChatMemberUpdated, + OnChatJoinRequest ): pass diff --git a/pyrogram/methods/decorators/on_callback_query.py b/pyrogram/methods/decorators/on_callback_query.py index 884fbb95f3..07e15a3e78 100644 --- a/pyrogram/methods/decorators/on_callback_query.py +++ b/pyrogram/methods/decorators/on_callback_query.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2021 Dan +# Copyright (C) 2017-present Dan # # This file is part of Pyrogram. # @@ -20,15 +20,14 @@ import pyrogram from pyrogram.filters import Filter -from pyrogram.scaffold import Scaffold -class OnCallbackQuery(Scaffold): +class OnCallbackQuery: def on_callback_query( self=None, filters=None, group: int = 0 - ) -> callable: + ) -> Callable: """Decorator for handling callback queries. This does the same thing as :meth:`~pyrogram.Client.add_handler` using the diff --git a/pyrogram/methods/decorators/on_chat_join_request.py b/pyrogram/methods/decorators/on_chat_join_request.py new file mode 100644 index 0000000000..57fb709cb5 --- /dev/null +++ b/pyrogram/methods/decorators/on_chat_join_request.py @@ -0,0 +1,60 @@ +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-present Dan +# +# This file is part of Pyrogram. +# +# Pyrogram is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License as published +# by the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Pyrogram is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with Pyrogram. If not, see . + +from typing import Callable + +import pyrogram +from pyrogram.filters import Filter + + +class OnChatJoinRequest: + def on_chat_join_request( + self=None, + filters=None, + group: int = 0 + ) -> Callable: + """Decorator for handling chat join requests. + + This does the same thing as :meth:`~pyrogram.Client.add_handler` using the + :obj:`~pyrogram.handlers.ChatJoinRequestHandler`. + + Parameters: + filters (:obj:`~pyrogram.filters`, *optional*): + Pass one or more filters to allow only a subset of updates to be passed in your function. + + group (``int``, *optional*): + The group identifier, defaults to 0. + """ + + def decorator(func: Callable) -> Callable: + if isinstance(self, pyrogram.Client): + self.add_handler(pyrogram.handlers.ChatJoinRequestHandler(func, filters), group) + elif isinstance(self, Filter) or self is None: + if not hasattr(func, "handlers"): + func.handlers = [] + + func.handlers.append( + ( + pyrogram.handlers.ChatJoinRequestHandler(func, self), + group if filters is None else filters + ) + ) + + return func + + return decorator diff --git a/pyrogram/methods/decorators/on_chat_member_updated.py b/pyrogram/methods/decorators/on_chat_member_updated.py index 080daa1fc2..c2f0e888a8 100644 --- a/pyrogram/methods/decorators/on_chat_member_updated.py +++ b/pyrogram/methods/decorators/on_chat_member_updated.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2021 Dan +# Copyright (C) 2017-present Dan # # This file is part of Pyrogram. # @@ -20,15 +20,14 @@ import pyrogram from pyrogram.filters import Filter -from pyrogram.scaffold import Scaffold -class OnChatMemberUpdated(Scaffold): +class OnChatMemberUpdated: def on_chat_member_updated( self=None, filters=None, group: int = 0 - ) -> callable: + ) -> Callable: """Decorator for handling event changes on chat members. This does the same thing as :meth:`~pyrogram.Client.add_handler` using the diff --git a/pyrogram/methods/decorators/on_chosen_inline_result.py b/pyrogram/methods/decorators/on_chosen_inline_result.py index 4e972a06a3..090f6c0425 100644 --- a/pyrogram/methods/decorators/on_chosen_inline_result.py +++ b/pyrogram/methods/decorators/on_chosen_inline_result.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2021 Dan +# Copyright (C) 2017-present Dan # # This file is part of Pyrogram. # @@ -20,15 +20,14 @@ import pyrogram from pyrogram.filters import Filter -from pyrogram.scaffold import Scaffold -class OnChosenInlineResult(Scaffold): +class OnChosenInlineResult: def on_chosen_inline_result( self=None, filters=None, group: int = 0 - ) -> callable: + ) -> Callable: """Decorator for handling chosen inline results. This does the same thing as :meth:`~pyrogram.Client.add_handler` using the diff --git a/pyrogram/methods/decorators/on_deleted_messages.py b/pyrogram/methods/decorators/on_deleted_messages.py index d093310c78..9565c11329 100644 --- a/pyrogram/methods/decorators/on_deleted_messages.py +++ b/pyrogram/methods/decorators/on_deleted_messages.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2021 Dan +# Copyright (C) 2017-present Dan # # This file is part of Pyrogram. # @@ -20,15 +20,14 @@ import pyrogram from pyrogram.filters import Filter -from pyrogram.scaffold import Scaffold -class OnDeletedMessages(Scaffold): +class OnDeletedMessages: def on_deleted_messages( self=None, filters=None, group: int = 0 - ) -> callable: + ) -> Callable: """Decorator for handling deleted messages. This does the same thing as :meth:`~pyrogram.Client.add_handler` using the diff --git a/pyrogram/methods/decorators/on_disconnect.py b/pyrogram/methods/decorators/on_disconnect.py index b6dd821c32..26aa62f8fb 100644 --- a/pyrogram/methods/decorators/on_disconnect.py +++ b/pyrogram/methods/decorators/on_disconnect.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2021 Dan +# Copyright (C) 2017-present Dan # # This file is part of Pyrogram. # @@ -19,11 +19,10 @@ from typing import Callable import pyrogram -from pyrogram.scaffold import Scaffold -class OnDisconnect(Scaffold): - def on_disconnect(self=None) -> callable: +class OnDisconnect: + def on_disconnect(self=None) -> Callable: """Decorator for handling disconnections. This does the same thing as :meth:`~pyrogram.Client.add_handler` using the @@ -33,6 +32,11 @@ def on_disconnect(self=None) -> callable: def decorator(func: Callable) -> Callable: if isinstance(self, pyrogram.Client): self.add_handler(pyrogram.handlers.DisconnectHandler(func)) + else: + if not hasattr(func, "handlers"): + func.handlers = [] + + func.handlers.append((pyrogram.handlers.DisconnectHandler(func), 0)) return func diff --git a/pyrogram/methods/decorators/on_edited_message.py b/pyrogram/methods/decorators/on_edited_message.py new file mode 100644 index 0000000000..a8c86bb6d2 --- /dev/null +++ b/pyrogram/methods/decorators/on_edited_message.py @@ -0,0 +1,61 @@ +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-present Dan +# +# This file is part of Pyrogram. +# +# Pyrogram is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License as published +# by the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Pyrogram is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with Pyrogram. If not, see . + +from typing import Callable + +import pyrogram +from pyrogram.filters import Filter + + +class OnEditedMessage: + def on_edited_message( + self=None, + filters=None, + group: int = 0 + ) -> Callable: + """Decorator for handling edited messages. + + This does the same thing as :meth:`~pyrogram.Client.add_handler` using the + :obj:`~pyrogram.handlers.EditedMessageHandler`. + + Parameters: + filters (:obj:`~pyrogram.filters`, *optional*): + Pass one or more filters to allow only a subset of messages to be passed + in your function. + + group (``int``, *optional*): + The group identifier, defaults to 0. + """ + + def decorator(func: Callable) -> Callable: + if isinstance(self, pyrogram.Client): + self.add_handler(pyrogram.handlers.EditedMessageHandler(func, filters), group) + elif isinstance(self, Filter) or self is None: + if not hasattr(func, "handlers"): + func.handlers = [] + + func.handlers.append( + ( + pyrogram.handlers.EditedMessageHandler(func, self), + group if filters is None else filters + ) + ) + + return func + + return decorator diff --git a/pyrogram/methods/decorators/on_inline_query.py b/pyrogram/methods/decorators/on_inline_query.py index 60463f66d8..6b53a464de 100644 --- a/pyrogram/methods/decorators/on_inline_query.py +++ b/pyrogram/methods/decorators/on_inline_query.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2021 Dan +# Copyright (C) 2017-present Dan # # This file is part of Pyrogram. # @@ -20,15 +20,14 @@ import pyrogram from pyrogram.filters import Filter -from pyrogram.scaffold import Scaffold -class OnInlineQuery(Scaffold): +class OnInlineQuery: def on_inline_query( self=None, filters=None, group: int = 0 - ) -> callable: + ) -> Callable: """Decorator for handling inline queries. This does the same thing as :meth:`~pyrogram.Client.add_handler` using the diff --git a/pyrogram/methods/decorators/on_message.py b/pyrogram/methods/decorators/on_message.py index ec23084f7d..220c12bbcc 100644 --- a/pyrogram/methods/decorators/on_message.py +++ b/pyrogram/methods/decorators/on_message.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2021 Dan +# Copyright (C) 2017-present Dan # # This file is part of Pyrogram. # @@ -20,16 +20,15 @@ import pyrogram from pyrogram.filters import Filter -from pyrogram.scaffold import Scaffold -class OnMessage(Scaffold): +class OnMessage: def on_message( self=None, filters=None, group: int = 0 - ) -> callable: - """Decorator for handling messages. + ) -> Callable: + """Decorator for handling new messages. This does the same thing as :meth:`~pyrogram.Client.add_handler` using the :obj:`~pyrogram.handlers.MessageHandler`. diff --git a/pyrogram/methods/decorators/on_poll.py b/pyrogram/methods/decorators/on_poll.py index fc49b1925a..6990c456b6 100644 --- a/pyrogram/methods/decorators/on_poll.py +++ b/pyrogram/methods/decorators/on_poll.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2021 Dan +# Copyright (C) 2017-present Dan # # This file is part of Pyrogram. # @@ -20,15 +20,14 @@ import pyrogram from pyrogram.filters import Filter -from pyrogram.scaffold import Scaffold -class OnPoll(Scaffold): +class OnPoll: def on_poll( self=None, filters=None, group: int = 0 - ) -> callable: + ) -> Callable: """Decorator for handling poll updates. This does the same thing as :meth:`~pyrogram.Client.add_handler` using the diff --git a/pyrogram/methods/decorators/on_raw_update.py b/pyrogram/methods/decorators/on_raw_update.py index 0ce6bf5df9..644bc8a5b9 100644 --- a/pyrogram/methods/decorators/on_raw_update.py +++ b/pyrogram/methods/decorators/on_raw_update.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2021 Dan +# Copyright (C) 2017-present Dan # # This file is part of Pyrogram. # @@ -19,14 +19,13 @@ from typing import Callable import pyrogram -from pyrogram.scaffold import Scaffold -class OnRawUpdate(Scaffold): +class OnRawUpdate: def on_raw_update( self=None, group: int = 0 - ) -> callable: + ) -> Callable: """Decorator for handling raw updates. This does the same thing as :meth:`~pyrogram.Client.add_handler` using the @@ -47,7 +46,7 @@ def decorator(func: Callable) -> Callable: func.handlers.append( ( pyrogram.handlers.RawUpdateHandler(func), - group if self is None else group + group ) ) diff --git a/pyrogram/methods/decorators/on_user_status.py b/pyrogram/methods/decorators/on_user_status.py index fda3fb800c..a4328c37f1 100644 --- a/pyrogram/methods/decorators/on_user_status.py +++ b/pyrogram/methods/decorators/on_user_status.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2021 Dan +# Copyright (C) 2017-present Dan # # This file is part of Pyrogram. # @@ -20,15 +20,14 @@ import pyrogram from pyrogram.filters import Filter -from pyrogram.scaffold import Scaffold -class OnUserStatus(Scaffold): +class OnUserStatus: def on_user_status( self=None, filters=None, group: int = 0 - ) -> callable: + ) -> Callable: """Decorator for handling user status updates. This does the same thing as :meth:`~pyrogram.Client.add_handler` using the :obj:`~pyrogram.handlers.UserStatusHandler`. diff --git a/pyrogram/methods/invite_links/__init__.py b/pyrogram/methods/invite_links/__init__.py index 198ce1bd8f..67c1d14958 100644 --- a/pyrogram/methods/invite_links/__init__.py +++ b/pyrogram/methods/invite_links/__init__.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2021 Dan +# Copyright (C) 2017-present Dan # # This file is part of Pyrogram. # @@ -17,17 +17,22 @@ # along with Pyrogram. If not, see . +from .approve_all_chat_join_requests import ApproveAllChatJoinRequests +from .approve_chat_join_request import ApproveChatJoinRequest from .create_chat_invite_link import CreateChatInviteLink +from .decline_all_chat_join_requests import DeclineAllChatJoinRequests +from .decline_chat_join_request import DeclineChatJoinRequest from .delete_chat_admin_invite_links import DeleteChatAdminInviteLinks from .delete_chat_invite_link import DeleteChatInviteLink from .edit_chat_invite_link import EditChatInviteLink from .export_chat_invite_link import ExportChatInviteLink -from .get_chat_admins_with_invite_links import GetChatAdminsWithInviteLinks -from .get_chat_invite_link import GetChatInviteLink -from .get_chat_invite_link_members import GetChatInviteLinkMembers -from .get_chat_invite_link_members_count import GetChatInviteLinkMembersCount from .get_chat_admin_invite_links import GetChatAdminInviteLinks from .get_chat_admin_invite_links_count import GetChatAdminInviteLinksCount +from .get_chat_admins_with_invite_links import GetChatAdminsWithInviteLinks +from .get_chat_invite_link import GetChatInviteLink +from .get_chat_invite_link_joiners import GetChatInviteLinkJoiners +from .get_chat_invite_link_joiners_count import GetChatInviteLinkJoinersCount +from .get_chat_join_requests import GetChatJoinRequests from .revoke_chat_invite_link import RevokeChatInviteLink @@ -36,13 +41,18 @@ class InviteLinks( DeleteChatInviteLink, EditChatInviteLink, CreateChatInviteLink, - GetChatInviteLinkMembers, - GetChatInviteLinkMembersCount, + GetChatInviteLinkJoiners, + GetChatInviteLinkJoinersCount, GetChatAdminInviteLinks, ExportChatInviteLink, DeleteChatAdminInviteLinks, GetChatAdminInviteLinksCount, GetChatAdminsWithInviteLinks, - GetChatInviteLink + GetChatInviteLink, + ApproveChatJoinRequest, + DeclineChatJoinRequest, + ApproveAllChatJoinRequests, + DeclineAllChatJoinRequests, + GetChatJoinRequests ): - pass \ No newline at end of file + pass diff --git a/pyrogram/methods/invite_links/approve_all_chat_join_requests.py b/pyrogram/methods/invite_links/approve_all_chat_join_requests.py new file mode 100644 index 0000000000..623fd87fcc --- /dev/null +++ b/pyrogram/methods/invite_links/approve_all_chat_join_requests.py @@ -0,0 +1,55 @@ +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-present Dan +# +# This file is part of Pyrogram. +# +# Pyrogram is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License as published +# by the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Pyrogram is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with Pyrogram. If not, see . + +from typing import Union + +import pyrogram +from pyrogram import raw + + +class ApproveAllChatJoinRequests: + async def approve_all_chat_join_requests( + self: "pyrogram.Client", + chat_id: Union[int, str], + invite_link: str = None + ) -> bool: + """Approve all pending join requests in a chat. + + .. include:: /_includes/usable-by/users.rst + + Parameters: + chat_id (``int`` | ``str``): + Unique identifier for the target chat or username of the target channel/supergroup + (in the format @username). + + invite_link (``str``, *optional*): + Pass an invite link to approve only its join requests. + By default, all join requests are approved. + + Returns: + ``bool``: True on success. + """ + await self.invoke( + raw.functions.messages.HideAllChatJoinRequests( + peer=await self.resolve_peer(chat_id), + approved=True, + link=invite_link + ) + ) + + return True diff --git a/pyrogram/methods/invite_links/approve_chat_join_request.py b/pyrogram/methods/invite_links/approve_chat_join_request.py new file mode 100644 index 0000000000..2fc4e6d309 --- /dev/null +++ b/pyrogram/methods/invite_links/approve_chat_join_request.py @@ -0,0 +1,57 @@ +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-present Dan +# +# This file is part of Pyrogram. +# +# Pyrogram is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License as published +# by the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Pyrogram is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with Pyrogram. If not, see . + +from typing import Union + +import pyrogram +from pyrogram import raw + + +class ApproveChatJoinRequest: + async def approve_chat_join_request( + self: "pyrogram.Client", + chat_id: Union[int, str], + user_id: int, + ) -> bool: + """Approve a chat join request. + + You must be an administrator in the chat for this to work and must have the *can_invite_users* administrator + right. + + .. include:: /_includes/usable-by/users-bots.rst + + Parameters: + chat_id (``int`` | ``str``): + Unique identifier for the target chat or username of the target channel/supergroup + (in the format @username). + + user_id (``int``): + Unique identifier of the target user. + + Returns: + ``bool``: True on success. + """ + await self.invoke( + raw.functions.messages.HideChatJoinRequest( + peer=await self.resolve_peer(chat_id), + user_id=await self.resolve_peer(user_id), + approved=True + ) + ) + + return True diff --git a/pyrogram/methods/invite_links/create_chat_invite_link.py b/pyrogram/methods/invite_links/create_chat_invite_link.py index 006e7e1445..ccf8d6505a 100644 --- a/pyrogram/methods/invite_links/create_chat_invite_link.py +++ b/pyrogram/methods/invite_links/create_chat_invite_link.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2021 Dan +# Copyright (C) 2017-present Dan # # This file is part of Pyrogram. # @@ -16,19 +16,22 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . +from datetime import datetime from typing import Union -from pyrogram import raw +import pyrogram +from pyrogram import raw, utils from pyrogram import types -from pyrogram.scaffold import Scaffold -class CreateChatInviteLink(Scaffold): +class CreateChatInviteLink: async def create_chat_invite_link( - self, + self: "pyrogram.Client", chat_id: Union[int, str], - expire_date: int = None, + name: str = None, + expire_date: datetime = None, member_limit: int = None, + creates_join_request: bool = None ) -> "types.ChatInviteLink": """Create an additional invite link for a chat. @@ -36,13 +39,18 @@ async def create_chat_invite_link( The link can be revoked using the method :meth:`~pyrogram.Client.revoke_chat_invite_link`. + .. include:: /_includes/usable-by/users-bots.rst + Parameters: chat_id (``int`` | ``str``): Unique identifier for the target chat or username of the target channel/supergroup (in the format @username). - expire_date (``int``, *optional*): - Point in time (Unix timestamp) when the link will expire. + name (``str``, *optional*): + Invite link name. + + expire_date (:py:obj:`~datetime.datetime`, *optional*): + Point in time when the link will expire. Defaults to None (no expiration date). member_limit (``int``, *optional*): @@ -50,6 +58,10 @@ async def create_chat_invite_link( this invite link; 1-99999. Defaults to None (no member limit). + creates_join_request (``bool``, *optional*): + True, if users joining the chat via the link need to be approved by chat administrators. + If True, member_limit can't be specified. + Returns: :obj:`~pyrogram.types.ChatInviteLink`: On success, the new invite link is returned. @@ -57,16 +69,18 @@ async def create_chat_invite_link( .. code-block:: python # Create a new link without limits - link = app.create_chat_invite_link(chat_id) + link = await app.create_chat_invite_link(chat_id) - # Create a new link for up to 7 new users - link = app.create_chat_invite_link(chat_id, member_limit=7) + # Create a new link for up to 3 new users + link = await app.create_chat_invite_link(chat_id, member_limit=3) """ - r = await self.send( + r = await self.invoke( raw.functions.messages.ExportChatInvite( peer=await self.resolve_peer(chat_id), - expire_date=expire_date, + expire_date=utils.datetime_to_timestamp(expire_date), usage_limit=member_limit, + title=name, + request_needed=creates_join_request ) ) diff --git a/pyrogram/methods/invite_links/decline_all_chat_join_requests.py b/pyrogram/methods/invite_links/decline_all_chat_join_requests.py new file mode 100644 index 0000000000..9cf5095507 --- /dev/null +++ b/pyrogram/methods/invite_links/decline_all_chat_join_requests.py @@ -0,0 +1,55 @@ +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-present Dan +# +# This file is part of Pyrogram. +# +# Pyrogram is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License as published +# by the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Pyrogram is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with Pyrogram. If not, see . + +from typing import Union + +import pyrogram +from pyrogram import raw + + +class DeclineAllChatJoinRequests: + async def decline_all_chat_join_requests( + self: "pyrogram.Client", + chat_id: Union[int, str], + invite_link: str = None + ) -> bool: + """Decline all pending join requests in a chat. + + .. include:: /_includes/usable-by/users.rst + + Parameters: + chat_id (``int`` | ``str``): + Unique identifier for the target chat or username of the target channel/supergroup + (in the format @username). + + invite_link (``str``, *optional*): + Pass an invite link to decline only its join requests. + By default, all join requests are declined. + + Returns: + ``bool``: True on success. + """ + await self.invoke( + raw.functions.messages.HideAllChatJoinRequests( + peer=await self.resolve_peer(chat_id), + approved=False, + link=invite_link + ) + ) + + return True diff --git a/pyrogram/methods/invite_links/decline_chat_join_request.py b/pyrogram/methods/invite_links/decline_chat_join_request.py new file mode 100644 index 0000000000..9a35b8ee4b --- /dev/null +++ b/pyrogram/methods/invite_links/decline_chat_join_request.py @@ -0,0 +1,57 @@ +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-present Dan +# +# This file is part of Pyrogram. +# +# Pyrogram is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License as published +# by the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Pyrogram is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with Pyrogram. If not, see . + +from typing import Union + +import pyrogram +from pyrogram import raw + + +class DeclineChatJoinRequest: + async def decline_chat_join_request( + self: "pyrogram.Client", + chat_id: Union[int, str], + user_id: int, + ) -> bool: + """Decline a chat join request. + + You must be an administrator in the chat for this to work and must have the *can_invite_users* administrator + right. + + .. include:: /_includes/usable-by/users-bots.rst + + Parameters: + chat_id (``int`` | ``str``): + Unique identifier for the target chat or username of the target channel/supergroup + (in the format @username). + + user_id (``int``): + Unique identifier of the target user. + + Returns: + ``bool``: True on success. + """ + await self.invoke( + raw.functions.messages.HideChatJoinRequest( + peer=await self.resolve_peer(chat_id), + user_id=await self.resolve_peer(user_id), + approved=False + ) + ) + + return True diff --git a/pyrogram/methods/invite_links/delete_chat_admin_invite_links.py b/pyrogram/methods/invite_links/delete_chat_admin_invite_links.py index 94a3740890..32ef1de5e7 100644 --- a/pyrogram/methods/invite_links/delete_chat_admin_invite_links.py +++ b/pyrogram/methods/invite_links/delete_chat_admin_invite_links.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2021 Dan +# Copyright (C) 2017-present Dan # # This file is part of Pyrogram. # @@ -18,18 +18,20 @@ from typing import Union +import pyrogram from pyrogram import raw -from pyrogram.scaffold import Scaffold -class DeleteChatAdminInviteLinks(Scaffold): +class DeleteChatAdminInviteLinks: async def delete_chat_admin_invite_links( - self, + self: "pyrogram.Client", chat_id: Union[int, str], admin_id: Union[int, str], ) -> bool: """Delete all revoked invite links of an administrator. + .. include:: /_includes/usable-by/users.rst + Parameters: chat_id (``int`` | ``str``): Unique identifier for the target chat or username of the target channel/supergroup @@ -44,7 +46,7 @@ async def delete_chat_admin_invite_links( ``bool``: On success ``True`` is returned. """ - return await self.send( + return await self.invoke( raw.functions.messages.DeleteRevokedExportedChatInvites( peer=await self.resolve_peer(chat_id), admin_id=await self.resolve_peer(admin_id), diff --git a/pyrogram/methods/invite_links/delete_chat_invite_link.py b/pyrogram/methods/invite_links/delete_chat_invite_link.py index 987cc78129..82db623abe 100644 --- a/pyrogram/methods/invite_links/delete_chat_invite_link.py +++ b/pyrogram/methods/invite_links/delete_chat_invite_link.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2021 Dan +# Copyright (C) 2017-present Dan # # This file is part of Pyrogram. # @@ -18,18 +18,20 @@ from typing import Union +import pyrogram from pyrogram import raw -from pyrogram.scaffold import Scaffold -class DeleteChatInviteLink(Scaffold): +class DeleteChatInviteLink: async def delete_chat_invite_link( - self, + self: "pyrogram.Client", chat_id: Union[int, str], invite_link: str, ) -> bool: """Delete an already revoked invite link. + .. include:: /_includes/usable-by/users.rst + Parameters: chat_id (``int`` | ``str``): Unique identifier for the target chat or username of the target channel/supergroup @@ -42,7 +44,7 @@ async def delete_chat_invite_link( ``bool``: On success ``True`` is returned. """ - return await self.send( + return await self.invoke( raw.functions.messages.DeleteExportedChatInvite( peer=await self.resolve_peer(chat_id), link=invite_link, diff --git a/pyrogram/methods/invite_links/edit_chat_invite_link.py b/pyrogram/methods/invite_links/edit_chat_invite_link.py index 07c84d3c4d..553c25dd17 100644 --- a/pyrogram/methods/invite_links/edit_chat_invite_link.py +++ b/pyrogram/methods/invite_links/edit_chat_invite_link.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2021 Dan +# Copyright (C) 2017-present Dan # # This file is part of Pyrogram. # @@ -16,25 +16,30 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . +from datetime import datetime from typing import Union -from pyrogram import raw +import pyrogram +from pyrogram import raw, utils from pyrogram import types -from pyrogram.scaffold import Scaffold -class EditChatInviteLink(Scaffold): +class EditChatInviteLink: async def edit_chat_invite_link( - self, + self: "pyrogram.Client", chat_id: Union[int, str], invite_link: str, - expire_date: int = None, + name: str = None, + expire_date: datetime = None, member_limit: int = None, + creates_join_request: bool = None ) -> "types.ChatInviteLink": """Edit a non-primary invite link. You must be an administrator in the chat for this to work and must have the appropriate admin rights. + .. include:: /_includes/usable-by/users-bots.rst + Parameters: chat_id (``int`` | ``str``): Unique identifier for the target chat or username of the target channel/supergroup @@ -43,15 +48,22 @@ async def edit_chat_invite_link( invite_link (``str``): The invite link to edit - expire_date (``int``, *optional*): - Point in time (Unix timestamp) when the link will expire. - Defaults to None (no change), pass 0 to set no expiration date. + name (``str``, *optional*): + Invite link name. + + expire_date (:py:obj:`~datetime.datetime`, *optional*): + Point in time when the link will expire. + Defaults to None (no change), pass None to set no expiration date. member_limit (``int``, *optional*): Maximum number of users that can be members of the chat simultaneously after joining the chat via this invite link; 1-99999. Defaults to None (no change), pass 0 to set no member limit. + creates_join_request (``bool``, *optional*): + True, if users joining the chat via the link need to be approved by chat administrators. + If True, member_limit can't be specified. + Returns: :obj:`~pyrogram.types.ChatInviteLink`: On success, the new invite link is returned @@ -59,17 +71,19 @@ async def edit_chat_invite_link( .. code-block:: python # Edit the member limit of a link - link = app.edit_chat_invite_link(chat_id, invite_link, member_limit=9) + link = await app.edit_chat_invite_link(chat_id, invite_link, member_limit=5) # Set no expiration date of a link - link = app.edit_chat_invite_link(chat_id, invite_link, expire_date=0) + link = await app.edit_chat_invite_link(chat_id, invite_link, expire_date=0) """ - r = await self.send( + r = await self.invoke( raw.functions.messages.EditExportedChatInvite( peer=await self.resolve_peer(chat_id), link=invite_link, - expire_date=expire_date, + expire_date=utils.datetime_to_timestamp(expire_date), usage_limit=member_limit, + title=name, + request_needed=creates_join_request ) ) diff --git a/pyrogram/methods/invite_links/export_chat_invite_link.py b/pyrogram/methods/invite_links/export_chat_invite_link.py index ee59750cbb..f06caf0f9a 100644 --- a/pyrogram/methods/invite_links/export_chat_invite_link.py +++ b/pyrogram/methods/invite_links/export_chat_invite_link.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2021 Dan +# Copyright (C) 2017-present Dan # # This file is part of Pyrogram. # @@ -18,14 +18,14 @@ from typing import Union +import pyrogram from pyrogram import raw from pyrogram import types -from pyrogram.scaffold import Scaffold -class ExportChatInviteLink(Scaffold): +class ExportChatInviteLink: async def export_chat_invite_link( - self, + self: "pyrogram.Client", chat_id: Union[int, str], ) -> "types.ChatInviteLink": """Generate a new primary invite link for a chat; any previously generated primary link is revoked. @@ -39,6 +39,8 @@ async def export_chat_invite_link( :meth:`~pyrogram.Client.get_chat` method. If your bot needs to generate a new invite link replacing its previous one, use this method again. + .. include:: /_includes/usable-by/users-bots.rst + Parameters: chat_id (``int`` | ``str``): Unique identifier for the target chat or username of the target channel/supergroup @@ -51,9 +53,9 @@ async def export_chat_invite_link( .. code-block:: python # Generate a new primary link - link = app.export_chat_invite_link(chat_id) + link = await app.export_chat_invite_link(chat_id) """ - r = await self.send( + r = await self.invoke( raw.functions.messages.ExportChatInvite( peer=await self.resolve_peer(chat_id), legacy_revoke_permanent=True diff --git a/pyrogram/methods/invite_links/get_chat_admin_invite_links.py b/pyrogram/methods/invite_links/get_chat_admin_invite_links.py index ade3a84be2..62acca10e8 100644 --- a/pyrogram/methods/invite_links/get_chat_admin_invite_links.py +++ b/pyrogram/methods/invite_links/get_chat_admin_invite_links.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2021 Dan +# Copyright (C) 2017-present Dan # # This file is part of Pyrogram. # @@ -18,14 +18,14 @@ from typing import Union, Optional, AsyncGenerator +import pyrogram from pyrogram import raw from pyrogram import types -from pyrogram.scaffold import Scaffold -class GetChatAdminInviteLinks(Scaffold): +class GetChatAdminInviteLinks: async def get_chat_admin_invite_links( - self, + self: "pyrogram.Client", chat_id: Union[int, str], admin_id: Union[int, str], revoked: bool = False, @@ -38,6 +38,8 @@ async def get_chat_admin_invite_links( As an administrator you can only get your own links you have exported. As the chat or channel owner you can get everyones links. + .. include:: /_includes/usable-by/users.rst + Parameters: chat_id (``int`` | ``str``): Unique identifier for the target chat or username of the target channel/supergroup @@ -70,7 +72,7 @@ async def get_chat_admin_invite_links( offset_link = None while True: - r = await self.send( + r = await self.invoke( raw.functions.messages.GetExportedChatInvites( peer=await self.resolve_peer(chat_id), admin_id=await self.resolve_peer(admin_id), diff --git a/pyrogram/methods/invite_links/get_chat_admin_invite_links_count.py b/pyrogram/methods/invite_links/get_chat_admin_invite_links_count.py index 5c99405a53..528876ed4e 100644 --- a/pyrogram/methods/invite_links/get_chat_admin_invite_links_count.py +++ b/pyrogram/methods/invite_links/get_chat_admin_invite_links_count.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2021 Dan +# Copyright (C) 2017-present Dan # # This file is part of Pyrogram. # @@ -18,19 +18,21 @@ from typing import Union +import pyrogram from pyrogram import raw -from pyrogram.scaffold import Scaffold -class GetChatAdminInviteLinksCount(Scaffold): +class GetChatAdminInviteLinksCount: async def get_chat_admin_invite_links_count( - self, + self: "pyrogram.Client", chat_id: Union[int, str], admin_id: Union[int, str], revoked: bool = False, ) -> int: """Get the count of the invite links created by an administrator in a chat. + .. include:: /_includes/usable-by/users.rst + Parameters: chat_id (``int`` | ``str``): Unique identifier for the target chat or username of the target channel/supergroup @@ -48,7 +50,7 @@ async def get_chat_admin_invite_links_count( Returns: ``int``: On success, the invite links count is returned. """ - r = await self.send( + r = await self.invoke( raw.functions.messages.GetExportedChatInvites( peer=await self.resolve_peer(chat_id), admin_id=await self.resolve_peer(admin_id), diff --git a/pyrogram/methods/invite_links/get_chat_admins_with_invite_links.py b/pyrogram/methods/invite_links/get_chat_admins_with_invite_links.py index 1f2cc1c197..f283e53464 100644 --- a/pyrogram/methods/invite_links/get_chat_admins_with_invite_links.py +++ b/pyrogram/methods/invite_links/get_chat_admins_with_invite_links.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2021 Dan +# Copyright (C) 2017-present Dan # # This file is part of Pyrogram. # @@ -18,20 +18,22 @@ from typing import Union +import pyrogram from pyrogram import raw, types -from pyrogram.scaffold import Scaffold -class GetChatAdminsWithInviteLinks(Scaffold): +class GetChatAdminsWithInviteLinks: async def get_chat_admins_with_invite_links( - self, + self: "pyrogram.Client", chat_id: Union[int, str], ): """Get the list of the administrators that have exported invite links in a chat. You must be the owner of a chat for this to work. - Args: + .. include:: /_includes/usable-by/users.rst + + Parameters: chat_id (``int`` | ``str``): Unique identifier for the target chat or username of the target channel/supergroup (in the format @username). @@ -40,7 +42,7 @@ async def get_chat_admins_with_invite_links( List of :obj:`~pyrogram.types.ChatAdminWithInviteLink`: On success, the list of admins that have exported invite links is returned. """ - r = await self.send( + r = await self.invoke( raw.functions.messages.GetAdminsWithInvites( peer=await self.resolve_peer(chat_id) ) diff --git a/pyrogram/methods/invite_links/get_chat_invite_link.py b/pyrogram/methods/invite_links/get_chat_invite_link.py index c5e0339901..8ad575f34a 100644 --- a/pyrogram/methods/invite_links/get_chat_invite_link.py +++ b/pyrogram/methods/invite_links/get_chat_invite_link.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2021 Dan +# Copyright (C) 2017-present Dan # # This file is part of Pyrogram. # @@ -18,19 +18,21 @@ from typing import Union +import pyrogram from pyrogram import raw from pyrogram import types -from pyrogram.scaffold import Scaffold -class GetChatInviteLink(Scaffold): +class GetChatInviteLink: async def get_chat_invite_link( - self, + self: "pyrogram.Client", chat_id: Union[int, str], invite_link: str, ) -> "types.ChatInviteLink": """Get detailed information about a chat invite link. + .. include:: /_includes/usable-by/users-bots.rst + Parameters: chat_id (``int`` | ``str``): Unique identifier for the target chat or username of the target channel/supergroup @@ -42,7 +44,7 @@ async def get_chat_invite_link( Returns: :obj:`~pyrogram.types.ChatInviteLink`: On success, the invite link is returned. """ - r = await self.send( + r = await self.invoke( raw.functions.messages.GetExportedChatInvite( peer=await self.resolve_peer(chat_id), link=invite_link diff --git a/pyrogram/methods/invite_links/get_chat_invite_link_members.py b/pyrogram/methods/invite_links/get_chat_invite_link_joiners.py similarity index 80% rename from pyrogram/methods/invite_links/get_chat_invite_link_members.py rename to pyrogram/methods/invite_links/get_chat_invite_link_joiners.py index e48422ea30..c1fc43a71f 100644 --- a/pyrogram/methods/invite_links/get_chat_invite_link_members.py +++ b/pyrogram/methods/invite_links/get_chat_invite_link_joiners.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2021 Dan +# Copyright (C) 2017-present Dan # # This file is part of Pyrogram. # @@ -18,20 +18,22 @@ from typing import Union, Optional, AsyncGenerator +import pyrogram from pyrogram import raw from pyrogram import types -from pyrogram.scaffold import Scaffold -class GetChatInviteLinkMembers(Scaffold): - async def get_chat_invite_link_members( - self, +class GetChatInviteLinkJoiners: + async def get_chat_invite_link_joiners( + self: "pyrogram.Client", chat_id: Union[int, str], invite_link: str, limit: int = 0 - ) -> Optional[AsyncGenerator["types.ChatMember", None]]: + ) -> Optional[AsyncGenerator["types.ChatJoiner", None]]: """Get the members who joined the chat with the invite link. + .. include:: /_includes/usable-by/users.rst + Parameters: chat_id (``int`` | ``str``): Unique identifier for the target chat or username of the target channel/supergroup @@ -45,10 +47,10 @@ async def get_chat_invite_link_members( By default, no limit is applied and all invite links are returned. Returns: - ``Generator``: A generator yielding :obj:`~pyrogram.types.ChatMember` objects. + ``Generator``: A generator yielding :obj:`~pyrogram.types.ChatJoiner` objects. Yields: - :obj:`~pyrogram.types.ChatMember` objects. + :obj:`~pyrogram.types.ChatJoiner` objects. """ current = 0 total = abs(limit) or (1 << 31) - 1 @@ -58,7 +60,7 @@ async def get_chat_invite_link_members( offset_user = raw.types.InputUserEmpty() while True: - r = await self.send( + r = await self.invoke( raw.functions.messages.GetChatInviteImporters( peer=await self.resolve_peer(chat_id), link=invite_link, @@ -77,13 +79,7 @@ async def get_chat_invite_link_members( offset_user = await self.resolve_peer(r.importers[-1].user_id) for i in r.importers: - user = types.User._parse(self, users[i.user_id]) - - yield types.ChatMember( - user=user, - status="member", - joined_date=i.date - ) + yield types.ChatJoiner._parse(self, i, users) current += 1 diff --git a/pyrogram/methods/invite_links/get_chat_invite_link_members_count.py b/pyrogram/methods/invite_links/get_chat_invite_link_joiners_count.py similarity index 85% rename from pyrogram/methods/invite_links/get_chat_invite_link_members_count.py rename to pyrogram/methods/invite_links/get_chat_invite_link_joiners_count.py index bd6a2fcc81..c591be1927 100644 --- a/pyrogram/methods/invite_links/get_chat_invite_link_members_count.py +++ b/pyrogram/methods/invite_links/get_chat_invite_link_joiners_count.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2021 Dan +# Copyright (C) 2017-present Dan # # This file is part of Pyrogram. # @@ -18,18 +18,20 @@ from typing import Union +import pyrogram from pyrogram import raw -from pyrogram.scaffold import Scaffold -class GetChatInviteLinkMembersCount(Scaffold): - async def get_chat_invite_link_members_count( - self, +class GetChatInviteLinkJoinersCount: + async def get_chat_invite_link_joiners_count( + self: "pyrogram.Client", chat_id: Union[int, str], invite_link: str ) -> int: """Get the count of the members who joined the chat with the invite link. + .. include:: /_includes/usable-by/users.rst + Parameters: chat_id (``int`` | ``str``): Unique identifier for the target chat or username of the target channel/supergroup @@ -41,7 +43,7 @@ async def get_chat_invite_link_members_count( Returns: ``int``: On success, the joined chat members count is returned. """ - r = await self.send( + r = await self.invoke( raw.functions.messages.GetChatInviteImporters( peer=await self.resolve_peer(chat_id), link=invite_link, diff --git a/pyrogram/methods/invite_links/get_chat_join_requests.py b/pyrogram/methods/invite_links/get_chat_join_requests.py new file mode 100644 index 0000000000..a75498e2f6 --- /dev/null +++ b/pyrogram/methods/invite_links/get_chat_join_requests.py @@ -0,0 +1,88 @@ +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-present Dan +# +# This file is part of Pyrogram. +# +# Pyrogram is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License as published +# by the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Pyrogram is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with Pyrogram. If not, see . + +from typing import Union, Optional, AsyncGenerator + +import pyrogram +from pyrogram import raw +from pyrogram import types + + +class GetChatJoinRequests: + async def get_chat_join_requests( + self: "pyrogram.Client", + chat_id: Union[int, str], + limit: int = 0, + query: str = "" + ) -> Optional[AsyncGenerator["types.ChatJoiner", None]]: + """Get the pending join requests of a chat. + + .. include:: /_includes/usable-by/users.rst + + Parameters: + chat_id (``int`` | ``str``): + Unique identifier for the target chat or username of the target channel/supergroup + (in the format @username). + + limit (``int``, *optional*): + Limits the number of invite links to be retrieved. + By default, no limit is applied and all invite links are returned. + + query (``str``, *optional*): + Query to search for a user. + + Returns: + ``Generator``: A generator yielding :obj:`~pyrogram.types.ChatJoiner` objects. + + Yields: + :obj:`~pyrogram.types.ChatJoiner` objects. + """ + current = 0 + total = abs(limit) or (1 << 31) - 1 + limit = min(100, total) + + offset_date = 0 + offset_user = raw.types.InputUserEmpty() + + while True: + r = await self.invoke( + raw.functions.messages.GetChatInviteImporters( + peer=await self.resolve_peer(chat_id), + limit=limit, + offset_date=offset_date, + offset_user=offset_user, + requested=True, + q=query + ) + ) + + if not r.importers: + break + + users = {i.id: i for i in r.users} + + offset_date = r.importers[-1].date + offset_user = await self.resolve_peer(r.importers[-1].user_id) + + for i in r.importers: + yield types.ChatJoiner._parse(self, i, users) + + current += 1 + + if current >= total: + return diff --git a/pyrogram/methods/invite_links/revoke_chat_invite_link.py b/pyrogram/methods/invite_links/revoke_chat_invite_link.py index b49a51dd4c..ff55a04e03 100644 --- a/pyrogram/methods/invite_links/revoke_chat_invite_link.py +++ b/pyrogram/methods/invite_links/revoke_chat_invite_link.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2021 Dan +# Copyright (C) 2017-present Dan # # This file is part of Pyrogram. # @@ -18,14 +18,14 @@ from typing import Union +import pyrogram from pyrogram import raw from pyrogram import types -from pyrogram.scaffold import Scaffold -class RevokeChatInviteLink(Scaffold): +class RevokeChatInviteLink: async def revoke_chat_invite_link( - self, + self: "pyrogram.Client", chat_id: Union[int, str], invite_link: str, ) -> "types.ChatInviteLink": @@ -35,6 +35,8 @@ async def revoke_chat_invite_link( You must be an administrator in the chat for this to work and must have the appropriate admin rights. + .. include:: /_includes/usable-by/users-bots.rst + Parameters: chat_id (``int`` | ``str``): Unique identifier for the target chat or username of the target channel/supergroup @@ -47,7 +49,7 @@ async def revoke_chat_invite_link( :obj:`~pyrogram.types.ChatInviteLink`: On success, the invite link object is returned. """ - r = await self.send( + r = await self.invoke( raw.functions.messages.EditExportedChatInvite( peer=await self.resolve_peer(chat_id), link=invite_link, diff --git a/pyrogram/methods/messages/__init__.py b/pyrogram/methods/messages/__init__.py index 45cccb7e4e..7a5d2d49be 100644 --- a/pyrogram/methods/messages/__init__.py +++ b/pyrogram/methods/messages/__init__.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2021 Dan +# Copyright (C) 2017-present Dan # # This file is part of Pyrogram. # @@ -29,12 +29,15 @@ from .edit_message_reply_markup import EditMessageReplyMarkup from .edit_message_text import EditMessageText from .forward_messages import ForwardMessages -from .get_history import GetHistory -from .get_history_count import GetHistoryCount +from .get_chat_history import GetChatHistory +from .get_chat_history_count import GetChatHistoryCount +from .get_custom_emoji_stickers import GetCustomEmojiStickers +from .get_discussion_message import GetDiscussionMessage +from .get_discussion_replies import GetDiscussionReplies +from .get_discussion_replies_count import GetDiscussionRepliesCount from .get_media_group import GetMediaGroup from .get_messages import GetMessages -from .iter_history import IterHistory -from .read_history import ReadHistory +from .read_chat_history import ReadChatHistory from .retract_vote import RetractVote from .search_global import SearchGlobal from .search_global_count import SearchGlobalCount @@ -52,12 +55,14 @@ from .send_message import SendMessage from .send_photo import SendPhoto from .send_poll import SendPoll +from .send_reaction import SendReaction from .send_sticker import SendSticker from .send_venue import SendVenue from .send_video import SendVideo from .send_video_note import SendVideoNote from .send_voice import SendVoice from .stop_poll import StopPoll +from .stream_media import StreamMedia from .vote_poll import VotePoll @@ -68,7 +73,6 @@ class Messages( EditMessageMedia, EditMessageText, ForwardMessages, - GetHistory, GetMediaGroup, GetMessages, SendAudio, @@ -90,10 +94,10 @@ class Messages( StopPoll, RetractVote, DownloadMedia, - IterHistory, + GetChatHistory, SendCachedMedia, - GetHistoryCount, - ReadHistory, + GetChatHistoryCount, + ReadChatHistory, EditInlineText, EditInlineCaption, EditInlineMedia, @@ -104,6 +108,12 @@ class Messages( CopyMessage, CopyMediaGroup, SearchMessagesCount, - SearchGlobalCount + SearchGlobalCount, + GetDiscussionMessage, + SendReaction, + GetDiscussionReplies, + GetDiscussionRepliesCount, + StreamMedia, + GetCustomEmojiStickers ): pass diff --git a/pyrogram/methods/messages/copy_media_group.py b/pyrogram/methods/messages/copy_media_group.py index 7845863ccd..52911ea0bc 100644 --- a/pyrogram/methods/messages/copy_media_group.py +++ b/pyrogram/methods/messages/copy_media_group.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2021 Dan +# Copyright (C) 2017-present Dan # # This file is part of Pyrogram. # @@ -16,25 +16,28 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . -from typing import Union, List, Optional +from datetime import datetime +from typing import Union, List +import pyrogram from pyrogram import types, utils, raw -from pyrogram.scaffold import Scaffold -class CopyMediaGroup(Scaffold): +class CopyMediaGroup: async def copy_media_group( - self, + self: "pyrogram.Client", chat_id: Union[int, str], from_chat_id: Union[int, str], message_id: int, captions: Union[List[str], str] = None, disable_notification: bool = None, reply_to_message_id: int = None, - schedule_date: int = None, + schedule_date: datetime = None, ) -> List["types.Message"]: """Copy a media group by providing one of the message ids. + .. include:: /_includes/usable-by/users-bots.rst + Parameters: chat_id (``int`` | ``str``): Unique identifier (int) or username (str) of the target chat. @@ -65,8 +68,8 @@ async def copy_media_group( reply_to_message_id (``int``, *optional*): If the message is a reply, ID of the original message. - schedule_date (``int``, *optional*): - Date when the message will be automatically sent. Unix time. + schedule_date (:py:obj:`~datetime.datetime`, *optional*): + Date when the message will be automatically sent. Returns: List of :obj:`~pyrogram.types.Message`: On success, a list of copied messages is returned. @@ -75,9 +78,12 @@ async def copy_media_group( .. code-block:: python # Copy a media group - app.copy_media_group("me", source_chat, message_id) - app.copy_media_group("me", source_chat, message_id, captions="single caption") - app.copy_media_group("me", source_chat, message_id, captions=["caption 1", None, ""]) + await app.copy_media_group(to_chat, from_chat, 123) + + await app.copy_media_group(to_chat, from_chat, 123, captions="single caption") + + await app.copy_media_group(to_chat, from_chat, 123, + captions=["caption 1", None, ""]) """ media_group = await self.get_media_group(from_chat_id, message_id) @@ -103,17 +109,18 @@ async def copy_media_group( **await self.parser.parse( captions[i] if isinstance(captions, list) and i < len(captions) and captions[i] else captions if isinstance(captions, str) and i == 0 else - message.caption if message.caption and message.caption != "None" and not type(captions) is str else "") + message.caption if message.caption and message.caption != "None" and not type( + captions) is str else "") ) ) - r = await self.send( + r = await self.invoke( raw.functions.messages.SendMultiMedia( peer=await self.resolve_peer(chat_id), multi_media=multi_media, silent=disable_notification or None, reply_to_msg_id=reply_to_message_id, - schedule_date=schedule_date + schedule_date=utils.datetime_to_timestamp(schedule_date) ), sleep_threshold=60 ) diff --git a/pyrogram/methods/messages/copy_message.py b/pyrogram/methods/messages/copy_message.py index 3250ac8906..0b6624b28d 100644 --- a/pyrogram/methods/messages/copy_message.py +++ b/pyrogram/methods/messages/copy_message.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2021 Dan +# Copyright (C) 2017-present Dan # # This file is part of Pyrogram. # @@ -17,38 +17,42 @@ # along with Pyrogram. If not, see . import logging +from datetime import datetime from typing import Union, List, Optional -from pyrogram import types -from pyrogram.scaffold import Scaffold +import pyrogram +from pyrogram import types, enums log = logging.getLogger(__name__) -class CopyMessage(Scaffold): +class CopyMessage: async def copy_message( - self, + self: "pyrogram.Client", chat_id: Union[int, str], from_chat_id: Union[int, str], message_id: int, caption: str = None, - parse_mode: Optional[str] = object, + parse_mode: Optional["enums.ParseMode"] = None, caption_entities: List["types.MessageEntity"] = None, disable_notification: bool = None, reply_to_message_id: int = None, - schedule_date: int = None, + schedule_date: datetime = None, + protect_content: bool = None, reply_markup: Union[ "types.InlineKeyboardMarkup", "types.ReplyKeyboardMarkup", "types.ReplyKeyboardRemove", "types.ForceReply" ] = None - ) -> List["types.Message"]: + ) -> "types.Message": """Copy messages of any kind. The method is analogous to the method :meth:`~Client.forward_messages`, but the copied message doesn't have a link to the original message. + .. include:: /_includes/usable-by/users-bots.rst + Parameters: chat_id (``int`` | ``str``): Unique identifier (int) or username (str) of the target chat. @@ -68,12 +72,9 @@ async def copy_message( If not specified, the original caption is kept. Pass "" (empty string) to remove the caption. - parse_mode (``str``, *optional*): + parse_mode (:obj:`~pyrogram.enums.ParseMode`, *optional*): By default, texts are parsed using both Markdown and HTML styles. You can combine both syntaxes together. - Pass "markdown" or "md" to enable Markdown-style parsing only. - Pass "html" to enable HTML-style parsing only. - Pass None to completely disable style parsing. caption_entities (List of :obj:`~pyrogram.types.MessageEntity`): List of special entities that appear in the new caption, which can be specified instead of *parse_mode*. @@ -85,8 +86,11 @@ async def copy_message( reply_to_message_id (``int``, *optional*): If the message is a reply, ID of the original message. - schedule_date (``int``, *optional*): - Date when the message will be automatically sent. Unix time. + schedule_date (:py:obj:`~datetime.datetime`, *optional*): + Date when the message will be automatically sent. + + protect_content (``bool``, *optional*): + Protects the contents of the sent message from forwarding and saving. reply_markup (:obj:`~pyrogram.types.InlineKeyboardMarkup` | :obj:`~pyrogram.types.ReplyKeyboardMarkup` | :obj:`~pyrogram.types.ReplyKeyboardRemove` | :obj:`~pyrogram.types.ForceReply`, *optional*): Additional interface options. An object for an inline keyboard, custom reply keyboard, @@ -99,7 +103,7 @@ async def copy_message( .. code-block:: python # Copy a message - app.copy_message("me", "pyrogram", 20) + await app.copy_message(to_chat, from_chat, 123) """ message: types.Message = await self.get_messages(from_chat_id, message_id) @@ -112,5 +116,6 @@ async def copy_message( disable_notification=disable_notification, reply_to_message_id=reply_to_message_id, schedule_date=schedule_date, + protect_content=protect_content, reply_markup=reply_markup ) diff --git a/pyrogram/methods/messages/delete_messages.py b/pyrogram/methods/messages/delete_messages.py index ba9da8967e..07c3a7b85b 100644 --- a/pyrogram/methods/messages/delete_messages.py +++ b/pyrogram/methods/messages/delete_messages.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2021 Dan +# Copyright (C) 2017-present Dan # # This file is part of Pyrogram. # @@ -18,28 +18,29 @@ from typing import Union, Iterable +import pyrogram from pyrogram import raw -from pyrogram.scaffold import Scaffold -class DeleteMessages(Scaffold): +class DeleteMessages: async def delete_messages( - self, + self: "pyrogram.Client", chat_id: Union[int, str], message_ids: Union[int, Iterable[int]], revoke: bool = True - ) -> bool: + ) -> int: """Delete messages, including service messages. + .. include:: /_includes/usable-by/users-bots.rst + Parameters: chat_id (``int`` | ``str``): Unique identifier (int) or username (str) of the target chat. For your personal cloud (Saved Messages) you can simply use "me" or "self". For a contact that exists in your Telegram address book you can use his phone number (str). - message_ids (``int`` | ``Iterable[int]``): - A list of Message identifiers to delete (integers) or a single message id. - Iterators and Generators are also accepted. + message_ids (``int`` | Iterable of ``int``): + An iterable of message identifiers to delete (integers) or a single message id. revoke (``bool``, *optional*): Deletes messages on both parts. @@ -48,38 +49,36 @@ async def delete_messages( Defaults to True. Returns: - ``bool``: True on success, False otherwise. + ``int``: Amount of affected messages Example: .. code-block:: python # Delete one message - app.delete_messages(chat_id, message_id) + await app.delete_messages(chat_id, message_id) # Delete multiple messages at once - app.delete_messages(chat_id, list_of_message_ids) + await app.delete_messages(chat_id, list_of_message_ids) # Delete messages only on your side (without revoking) - app.delete_messages(chat_id, message_id, revoke=False) + await app.delete_messages(chat_id, message_id, revoke=False) """ peer = await self.resolve_peer(chat_id) message_ids = list(message_ids) if not isinstance(message_ids, int) else [message_ids] if isinstance(peer, raw.types.InputPeerChannel): - r = await self.send( + r = await self.invoke( raw.functions.channels.DeleteMessages( channel=peer, id=message_ids ) ) else: - r = await self.send( + r = await self.invoke( raw.functions.messages.DeleteMessages( id=message_ids, - revoke=revoke or None + revoke=revoke ) ) - # Deleting messages you don't have right onto, won't raise any error. - # Check for pts_count, which is 0 in case deletes fail. - return bool(r.pts_count) + return r.pts_count diff --git a/pyrogram/methods/messages/download_media.py b/pyrogram/methods/messages/download_media.py index 826da89e81..4f44ff254f 100644 --- a/pyrogram/methods/messages/download_media.py +++ b/pyrogram/methods/messages/download_media.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2021 Dan +# Copyright (C) 2017-present Dan # # This file is part of Pyrogram. # @@ -18,28 +18,30 @@ import asyncio import os -import time from datetime import datetime -from typing import Union, Optional +from typing import Union, Optional, Callable, BinaryIO +import pyrogram from pyrogram import types from pyrogram.file_id import FileId, FileType, PHOTO_TYPES -from pyrogram.scaffold import Scaffold DEFAULT_DOWNLOAD_DIR = "downloads/" -class DownloadMedia(Scaffold): +class DownloadMedia: async def download_media( - self, + self: "pyrogram.Client", message: Union["types.Message", str], file_name: str = DEFAULT_DOWNLOAD_DIR, + in_memory: bool = False, block: bool = True, - progress: callable = None, + progress: Callable = None, progress_args: tuple = () - ) -> Optional[str]: + ) -> Optional[Union[str, BinaryIO]]: """Download the media from a message. + .. include:: /_includes/usable-by/users-bots.rst + Parameters: message (:obj:`~pyrogram.types.Message` | ``str``): Pass a Message containing the media, the media itself (message.audio, message.video, ...) or a file id @@ -51,11 +53,16 @@ async def download_media( You can also specify a path for downloading files in a custom location: paths that end with "/" are considered directories. All non-existent folders will be created automatically. + in_memory (``bool``, *optional*): + Pass True to download the media in-memory. + A binary file-like object with its attribute ".name" set will be returned. + Defaults to False. + block (``bool``, *optional*): Blocks the code execution until the file has been downloaded. Defaults to True. - progress (``callable``, *optional*): + progress (``Callable``, *optional*): Pass a callback function to view the file transmission progress. The function must take *(current, total)* as positional arguments (look at Other Parameters below for a detailed description) and will be called back each time a new file chunk has been successfully @@ -78,27 +85,39 @@ async def download_media( You can either keep ``*args`` or add every single extra argument in your function signature. Returns: - ``str`` | ``None``: On success, the absolute path of the downloaded file is returned, otherwise, in case - the download failed or was deliberately stopped with :meth:`~pyrogram.Client.stop_transmission`, None is - returned. + ``str`` | ``None`` | ``BinaryIO``: On success, the absolute path of the downloaded file is returned, + otherwise, in case the download failed or was deliberately stopped with + :meth:`~pyrogram.Client.stop_transmission`, None is returned. + Otherwise, in case ``in_memory=True``, a binary file-like object with its attribute ".name" set is returned. Raises: ValueError: if the message doesn't contain any downloadable media Example: + Download media to file + .. code-block:: python # Download from Message - app.download_media(message) + await app.download_media(message) # Download from file id - app.download_media("CAADBAADzg4AAvLQYAEz_x2EOgdRwBYE") + await app.download_media(message.photo.file_id) # Keep track of the progress while downloading - def progress(current, total): + async def progress(current, total): print(f"{current * 100 / total:.1f}%") - app.download_media(message, progress=progress) + await app.download_media(message, progress=progress) + + Download media in-memory + + .. code-block:: python + + file = await app.download_media(message, in_memory=True) + + file_name = file.name + file_bytes = bytes(file.getbuffer()) """ available_media = ("audio", "document", "photo", "sticker", "animation", "video", "voice", "video_note", "new_chat_photo") @@ -125,7 +144,7 @@ def progress(current, total): media_file_name = getattr(media, "file_name", "") file_size = getattr(media, "file_size", 0) mime_type = getattr(media, "mime_type", "") - date = getattr(media, "date", 0) + date = getattr(media, "date", None) directory, file_name = os.path.split(file_name) file_name = file_name or media_file_name or "" @@ -153,12 +172,14 @@ def progress(current, total): file_name = "{}_{}_{}{}".format( FileType(file_id_obj.file_type).name.lower(), - datetime.fromtimestamp(date or time.time()).strftime("%Y-%m-%d_%H-%M-%S"), + (date or datetime.now()).strftime("%Y-%m-%d_%H-%M-%S"), self.rnd_id(), extension ) - downloader = self.handle_download((file_id_obj, directory, file_name, file_size, progress, progress_args)) + downloader = self.handle_download( + (file_id_obj, directory, file_name, in_memory, file_size, progress, progress_args) + ) if block: return await downloader diff --git a/pyrogram/methods/messages/edit_inline_caption.py b/pyrogram/methods/messages/edit_inline_caption.py index b291589731..69c7333416 100644 --- a/pyrogram/methods/messages/edit_inline_caption.py +++ b/pyrogram/methods/messages/edit_inline_caption.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2021 Dan +# Copyright (C) 2017-present Dan # # This file is part of Pyrogram. # @@ -18,20 +18,22 @@ from typing import Optional -from pyrogram import types -from pyrogram.scaffold import Scaffold +import pyrogram +from pyrogram import types, enums -class EditInlineCaption(Scaffold): +class EditInlineCaption: async def edit_inline_caption( - self, + self: "pyrogram.Client", inline_message_id: str, caption: str, - parse_mode: Optional[str] = object, + parse_mode: Optional["enums.ParseMode"] = None, reply_markup: "types.InlineKeyboardMarkup" = None ) -> bool: """Edit the caption of inline media messages. + .. include:: /_includes/usable-by/bots.rst + Parameters: inline_message_id (``str``): Identifier of the inline message. @@ -39,12 +41,9 @@ async def edit_inline_caption( caption (``str``): New caption of the media message. - parse_mode (``str``, *optional*): + parse_mode (:obj:`~pyrogram.enums.ParseMode`, *optional*): By default, texts are parsed using both Markdown and HTML styles. You can combine both syntaxes together. - Pass "markdown" or "md" to enable Markdown-style parsing only. - Pass "html" to enable HTML-style parsing only. - Pass None to completely disable style parsing. reply_markup (:obj:`~pyrogram.types.InlineKeyboardMarkup`, *optional*): An InlineKeyboardMarkup object. @@ -56,7 +55,7 @@ async def edit_inline_caption( .. code-block:: python # Bots only - app.edit_inline_caption(inline_message_id, "new media caption") + await app.edit_inline_caption(inline_message_id, "new media caption") """ return await self.edit_inline_text( inline_message_id=inline_message_id, diff --git a/pyrogram/methods/messages/edit_inline_media.py b/pyrogram/methods/messages/edit_inline_media.py index 13b2736649..7ab424a4f2 100644 --- a/pyrogram/methods/messages/edit_inline_media.py +++ b/pyrogram/methods/messages/edit_inline_media.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2021 Dan +# Copyright (C) 2017-present Dan # # This file is part of Pyrogram. # @@ -16,20 +16,25 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . +import asyncio +import io import os import re +import pyrogram from pyrogram import raw from pyrogram import types from pyrogram import utils +from pyrogram.errors import RPCError, MediaEmpty from pyrogram.file_id import FileType -from pyrogram.scaffold import Scaffold from .inline_session import get_session -class EditInlineMedia(Scaffold): +class EditInlineMedia: + MAX_RETRIES = 3 + async def edit_inline_media( - self, + self: "pyrogram.Client", inline_message_id: str, media: "types.InputMedia", reply_markup: "types.InlineKeyboardMarkup" = None @@ -39,6 +44,8 @@ async def edit_inline_media( When the inline message is edited, a new file can't be uploaded. Use a previously uploaded file via its file_id or specify a URL. + .. include:: /_includes/usable-by/bots.rst + Parameters: inline_message_id (``str``): Required if *chat_id* and *message_id* are not specified. @@ -61,113 +68,125 @@ async def edit_inline_media( # Bots only # Replace the current media with a local photo - app.edit_inline_media(inline_message_id, InputMediaPhoto("new_photo.jpg")) + await app.edit_inline_media(inline_message_id, InputMediaPhoto("new_photo.jpg")) # Replace the current media with a local video - app.edit_inline_media(inline_message_id, InputMediaVideo("new_video.mp4")) + await app.edit_inline_media(inline_message_id, InputMediaVideo("new_video.mp4")) # Replace the current media with a local audio - app.edit_inline_media(inline_message_id, InputMediaAudio("new_audio.mp3")) + await app.edit_inline_media(inline_message_id, InputMediaAudio("new_audio.mp3")) """ caption = media.caption parse_mode = media.parse_mode + is_bytes_io = isinstance(media.media, io.BytesIO) + is_uploaded_file = is_bytes_io or os.path.isfile(media.media) + + is_external_url = not is_uploaded_file and re.match("^https?://", media.media) + + if is_bytes_io and not hasattr(media.media, "name"): + media.media.name = "media" + + if is_uploaded_file: + filename_attribute = [ + raw.types.DocumentAttributeFilename( + file_name=media.media.name if is_bytes_io else os.path.basename(media.media) + ) + ] + else: + filename_attribute = [] + if isinstance(media, types.InputMediaPhoto): - if os.path.isfile(media.media): + if is_uploaded_file: media = raw.types.InputMediaUploadedPhoto( - file=await self.save_file(media.media) + file=await self.save_file(media.media), + spoiler=media.has_spoiler ) - elif re.match("^https?://", media.media): + elif is_external_url: media = raw.types.InputMediaPhotoExternal( - url=media.media + url=media.media, + spoiler=media.has_spoiler ) else: media = utils.get_input_media_from_file_id(media.media, FileType.PHOTO) elif isinstance(media, types.InputMediaVideo): - if os.path.isfile(media.media): + if is_uploaded_file: media = raw.types.InputMediaUploadedDocument( - mime_type=self.guess_mime_type(media.media) or "video/mp4", + mime_type=(None if is_bytes_io else self.guess_mime_type(media.media)) or "video/mp4", thumb=await self.save_file(media.thumb), file=await self.save_file(media.media), + spoiler=media.has_spoiler, attributes=[ - raw.types.DocumentAttributeVideo( - supports_streaming=media.supports_streaming or None, - duration=media.duration, - w=media.width, - h=media.height - ), - raw.types.DocumentAttributeFilename( - file_name=os.path.basename(media.media) - ) - ] + raw.types.DocumentAttributeVideo( + supports_streaming=media.supports_streaming or None, + duration=media.duration, + w=media.width, + h=media.height + ) + ] + filename_attribute ) - elif re.match("^https?://", media.media): + elif is_external_url: media = raw.types.InputMediaDocumentExternal( - url=media.media + url=media.media, + spoiler=media.has_spoiler ) else: media = utils.get_input_media_from_file_id(media.media, FileType.VIDEO) elif isinstance(media, types.InputMediaAudio): - if os.path.isfile(media.media): + if is_uploaded_file: media = raw.types.InputMediaUploadedDocument( - mime_type=self.guess_mime_type(media.media) or "audio/mpeg", + mime_type=(None if is_bytes_io else self.guess_mime_type(media.media)) or "audio/mpeg", thumb=await self.save_file(media.thumb), file=await self.save_file(media.media), attributes=[ - raw.types.DocumentAttributeAudio( - duration=media.duration, - performer=media.performer, - title=media.title - ), - raw.types.DocumentAttributeFilename( - file_name=os.path.basename(media.media) - ) - ] + raw.types.DocumentAttributeAudio( + duration=media.duration, + performer=media.performer, + title=media.title + ) + ] + filename_attribute ) - elif re.match("^https?://", media.media): + elif is_external_url: media = raw.types.InputMediaDocumentExternal( url=media.media ) else: media = utils.get_input_media_from_file_id(media.media, FileType.AUDIO) elif isinstance(media, types.InputMediaAnimation): - if os.path.isfile(media.media): + if is_uploaded_file: media = raw.types.InputMediaUploadedDocument( - mime_type=self.guess_mime_type(media.media) or "video/mp4", + mime_type=(None if is_bytes_io else self.guess_mime_type(media.media)) or "video/mp4", thumb=await self.save_file(media.thumb), file=await self.save_file(media.media), + spoiler=media.has_spoiler, attributes=[ - raw.types.DocumentAttributeVideo( - supports_streaming=True, - duration=media.duration, - w=media.width, - h=media.height - ), - raw.types.DocumentAttributeFilename( - file_name=os.path.basename(media.media) - ), - raw.types.DocumentAttributeAnimated() - ] + raw.types.DocumentAttributeVideo( + supports_streaming=True, + duration=media.duration, + w=media.width, + h=media.height + ), + raw.types.DocumentAttributeAnimated() + ] + filename_attribute, + nosound_video=True ) - elif re.match("^https?://", media.media): + elif is_external_url: media = raw.types.InputMediaDocumentExternal( - url=media.media + url=media.media, + spoiler=media.has_spoiler ) else: media = utils.get_input_media_from_file_id(media.media, FileType.ANIMATION) elif isinstance(media, types.InputMediaDocument): - if os.path.isfile(media.media): + if is_uploaded_file: media = raw.types.InputMediaUploadedDocument( - mime_type=self.guess_mime_type(media.media) or "application/zip", + mime_type=(None if is_bytes_io else self.guess_mime_type(media.media)) or "application/zip", thumb=await self.save_file(media.thumb), file=await self.save_file(media.media), - attributes=[ - raw.types.DocumentAttributeFilename( - file_name=os.path.basename(media.media) - ) - ] + attributes=filename_attribute, + force_file=True ) - elif re.match("^https?://", media.media): + elif is_external_url: media = raw.types.InputMediaDocumentExternal( url=media.media ) @@ -179,12 +198,47 @@ async def edit_inline_media( session = await get_session(self, dc_id) - return await session.send( - raw.functions.messages.EditInlineBotMessage( - id=unpacked, - media=media, - reply_markup=await reply_markup.write(self) if reply_markup else None, - **await self.parser.parse(caption, parse_mode) - ), - sleep_threshold=self.sleep_threshold - ) + if is_uploaded_file: + uploaded_media = await self.invoke( + raw.functions.messages.UploadMedia( + peer=raw.types.InputPeerSelf(), + media=media + ) + ) + + actual_media = raw.types.InputMediaPhoto( + id=raw.types.InputPhoto( + id=uploaded_media.photo.id, + access_hash=uploaded_media.photo.access_hash, + file_reference=uploaded_media.photo.file_reference + ), + spoiler=getattr(media, "has_spoiler", None) + ) if isinstance(media, types.InputMediaPhoto) else raw.types.InputMediaDocument( + id=raw.types.InputDocument( + id=uploaded_media.document.id, + access_hash=uploaded_media.document.access_hash, + file_reference=uploaded_media.document.file_reference + ), + spoiler=getattr(media, "has_spoiler", None) + ) + else: + actual_media = media + + for i in range(self.MAX_RETRIES): + try: + return await session.invoke( + raw.functions.messages.EditInlineBotMessage( + id=unpacked, + media=actual_media, + reply_markup=await reply_markup.write(self) if reply_markup else None, + **await self.parser.parse(caption, parse_mode) + ), + sleep_threshold=self.sleep_threshold + ) + except RPCError as e: + if i == self.MAX_RETRIES - 1: + raise + + if isinstance(e, MediaEmpty): + # Must wait due to a server race condition + await asyncio.sleep(1) diff --git a/pyrogram/methods/messages/edit_inline_reply_markup.py b/pyrogram/methods/messages/edit_inline_reply_markup.py index 5966d4b7d2..e2ef40e160 100644 --- a/pyrogram/methods/messages/edit_inline_reply_markup.py +++ b/pyrogram/methods/messages/edit_inline_reply_markup.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2021 Dan +# Copyright (C) 2017-present Dan # # This file is part of Pyrogram. # @@ -16,21 +16,23 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . +import pyrogram from pyrogram import raw from pyrogram import types from pyrogram import utils -from pyrogram.scaffold import Scaffold from .inline_session import get_session -class EditInlineReplyMarkup(Scaffold): +class EditInlineReplyMarkup: async def edit_inline_reply_markup( - self, + self: "pyrogram.Client", inline_message_id: str, reply_markup: "types.InlineKeyboardMarkup" = None ) -> bool: """Edit only the reply markup of inline messages sent via the bot (for inline bots). + .. include:: /_includes/usable-by/bots.rst + Parameters: inline_message_id (``str``): Identifier of the inline message. @@ -47,7 +49,7 @@ async def edit_inline_reply_markup( from pyrogram.types import InlineKeyboardMarkup, InlineKeyboardButton # Bots only - app.edit_inline_reply_markup( + await app.edit_inline_reply_markup( inline_message_id, InlineKeyboardMarkup([[ InlineKeyboardButton("New button", callback_data="new_data")]])) @@ -58,7 +60,7 @@ async def edit_inline_reply_markup( session = await get_session(self, dc_id) - return await session.send( + return await session.invoke( raw.functions.messages.EditInlineBotMessage( id=unpacked, reply_markup=await reply_markup.write(self) if reply_markup else None, diff --git a/pyrogram/methods/messages/edit_inline_text.py b/pyrogram/methods/messages/edit_inline_text.py index e9a79e1407..354c55a339 100644 --- a/pyrogram/methods/messages/edit_inline_text.py +++ b/pyrogram/methods/messages/edit_inline_text.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2021 Dan +# Copyright (C) 2017-present Dan # # This file is part of Pyrogram. # @@ -18,24 +18,26 @@ from typing import Optional -from pyrogram import raw +import pyrogram +from pyrogram import raw, enums from pyrogram import types from pyrogram import utils -from pyrogram.scaffold import Scaffold from .inline_session import get_session -class EditInlineText(Scaffold): +class EditInlineText: async def edit_inline_text( - self, + self: "pyrogram.Client", inline_message_id: str, text: str, - parse_mode: Optional[str] = object, + parse_mode: Optional["enums.ParseMode"] = None, disable_web_page_preview: bool = None, reply_markup: "types.InlineKeyboardMarkup" = None ) -> bool: """Edit the text of inline messages. + .. include:: /_includes/usable-by/bots.rst + Parameters: inline_message_id (``str``): Identifier of the inline message. @@ -43,12 +45,9 @@ async def edit_inline_text( text (``str``): New text of the message. - parse_mode (``str``, *optional*): + parse_mode (:obj:`~pyrogram.enums.ParseMode`, *optional*): By default, texts are parsed using both Markdown and HTML styles. You can combine both syntaxes together. - Pass "markdown" or "md" to enable Markdown-style parsing only. - Pass "html" to enable HTML-style parsing only. - Pass None to completely disable style parsing. disable_web_page_preview (``bool``, *optional*): Disables link previews for links in this message. @@ -65,10 +64,10 @@ async def edit_inline_text( # Bots only # Simple edit text - app.edit_inline_text(inline_message_id, "new text") + await app.edit_inline_text(inline_message_id, "new text") # Take the same text message, remove the web page preview only - app.edit_inline_text( + await app.edit_inline_text( inline_message_id, message.text, disable_web_page_preview=True) """ @@ -78,7 +77,7 @@ async def edit_inline_text( session = await get_session(self, dc_id) - return await session.send( + return await session.invoke( raw.functions.messages.EditInlineBotMessage( id=unpacked, no_webpage=disable_web_page_preview or None, diff --git a/pyrogram/methods/messages/edit_message_caption.py b/pyrogram/methods/messages/edit_message_caption.py index 53e0911c2d..2e4a45a694 100644 --- a/pyrogram/methods/messages/edit_message_caption.py +++ b/pyrogram/methods/messages/edit_message_caption.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2021 Dan +# Copyright (C) 2017-present Dan # # This file is part of Pyrogram. # @@ -18,22 +18,24 @@ from typing import Union, List, Optional -from pyrogram import types -from pyrogram.scaffold import Scaffold +import pyrogram +from pyrogram import types, enums -class EditMessageCaption(Scaffold): +class EditMessageCaption: async def edit_message_caption( - self, + self: "pyrogram.Client", chat_id: Union[int, str], message_id: int, caption: str, - parse_mode: Optional[str] = object, + parse_mode: Optional["enums.ParseMode"] = None, caption_entities: List["types.MessageEntity"] = None, reply_markup: "types.InlineKeyboardMarkup" = None ) -> "types.Message": """Edit the caption of media messages. + .. include:: /_includes/usable-by/users-bots.rst + Parameters: chat_id (``int`` | ``str``): Unique identifier (int) or username (str) of the target chat. @@ -46,12 +48,9 @@ async def edit_message_caption( caption (``str``): New caption of the media message. - parse_mode (``str``, *optional*): + parse_mode (:obj:`~pyrogram.enums.ParseMode`, *optional*): By default, texts are parsed using both Markdown and HTML styles. You can combine both syntaxes together. - Pass "markdown" or "md" to enable Markdown-style parsing only. - Pass "html" to enable HTML-style parsing only. - Pass None to completely disable style parsing. caption_entities (List of :obj:`~pyrogram.types.MessageEntity`): List of special entities that appear in the caption, which can be specified instead of *parse_mode*. @@ -65,7 +64,7 @@ async def edit_message_caption( Example: .. code-block:: python - app.edit_message_caption(chat_id, message_id, "new media caption") + await app.edit_message_caption(chat_id, message_id, "new media caption") """ return await self.edit_message_text( chat_id=chat_id, diff --git a/pyrogram/methods/messages/edit_message_media.py b/pyrogram/methods/messages/edit_message_media.py index a0d365dcc9..5a34f13875 100644 --- a/pyrogram/methods/messages/edit_message_media.py +++ b/pyrogram/methods/messages/edit_message_media.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2021 Dan +# Copyright (C) 2017-present Dan # # This file is part of Pyrogram. # @@ -16,20 +16,21 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . +import io import os import re from typing import Union +import pyrogram from pyrogram import raw from pyrogram import types from pyrogram import utils from pyrogram.file_id import FileType -from pyrogram.scaffold import Scaffold -class EditMessageMedia(Scaffold): +class EditMessageMedia: async def edit_message_media( - self, + self: "pyrogram.Client", chat_id: Union[int, str], message_id: int, media: "types.InputMedia", @@ -41,6 +42,8 @@ async def edit_message_media( If a message is a part of a message album, then it can be edited only to a photo or a video. Otherwise, the message type can be changed arbitrarily. + .. include:: /_includes/usable-by/users-bots.rst + Parameters: chat_id (``int`` | ``str``): Unique identifier (int) or username (str) of the target chat. @@ -69,13 +72,16 @@ async def edit_message_media( from pyrogram.types import InputMediaPhoto, InputMediaVideo, InputMediaAudio # Replace the current media with a local photo - app.edit_message_media(chat_id, message_id, InputMediaPhoto("new_photo.jpg")) + await app.edit_message_media(chat_id, message_id, + InputMediaPhoto("new_photo.jpg")) # Replace the current media with a local video - app.edit_message_media(chat_id, message_id, InputMediaVideo("new_video.mp4")) + await app.edit_message_media(chat_id, message_id, + InputMediaVideo("new_video.mp4")) # Replace the current media with a local audio - app.edit_message_media(chat_id, message_id, InputMediaAudio("new_audio.mp3")) + await app.edit_message_media(chat_id, message_id, + InputMediaAudio("new_audio.mp3")) """ caption = media.caption parse_mode = media.parse_mode @@ -86,37 +92,41 @@ async def edit_message_media( message, entities = (await self.parser.parse(caption, parse_mode)).values() if isinstance(media, types.InputMediaPhoto): - if os.path.isfile(media.media): - media = await self.send( + if isinstance(media.media, io.BytesIO) or os.path.isfile(media.media): + uploaded_media = await self.invoke( raw.functions.messages.UploadMedia( peer=await self.resolve_peer(chat_id), media=raw.types.InputMediaUploadedPhoto( - file=await self.save_file(media.media) + file=await self.save_file(media.media), + spoiler=media.has_spoiler ) ) ) media = raw.types.InputMediaPhoto( id=raw.types.InputPhoto( - id=media.photo.id, - access_hash=media.photo.access_hash, - file_reference=media.photo.file_reference - ) + id=uploaded_media.photo.id, + access_hash=uploaded_media.photo.access_hash, + file_reference=uploaded_media.photo.file_reference + ), + spoiler=media.has_spoiler ) elif re.match("^https?://", media.media): media = raw.types.InputMediaPhotoExternal( - url=media.media + url=media.media, + spoiler=media.has_spoiler ) else: media = utils.get_input_media_from_file_id(media.media, FileType.PHOTO) elif isinstance(media, types.InputMediaVideo): - if os.path.isfile(media.media): - media = await self.send( + if isinstance(media.media, io.BytesIO) or os.path.isfile(media.media): + uploaded_media = await self.invoke( raw.functions.messages.UploadMedia( peer=await self.resolve_peer(chat_id), media=raw.types.InputMediaUploadedDocument( mime_type=self.guess_mime_type(media.media) or "video/mp4", thumb=await self.save_file(media.thumb), + spoiler=media.has_spoiler, file=await self.save_file(media.media), attributes=[ raw.types.DocumentAttributeVideo( @@ -135,20 +145,22 @@ async def edit_message_media( media = raw.types.InputMediaDocument( id=raw.types.InputDocument( - id=media.document.id, - access_hash=media.document.access_hash, - file_reference=media.document.file_reference - ) + id=uploaded_media.document.id, + access_hash=uploaded_media.document.access_hash, + file_reference=uploaded_media.document.file_reference + ), + spoiler=media.has_spoiler ) elif re.match("^https?://", media.media): media = raw.types.InputMediaDocumentExternal( - url=media.media + url=media.media, + spoiler=media.has_spoiler ) else: media = utils.get_input_media_from_file_id(media.media, FileType.VIDEO) elif isinstance(media, types.InputMediaAudio): - if os.path.isfile(media.media): - media = await self.send( + if isinstance(media.media, io.BytesIO) or os.path.isfile(media.media): + media = await self.invoke( raw.functions.messages.UploadMedia( peer=await self.resolve_peer(chat_id), media=raw.types.InputMediaUploadedDocument( @@ -183,13 +195,14 @@ async def edit_message_media( else: media = utils.get_input_media_from_file_id(media.media, FileType.AUDIO) elif isinstance(media, types.InputMediaAnimation): - if os.path.isfile(media.media): - media = await self.send( + if isinstance(media.media, io.BytesIO) or os.path.isfile(media.media): + uploaded_media = await self.invoke( raw.functions.messages.UploadMedia( peer=await self.resolve_peer(chat_id), media=raw.types.InputMediaUploadedDocument( mime_type=self.guess_mime_type(media.media) or "video/mp4", thumb=await self.save_file(media.thumb), + spoiler=media.has_spoiler, file=await self.save_file(media.media), attributes=[ raw.types.DocumentAttributeVideo( @@ -209,20 +222,22 @@ async def edit_message_media( media = raw.types.InputMediaDocument( id=raw.types.InputDocument( - id=media.document.id, - access_hash=media.document.access_hash, - file_reference=media.document.file_reference - ) + id=uploaded_media.document.id, + access_hash=uploaded_media.document.access_hash, + file_reference=uploaded_media.document.file_reference + ), + spoiler=media.has_spoiler ) elif re.match("^https?://", media.media): media = raw.types.InputMediaDocumentExternal( - url=media.media + url=media.media, + spoiler=media.has_spoiler ) else: media = utils.get_input_media_from_file_id(media.media, FileType.ANIMATION) elif isinstance(media, types.InputMediaDocument): - if os.path.isfile(media.media): - media = await self.send( + if isinstance(media.media, io.BytesIO) or os.path.isfile(media.media): + media = await self.invoke( raw.functions.messages.UploadMedia( peer=await self.resolve_peer(chat_id), media=raw.types.InputMediaUploadedDocument( @@ -252,7 +267,7 @@ async def edit_message_media( else: media = utils.get_input_media_from_file_id(media.media, FileType.DOCUMENT) - r = await self.send( + r = await self.invoke( raw.functions.messages.EditMessage( peer=await self.resolve_peer(chat_id), id=message_id, diff --git a/pyrogram/methods/messages/edit_message_reply_markup.py b/pyrogram/methods/messages/edit_message_reply_markup.py index aefb87e9e5..1cd75c1a8b 100644 --- a/pyrogram/methods/messages/edit_message_reply_markup.py +++ b/pyrogram/methods/messages/edit_message_reply_markup.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2021 Dan +# Copyright (C) 2017-present Dan # # This file is part of Pyrogram. # @@ -18,20 +18,22 @@ from typing import Union +import pyrogram from pyrogram import raw from pyrogram import types -from pyrogram.scaffold import Scaffold -class EditMessageReplyMarkup(Scaffold): +class EditMessageReplyMarkup: async def edit_message_reply_markup( - self, + self: "pyrogram.Client", chat_id: Union[int, str], message_id: int, reply_markup: "types.InlineKeyboardMarkup" = None, ) -> "types.Message": """Edit only the reply markup of messages sent by the bot. + .. include:: /_includes/usable-by/bots.rst + Parameters: chat_id (``int`` | ``str``): Unique identifier (int) or username (str) of the target chat. @@ -53,12 +55,12 @@ async def edit_message_reply_markup( from pyrogram.types import InlineKeyboardMarkup, InlineKeyboardButton # Bots only - app.edit_message_reply_markup( + await app.edit_message_reply_markup( chat_id, message_id, InlineKeyboardMarkup([[ InlineKeyboardButton("New button", callback_data="new_data")]])) """ - r = await self.send( + r = await self.invoke( raw.functions.messages.EditMessage( peer=await self.resolve_peer(chat_id), id=message_id, diff --git a/pyrogram/methods/messages/edit_message_text.py b/pyrogram/methods/messages/edit_message_text.py index 443d71f6af..540b6aa63c 100644 --- a/pyrogram/methods/messages/edit_message_text.py +++ b/pyrogram/methods/messages/edit_message_text.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2021 Dan +# Copyright (C) 2017-present Dan # # This file is part of Pyrogram. # @@ -18,25 +18,27 @@ from typing import Union, List, Optional -from pyrogram import raw +import pyrogram +from pyrogram import raw, enums from pyrogram import types from pyrogram import utils -from pyrogram.scaffold import Scaffold -class EditMessageText(Scaffold): +class EditMessageText: async def edit_message_text( - self, + self: "pyrogram.Client", chat_id: Union[int, str], message_id: int, text: str, - parse_mode: Optional[str] = object, + parse_mode: Optional["enums.ParseMode"] = None, entities: List["types.MessageEntity"] = None, disable_web_page_preview: bool = None, reply_markup: "types.InlineKeyboardMarkup" = None ) -> "types.Message": """Edit the text of messages. + .. include:: /_includes/usable-by/users-bots.rst + Parameters: chat_id (``int`` | ``str``): Unique identifier (int) or username (str) of the target chat. @@ -49,12 +51,9 @@ async def edit_message_text( text (``str``): New text of the message. - parse_mode (``str``, *optional*): + parse_mode (:obj:`~pyrogram.enums.ParseMode`, *optional*): By default, texts are parsed using both Markdown and HTML styles. You can combine both syntaxes together. - Pass "markdown" or "md" to enable Markdown-style parsing only. - Pass "html" to enable HTML-style parsing only. - Pass None to completely disable style parsing. entities (List of :obj:`~pyrogram.types.MessageEntity`): List of special entities that appear in message text, which can be specified instead of *parse_mode*. @@ -72,15 +71,15 @@ async def edit_message_text( .. code-block:: python # Simple edit text - app.edit_message_text(chat_id, message_id, "new text") + await app.edit_message_text(chat_id, message_id, "new text") # Take the same text message, remove the web page preview only - app.edit_message_text( + await app.edit_message_text( chat_id, message_id, message.text, disable_web_page_preview=True) """ - r = await self.send( + r = await self.invoke( raw.functions.messages.EditMessage( peer=await self.resolve_peer(chat_id), id=message_id, diff --git a/pyrogram/methods/messages/forward_messages.py b/pyrogram/methods/messages/forward_messages.py index b67e50ec0b..8635e17101 100644 --- a/pyrogram/methods/messages/forward_messages.py +++ b/pyrogram/methods/messages/forward_messages.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2021 Dan +# Copyright (C) 2017-present Dan # # This file is part of Pyrogram. # @@ -16,24 +16,28 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . -from typing import Union, Iterable, List +from datetime import datetime +from typing import Union, List, Iterable -from pyrogram import raw +import pyrogram +from pyrogram import raw, utils from pyrogram import types -from pyrogram.scaffold import Scaffold -class ForwardMessages(Scaffold): +class ForwardMessages: async def forward_messages( - self, + self: "pyrogram.Client", chat_id: Union[int, str], from_chat_id: Union[int, str], message_ids: Union[int, Iterable[int]], disable_notification: bool = None, - schedule_date: int = None + schedule_date: datetime = None, + protect_content: bool = None ) -> Union["types.Message", List["types.Message"]]: """Forward messages of any kind. + .. include:: /_includes/usable-by/users-bots.rst + Parameters: chat_id (``int`` | ``str``): Unique identifier (int) or username (str) of the target chat. @@ -45,44 +49,45 @@ async def forward_messages( For your personal cloud (Saved Messages) you can simply use "me" or "self". For a contact that exists in your Telegram address book you can use his phone number (str). - message_ids (``int`` | List of ``int``): - A list of Message identifiers in the chat specified in *from_chat_id* or a single message id. - Iterators and Generators are also accepted. + message_ids (``int`` | Iterable of ``int``): + An iterable of message identifiers in the chat specified in *from_chat_id* or a single message id. disable_notification (``bool``, *optional*): Sends the message silently. Users will receive a notification with no sound. - schedule_date (``int``, *optional*): - Date when the message will be automatically sent. Unix time. + schedule_date (:py:obj:`~datetime.datetime`, *optional*): + Date when the message will be automatically sent. + + protect_content (``bool``, *optional*): + Protects the contents of the sent message from forwarding and saving. Returns: - :obj:`~pyrogram.types.Message` | List of :obj:`~pyrogram.types.Message`: In case *message_ids* was an - integer, the single forwarded message is returned, otherwise, in case *message_ids* was an iterable, - the returned value will be a list of messages, even if such iterable contained just a single element. + :obj:`~pyrogram.types.Message` | List of :obj:`~pyrogram.types.Message`: In case *message_ids* was not + a list, a single message is returned, otherwise a list of messages is returned. Example: .. code-block:: python - :emphasize-lines: 2,5 # Forward a single message - app.forward_messages("me", "pyrogram", 20) + await app.forward_messages(to_chat, from_chat, 123) # Forward multiple messages at once - app.forward_messages("me", "pyrogram", [3, 20, 27]) + await app.forward_messages(to_chat, from_chat, [1, 2, 3]) """ is_iterable = not isinstance(message_ids, int) message_ids = list(message_ids) if is_iterable else [message_ids] - r = await self.send( + r = await self.invoke( raw.functions.messages.ForwardMessages( to_peer=await self.resolve_peer(chat_id), from_peer=await self.resolve_peer(from_chat_id), id=message_ids, silent=disable_notification or None, random_id=[self.rnd_id() for _ in message_ids], - schedule_date=schedule_date + schedule_date=utils.datetime_to_timestamp(schedule_date), + noforwards=protect_content ) ) diff --git a/pyrogram/methods/messages/iter_history.py b/pyrogram/methods/messages/get_chat_history.py similarity index 61% rename from pyrogram/methods/messages/iter_history.py rename to pyrogram/methods/messages/get_chat_history.py index 9fecc8743e..b384c6ba8f 100644 --- a/pyrogram/methods/messages/iter_history.py +++ b/pyrogram/methods/messages/get_chat_history.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2021 Dan +# Copyright (C) 2017-present Dan # # This file is part of Pyrogram. # @@ -16,27 +16,53 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . +from datetime import datetime from typing import Union, Optional, AsyncGenerator -from pyrogram import types -from pyrogram.scaffold import Scaffold - - -class IterHistory(Scaffold): - async def iter_history( - self, +import pyrogram +from pyrogram import types, raw, utils + + +async def get_chunk( + *, + client: "pyrogram.Client", + chat_id: Union[int, str], + limit: int = 0, + offset: int = 0, + from_message_id: int = 0, + from_date: datetime = utils.zero_datetime() +): + messages = await client.invoke( + raw.functions.messages.GetHistory( + peer=await client.resolve_peer(chat_id), + offset_id=from_message_id, + offset_date=utils.datetime_to_timestamp(from_date), + add_offset=offset, + limit=limit, + max_id=0, + min_id=0, + hash=0 + ), + sleep_threshold=60 + ) + + return await utils.parse_messages(client, messages, replies=0) + + +class GetChatHistory: + async def get_chat_history( + self: "pyrogram.Client", chat_id: Union[int, str], limit: int = 0, offset: int = 0, offset_id: int = 0, - offset_date: int = 0, - reverse: bool = False + offset_date: datetime = utils.zero_datetime() ) -> Optional[AsyncGenerator["types.Message", None]]: - """Iterate through a chat history sequentially. + """Get messages from a chat history. - This convenience method does the same as repeatedly calling :meth:`~pyrogram.Client.get_history` in a loop, thus saving - you from the hassle of setting up boilerplate code. It is useful for getting the whole chat history with a - single call. + The messages are returned in reverse chronological order. + + .. include:: /_includes/usable-by/users.rst Parameters: chat_id (``int`` | ``str``): @@ -55,11 +81,8 @@ async def iter_history( offset_id (``int``, *optional*): Identifier of the first message to be returned. - offset_date (``int``, *optional*): - Pass a date in Unix time as offset to retrieve only older messages starting from that date. - - reverse (``bool``, *optional*): - Pass True to retrieve the messages in reversed order (from older to most recent). + offset_date (:py:obj:`~datetime.datetime`, *optional*): + Pass a date as offset to retrieve only older messages starting from that date. Returns: ``Generator``: A generator yielding :obj:`~pyrogram.types.Message` objects. @@ -67,28 +90,27 @@ async def iter_history( Example: .. code-block:: python - for message in app.iter_history("pyrogram"): + async for message in app.get_chat_history(chat_id): print(message.text) """ - offset_id = offset_id or (1 if reverse else 0) current = 0 total = limit or (1 << 31) - 1 limit = min(100, total) while True: - messages = await self.get_history( + messages = await get_chunk( + client=self, chat_id=chat_id, limit=limit, offset=offset, - offset_id=offset_id, - offset_date=offset_date, - reverse=reverse + from_message_id=offset_id, + from_date=offset_date ) if not messages: return - offset_id = messages[-1].message_id + (1 if reverse else 0) + offset_id = messages[-1].id for message in messages: yield message diff --git a/pyrogram/methods/messages/get_history_count.py b/pyrogram/methods/messages/get_chat_history_count.py similarity index 86% rename from pyrogram/methods/messages/get_history_count.py rename to pyrogram/methods/messages/get_chat_history_count.py index 191a58e083..1926224b35 100644 --- a/pyrogram/methods/messages/get_history_count.py +++ b/pyrogram/methods/messages/get_chat_history_count.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2021 Dan +# Copyright (C) 2017-present Dan # # This file is part of Pyrogram. # @@ -19,15 +19,15 @@ import logging from typing import Union +import pyrogram from pyrogram import raw -from pyrogram.scaffold import Scaffold log = logging.getLogger(__name__) -class GetHistoryCount(Scaffold): - async def get_history_count( - self, +class GetChatHistoryCount: + async def get_chat_history_count( + self: "pyrogram.Client", chat_id: Union[int, str] ) -> int: """Get the total count of messages in a chat. @@ -38,6 +38,8 @@ async def get_history_count( a **private** or a **basic group** chat has with a single method call. To overcome this limitation, Pyrogram has to iterate over all the messages. Channels and supergroups are not affected by this limitation. + .. include:: /_includes/usable-by/users.rst + Parameters: chat_id (``int`` | ``str``): Unique identifier (int) or username (str) of the target chat. @@ -48,10 +50,10 @@ async def get_history_count( Example: .. code-block:: python - app.get_history_count("pyrogramchat") + await app.get_history_count(chat_id) """ - r = await self.send( + r = await self.invoke( raw.functions.messages.GetHistory( peer=await self.resolve_peer(chat_id), offset_id=0, diff --git a/pyrogram/methods/messages/get_custom_emoji_stickers.py b/pyrogram/methods/messages/get_custom_emoji_stickers.py new file mode 100644 index 0000000000..7a71f0587c --- /dev/null +++ b/pyrogram/methods/messages/get_custom_emoji_stickers.py @@ -0,0 +1,55 @@ +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-present Dan +# +# This file is part of Pyrogram. +# +# Pyrogram is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License as published +# by the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Pyrogram is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with Pyrogram. If not, see . + +from typing import List + +import pyrogram +from pyrogram import raw +from pyrogram import types + + +class GetCustomEmojiStickers: + async def get_custom_emoji_stickers( + self: "pyrogram.Client", + custom_emoji_ids: List[int], + ) -> List["types.Sticker"]: + """Get information about custom emoji stickers by their identifiers. + + .. include:: /_includes/usable-by/users-bots.rst + + Parameters: + custom_emoji_ids (List of ``int``): + List of custom emoji identifiers. + At most 200 custom emoji identifiers can be specified. + + Returns: + List of :obj:`~pyrogram.types.Sticker`: On success, a list of sticker objects is returned. + """ + result = await self.invoke( + raw.functions.messages.GetCustomEmojiDocuments( + document_id=custom_emoji_ids + ) + ) + + stickers = [] + for item in result: + attributes = {type(i): i for i in item.attributes} + sticker = await types.Sticker._parse(self, item, attributes) + stickers.append(sticker) + + return pyrogram.types.List(stickers) diff --git a/pyrogram/methods/messages/get_discussion_message.py b/pyrogram/methods/messages/get_discussion_message.py new file mode 100644 index 0000000000..58a6b7044d --- /dev/null +++ b/pyrogram/methods/messages/get_discussion_message.py @@ -0,0 +1,65 @@ +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-present Dan +# +# This file is part of Pyrogram. +# +# Pyrogram is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License as published +# by the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Pyrogram is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with Pyrogram. If not, see . + +from typing import Union + +import pyrogram +from pyrogram import raw +from pyrogram import types + + +class GetDiscussionMessage: + async def get_discussion_message( + self: "pyrogram.Client", + chat_id: Union[int, str], + message_id: int, + ) -> "types.Message": + """Get the first discussion message of a channel post or a discussion thread in a group. + + Reply to the returned message to leave a comment on the linked channel post or to continue + the discussion thread. + + .. include:: /_includes/usable-by/users.rst + + Parameters: + chat_id (``int`` | ``str``): + Unique identifier (int) or username (str) of the target chat. + + message_id (``int``): + Message id. + + Example: + .. code-block:: python + + # Get the discussion message + m = await app.get_discussion_message(channel_id, message_id) + + # Comment to the post by replying + await m.reply("comment") + """ + r = await self.invoke( + raw.functions.messages.GetDiscussionMessage( + peer=await self.resolve_peer(chat_id), + msg_id=message_id + ) + ) + + users = {u.id: u for u in r.users} + chats = {c.id: c for c in r.chats} + + return await types.Message._parse(self, r.messages[0], users, chats) diff --git a/pyrogram/methods/messages/get_discussion_replies.py b/pyrogram/methods/messages/get_discussion_replies.py new file mode 100644 index 0000000000..dd23751bb1 --- /dev/null +++ b/pyrogram/methods/messages/get_discussion_replies.py @@ -0,0 +1,86 @@ +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-present Dan +# +# This file is part of Pyrogram. +# +# Pyrogram is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License as published +# by the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Pyrogram is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with Pyrogram. If not, see . + +from typing import Union, Optional, AsyncGenerator + +import pyrogram +from pyrogram import types, raw + + +class GetDiscussionReplies: + async def get_discussion_replies( + self: "pyrogram.Client", + chat_id: Union[int, str], + message_id: int, + limit: int = 0, + ) -> Optional[AsyncGenerator["types.Message", None]]: + """Get the message replies of a discussion thread. + + .. include:: /_includes/usable-by/users.rst + + Parameters: + chat_id (``int`` | ``str``): + Unique identifier (int) or username (str) of the target chat. + + message_id (``int``): + Message id. + + limit (``int``, *optional*): + Limits the number of messages to be retrieved. + By default, no limit is applied and all messages are returned. + + Example: + .. code-block:: python + + async for message in app.get_discussion_replies(chat_id, message_id): + print(message) + """ + + current = 0 + total = limit or (1 << 31) - 1 + limit = min(100, total) + + while True: + r = await self.invoke( + raw.functions.messages.GetReplies( + peer=await self.resolve_peer(chat_id), + msg_id=message_id, + offset_id=0, + offset_date=0, + add_offset=current, + limit=limit, + max_id=0, + min_id=0, + hash=0 + ) + ) + + users = {u.id: u for u in r.users} + chats = {c.id: c for c in r.chats} + messages = r.messages + + if not messages: + return + + for message in messages: + yield await types.Message._parse(self, message, users, chats, replies=0) + + current += 1 + + if current >= total: + return diff --git a/pyrogram/methods/messages/get_discussion_replies_count.py b/pyrogram/methods/messages/get_discussion_replies_count.py new file mode 100644 index 0000000000..bbb90ac3a2 --- /dev/null +++ b/pyrogram/methods/messages/get_discussion_replies_count.py @@ -0,0 +1,62 @@ +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-present Dan +# +# This file is part of Pyrogram. +# +# Pyrogram is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License as published +# by the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Pyrogram is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with Pyrogram. If not, see . + +from typing import Union + +import pyrogram +from pyrogram import raw + + +class GetDiscussionRepliesCount: + async def get_discussion_replies_count( + self: "pyrogram.Client", + chat_id: Union[int, str], + message_id: int, + ) -> int: + """Get the total count of replies in a discussion thread. + + .. include:: /_includes/usable-by/users.rst + + Parameters: + chat_id (``int`` | ``str``): + Unique identifier (int) or username (str) of the target chat. + + message_id (``int``): + Message id. + + Example: + .. code-block:: python + + count = await app.get_discussion_replies_count(chat_id, message_id) + """ + + r = await self.invoke( + raw.functions.messages.GetReplies( + peer=await self.resolve_peer(chat_id), + msg_id=message_id, + offset_id=0, + offset_date=0, + add_offset=0, + limit=1, + max_id=0, + min_id=0, + hash=0 + ) + ) + + return r.count diff --git a/pyrogram/methods/messages/get_history.py b/pyrogram/methods/messages/get_history.py deleted file mode 100644 index 0227cd6e12..0000000000 --- a/pyrogram/methods/messages/get_history.py +++ /dev/null @@ -1,106 +0,0 @@ -# Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2021 Dan -# -# This file is part of Pyrogram. -# -# Pyrogram is free software: you can redistribute it and/or modify -# it under the terms of the GNU Lesser General Public License as published -# by the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# Pyrogram is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public License -# along with Pyrogram. If not, see . - -import logging -from typing import Union, List - -from pyrogram import raw -from pyrogram import types -from pyrogram import utils -from pyrogram.scaffold import Scaffold - -log = logging.getLogger(__name__) - - -class GetHistory(Scaffold): - async def get_history( - self, - chat_id: Union[int, str], - limit: int = 100, - offset: int = 0, - offset_id: int = 0, - offset_date: int = 0, - reverse: bool = False - ) -> List["types.Message"]: - """Retrieve a chunk of the history of a chat. - - You can get up to 100 messages at once. - For a more convenient way of getting a chat history see :meth:`~pyrogram.Client.iter_history`. - - Parameters: - chat_id (``int`` | ``str``): - Unique identifier (int) or username (str) of the target chat. - For your personal cloud (Saved Messages) you can simply use "me" or "self". - For a contact that exists in your Telegram address book you can use his phone number (str). - - limit (``int``, *optional*): - Limits the number of messages to be retrieved. - By default, the first 100 messages are returned. - - offset (``int``, *optional*): - Sequential number of the first message to be returned. Defaults to 0 (most recent message). - Negative values are also accepted and become useful in case you set offset_id or offset_date. - - offset_id (``int``, *optional*): - Pass a message identifier as offset to retrieve only older messages starting from that message. - - offset_date (``int``, *optional*): - Pass a date in Unix time as offset to retrieve only older messages starting from that date. - - reverse (``bool``, *optional*): - Pass True to retrieve the messages in reversed order (from older to most recent). - - Returns: - List of :obj:`~pyrogram.types.Message` - On success, a list of the retrieved messages is returned. - - Example: - .. code-block:: python - - # Get the last 100 messages of a chat - app.get_history("pyrogramchat") - - # Get the last 3 messages of a chat - app.get_history("pyrogramchat", limit=3) - - # Get 3 messages after skipping the first 5 - app.get_history("pyrogramchat", offset=5, limit=3) - """ - - offset_id = offset_id or (1 if reverse else 0) - - messages = await utils.parse_messages( - self, - await self.send( - raw.functions.messages.GetHistory( - peer=await self.resolve_peer(chat_id), - offset_id=offset_id, - offset_date=offset_date, - add_offset=offset * (-1 if reverse else 1) - (limit if reverse else 0), - limit=limit, - max_id=0, - min_id=0, - hash=0 - ), - sleep_threshold=60 - ) - ) - - if reverse: - messages.reverse() - - return messages diff --git a/pyrogram/methods/messages/get_media_group.py b/pyrogram/methods/messages/get_media_group.py index 83fff12a15..97770d90f5 100644 --- a/pyrogram/methods/messages/get_media_group.py +++ b/pyrogram/methods/messages/get_media_group.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2021 Dan +# Copyright (C) 2017-present Dan # # This file is part of Pyrogram. # @@ -19,20 +19,22 @@ import logging from typing import Union, List +import pyrogram from pyrogram import types -from pyrogram.scaffold import Scaffold log = logging.getLogger(__name__) -class GetMediaGroup(Scaffold): +class GetMediaGroup: async def get_media_group( - self, + self: "pyrogram.Client", chat_id: Union[int, str], message_id: int ) -> List["types.Message"]: """Get the media group a message belongs to. - + + .. include:: /_includes/usable-by/users-bots.rst + Parameters: chat_id (``int`` | ``str``): Unique identifier (int) or username (str) of the target chat. @@ -50,14 +52,11 @@ async def get_media_group( In case the passed message_id is negative or equal 0. In case target message doesn't belong to a media group. """ - - # There can be maximum 10 items in a media group. - messages = await self.get_messages(chat_id, [msg_id for msg_id in range(message_id - 9, message_id + 10)], - replies=0) if message_id <= 0: raise ValueError("Passed message_id is negative or equal to zero.") + # Get messages with id from `id - 9` to `id + 10` to get all possible media group messages. messages = await self.get_messages( chat_id=chat_id, message_ids=[msg_id for msg_id in range(message_id - 9, message_id + 10)], @@ -65,8 +64,8 @@ async def get_media_group( ) # There can be maximum 10 items in a media group. - # The if/else condition to fix the problem of getting correct `media_group_id` when it has `message_id` less then 10. - media_group_id = messages[9].media_group_id if len(messages) == 19 else messages[message_id-1].media_group_id + # If/else condition to fix the problem of getting correct `media_group_id` when `message_id` is less than 10. + media_group_id = messages[9].media_group_id if len(messages) == 19 else messages[message_id - 1].media_group_id if media_group_id is None: raise ValueError("The message doesn't belong to a media group") diff --git a/pyrogram/methods/messages/get_messages.py b/pyrogram/methods/messages/get_messages.py index 39455ec42a..3f398c36eb 100644 --- a/pyrogram/methods/messages/get_messages.py +++ b/pyrogram/methods/messages/get_messages.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2021 Dan +# Copyright (C) 2017-present Dan # # This file is part of Pyrogram. # @@ -17,12 +17,12 @@ # along with Pyrogram. If not, see . import logging -from typing import Union, Iterable, List +from typing import Union, List, Iterable +import pyrogram from pyrogram import raw from pyrogram import types from pyrogram import utils -from pyrogram.scaffold import Scaffold log = logging.getLogger(__name__) @@ -30,9 +30,9 @@ # TODO: Rewrite using a flag for replied messages and have message_ids non-optional -class GetMessages(Scaffold): +class GetMessages: async def get_messages( - self, + self: "pyrogram.Client", chat_id: Union[int, str], message_ids: Union[int, Iterable[int]] = None, reply_to_message_ids: Union[int, Iterable[int]] = None, @@ -42,19 +42,21 @@ async def get_messages( You can retrieve up to 200 messages at once. + .. include:: /_includes/usable-by/users-bots.rst + Parameters: chat_id (``int`` | ``str``): Unique identifier (int) or username (str) of the target chat. For your personal cloud (Saved Messages) you can simply use "me" or "self". For a contact that exists in your Telegram address book you can use his phone number (str). - message_ids (``iterable``, *optional*): - Pass a single message identifier or a list of message ids (as integers) to get the content of the - message themselves. Iterators and Generators are also accepted. + message_ids (``int`` | Iterable of ``int``, *optional*): + Pass a single message identifier or an iterable of message ids (as integers) to get the content of the + message themselves. - reply_to_message_ids (``iterable``, *optional*): - Pass a single message identifier or a list of message ids (as integers) to get the content of - the previous message you replied to using this message. Iterators and Generators are also accepted. + reply_to_message_ids (``int`` | Iterable of ``int``, *optional*): + Pass a single message identifier or an iterable of message ids (as integers) to get the content of + the previous message you replied to using this message. If *message_ids* is set, this argument will be ignored. replies (``int``, *optional*): @@ -63,27 +65,26 @@ async def get_messages( Defaults to 1. Returns: - :obj:`~pyrogram.types.Message` | List of :obj:`~pyrogram.types.Message`: In case *message_ids* was an - integer, the single requested message is returned, otherwise, in case *message_ids* was an iterable, the - returned value will be a list of messages, even if such iterable contained just a single element. + :obj:`~pyrogram.types.Message` | List of :obj:`~pyrogram.types.Message`: In case *message_ids* was not + a list, a single message is returned, otherwise a list of messages is returned. Example: .. code-block:: python # Get one message - app.get_messages("pyrogramchat", 51110) + await app.get_messages(chat_id, 12345) # Get more than one message (list of messages) - app.get_messages("pyrogramchat", [44625, 51110]) + await app.get_messages(chat_id, [12345, 12346]) # Get message by ignoring any replied-to message - app.get_messages(chat_id, message_id, replies=0) + await app.get_messages(chat_id, message_id, replies=0) # Get message with all chained replied-to messages - app.get_messages(chat_id, message_id, replies=-1) + await app.get_messages(chat_id, message_id, replies=-1) # Get the replied-to message of a message - app.get_messages(chat_id, reply_to_message_ids=message_id) + await app.get_messages(chat_id, reply_to_message_ids=message_id) Raises: ValueError: In case of invalid arguments. @@ -111,7 +112,7 @@ async def get_messages( else: rpc = raw.functions.messages.GetMessages(id=ids) - r = await self.send(rpc, sleep_threshold=-1) + r = await self.invoke(rpc, sleep_threshold=-1) messages = await utils.parse_messages(self, r, replies=replies) diff --git a/pyrogram/methods/messages/inline_session.py b/pyrogram/methods/messages/inline_session.py index 354cb70f69..a514f5a68c 100644 --- a/pyrogram/methods/messages/inline_session.py +++ b/pyrogram/methods/messages/inline_session.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2021 Dan +# Copyright (C) 2017-present Dan # # This file is part of Pyrogram. # @@ -33,21 +33,21 @@ async def get_session(client: "pyrogram.Client", dc_id: int): session = client.media_sessions[dc_id] = Session( client, dc_id, - await Auth(client, dc_id, False).create(), - False, is_media=True + await Auth(client, dc_id, await client.storage.test_mode()).create(), + await client.storage.test_mode(), is_media=True ) await session.start() for _ in range(3): - exported_auth = await client.send( + exported_auth = await client.invoke( raw.functions.auth.ExportAuthorization( dc_id=dc_id ) ) try: - await session.send( + await session.invoke( raw.functions.auth.ImportAuthorization( id=exported_auth.id, bytes=exported_auth.bytes diff --git a/pyrogram/methods/messages/read_history.py b/pyrogram/methods/messages/read_chat_history.py similarity index 85% rename from pyrogram/methods/messages/read_history.py rename to pyrogram/methods/messages/read_chat_history.py index d06d8dcb0c..4b96273936 100644 --- a/pyrogram/methods/messages/read_history.py +++ b/pyrogram/methods/messages/read_chat_history.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2021 Dan +# Copyright (C) 2017-present Dan # # This file is part of Pyrogram. # @@ -18,18 +18,20 @@ from typing import Union +import pyrogram from pyrogram import raw -from pyrogram.scaffold import Scaffold -class ReadHistory(Scaffold): - async def read_history( - self, +class ReadChatHistory: + async def read_chat_history( + self: "pyrogram.Client", chat_id: Union[int, str], max_id: int = 0 ) -> bool: """Mark a chat's message history as read. + .. include:: /_includes/usable-by/users.rst + Parameters: chat_id (``int`` | ``str``): Unique identifier (int) or username (str) of the target chat. @@ -47,10 +49,10 @@ async def read_history( .. code-block:: python # Mark the whole chat as read - app.read_history("pyrogramlounge") + await app.read_chat_history(chat_id) # Mark messages as read only up to the given message id - app.read_history("pyrogramlounge", 123456) + await app.read_chat_history(chat_id, 12345) """ peer = await self.resolve_peer(chat_id) @@ -66,6 +68,6 @@ async def read_history( max_id=max_id ) - await self.send(q) + await self.invoke(q) return True diff --git a/pyrogram/methods/messages/retract_vote.py b/pyrogram/methods/messages/retract_vote.py index 2f9ec33ced..c456f4d08b 100644 --- a/pyrogram/methods/messages/retract_vote.py +++ b/pyrogram/methods/messages/retract_vote.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2021 Dan +# Copyright (C) 2017-present Dan # # This file is part of Pyrogram. # @@ -18,19 +18,21 @@ from typing import Union +import pyrogram from pyrogram import raw from pyrogram import types -from pyrogram.scaffold import Scaffold -class RetractVote(Scaffold): +class RetractVote: async def retract_vote( - self, + self: "pyrogram.Client", chat_id: Union[int, str], message_id: int ) -> "types.Poll": """Retract your vote in a poll. + .. include:: /_includes/usable-by/users.rst + Parameters: chat_id (``int`` | ``str``): Unique identifier (int) or username (str) of the target chat. @@ -46,9 +48,9 @@ async def retract_vote( Example: .. code-block:: python - app.retract_vote(chat_id, message_id) + await app.retract_vote(chat_id, message_id) """ - r = await self.send( + r = await self.invoke( raw.functions.messages.SendVote( peer=await self.resolve_peer(chat_id), msg_id=message_id, diff --git a/pyrogram/methods/messages/search_global.py b/pyrogram/methods/messages/search_global.py index bac5d8049b..f566c98150 100644 --- a/pyrogram/methods/messages/search_global.py +++ b/pyrogram/methods/messages/search_global.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2021 Dan +# Copyright (C) 2017-present Dan # # This file is part of Pyrogram. # @@ -18,68 +18,38 @@ from typing import AsyncGenerator, Optional -from pyrogram import raw +import pyrogram +from pyrogram import raw, enums from pyrogram import types from pyrogram import utils -from pyrogram.scaffold import Scaffold -class Filters: - EMPTY = raw.types.InputMessagesFilterEmpty() - PHOTO = raw.types.InputMessagesFilterPhotos() - VIDEO = raw.types.InputMessagesFilterVideo() - PHOTO_VIDEO = raw.types.InputMessagesFilterPhotoVideo() - DOCUMENT = raw.types.InputMessagesFilterDocument() - URL = raw.types.InputMessagesFilterUrl() - ANIMATION = raw.types.InputMessagesFilterGif() - VOICE_NOTE = raw.types.InputMessagesFilterVoice() - AUDIO = raw.types.InputMessagesFilterMusic() - CHAT_PHOTO = raw.types.InputMessagesFilterChatPhotos() - AUDIO_VIDEO_NOTE = raw.types.InputMessagesFilterRoundVideo() - VIDEO_NOTE = raw.types.InputMessagesFilterRoundVideo() - LOCATION = raw.types.InputMessagesFilterGeo() - CONTACT = raw.types.InputMessagesFilterContacts() - - -POSSIBLE_VALUES = list(map(lambda x: x.lower(), filter(lambda x: not x.startswith("__"), Filters.__dict__.keys()))) - - -class SearchGlobal(Scaffold): +class SearchGlobal: async def search_global( - self, + self: "pyrogram.Client", query: str = "", - filter: str = "empty", + filter: "enums.MessagesFilter" = enums.MessagesFilter.EMPTY, limit: int = 0, ) -> Optional[AsyncGenerator["types.Message", None]]: """Search messages globally from all of your chats. + If you want to get the messages count only, see :meth:`~pyrogram.Client.search_global_count`. + .. note:: Due to server-side limitations, you can only get up to around ~10,000 messages and each message retrieved will not have any *reply_to_message* field. + .. include:: /_includes/usable-by/users.rst + Parameters: query (``str``, *optional*): Text query string. Use "@" to search for mentions. - filter (``str``, *optional*): - Pass a filter in order to search for specific kind of messages only: - - - ``"empty"``: Search for all kind of messages (default). - - ``"photo"``: Search for photos. - - ``"video"``: Search for video. - - ``"photo_video"``: Search for either photo or video. - - ``"document"``: Search for documents (generic files). - - ``"url"``: Search for messages containing URLs (web links). - - ``"animation"``: Search for animations (GIFs). - - ``"voice_note"``: Search for voice notes. - - ``"audio"``: Search for audio files (music). - - ``"chat_photo"``: Search for chat photos. - - ``"audio_video_note"``: Search for either audio or video notes. - - ``"video_note"``: Search for video notes. - - ``"location"``: Search for location messages. - - ``"contact"``: Search for contact messages. + filter (:obj:`~pyrogram.enums.MessagesFilter`, *optional*): + Pass a filter in order to search for specific kind of messages only. + Defaults to any message (no filter). limit (``int``, *optional*): Limits the number of messages to be retrieved. @@ -91,19 +61,16 @@ async def search_global( Example: .. code-block:: python - # Search for "pyrogram". Get the first 420 results - for message in app.search_global("pyrogram", limit=420): + from pyrogram import enums + + # Search for "pyrogram". Get the first 50 results + async for message in app.search_global("pyrogram", limit=50): print(message.text) - # Search for recent photos from Global. Get the first 69 results - for message in app.search_global(filter="photo", limit=69): + # Search for recent photos from Global. Get the first 20 results + async for message in app.search_global(filter=enums.MessagesFilter.PHOTO, limit=20): print(message.photo) """ - try: - filter = Filters.__dict__[filter.upper()] - except KeyError: - raise ValueError('Invalid filter "{}". Possible values are: {}'.format( - filter, ", ".join(f'"{v}"' for v in POSSIBLE_VALUES))) from None current = 0 # There seems to be an hard limit of 10k, beyond which Telegram starts spitting one message at a time. total = abs(limit) or (1 << 31) @@ -116,10 +83,10 @@ async def search_global( while True: messages = await utils.parse_messages( self, - await self.send( + await self.invoke( raw.functions.messages.SearchGlobal( q=query, - filter=filter, + filter=filter.value(), min_date=0, max_date=0, offset_rate=offset_date, @@ -137,9 +104,9 @@ async def search_global( last = messages[-1] - offset_date = last.date + offset_date = utils.datetime_to_timestamp(last.date) offset_peer = await self.resolve_peer(last.chat.id) - offset_id = last.message_id + offset_id = last.id for message in messages: yield message diff --git a/pyrogram/methods/messages/search_global_count.py b/pyrogram/methods/messages/search_global_count.py index 113ef81362..8323a821d2 100644 --- a/pyrogram/methods/messages/search_global_count.py +++ b/pyrogram/methods/messages/search_global_count.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2021 Dan +# Copyright (C) 2017-present Dan # # This file is part of Pyrogram. # @@ -16,57 +16,37 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . -from pyrogram import raw -from pyrogram.scaffold import Scaffold -from .search_messages import Filters, POSSIBLE_VALUES +import pyrogram +from pyrogram import raw, enums -class SearchGlobalCount(Scaffold): +class SearchGlobalCount: async def search_global_count( - self, + self: "pyrogram.Client", query: str = "", - filter: str = "empty", + filter: "enums.MessagesFilter" = enums.MessagesFilter.EMPTY, ) -> int: """Get the count of messages resulting from a global search. If you want to get the actual messages, see :meth:`~pyrogram.Client.search_global`. + .. include:: /_includes/usable-by/users.rst + Parameters: query (``str``, *optional*): Text query string. Use "@" to search for mentions. - filter (``str``, *optional*): + filter (:obj:`~pyrogram.enums.MessagesFilter`, *optional*): Pass a filter in order to search for specific kind of messages only: - - ``"empty"``: Search for all kind of messages (default). - - ``"photo"``: Search for photos. - - ``"video"``: Search for video. - - ``"photo_video"``: Search for either photo or video. - - ``"document"``: Search for documents (generic files). - - ``"url"``: Search for messages containing URLs (web links). - - ``"animation"``: Search for animations (GIFs). - - ``"voice_note"``: Search for voice notes. - - ``"audio"``: Search for audio files (music). - - ``"chat_photo"``: Search for chat photos. - - ``"audio_video_note"``: Search for either audio or video notes. - - ``"video_note"``: Search for video notes. - - ``"location"``: Search for location messages. - - ``"contact"``: Search for contact messages. - Returns: ``int``: On success, the messages count is returned. """ - try: - filter = Filters.__dict__[filter.upper()] - except KeyError: - raise ValueError('Invalid filter "{}". Possible values are: {}'.format( - filter, ", ".join(f'"{v}"' for v in POSSIBLE_VALUES))) from None - - r = await self.send( + r = await self.invoke( raw.functions.messages.SearchGlobal( q=query, - filter=filter, + filter=filter.value(), min_date=0, max_date=0, offset_rate=0, diff --git a/pyrogram/methods/messages/search_messages.py b/pyrogram/methods/messages/search_messages.py index f8d0cb33d2..4497ff1cd3 100644 --- a/pyrogram/methods/messages/search_messages.py +++ b/pyrogram/methods/messages/search_messages.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2021 Dan +# Copyright (C) 2017-present Dan # # This file is part of Pyrogram. # @@ -18,56 +18,25 @@ from typing import Union, List, AsyncGenerator, Optional -from pyrogram import raw -from pyrogram import types -from pyrogram import utils -from pyrogram.scaffold import Scaffold - - -class Filters: - EMPTY = raw.types.InputMessagesFilterEmpty() - PHOTO = raw.types.InputMessagesFilterPhotos() - VIDEO = raw.types.InputMessagesFilterVideo() - PHOTO_VIDEO = raw.types.InputMessagesFilterPhotoVideo() - DOCUMENT = raw.types.InputMessagesFilterDocument() - URL = raw.types.InputMessagesFilterUrl() - ANIMATION = raw.types.InputMessagesFilterGif() - VOICE_NOTE = raw.types.InputMessagesFilterVoice() - AUDIO = raw.types.InputMessagesFilterMusic() - CHAT_PHOTO = raw.types.InputMessagesFilterChatPhotos() - PHONE_CALL = raw.types.InputMessagesFilterPhoneCalls() - AUDIO_VIDEO_NOTE = raw.types.InputMessagesFilterRoundVideo() - VIDEO_NOTE = raw.types.InputMessagesFilterRoundVideo() - MENTION = raw.types.InputMessagesFilterMyMentions() - LOCATION = raw.types.InputMessagesFilterGeo() - CONTACT = raw.types.InputMessagesFilterContacts() - PINNED = raw.types.InputMessagesFilterPinned() - - -POSSIBLE_VALUES = list(map(lambda x: x.lower(), filter(lambda x: not x.startswith("__"), Filters.__dict__.keys()))) +import pyrogram +from pyrogram import raw, types, utils, enums # noinspection PyShadowingBuiltins async def get_chunk( - client: Scaffold, + client, chat_id: Union[int, str], query: str = "", - filter: str = "empty", + filter: "enums.MessagesFilter" = enums.MessagesFilter.EMPTY, offset: int = 0, limit: int = 100, from_user: Union[int, str] = None ) -> List["types.Message"]: - try: - filter = Filters.__dict__[filter.upper()] - except KeyError: - raise ValueError('Invalid filter "{}". Possible values are: {}'.format( - filter, ", ".join(f'"{v}"' for v in POSSIBLE_VALUES))) from None - - r = await client.send( + r = await client.invoke( raw.functions.messages.Search( peer=await client.resolve_peer(chat_id), q=query, - filter=filter, + filter=filter.value(), min_date=0, max_date=0, offset_id=0, @@ -85,22 +54,26 @@ async def get_chunk( sleep_threshold=60 ) - return await utils.parse_messages(client, r) + return await utils.parse_messages(client, r, replies=0) -class SearchMessages(Scaffold): +class SearchMessages: # noinspection PyShadowingBuiltins async def search_messages( - self, + self: "pyrogram.Client", chat_id: Union[int, str], query: str = "", offset: int = 0, - filter: str = "empty", + filter: "enums.MessagesFilter" = enums.MessagesFilter.EMPTY, limit: int = 0, from_user: Union[int, str] = None ) -> Optional[AsyncGenerator["types.Message", None]]: """Search for text and media messages inside a specific chat. + If you want to get the messages count only, see :meth:`~pyrogram.Client.search_messages_count`. + + .. include:: /_includes/usable-by/users.rst + Parameters: chat_id (``int`` | ``str``): Unique identifier (int) or username (str) of the target chat. @@ -117,32 +90,15 @@ async def search_messages( Sequential number of the first message to be returned. Defaults to 0. - filter (``str``, *optional*): - Pass a filter in order to search for specific kind of messages only: - - - ``"empty"``: Search for all kind of messages (default). - - ``"photo"``: Search for photos. - - ``"video"``: Search for video. - - ``"photo_video"``: Search for either photo or video. - - ``"document"``: Search for documents (generic files). - - ``"url"``: Search for messages containing URLs (web links). - - ``"animation"``: Search for animations (GIFs). - - ``"voice_note"``: Search for voice notes. - - ``"audio"``: Search for audio files (music). - - ``"chat_photo"``: Search for chat photos. - - ``"phone_call"``: Search for phone calls. - - ``"audio_video_note"``: Search for either audio or video notes. - - ``"video_note"``: Search for video notes. - - ``"mention"``: Search for messages containing mentions to yourself. - - ``"location"``: Search for location messages. - - ``"contact"``: Search for contact messages. - - ``"pinned"``: Search for pinned messages. + filter (:obj:`~pyrogram.enums.MessagesFilter`, *optional*): + Pass a filter in order to search for specific kind of messages only. + Defaults to any message (no filter). limit (``int``, *optional*): Limits the number of messages to be retrieved. By default, no limit is applied and all messages are returned. - from_user (``int`` | ``str``): + from_user (``int`` | ``str``, *optional*): Unique identifier (int) or username (str) of the target user you want to search for messages from. Returns: @@ -151,18 +107,21 @@ async def search_messages( Example: .. code-block:: python - # Search for text messages in @pyrogramchat. Get the last 333 results - for message in app.search_messages("pyrogramchat", query="dan", limit=333): + from pyrogram import enums + + # Search for text messages in chat. Get the last 120 results + async for message in app.search_messages(chat_id, query="hello", limit=120): print(message.text) - # Search for pinned messages in @pyrogramchat - for message in app.search_messages("pyrogramchat", filter="pinned"): + # Search for pinned messages in chat + async for message in app.search_messages(chat_id, filter=enums.MessagesFilter.PINNED): print(message.text) - # Search for messages containing "hi" sent by @haskell in @pyrogramchat - for message in app.search_messages("pyrogramchat", "hi", from_user="haskell"): + # Search for messages containing "hello" sent by yourself in chat + async for message in app.search_messages(chat, "hello", from_user="me"): print(message.text) """ + current = 0 total = abs(limit) or (1 << 31) - 1 limit = min(100, total) diff --git a/pyrogram/methods/messages/search_messages_count.py b/pyrogram/methods/messages/search_messages_count.py index 02e0c80366..ea9ae4d18e 100644 --- a/pyrogram/methods/messages/search_messages_count.py +++ b/pyrogram/methods/messages/search_messages_count.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2021 Dan +# Copyright (C) 2017-present Dan # # This file is part of Pyrogram. # @@ -18,23 +18,24 @@ from typing import Union -from pyrogram import raw -from pyrogram.scaffold import Scaffold -from .search_messages import Filters, POSSIBLE_VALUES +import pyrogram +from pyrogram import raw, enums -class SearchMessagesCount(Scaffold): +class SearchMessagesCount: async def search_messages_count( - self, + self: "pyrogram.Client", chat_id: Union[int, str], query: str = "", - filter: str = "empty", + filter: "enums.MessagesFilter" = enums.MessagesFilter.EMPTY, from_user: Union[int, str] = None ) -> int: """Get the count of messages resulting from a search inside a chat. If you want to get the actual messages, see :meth:`~pyrogram.Client.search_messages`. + .. include:: /_includes/usable-by/users.rst + Parameters: chat_id (``int`` | ``str``): Unique identifier (int) or username (str) of the target chat. @@ -47,44 +48,20 @@ async def search_messages_count( When passed while searching for media messages, the query will be applied to captions. Defaults to "" (empty string). - filter (``str``, *optional*): + filter (:obj:`~pyrogram.enums.MessagesFilter`, *optional*): Pass a filter in order to search for specific kind of messages only: - - ``"empty"``: Search for all kind of messages (default). - - ``"photo"``: Search for photos. - - ``"video"``: Search for video. - - ``"photo_video"``: Search for either photo or video. - - ``"document"``: Search for documents (generic files). - - ``"url"``: Search for messages containing URLs (web links). - - ``"animation"``: Search for animations (GIFs). - - ``"voice_note"``: Search for voice notes. - - ``"audio"``: Search for audio files (music). - - ``"chat_photo"``: Search for chat photos. - - ``"phone_call"``: Search for phone calls. - - ``"audio_video_note"``: Search for either audio or video notes. - - ``"video_note"``: Search for video notes. - - ``"mention"``: Search for messages containing mentions to yourself. - - ``"location"``: Search for location messages. - - ``"contact"``: Search for contact messages. - - ``"pinned"``: Search for pinned messages. - from_user (``int`` | ``str``, *optional*): Unique identifier (int) or username (str) of the target user you want to search for messages from. Returns: ``int``: On success, the messages count is returned. """ - try: - filter = Filters.__dict__[filter.upper()] - except KeyError: - raise ValueError('Invalid filter "{}". Possible values are: {}'.format( - filter, ", ".join(f'"{v}"' for v in POSSIBLE_VALUES))) from None - - r = await self.send( + r = await self.invoke( raw.functions.messages.Search( peer=await self.resolve_peer(chat_id), q=query, - filter=filter, + filter=filter.value(), min_date=0, max_date=0, offset_id=0, diff --git a/pyrogram/methods/messages/send_animation.py b/pyrogram/methods/messages/send_animation.py index d365ae562c..bac16bac9f 100644 --- a/pyrogram/methods/messages/send_animation.py +++ b/pyrogram/methods/messages/send_animation.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2021 Dan +# Copyright (C) 2017-present Dan # # This file is part of Pyrogram. # @@ -18,26 +18,28 @@ import os import re -from typing import Union, BinaryIO, List, Optional +from datetime import datetime +from typing import Union, BinaryIO, List, Optional, Callable -from pyrogram import StopTransmission +import pyrogram +from pyrogram import StopTransmission, enums from pyrogram import raw from pyrogram import types from pyrogram import utils from pyrogram.errors import FilePartMissing from pyrogram.file_id import FileType -from pyrogram.scaffold import Scaffold -class SendAnimation(Scaffold): +class SendAnimation: async def send_animation( - self, + self: "pyrogram.Client", chat_id: Union[int, str], animation: Union[str, BinaryIO], caption: str = "", unsave: bool = False, - parse_mode: Optional[str] = object, + parse_mode: Optional["enums.ParseMode"] = None, caption_entities: List["types.MessageEntity"] = None, + has_spoiler: bool = None, duration: int = 0, width: int = 0, height: int = 0, @@ -45,18 +47,21 @@ async def send_animation( file_name: str = None, disable_notification: bool = None, reply_to_message_id: int = None, - schedule_date: int = None, + schedule_date: datetime = None, + protect_content: bool = None, reply_markup: Union[ "types.InlineKeyboardMarkup", "types.ReplyKeyboardMarkup", "types.ReplyKeyboardRemove", "types.ForceReply" ] = None, - progress: callable = None, + progress: Callable = None, progress_args: tuple = () ) -> Optional["types.Message"]: """Send animation files (animation or H.264/MPEG-4 AVC video without sound). + .. include:: /_includes/usable-by/users-bots.rst + Parameters: chat_id (``int`` | ``str``): Unique identifier (int) or username (str) of the target chat. @@ -77,16 +82,16 @@ async def send_animation( By default, the server will save into your own collection any new animation you send. Pass True to automatically unsave the sent animation. Defaults to False. - parse_mode (``str``, *optional*): + parse_mode (:obj:`~pyrogram.enums.ParseMode`, *optional*): By default, texts are parsed using both Markdown and HTML styles. You can combine both syntaxes together. - Pass "markdown" or "md" to enable Markdown-style parsing only. - Pass "html" to enable HTML-style parsing only. - Pass None to completely disable style parsing. caption_entities (List of :obj:`~pyrogram.types.MessageEntity`): List of special entities that appear in the caption, which can be specified instead of *parse_mode*. + has_spoiler (``bool``, *optional*): + Pass True if the animation needs to be covered with a spoiler animation. + duration (``int``, *optional*): Duration of sent animation in seconds. @@ -113,14 +118,17 @@ async def send_animation( reply_to_message_id (``int``, *optional*): If the message is a reply, ID of the original message. - schedule_date (``int``, *optional*): - Date when the message will be automatically sent. Unix time. + schedule_date (:py:obj:`~datetime.datetime`, *optional*): + Date when the message will be automatically sent. + + protect_content (``bool``, *optional*): + Protects the contents of the sent message from forwarding and saving. reply_markup (:obj:`~pyrogram.types.InlineKeyboardMarkup` | :obj:`~pyrogram.types.ReplyKeyboardMarkup` | :obj:`~pyrogram.types.ReplyKeyboardRemove` | :obj:`~pyrogram.types.ForceReply`, *optional*): Additional interface options. An object for an inline keyboard, custom reply keyboard, instructions to remove reply keyboard or to force a reply from the user. - progress (``callable``, *optional*): + progress (``Callable``, *optional*): Pass a callback function to view the file transmission progress. The function must take *(current, total)* as positional arguments (look at Other Parameters below for a detailed description) and will be called back each time a new file chunk has been successfully @@ -151,19 +159,19 @@ async def send_animation( .. code-block:: python # Send animation by uploading from local file - app.send_animation("me", "animation.gif") + await app.send_animation("me", "animation.gif") # Add caption to the animation - app.send_animation("me", "animation.gif", caption="cat") + await app.send_animation("me", "animation.gif", caption="animation caption") # Unsave the animation once is sent - app.send_animation("me", "animation.gif", unsave=True) + await app.send_animation("me", "animation.gif", unsave=True) # Keep track of the progress while uploading - def progress(current, total): + async def progress(current, total): print(f"{current * 100 / total:.1f}%") - app.send_animation("me", "animation.gif", progress=progress) + await app.send_animation("me", "animation.gif", progress=progress) """ file = None @@ -176,6 +184,7 @@ def progress(current, total): mime_type=self.guess_mime_type(animation) or "video/mp4", file=file, thumb=thumb, + spoiler=has_spoiler, attributes=[ raw.types.DocumentAttributeVideo( supports_streaming=True, @@ -189,7 +198,8 @@ def progress(current, total): ) elif re.match("^https?://", animation): media = raw.types.InputMediaDocumentExternal( - url=animation + url=animation, + spoiler=has_spoiler ) else: media = utils.get_input_media_from_file_id(animation, FileType.ANIMATION) @@ -197,9 +207,10 @@ def progress(current, total): thumb = await self.save_file(thumb) file = await self.save_file(animation, progress=progress, progress_args=progress_args) media = raw.types.InputMediaUploadedDocument( - mime_type=self.guess_mime_type(animation.name) or "video/mp4", + mime_type=self.guess_mime_type(file_name or animation.name) or "video/mp4", file=file, thumb=thumb, + spoiler=has_spoiler, attributes=[ raw.types.DocumentAttributeVideo( supports_streaming=True, @@ -207,27 +218,28 @@ def progress(current, total): w=width, h=height ), - raw.types.DocumentAttributeFilename(file_name=animation.name), + raw.types.DocumentAttributeFilename(file_name=file_name or animation.name), raw.types.DocumentAttributeAnimated() ] ) while True: try: - r = await self.send( + r = await self.invoke( raw.functions.messages.SendMedia( peer=await self.resolve_peer(chat_id), media=media, silent=disable_notification or None, reply_to_msg_id=reply_to_message_id, random_id=self.rnd_id(), - schedule_date=schedule_date, + schedule_date=utils.datetime_to_timestamp(schedule_date), + noforwards=protect_content, reply_markup=await reply_markup.write(self) if reply_markup else None, **await utils.parse_text_entities(self, caption, parse_mode, caption_entities) ) ) except FilePartMissing as e: - await self.save_file(animation, file_id=file.id, file_part=e.x) + await self.save_file(animation, file_id=file.id, file_part=e.value) else: for i in r.updates: if isinstance(i, (raw.types.UpdateNewMessage, @@ -246,7 +258,7 @@ def progress(current, total): document.file_id, FileType.ANIMATION ).id - await self.send( + await self.invoke( raw.functions.messages.SaveGif( id=document_id, unsave=True diff --git a/pyrogram/methods/messages/send_audio.py b/pyrogram/methods/messages/send_audio.py index 4fa683a9ea..7a81df0195 100644 --- a/pyrogram/methods/messages/send_audio.py +++ b/pyrogram/methods/messages/send_audio.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2021 Dan +# Copyright (C) 2017-present Dan # # This file is part of Pyrogram. # @@ -18,24 +18,25 @@ import os import re -from typing import Union, BinaryIO, List, Optional +from datetime import datetime +from typing import Union, BinaryIO, List, Optional, Callable -from pyrogram import StopTransmission +import pyrogram +from pyrogram import StopTransmission, enums from pyrogram import raw from pyrogram import types from pyrogram import utils from pyrogram.errors import FilePartMissing from pyrogram.file_id import FileType -from pyrogram.scaffold import Scaffold -class SendAudio(Scaffold): +class SendAudio: async def send_audio( - self, + self: "pyrogram.Client", chat_id: Union[int, str], audio: Union[str, BinaryIO], caption: str = "", - parse_mode: Optional[str] = object, + parse_mode: Optional["enums.ParseMode"] = None, caption_entities: List["types.MessageEntity"] = None, duration: int = 0, performer: str = None, @@ -44,20 +45,23 @@ async def send_audio( file_name: str = None, disable_notification: bool = None, reply_to_message_id: int = None, - schedule_date: int = None, + schedule_date: datetime = None, + protect_content: bool = None, reply_markup: Union[ "types.InlineKeyboardMarkup", "types.ReplyKeyboardMarkup", "types.ReplyKeyboardRemove", "types.ForceReply" ] = None, - progress: callable = None, + progress: Callable = None, progress_args: tuple = () ) -> Optional["types.Message"]: """Send audio files. For sending voice messages, use the :meth:`~pyrogram.Client.send_voice` method instead. + .. include:: /_includes/usable-by/users-bots.rst + Parameters: chat_id (``int`` | ``str``): Unique identifier (int) or username (str) of the target chat. @@ -74,12 +78,9 @@ async def send_audio( caption (``str``, *optional*): Audio caption, 0-1024 characters. - parse_mode (``str``, *optional*): + parse_mode (:obj:`~pyrogram.enums.ParseMode`, *optional*): By default, texts are parsed using both Markdown and HTML styles. You can combine both syntaxes together. - Pass "markdown" or "md" to enable Markdown-style parsing only. - Pass "html" to enable HTML-style parsing only. - Pass None to completely disable style parsing. caption_entities (List of :obj:`~pyrogram.types.MessageEntity`): List of special entities that appear in the caption, which can be specified instead of *parse_mode*. @@ -110,14 +111,17 @@ async def send_audio( reply_to_message_id (``int``, *optional*): If the message is a reply, ID of the original message. - schedule_date (``int``, *optional*): - Date when the message will be automatically sent. Unix time. + schedule_date (:py:obj:`~datetime.datetime`, *optional*): + Date when the message will be automatically sent. + + protect_content (``bool``, *optional*): + Protects the contents of the sent message from forwarding and saving. reply_markup (:obj:`~pyrogram.types.InlineKeyboardMarkup` | :obj:`~pyrogram.types.ReplyKeyboardMarkup` | :obj:`~pyrogram.types.ReplyKeyboardRemove` | :obj:`~pyrogram.types.ForceReply`, *optional*): Additional interface options. An object for an inline keyboard, custom reply keyboard, instructions to remove reply keyboard or to force a reply from the user. - progress (``callable``, *optional*): + progress (``Callable``, *optional*): Pass a callback function to view the file transmission progress. The function must take *(current, total)* as positional arguments (look at Other Parameters below for a detailed description) and will be called back each time a new file chunk has been successfully @@ -145,24 +149,23 @@ async def send_audio( Example: .. code-block:: python - :emphasize-lines: 2,5,8-10,13-16 # Send audio file by uploading from file - app.send_audio("me", "audio.mp3") + await app.send_audio("me", "audio.mp3") # Add caption to the audio - app.send_audio("me", "audio.mp3", caption="shoegaze") + await app.send_audio("me", "audio.mp3", caption="audio caption") # Set audio metadata - app.send_audio( + await app.send_audio( "me", "audio.mp3", - title="Printemps émeraude", performer="Alcest", duration=440) + title="Title", performer="Performer", duration=234) # Keep track of the progress while uploading - def progress(current, total): + async def progress(current, total): print(f"{current * 100 / total:.1f}%") - app.send_audio("me", "audio.mp3", progress=progress) + await app.send_audio("me", "audio.mp3", progress=progress) """ file = None @@ -194,7 +197,7 @@ def progress(current, total): thumb = await self.save_file(thumb) file = await self.save_file(audio, progress=progress, progress_args=progress_args) media = raw.types.InputMediaUploadedDocument( - mime_type=self.guess_mime_type(audio.name) or "audio/mpeg", + mime_type=self.guess_mime_type(file_name or audio.name) or "audio/mpeg", file=file, thumb=thumb, attributes=[ @@ -203,26 +206,27 @@ def progress(current, total): performer=performer, title=title ), - raw.types.DocumentAttributeFilename(file_name=audio.name) + raw.types.DocumentAttributeFilename(file_name=file_name or audio.name) ] ) while True: try: - r = await self.send( + r = await self.invoke( raw.functions.messages.SendMedia( peer=await self.resolve_peer(chat_id), media=media, silent=disable_notification or None, reply_to_msg_id=reply_to_message_id, random_id=self.rnd_id(), - schedule_date=schedule_date, + schedule_date=utils.datetime_to_timestamp(schedule_date), + noforwards=protect_content, reply_markup=await reply_markup.write(self) if reply_markup else None, **await utils.parse_text_entities(self, caption, parse_mode, caption_entities) ) ) except FilePartMissing as e: - await self.save_file(audio, file_id=file.id, file_part=e.x) + await self.save_file(audio, file_id=file.id, file_part=e.value) else: for i in r.updates: if isinstance(i, (raw.types.UpdateNewMessage, diff --git a/pyrogram/methods/messages/send_cached_media.py b/pyrogram/methods/messages/send_cached_media.py index 1f1517e70f..5a9e2e1f1a 100644 --- a/pyrogram/methods/messages/send_cached_media.py +++ b/pyrogram/methods/messages/send_cached_media.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2021 Dan +# Copyright (C) 2017-present Dan # # This file is part of Pyrogram. # @@ -16,25 +16,27 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . +from datetime import datetime from typing import Union, List, Optional -from pyrogram import raw +import pyrogram +from pyrogram import raw, enums from pyrogram import types from pyrogram import utils -from pyrogram.scaffold import Scaffold -class SendCachedMedia(Scaffold): +class SendCachedMedia: async def send_cached_media( - self, + self: "pyrogram.Client", chat_id: Union[int, str], file_id: str, caption: str = "", - parse_mode: Optional[str] = object, + parse_mode: Optional["enums.ParseMode"] = None, caption_entities: List["types.MessageEntity"] = None, disable_notification: bool = None, reply_to_message_id: int = None, - schedule_date: int = None, + schedule_date: datetime = None, + protect_content: bool = None, reply_markup: Union[ "types.InlineKeyboardMarkup", "types.ReplyKeyboardMarkup", @@ -48,6 +50,8 @@ async def send_cached_media( It does the same as calling the relevant method for sending media using a file_id, thus saving you from the hassle of using the correct method for the media the file_id is pointing to. + .. include:: /_includes/usable-by/users-bots.rst + Parameters: chat_id (``int`` | ``str``): Unique identifier (int) or username (str) of the target chat. @@ -61,12 +65,9 @@ async def send_cached_media( caption (``str``, *optional*): Media caption, 0-1024 characters. - parse_mode (``str``, *optional*): + parse_mode (:obj:`~pyrogram.enums.ParseMode`, *optional*): By default, texts are parsed using both Markdown and HTML styles. You can combine both syntaxes together. - Pass "markdown" or "md" to enable Markdown-style parsing only. - Pass "html" to enable HTML-style parsing only. - Pass None to completely disable style parsing. caption_entities (List of :obj:`~pyrogram.types.MessageEntity`): List of special entities that appear in the caption, which can be specified instead of *parse_mode*. @@ -78,8 +79,11 @@ async def send_cached_media( reply_to_message_id (``int``, *optional*): If the message is a reply, ID of the original message. - schedule_date (``int``, *optional*): - Date when the message will be automatically sent. Unix time. + schedule_date (:py:obj:`~datetime.datetime`, *optional*): + Date when the message will be automatically sent. + + protect_content (``bool``, *optional*): + Protects the contents of the sent message from forwarding and saving. reply_markup (:obj:`~pyrogram.types.InlineKeyboardMarkup` | :obj:`~pyrogram.types.ReplyKeyboardMarkup` | :obj:`~pyrogram.types.ReplyKeyboardRemove` | :obj:`~pyrogram.types.ForceReply`, *optional*): Additional interface options. An object for an inline keyboard, custom reply keyboard, @@ -91,17 +95,18 @@ async def send_cached_media( Example: .. code-block:: python - app.send_cached_media("me", "CAADBAADzg4AAvLQYAEz_x2EOgdRwBYE") + await app.send_cached_media("me", file_id) """ - r = await self.send( + r = await self.invoke( raw.functions.messages.SendMedia( peer=await self.resolve_peer(chat_id), media=utils.get_input_media_from_file_id(file_id), silent=disable_notification or None, reply_to_msg_id=reply_to_message_id, random_id=self.rnd_id(), - schedule_date=schedule_date, + schedule_date=utils.datetime_to_timestamp(schedule_date), + noforwards=protect_content, reply_markup=await reply_markup.write(self) if reply_markup else None, **await utils.parse_text_entities(self, caption, parse_mode, caption_entities) ) diff --git a/pyrogram/methods/messages/send_chat_action.py b/pyrogram/methods/messages/send_chat_action.py index 586d1ea746..44b16628f8 100644 --- a/pyrogram/methods/messages/send_chat_action.py +++ b/pyrogram/methods/messages/send_chat_action.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2021 Dan +# Copyright (C) 2017-present Dan # # This file is part of Pyrogram. # @@ -16,50 +16,30 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . -import json from typing import Union -from pyrogram import raw -from pyrogram.scaffold import Scaffold +import pyrogram +from pyrogram import raw, enums -class ChatAction: - TYPING = raw.types.SendMessageTypingAction - UPLOAD_PHOTO = raw.types.SendMessageUploadPhotoAction - RECORD_VIDEO = raw.types.SendMessageRecordVideoAction - UPLOAD_VIDEO = raw.types.SendMessageUploadVideoAction - RECORD_AUDIO = raw.types.SendMessageRecordAudioAction - UPLOAD_AUDIO = raw.types.SendMessageUploadAudioAction - UPLOAD_DOCUMENT = raw.types.SendMessageUploadDocumentAction - FIND_LOCATION = raw.types.SendMessageGeoLocationAction - RECORD_VIDEO_NOTE = raw.types.SendMessageRecordRoundAction - UPLOAD_VIDEO_NOTE = raw.types.SendMessageUploadRoundAction - PLAYING = raw.types.SendMessageGamePlayAction - CHOOSE_CONTACT = raw.types.SendMessageChooseContactAction - SPEAKING = raw.types.SpeakingInGroupCallAction - CANCEL = raw.types.SendMessageCancelAction - - -POSSIBLE_VALUES = list(map(lambda x: x.lower(), filter(lambda x: not x.startswith("__"), ChatAction.__dict__.keys()))) - - -class SendChatAction(Scaffold): - async def send_chat_action(self, chat_id: Union[int, str], action: str) -> bool: +class SendChatAction: + async def send_chat_action( + self: "pyrogram.Client", + chat_id: Union[int, str], + action: "enums.ChatAction" + ) -> bool: """Tell the other party that something is happening on your side. + .. include:: /_includes/usable-by/users-bots.rst + Parameters: chat_id (``int`` | ``str``): Unique identifier (int) or username (str) of the target chat. For your personal cloud (Saved Messages) you can simply use "me" or "self". For a contact that exists in your Telegram address book you can use his phone number (str). - action (``str``): - Type of action to broadcast. Choose one, depending on what the user is about to receive: *"typing"* for - text messages, *"upload_photo"* for photos, *"record_video"* or *"upload_video"* for videos, - *"record_audio"* or *"upload_audio"* for audio files, *"upload_document"* for general files, - *"find_location"* for location data, *"record_video_note"* or *"upload_video_note"* for video notes, - *"choose_contact"* for contacts, *"playing"* for games, *"speaking"* for speaking in group calls or - *"cancel"* to cancel any chat action currently displayed. + action (:obj:`~pyrogram.enums.ChatAction`): + Type of action to broadcast. Returns: ``bool``: On success, True is returned. @@ -70,31 +50,29 @@ async def send_chat_action(self, chat_id: Union[int, str], action: str) -> bool: Example: .. code-block:: python + from pyrogram import enums + # Send "typing" chat action - app.send_chat_action(chat_id, "typing") + await app.send_chat_action(chat_id, enums.ChatAction.TYPING) # Send "upload_video" chat action - app.send_chat_action(chat_id, "upload_video") + await app.send_chat_action(chat_id, enums.ChatAction.UPLOAD_VIDEO) # Send "playing" chat action - app.send_chat_action(chat_id, "playing") + await app.send_chat_action(chat_id, enums.ChatAction.PLAYING) # Cancel any current chat action - app.send_chat_action(chat_id, "cancel") + await app.send_chat_action(chat_id, enums.ChatAction.CANCEL) """ - try: - action = ChatAction.__dict__[action.upper()] - except KeyError: - raise ValueError("Invalid chat action '{}'. Possible values are: {}".format( - action, json.dumps(POSSIBLE_VALUES, indent=4))) from None + action_name = action.name.lower() - if "Upload" in action.__name__: - action = action(progress=0) + if "upload" in action_name or "history" in action_name: + action = action.value(progress=0) else: - action = action() + action = action.value() - return await self.send( + return await self.invoke( raw.functions.messages.SetTyping( peer=await self.resolve_peer(chat_id), action=action diff --git a/pyrogram/methods/messages/send_contact.py b/pyrogram/methods/messages/send_contact.py index 6317a8296a..30f7abd315 100644 --- a/pyrogram/methods/messages/send_contact.py +++ b/pyrogram/methods/messages/send_contact.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2021 Dan +# Copyright (C) 2017-present Dan # # This file is part of Pyrogram. # @@ -16,16 +16,17 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . +from datetime import datetime from typing import Union -from pyrogram import raw +import pyrogram +from pyrogram import raw, utils from pyrogram import types -from pyrogram.scaffold import Scaffold -class SendContact(Scaffold): +class SendContact: async def send_contact( - self, + self: "pyrogram.Client", chat_id: Union[int, str], phone_number: str, first_name: str, @@ -33,7 +34,8 @@ async def send_contact( vcard: str = None, disable_notification: bool = None, reply_to_message_id: int = None, - schedule_date: int = None, + schedule_date: datetime = None, + protect_content: bool = None, reply_markup: Union[ "types.InlineKeyboardMarkup", "types.ReplyKeyboardMarkup", @@ -43,6 +45,8 @@ async def send_contact( ) -> "types.Message": """Send phone contacts. + .. include:: /_includes/usable-by/users-bots.rst + Parameters: chat_id (``int`` | ``str``): Unique identifier (int) or username (str) of the target chat. @@ -68,8 +72,11 @@ async def send_contact( reply_to_message_id (``int``, *optional*): If the message is a reply, ID of the original message. - schedule_date (``int``, *optional*): - Date when the message will be automatically sent. Unix time. + schedule_date (:py:obj:`~datetime.datetime`, *optional*): + Date when the message will be automatically sent. + + protect_content (``bool``, *optional*): + Protects the contents of the sent message from forwarding and saving. reply_markup (:obj:`~pyrogram.types.InlineKeyboardMarkup` | :obj:`~pyrogram.types.ReplyKeyboardMarkup` | :obj:`~pyrogram.types.ReplyKeyboardRemove` | :obj:`~pyrogram.types.ForceReply`, *optional*): Additional interface options. An object for an inline keyboard, custom reply keyboard, @@ -81,9 +88,9 @@ async def send_contact( Example: .. code-block:: python - app.send_contact("me", "+39 123 456 7890", "Dan") + await app.send_contact("me", "+1-123-456-7890", "Name") """ - r = await self.send( + r = await self.invoke( raw.functions.messages.SendMedia( peer=await self.resolve_peer(chat_id), media=raw.types.InputMediaContact( @@ -96,7 +103,8 @@ async def send_contact( silent=disable_notification or None, reply_to_msg_id=reply_to_message_id, random_id=self.rnd_id(), - schedule_date=schedule_date, + schedule_date=utils.datetime_to_timestamp(schedule_date), + noforwards=protect_content, reply_markup=await reply_markup.write(self) if reply_markup else None ) ) diff --git a/pyrogram/methods/messages/send_dice.py b/pyrogram/methods/messages/send_dice.py index 4b7422b316..c657d85dd9 100644 --- a/pyrogram/methods/messages/send_dice.py +++ b/pyrogram/methods/messages/send_dice.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2021 Dan +# Copyright (C) 2017-present Dan # # This file is part of Pyrogram. # @@ -16,21 +16,23 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . +from datetime import datetime from typing import Union, Optional -from pyrogram import raw +import pyrogram +from pyrogram import raw, utils from pyrogram import types -from pyrogram.scaffold import Scaffold -class SendDice(Scaffold): +class SendDice: async def send_dice( - self, + self: "pyrogram.Client", chat_id: Union[int, str], emoji: str = "🎲", disable_notification: bool = None, reply_to_message_id: int = None, - schedule_date: int = None, + schedule_date: datetime = None, + protect_content: bool = None, reply_markup: Union[ "types.InlineKeyboardMarkup", "types.ReplyKeyboardMarkup", @@ -40,6 +42,8 @@ async def send_dice( ) -> Optional["types.Message"]: """Send a dice with a random value from 1 to 6. + .. include:: /_includes/usable-by/users-bots.rst + Parameters: chat_id (``int`` | ``str``): Unique identifier (int) or username (str) of the target chat. @@ -60,8 +64,11 @@ async def send_dice( reply_to_message_id (``int``, *optional*): If the message is a reply, ID of the original message. - schedule_date (``int``, *optional*): - Date when the message will be automatically sent. Unix time. + schedule_date (:py:obj:`~datetime.datetime`, *optional*): + Date when the message will be automatically sent. + + protect_content (``bool``, *optional*): + Protects the contents of the sent message from forwarding and saving. reply_markup (:obj:`~pyrogram.types.InlineKeyboardMarkup` | :obj:`~pyrogram.types.ReplyKeyboardMarkup` | :obj:`~pyrogram.types.ReplyKeyboardRemove` | :obj:`~pyrogram.types.ForceReply`, *optional*): Additional interface options. An object for an inline keyboard, custom reply keyboard, @@ -74,23 +81,24 @@ async def send_dice( .. code-block:: python # Send a dice - app.send_dice("pyrogramlounge") + await app.send_dice(chat_id) # Send a dart - app.send_dice("pyrogramlounge", "🎯") + await app.send_dice(chat_id, "🎯") # Send a basketball - app.send_dice("pyrogramlounge", "🏀") + await app.send_dice(chat_id, "🏀") """ - r = await self.send( + r = await self.invoke( raw.functions.messages.SendMedia( peer=await self.resolve_peer(chat_id), media=raw.types.InputMediaDice(emoticon=emoji), silent=disable_notification or None, reply_to_msg_id=reply_to_message_id, random_id=self.rnd_id(), - schedule_date=schedule_date, + schedule_date=utils.datetime_to_timestamp(schedule_date), + noforwards=protect_content, reply_markup=await reply_markup.write(self) if reply_markup else None, message="" ) diff --git a/pyrogram/methods/messages/send_document.py b/pyrogram/methods/messages/send_document.py index a7e342855f..b0a4a531e3 100644 --- a/pyrogram/methods/messages/send_document.py +++ b/pyrogram/methods/messages/send_document.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2021 Dan +# Copyright (C) 2017-present Dan # # This file is part of Pyrogram. # @@ -18,42 +18,46 @@ import os import re -from typing import Union, BinaryIO, List, Optional +from datetime import datetime +from typing import Union, BinaryIO, List, Optional, Callable -from pyrogram import StopTransmission +import pyrogram +from pyrogram import StopTransmission, enums from pyrogram import raw from pyrogram import types from pyrogram import utils from pyrogram.errors import FilePartMissing from pyrogram.file_id import FileType -from pyrogram.scaffold import Scaffold -class SendDocument(Scaffold): +class SendDocument: async def send_document( - self, + self: "pyrogram.Client", chat_id: Union[int, str], document: Union[str, BinaryIO], thumb: Union[str, BinaryIO] = None, caption: str = "", - parse_mode: Optional[str] = object, + parse_mode: Optional["enums.ParseMode"] = None, caption_entities: List["types.MessageEntity"] = None, file_name: str = None, force_document: bool = None, disable_notification: bool = None, reply_to_message_id: int = None, - schedule_date: int = None, + schedule_date: datetime = None, + protect_content: bool = None, reply_markup: Union[ "types.InlineKeyboardMarkup", "types.ReplyKeyboardMarkup", "types.ReplyKeyboardRemove", "types.ForceReply" ] = None, - progress: callable = None, + progress: Callable = None, progress_args: tuple = () ) -> Optional["types.Message"]: """Send generic files. + .. include:: /_includes/usable-by/users-bots.rst + Parameters: chat_id (``int`` | ``str``): Unique identifier (int) or username (str) of the target chat. @@ -76,12 +80,9 @@ async def send_document( caption (``str``, *optional*): Document caption, 0-1024 characters. - parse_mode (``str``, *optional*): + parse_mode (:obj:`~pyrogram.enums.ParseMode`, *optional*): By default, texts are parsed using both Markdown and HTML styles. You can combine both syntaxes together. - Pass "markdown" or "md" to enable Markdown-style parsing only. - Pass "html" to enable HTML-style parsing only. - Pass None to completely disable style parsing. caption_entities (List of :obj:`~pyrogram.types.MessageEntity`): List of special entities that appear in the caption, which can be specified instead of *parse_mode*. @@ -102,14 +103,17 @@ async def send_document( reply_to_message_id (``int``, *optional*): If the message is a reply, ID of the original message. - schedule_date (``int``, *optional*): - Date when the message will be automatically sent. Unix time. + schedule_date (:py:obj:`~datetime.datetime`, *optional*): + Date when the message will be automatically sent. + + protect_content (``bool``, *optional*): + Protects the contents of the sent message from forwarding and saving. reply_markup (:obj:`~pyrogram.types.InlineKeyboardMarkup` | :obj:`~pyrogram.types.ReplyKeyboardMarkup` | :obj:`~pyrogram.types.ReplyKeyboardRemove` | :obj:`~pyrogram.types.ForceReply`, *optional*): Additional interface options. An object for an inline keyboard, custom reply keyboard, instructions to remove reply keyboard or to force a reply from the user. - progress (``callable``, *optional*): + progress (``Callable``, *optional*): Pass a callback function to view the file transmission progress. The function must take *(current, total)* as positional arguments (look at Other Parameters below for a detailed description) and will be called back each time a new file chunk has been successfully @@ -139,16 +143,16 @@ async def send_document( .. code-block:: python # Send document by uploading from local file - app.send_document("me", "document.zip") + await app.send_document("me", "document.zip") # Add caption to the document file - app.send_document("me", "document.zip", caption="archive") + await app.send_document("me", "document.zip", caption="document caption") # Keep track of the progress while uploading - def progress(current, total): + async def progress(current, total): print(f"{current * 100 / total:.1f}%") - app.send_document("me", "document.zip", progress=progress) + await app.send_document("me", "document.zip", progress=progress) """ file = None @@ -176,30 +180,31 @@ def progress(current, total): thumb = await self.save_file(thumb) file = await self.save_file(document, progress=progress, progress_args=progress_args) media = raw.types.InputMediaUploadedDocument( - mime_type=self.guess_mime_type(document.name) or "application/zip", + mime_type=self.guess_mime_type(file_name or document.name) or "application/zip", file=file, thumb=thumb, attributes=[ - raw.types.DocumentAttributeFilename(file_name=document.name) + raw.types.DocumentAttributeFilename(file_name=file_name or document.name) ] ) while True: try: - r = await self.send( + r = await self.invoke( raw.functions.messages.SendMedia( peer=await self.resolve_peer(chat_id), media=media, silent=disable_notification or None, reply_to_msg_id=reply_to_message_id, random_id=self.rnd_id(), - schedule_date=schedule_date, + schedule_date=utils.datetime_to_timestamp(schedule_date), + noforwards=protect_content, reply_markup=await reply_markup.write(self) if reply_markup else None, **await utils.parse_text_entities(self, caption, parse_mode, caption_entities) ) ) except FilePartMissing as e: - await self.save_file(document, file_id=file.id, file_part=e.x) + await self.save_file(document, file_id=file.id, file_part=e.value) else: for i in r.updates: if isinstance(i, (raw.types.UpdateNewMessage, diff --git a/pyrogram/methods/messages/send_location.py b/pyrogram/methods/messages/send_location.py index e70d9661d0..2bd17681b0 100644 --- a/pyrogram/methods/messages/send_location.py +++ b/pyrogram/methods/messages/send_location.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2021 Dan +# Copyright (C) 2017-present Dan # # This file is part of Pyrogram. # @@ -16,22 +16,24 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . +from datetime import datetime from typing import Union -from pyrogram import raw +import pyrogram +from pyrogram import raw, utils from pyrogram import types -from pyrogram.scaffold import Scaffold -class SendLocation(Scaffold): +class SendLocation: async def send_location( - self, + self: "pyrogram.Client", chat_id: Union[int, str], latitude: float, longitude: float, disable_notification: bool = None, reply_to_message_id: int = None, - schedule_date: int = None, + schedule_date: datetime = None, + protect_content: bool = None, reply_markup: Union[ "types.InlineKeyboardMarkup", "types.ReplyKeyboardMarkup", @@ -41,6 +43,8 @@ async def send_location( ) -> "types.Message": """Send points on the map. + .. include:: /_includes/usable-by/users-bots.rst + Parameters: chat_id (``int`` | ``str``): Unique identifier (int) or username (str) of the target chat. @@ -60,8 +64,11 @@ async def send_location( reply_to_message_id (``int``, *optional*): If the message is a reply, ID of the original message - schedule_date (``int``, *optional*): - Date when the message will be automatically sent. Unix time. + schedule_date (:py:obj:`~datetime.datetime`, *optional*): + Date when the message will be automatically sent. + + protect_content (``bool``, *optional*): + Protects the contents of the sent message from forwarding and saving. reply_markup (:obj:`~pyrogram.types.InlineKeyboardMarkup` | :obj:`~pyrogram.types.ReplyKeyboardMarkup` | :obj:`~pyrogram.types.ReplyKeyboardRemove` | :obj:`~pyrogram.types.ForceReply`, *optional*): Additional interface options. An object for an inline keyboard, custom reply keyboard, @@ -73,9 +80,9 @@ async def send_location( Example: .. code-block:: python - app.send_location("me", 51.500729, -0.124583) + app.send_location("me", latitude, longitude) """ - r = await self.send( + r = await self.invoke( raw.functions.messages.SendMedia( peer=await self.resolve_peer(chat_id), media=raw.types.InputMediaGeoPoint( @@ -88,7 +95,8 @@ async def send_location( silent=disable_notification or None, reply_to_msg_id=reply_to_message_id, random_id=self.rnd_id(), - schedule_date=schedule_date, + schedule_date=utils.datetime_to_timestamp(schedule_date), + noforwards=protect_content, reply_markup=await reply_markup.write(self) if reply_markup else None ) ) diff --git a/pyrogram/methods/messages/send_media_group.py b/pyrogram/methods/messages/send_media_group.py index 4073ddecaa..a8b905de23 100644 --- a/pyrogram/methods/messages/send_media_group.py +++ b/pyrogram/methods/messages/send_media_group.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2021 Dan +# Copyright (C) 2017-present Dan # # This file is part of Pyrogram. # @@ -19,21 +19,22 @@ import logging import os import re +from datetime import datetime from typing import Union, List +import pyrogram from pyrogram import raw from pyrogram import types from pyrogram import utils from pyrogram.file_id import FileType -from pyrogram.scaffold import Scaffold log = logging.getLogger(__name__) -class SendMediaGroup(Scaffold): +class SendMediaGroup: # TODO: Add progress parameter async def send_media_group( - self, + self: "pyrogram.Client", chat_id: Union[int, str], media: List[Union[ "types.InputMediaPhoto", @@ -43,10 +44,13 @@ async def send_media_group( ]], disable_notification: bool = None, reply_to_message_id: int = None, - schedule_date: int = None, + schedule_date: datetime = None, + protect_content: bool = None, ) -> List["types.Message"]: """Send a group of photos or videos as an album. + .. include:: /_includes/usable-by/users-bots.rst + Parameters: chat_id (``int`` | ``str``): Unique identifier (int) or username (str) of the target chat. @@ -63,8 +67,11 @@ async def send_media_group( reply_to_message_id (``int``, *optional*): If the message is a reply, ID of the original message. - schedule_date (``int``, *optional*): - Date when the message will be automatically sent. Unix time. + schedule_date (:py:obj:`~datetime.datetime`, *optional*): + Date when the message will be automatically sent. + + protect_content (``bool``, *optional*): + Protects the contents of the sent message from forwarding and saving. Returns: List of :obj:`~pyrogram.types.Message`: On success, a list of the sent messages is returned. @@ -74,12 +81,12 @@ async def send_media_group( from pyrogram.types import InputMediaPhoto, InputMediaVideo - app.send_media_group( + await app.send_media_group( "me", [ InputMediaPhoto("photo1.jpg"), InputMediaPhoto("photo2.jpg", caption="photo caption"), - InputMediaVideo("video.mp4", caption="a video") + InputMediaVideo("video.mp4", caption="video caption") ] ) """ @@ -89,11 +96,12 @@ async def send_media_group( if isinstance(i, types.InputMediaPhoto): if isinstance(i.media, str): if os.path.isfile(i.media): - media = await self.send( + media = await self.invoke( raw.functions.messages.UploadMedia( peer=await self.resolve_peer(chat_id), media=raw.types.InputMediaUploadedPhoto( - file=await self.save_file(i.media) + file=await self.save_file(i.media), + spoiler=i.has_spoiler ) ) ) @@ -103,14 +111,16 @@ async def send_media_group( id=media.photo.id, access_hash=media.photo.access_hash, file_reference=media.photo.file_reference - ) + ), + spoiler=i.has_spoiler ) elif re.match("^https?://", i.media): - media = await self.send( + media = await self.invoke( raw.functions.messages.UploadMedia( peer=await self.resolve_peer(chat_id), media=raw.types.InputMediaPhotoExternal( - url=i.media + url=i.media, + spoiler=i.has_spoiler ) ) ) @@ -120,16 +130,18 @@ async def send_media_group( id=media.photo.id, access_hash=media.photo.access_hash, file_reference=media.photo.file_reference - ) + ), + spoiler=i.has_spoiler ) else: media = utils.get_input_media_from_file_id(i.media, FileType.PHOTO) else: - media = await self.send( + media = await self.invoke( raw.functions.messages.UploadMedia( peer=await self.resolve_peer(chat_id), media=raw.types.InputMediaUploadedPhoto( - file=await self.save_file(i.media) + file=await self.save_file(i.media), + spoiler=i.has_spoiler ) ) ) @@ -139,17 +151,19 @@ async def send_media_group( id=media.photo.id, access_hash=media.photo.access_hash, file_reference=media.photo.file_reference - ) + ), + spoiler=i.has_spoiler ) elif isinstance(i, types.InputMediaVideo): if isinstance(i.media, str): if os.path.isfile(i.media): - media = await self.send( + media = await self.invoke( raw.functions.messages.UploadMedia( peer=await self.resolve_peer(chat_id), media=raw.types.InputMediaUploadedDocument( file=await self.save_file(i.media), thumb=await self.save_file(i.thumb), + spoiler=i.has_spoiler, mime_type=self.guess_mime_type(i.media) or "video/mp4", attributes=[ raw.types.DocumentAttributeVideo( @@ -169,14 +183,16 @@ async def send_media_group( id=media.document.id, access_hash=media.document.access_hash, file_reference=media.document.file_reference - ) + ), + spoiler=i.has_spoiler ) elif re.match("^https?://", i.media): - media = await self.send( + media = await self.invoke( raw.functions.messages.UploadMedia( peer=await self.resolve_peer(chat_id), media=raw.types.InputMediaDocumentExternal( - url=i.media + url=i.media, + spoiler=i.has_spoiler ) ) ) @@ -186,17 +202,19 @@ async def send_media_group( id=media.document.id, access_hash=media.document.access_hash, file_reference=media.document.file_reference - ) + ), + spoiler=i.has_spoiler ) else: media = utils.get_input_media_from_file_id(i.media, FileType.VIDEO) else: - media = await self.send( + media = await self.invoke( raw.functions.messages.UploadMedia( peer=await self.resolve_peer(chat_id), media=raw.types.InputMediaUploadedDocument( file=await self.save_file(i.media), thumb=await self.save_file(i.thumb), + spoiler=i.has_spoiler, mime_type=self.guess_mime_type(getattr(i.media, "name", "video.mp4")) or "video/mp4", attributes=[ raw.types.DocumentAttributeVideo( @@ -216,12 +234,13 @@ async def send_media_group( id=media.document.id, access_hash=media.document.access_hash, file_reference=media.document.file_reference - ) + ), + spoiler=i.has_spoiler ) elif isinstance(i, types.InputMediaAudio): if isinstance(i.media, str): if os.path.isfile(i.media): - media = await self.send( + media = await self.invoke( raw.functions.messages.UploadMedia( peer=await self.resolve_peer(chat_id), media=raw.types.InputMediaUploadedDocument( @@ -248,7 +267,7 @@ async def send_media_group( ) ) elif re.match("^https?://", i.media): - media = await self.send( + media = await self.invoke( raw.functions.messages.UploadMedia( peer=await self.resolve_peer(chat_id), media=raw.types.InputMediaDocumentExternal( @@ -267,7 +286,7 @@ async def send_media_group( else: media = utils.get_input_media_from_file_id(i.media, FileType.AUDIO) else: - media = await self.send( + media = await self.invoke( raw.functions.messages.UploadMedia( peer=await self.resolve_peer(chat_id), media=raw.types.InputMediaUploadedDocument( @@ -296,7 +315,7 @@ async def send_media_group( elif isinstance(i, types.InputMediaDocument): if isinstance(i.media, str): if os.path.isfile(i.media): - media = await self.send( + media = await self.invoke( raw.functions.messages.UploadMedia( peer=await self.resolve_peer(chat_id), media=raw.types.InputMediaUploadedDocument( @@ -318,7 +337,7 @@ async def send_media_group( ) ) elif re.match("^https?://", i.media): - media = await self.send( + media = await self.invoke( raw.functions.messages.UploadMedia( peer=await self.resolve_peer(chat_id), media=raw.types.InputMediaDocumentExternal( @@ -337,7 +356,7 @@ async def send_media_group( else: media = utils.get_input_media_from_file_id(i.media, FileType.DOCUMENT) else: - media = await self.send( + media = await self.invoke( raw.functions.messages.UploadMedia( peer=await self.resolve_peer(chat_id), media=raw.types.InputMediaUploadedDocument( @@ -371,13 +390,14 @@ async def send_media_group( ) ) - r = await self.send( + r = await self.invoke( raw.functions.messages.SendMultiMedia( peer=await self.resolve_peer(chat_id), multi_media=multi_media, silent=disable_notification or None, reply_to_msg_id=reply_to_message_id, - schedule_date=schedule_date + schedule_date=utils.datetime_to_timestamp(schedule_date), + noforwards=protect_content ), sleep_threshold=60 ) diff --git a/pyrogram/methods/messages/send_message.py b/pyrogram/methods/messages/send_message.py index 552a233bbe..ad73f9a182 100644 --- a/pyrogram/methods/messages/send_message.py +++ b/pyrogram/methods/messages/send_message.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2021 Dan +# Copyright (C) 2017-present Dan # # This file is part of Pyrogram. # @@ -16,24 +16,26 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . +from datetime import datetime from typing import Union, List, Optional -from pyrogram import raw, utils +import pyrogram +from pyrogram import raw, utils, enums from pyrogram import types -from pyrogram.scaffold import Scaffold -class SendMessage(Scaffold): +class SendMessage: async def send_message( - self, + self: "pyrogram.Client", chat_id: Union[int, str], text: str, - parse_mode: Optional[str] = object, + parse_mode: Optional["enums.ParseMode"] = None, entities: List["types.MessageEntity"] = None, disable_web_page_preview: bool = None, disable_notification: bool = None, reply_to_message_id: int = None, - schedule_date: int = None, + schedule_date: datetime = None, + protect_content: bool = None, reply_markup: Union[ "types.InlineKeyboardMarkup", "types.ReplyKeyboardMarkup", @@ -43,6 +45,8 @@ async def send_message( ) -> "types.Message": """Send text messages. + .. include:: /_includes/usable-by/users-bots.rst + Parameters: chat_id (``int`` | ``str``): Unique identifier (int) or username (str) of the target chat. @@ -52,12 +56,9 @@ async def send_message( text (``str``): Text of the message to be sent. - parse_mode (``str``, *optional*): + parse_mode (:obj:`~pyrogram.enums.ParseMode`, *optional*): By default, texts are parsed using both Markdown and HTML styles. You can combine both syntaxes together. - Pass "markdown" or "md" to enable Markdown-style parsing only. - Pass "html" to enable HTML-style parsing only. - Pass None to completely disable style parsing. entities (List of :obj:`~pyrogram.types.MessageEntity`): List of special entities that appear in message text, which can be specified instead of *parse_mode*. @@ -72,8 +73,11 @@ async def send_message( reply_to_message_id (``int``, *optional*): If the message is a reply, ID of the original message. - schedule_date (``int``, *optional*): - Date when the message will be automatically sent. Unix time. + schedule_date (:py:obj:`~datetime.datetime`, *optional*): + Date when the message will be automatically sent. + + protect_content (``bool``, *optional*): + Protects the contents of the sent message from forwarding and saving. reply_markup (:obj:`~pyrogram.types.InlineKeyboardMarkup` | :obj:`~pyrogram.types.ReplyKeyboardMarkup` | :obj:`~pyrogram.types.ReplyKeyboardRemove` | :obj:`~pyrogram.types.ForceReply`, *optional*): Additional interface options. An object for an inline keyboard, custom reply keyboard, @@ -84,55 +88,53 @@ async def send_message( Example: .. code-block:: python - :emphasize-lines: 2,5,8,11,21-23,26-32 # Simple example - app.send_message("haskell", "Thanks for creating **Pyrogram**!") + await app.send_message("me", "Message sent with **Pyrogram**!") # Disable web page previews - app.send_message("me", "https://docs.pyrogram.org", disable_web_page_preview=True) + await app.send_message("me", "https://docs.pyrogram.org", + disable_web_page_preview=True) # Reply to a message using its id - app.send_message("me", "this is a reply", reply_to_message_id=12345) + await app.send_message("me", "this is a reply", reply_to_message_id=123) - # Force HTML-only styles for this request only - app.send_message("me", "**not bold**, italic", parse_mode="html") + .. code-block:: python - ## # For bots only, send messages with keyboards attached - ## from pyrogram.types import ( ReplyKeyboardMarkup, InlineKeyboardMarkup, InlineKeyboardButton) # Send a normal keyboard - app.send_message( + await app.send_message( chat_id, "Look at that button!", reply_markup=ReplyKeyboardMarkup([["Nice!"]])) # Send an inline keyboard - app.send_message( + await app.send_message( chat_id, "These are inline buttons", reply_markup=InlineKeyboardMarkup( [ - [InlineKeyboardButton("Data", callback_data="hidden_callback_data")], + [InlineKeyboardButton("Data", callback_data="callback_data")], [InlineKeyboardButton("Docs", url="https://docs.pyrogram.org")] ])) """ message, entities = (await utils.parse_text_entities(self, text, parse_mode, entities)).values() - r = await self.send( + r = await self.invoke( raw.functions.messages.SendMessage( peer=await self.resolve_peer(chat_id), no_webpage=disable_web_page_preview or None, silent=disable_notification or None, reply_to_msg_id=reply_to_message_id, random_id=self.rnd_id(), - schedule_date=schedule_date, + schedule_date=utils.datetime_to_timestamp(schedule_date), reply_markup=await reply_markup.write(self) if reply_markup else None, message=message, - entities=entities + entities=entities, + noforwards=protect_content ) ) @@ -146,20 +148,20 @@ async def send_message( ) return types.Message( - message_id=r.id, + id=r.id, chat=types.Chat( id=peer_id, - type="private", + type=enums.ChatType.PRIVATE, client=self ), text=message, - date=r.date, + date=utils.timestamp_to_datetime(r.date), outgoing=r.out, reply_markup=reply_markup, entities=[ types.MessageEntity._parse(None, entity, {}) for entity in entities - ], + ] if entities else None, client=self ) diff --git a/pyrogram/methods/messages/send_photo.py b/pyrogram/methods/messages/send_photo.py index fcc3c04c6c..61298a5c68 100644 --- a/pyrogram/methods/messages/send_photo.py +++ b/pyrogram/methods/messages/send_photo.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2021 Dan +# Copyright (C) 2017-present Dan # # This file is part of Pyrogram. # @@ -18,40 +18,44 @@ import os import re -from typing import Union, BinaryIO, List, Optional +from datetime import datetime +from typing import Union, BinaryIO, List, Optional, Callable import pyrogram -from pyrogram import raw +from pyrogram import raw, enums from pyrogram import types from pyrogram import utils from pyrogram.errors import FilePartMissing from pyrogram.file_id import FileType -from pyrogram.scaffold import Scaffold -class SendPhoto(Scaffold): +class SendPhoto: async def send_photo( - self, + self: "pyrogram.Client", chat_id: Union[int, str], photo: Union[str, BinaryIO], caption: str = "", - parse_mode: Optional[str] = object, + parse_mode: Optional["enums.ParseMode"] = None, caption_entities: List["types.MessageEntity"] = None, + has_spoiler: bool = None, ttl_seconds: int = None, disable_notification: bool = None, reply_to_message_id: int = None, - schedule_date: int = None, + schedule_date: datetime = None, + protect_content: bool = None, reply_markup: Union[ "types.InlineKeyboardMarkup", "types.ReplyKeyboardMarkup", "types.ReplyKeyboardRemove", "types.ForceReply" ] = None, - progress: callable = None, + progress: Callable = None, progress_args: tuple = () ) -> Optional["types.Message"]: """Send photos. + .. include:: /_includes/usable-by/users-bots.rst + Parameters: chat_id (``int`` | ``str``): Unique identifier (int) or username (str) of the target chat. @@ -68,16 +72,16 @@ async def send_photo( caption (``str``, *optional*): Photo caption, 0-1024 characters. - parse_mode (``str``, *optional*): + parse_mode (:obj:`~pyrogram.enums.ParseMode`, *optional*): By default, texts are parsed using both Markdown and HTML styles. You can combine both syntaxes together. - Pass "markdown" or "md" to enable Markdown-style parsing only. - Pass "html" to enable HTML-style parsing only. - Pass None to completely disable style parsing. caption_entities (List of :obj:`~pyrogram.types.MessageEntity`): List of special entities that appear in the caption, which can be specified instead of *parse_mode*. + has_spoiler (``bool``, *optional*): + Pass True if the photo needs to be covered with a spoiler animation. + ttl_seconds (``int``, *optional*): Self-Destruct Timer. If you set a timer, the photo will self-destruct in *ttl_seconds* @@ -90,14 +94,17 @@ async def send_photo( reply_to_message_id (``int``, *optional*): If the message is a reply, ID of the original message. - schedule_date (``int``, *optional*): - Date when the message will be automatically sent. Unix time. + schedule_date (:py:obj:`~datetime.datetime`, *optional*): + Date when the message will be automatically sent. + + protect_content (``bool``, *optional*): + Protects the contents of the sent message from forwarding and saving. reply_markup (:obj:`~pyrogram.types.InlineKeyboardMarkup` | :obj:`~pyrogram.types.ReplyKeyboardMarkup` | :obj:`~pyrogram.types.ReplyKeyboardRemove` | :obj:`~pyrogram.types.ForceReply`, *optional*): Additional interface options. An object for an inline keyboard, custom reply keyboard, instructions to remove reply keyboard or to force a reply from the user. - progress (``callable``, *optional*): + progress (``Callable``, *optional*): Pass a callback function to view the file transmission progress. The function must take *(current, total)* as positional arguments (look at Other Parameters below for a detailed description) and will be called back each time a new file chunk has been successfully @@ -127,16 +134,16 @@ async def send_photo( .. code-block:: python # Send photo by uploading from local file - app.send_photo("me", "photo.jpg") + await app.send_photo("me", "photo.jpg") # Send photo by uploading from URL - app.send_photo("me", "https://i.imgur.com/BQBTP7d.png") + await app.send_photo("me", "https://example.com/example.jpg") # Add caption to a photo - app.send_photo("me", "photo.jpg", caption="Holidays!") + await app.send_photo("me", "photo.jpg", caption="Caption") # Send self-destructing photo - app.send_photo("me", "photo.jpg", ttl_seconds=10) + await app.send_photo("me", "photo.jpg", ttl_seconds=10) """ file = None @@ -146,38 +153,42 @@ async def send_photo( file = await self.save_file(photo, progress=progress, progress_args=progress_args) media = raw.types.InputMediaUploadedPhoto( file=file, - ttl_seconds=ttl_seconds + ttl_seconds=ttl_seconds, + spoiler=has_spoiler, ) elif re.match("^https?://", photo): media = raw.types.InputMediaPhotoExternal( url=photo, - ttl_seconds=ttl_seconds + ttl_seconds=ttl_seconds, + spoiler=has_spoiler ) else: - media = utils.get_input_media_from_file_id(photo, FileType.PHOTO) + media = utils.get_input_media_from_file_id(photo, FileType.PHOTO, ttl_seconds=ttl_seconds) else: file = await self.save_file(photo, progress=progress, progress_args=progress_args) media = raw.types.InputMediaUploadedPhoto( file=file, - ttl_seconds=ttl_seconds + ttl_seconds=ttl_seconds, + spoiler=has_spoiler ) while True: try: - r = await self.send( + r = await self.invoke( raw.functions.messages.SendMedia( peer=await self.resolve_peer(chat_id), media=media, silent=disable_notification or None, reply_to_msg_id=reply_to_message_id, random_id=self.rnd_id(), - schedule_date=schedule_date, + schedule_date=utils.datetime_to_timestamp(schedule_date), + noforwards=protect_content, reply_markup=await reply_markup.write(self) if reply_markup else None, **await utils.parse_text_entities(self, caption, parse_mode, caption_entities) ) ) except FilePartMissing as e: - await self.save_file(photo, file_id=file.id, file_part=e.x) + await self.save_file(photo, file_id=file.id, file_part=e.value) else: for i in r.updates: if isinstance(i, (raw.types.UpdateNewMessage, diff --git a/pyrogram/methods/messages/send_poll.py b/pyrogram/methods/messages/send_poll.py index dc0ccf178c..267dcb6dad 100644 --- a/pyrogram/methods/messages/send_poll.py +++ b/pyrogram/methods/messages/send_poll.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2021 Dan +# Copyright (C) 2017-present Dan # # This file is part of Pyrogram. # @@ -16,26 +16,34 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . +from datetime import datetime from typing import Union, List -from pyrogram import raw -from pyrogram import types -from pyrogram.scaffold import Scaffold +import pyrogram +from pyrogram import raw, utils +from pyrogram import types, enums -class SendPoll(Scaffold): +class SendPoll: async def send_poll( - self, + self: "pyrogram.Client", chat_id: Union[int, str], question: str, options: List[str], is_anonymous: bool = True, + type: "enums.PollType" = enums.PollType.REGULAR, allows_multiple_answers: bool = None, - type: str = "regular", correct_option_id: int = None, + explanation: str = None, + explanation_parse_mode: "enums.ParseMode" = None, + explanation_entities: List["types.MessageEntity"] = None, + open_period: int = None, + close_date: datetime = None, + is_closed: bool = None, disable_notification: bool = None, + protect_content: bool = None, reply_to_message_id: int = None, - schedule_date: int = None, + schedule_date: datetime = None, reply_markup: Union[ "types.InlineKeyboardMarkup", "types.ReplyKeyboardMarkup", @@ -45,6 +53,8 @@ async def send_poll( ) -> "types.Message": """Send a new poll. + .. include:: /_includes/usable-by/users-bots.rst + Parameters: chat_id (``int`` | ``str``): Unique identifier (int) or username (str) of the target chat. @@ -61,27 +71,54 @@ async def send_poll( True, if the poll needs to be anonymous. Defaults to True. - type (``str``, *optional*): - Poll type, "quiz" or "regular". - Defaults to "regular" + type (:obj`~pyrogram.enums.PollType`, *optional*): + Poll type, :obj:`~pyrogram.enums.PollType.QUIZ` or :obj:`~pyrogram.enums.PollType.REGULAR`. + Defaults to :obj:`~pyrogram.enums.PollType.REGULAR`. allows_multiple_answers (``bool``, *optional*): True, if the poll allows multiple answers, ignored for polls in quiz mode. - Defaults to False + Defaults to False. correct_option_id (``int``, *optional*): - 0-based identifier of the correct answer option (the index of the correct option) - Required for polls in quiz mode. + 0-based identifier of the correct answer option, required for polls in quiz mode. + + explanation (``str``, *optional*): + Text that is shown when a user chooses an incorrect answer or taps on the lamp icon in a quiz-style + poll, 0-200 characters with at most 2 line feeds after entities parsing. + + explanation_parse_mode (:obj:`~pyrogram.enums.ParseMode`, *optional*): + By default, texts are parsed using both Markdown and HTML styles. + You can combine both syntaxes together. + + explanation_entities (List of :obj:`~pyrogram.types.MessageEntity`): + List of special entities that appear in the poll explanation, which can be specified instead of + *parse_mode*. + + open_period (``int``, *optional*): + Amount of time in seconds the poll will be active after creation, 5-600. + Can't be used together with *close_date*. + + close_date (:py:obj:`~datetime.datetime`, *optional*): + Point in time when the poll will be automatically closed. + Must be at least 5 and no more than 600 seconds in the future. + Can't be used together with *open_period*. + + is_closed (``bool``, *optional*): + Pass True, if the poll needs to be immediately closed. + This can be useful for poll preview. disable_notification (``bool``, *optional*): Sends the message silently. Users will receive a notification with no sound. + protect_content (``bool``, *optional*): + Protects the contents of the sent message from forwarding and saving. + reply_to_message_id (``int``, *optional*): If the message is a reply, ID of the original message. - schedule_date (``int``, *optional*): - Date when the message will be automatically sent. Unix time. + schedule_date (:py:obj:`~datetime.datetime`, *optional*): + Date when the message will be automatically sent. reply_markup (:obj:`~pyrogram.types.InlineKeyboardMarkup` | :obj:`~pyrogram.types.ReplyKeyboardMarkup` | :obj:`~pyrogram.types.ReplyKeyboardRemove` | :obj:`~pyrogram.types.ForceReply`, *optional*): Additional interface options. An object for an inline keyboard, custom reply keyboard, @@ -93,30 +130,41 @@ async def send_poll( Example: .. code-block:: python - app.send_poll(chat_id, "Is this a poll question?", ["Yes", "No", "Maybe"]) + await app.send_poll(chat_id, "Is this a poll question?", ["Yes", "No", "Maybe"]) """ - r = await self.send( + + solution, solution_entities = (await utils.parse_text_entities( + self, explanation, explanation_parse_mode, explanation_entities + )).values() + + r = await self.invoke( raw.functions.messages.SendMedia( peer=await self.resolve_peer(chat_id), media=raw.types.InputMediaPoll( poll=raw.types.Poll( - id=0, + id=self.rnd_id(), question=question, answers=[ - raw.types.PollAnswer(text=o, option=bytes([i])) - for i, o in enumerate(options) + raw.types.PollAnswer(text=text, option=bytes([i])) + for i, text in enumerate(options) ], - multiple_choice=allows_multiple_answers or None, - public_voters=not is_anonymous or None, - quiz=type == "quiz" or None + closed=is_closed, + public_voters=not is_anonymous, + multiple_choice=allows_multiple_answers, + quiz=type == enums.PollType.QUIZ or False, + close_period=open_period, + close_date=utils.datetime_to_timestamp(close_date) ), - correct_answers=None if correct_option_id is None else [bytes([correct_option_id])] + correct_answers=[bytes([correct_option_id])] if correct_option_id is not None else None, + solution=solution, + solution_entities=solution_entities or [] ), message="", - silent=disable_notification or None, + silent=disable_notification, reply_to_msg_id=reply_to_message_id, random_id=self.rnd_id(), - schedule_date=schedule_date, + schedule_date=utils.datetime_to_timestamp(schedule_date), + noforwards=protect_content, reply_markup=await reply_markup.write(self) if reply_markup else None ) ) diff --git a/pyrogram/methods/messages/send_reaction.py b/pyrogram/methods/messages/send_reaction.py new file mode 100644 index 0000000000..f4d1d34340 --- /dev/null +++ b/pyrogram/methods/messages/send_reaction.py @@ -0,0 +1,73 @@ +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-present Dan +# +# This file is part of Pyrogram. +# +# Pyrogram is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License as published +# by the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Pyrogram is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with Pyrogram. If not, see . + +from typing import Union + +import pyrogram +from pyrogram import raw + + +class SendReaction: + async def send_reaction( + self: "pyrogram.Client", + chat_id: Union[int, str], + message_id: int, + emoji: str = "", + big: bool = False + ) -> bool: + """Send a reaction to a message. + + .. include:: /_includes/usable-by/users.rst + + Parameters: + chat_id (``int`` | ``str``): + Unique identifier (int) or username (str) of the target chat. + + message_id (``int``): + Identifier of the message. + + emoji (``str``, *optional*): + Reaction emoji. + Pass "" as emoji (default) to retract the reaction. + + big (``bool``, *optional*): + Pass True to show a bigger and longer reaction. + Defaults to False. + + Returns: + ``bool``: On success, True is returned. + + Example: + .. code-block:: python + + # Send a reaction + await app.send_reaction(chat_id, message_id, "🔥") + + # Retract a reaction + await app.send_reaction(chat_id, message_id) + """ + await self.invoke( + raw.functions.messages.SendReaction( + peer=await self.resolve_peer(chat_id), + msg_id=message_id, + reaction=[raw.types.ReactionEmoji(emoticon=emoji)] if emoji else None, + big=big + ) + ) + + return True diff --git a/pyrogram/methods/messages/send_sticker.py b/pyrogram/methods/messages/send_sticker.py index c7ae122a57..ccfaada51e 100644 --- a/pyrogram/methods/messages/send_sticker.py +++ b/pyrogram/methods/messages/send_sticker.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2021 Dan +# Copyright (C) 2017-present Dan # # This file is part of Pyrogram. # @@ -18,36 +18,40 @@ import os import re -from typing import Union, BinaryIO, Optional +from datetime import datetime +from typing import Union, BinaryIO, Optional, Callable +import pyrogram from pyrogram import StopTransmission from pyrogram import raw from pyrogram import types from pyrogram import utils from pyrogram.errors import FilePartMissing from pyrogram.file_id import FileType -from pyrogram.scaffold import Scaffold -class SendSticker(Scaffold): +class SendSticker: async def send_sticker( - self, + self: "pyrogram.Client", chat_id: Union[int, str], sticker: Union[str, BinaryIO], disable_notification: bool = None, reply_to_message_id: int = None, - schedule_date: int = None, + schedule_date: datetime = None, + protect_content: bool = None, reply_markup: Union[ "types.InlineKeyboardMarkup", "types.ReplyKeyboardMarkup", "types.ReplyKeyboardRemove", "types.ForceReply" ] = None, - progress: callable = None, + progress: Callable = None, progress_args: tuple = () ) -> Optional["types.Message"]: """Send static .webp or animated .tgs stickers. + .. include:: /_includes/usable-by/users-bots.rst + Parameters: chat_id (``int`` | ``str``): Unique identifier (int) or username (str) of the target chat. @@ -68,14 +72,17 @@ async def send_sticker( reply_to_message_id (``int``, *optional*): If the message is a reply, ID of the original message. - schedule_date (``int``, *optional*): - Date when the message will be automatically sent. Unix time. + schedule_date (:py:obj:`~datetime.datetime`, *optional*): + Date when the message will be automatically sent. + + protect_content (``bool``, *optional*): + Protects the contents of the sent message from forwarding and saving. reply_markup (:obj:`~pyrogram.types.InlineKeyboardMarkup` | :obj:`~pyrogram.types.ReplyKeyboardMarkup` | :obj:`~pyrogram.types.ReplyKeyboardRemove` | :obj:`~pyrogram.types.ForceReply`, *optional*): Additional interface options. An object for an inline keyboard, custom reply keyboard, instructions to remove reply keyboard or to force a reply from the user. - progress (``callable``, *optional*): + progress (``Callable``, *optional*): Pass a callback function to view the file transmission progress. The function must take *(current, total)* as positional arguments (look at Other Parameters below for a detailed description) and will be called back each time a new file chunk has been successfully @@ -106,10 +113,10 @@ async def send_sticker( .. code-block:: python # Send sticker by uploading from local file - app.send_sticker("me", "sticker.webp") + await app.send_sticker("me", "sticker.webp") # Send sticker using file_id - app.send_sticker("me", "CAADBAADzg4AAvLQYAEz_x2EOgdRwBYE") + await app.send_sticker("me", file_id) """ file = None @@ -142,20 +149,21 @@ async def send_sticker( while True: try: - r = await self.send( + r = await self.invoke( raw.functions.messages.SendMedia( peer=await self.resolve_peer(chat_id), media=media, silent=disable_notification or None, reply_to_msg_id=reply_to_message_id, random_id=self.rnd_id(), - schedule_date=schedule_date, + schedule_date=utils.datetime_to_timestamp(schedule_date), + noforwards=protect_content, reply_markup=await reply_markup.write(self) if reply_markup else None, message="" ) ) except FilePartMissing as e: - await self.save_file(sticker, file_id=file.id, file_part=e.x) + await self.save_file(sticker, file_id=file.id, file_part=e.value) else: for i in r.updates: if isinstance(i, (raw.types.UpdateNewMessage, diff --git a/pyrogram/methods/messages/send_venue.py b/pyrogram/methods/messages/send_venue.py index 7dbae7a0e9..4d3167bf00 100644 --- a/pyrogram/methods/messages/send_venue.py +++ b/pyrogram/methods/messages/send_venue.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2021 Dan +# Copyright (C) 2017-present Dan # # This file is part of Pyrogram. # @@ -16,16 +16,17 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . +from datetime import datetime from typing import Union -from pyrogram import raw +import pyrogram +from pyrogram import raw, utils from pyrogram import types -from pyrogram.scaffold import Scaffold -class SendVenue(Scaffold): +class SendVenue: async def send_venue( - self, + self: "pyrogram.Client", chat_id: Union[int, str], latitude: float, longitude: float, @@ -35,7 +36,8 @@ async def send_venue( foursquare_type: str = "", disable_notification: bool = None, reply_to_message_id: int = None, - schedule_date: int = None, + schedule_date: datetime = None, + protect_content: bool = None, reply_markup: Union[ "types.InlineKeyboardMarkup", "types.ReplyKeyboardMarkup", @@ -45,6 +47,8 @@ async def send_venue( ) -> "types.Message": """Send information about a venue. + .. include:: /_includes/usable-by/users-bots.rst + Parameters: chat_id (``int`` | ``str``): Unique identifier (int) or username (str) of the target chat. @@ -77,8 +81,11 @@ async def send_venue( reply_to_message_id (``int``, *optional*): If the message is a reply, ID of the original message - schedule_date (``int``, *optional*): - Date when the message will be automatically sent. Unix time. + schedule_date (:py:obj:`~datetime.datetime`, *optional*): + Date when the message will be automatically sent. + + protect_content (``bool``, *optional*): + Protects the contents of the sent message from forwarding and saving. reply_markup (:obj:`~pyrogram.types.InlineKeyboardMarkup` | :obj:`~pyrogram.types.ReplyKeyboardMarkup` | :obj:`~pyrogram.types.ReplyKeyboardRemove` | :obj:`~pyrogram.types.ForceReply`, *optional*): Additional interface options. An object for an inline keyboard, custom reply keyboard, @@ -91,10 +98,10 @@ async def send_venue( .. code-block:: python app.send_venue( - "me", 51.500729, -0.124583, - "Elizabeth Tower", "Westminster, London SW1A 0AA, UK") + "me", latitude, longitude, + "Venue title", "Venue address") """ - r = await self.send( + r = await self.invoke( raw.functions.messages.SendMedia( peer=await self.resolve_peer(chat_id), media=raw.types.InputMediaVenue( @@ -112,7 +119,8 @@ async def send_venue( silent=disable_notification or None, reply_to_msg_id=reply_to_message_id, random_id=self.rnd_id(), - schedule_date=schedule_date, + schedule_date=utils.datetime_to_timestamp(schedule_date), + noforwards=protect_content, reply_markup=await reply_markup.write(self) if reply_markup else None ) ) diff --git a/pyrogram/methods/messages/send_video.py b/pyrogram/methods/messages/send_video.py index d42846eda4..e869dd172d 100644 --- a/pyrogram/methods/messages/send_video.py +++ b/pyrogram/methods/messages/send_video.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2021 Dan +# Copyright (C) 2017-present Dan # # This file is part of Pyrogram. # @@ -18,25 +18,27 @@ import os import re -from typing import Union, BinaryIO, List, Optional +from datetime import datetime +from typing import Union, BinaryIO, List, Optional, Callable -from pyrogram import StopTransmission +import pyrogram +from pyrogram import StopTransmission, enums from pyrogram import raw from pyrogram import types from pyrogram import utils from pyrogram.errors import FilePartMissing from pyrogram.file_id import FileType -from pyrogram.scaffold import Scaffold -class SendVideo(Scaffold): +class SendVideo: async def send_video( - self, + self: "pyrogram.Client", chat_id: Union[int, str], video: Union[str, BinaryIO], caption: str = "", - parse_mode: Optional[str] = object, + parse_mode: Optional["enums.ParseMode"] = None, caption_entities: List["types.MessageEntity"] = None, + has_spoiler: bool = None, ttl_seconds: int = None, duration: int = 0, width: int = 0, @@ -46,18 +48,21 @@ async def send_video( supports_streaming: bool = True, disable_notification: bool = None, reply_to_message_id: int = None, - schedule_date: int = None, + schedule_date: datetime = None, + protect_content: bool = None, reply_markup: Union[ "types.InlineKeyboardMarkup", "types.ReplyKeyboardMarkup", "types.ReplyKeyboardRemove", "types.ForceReply" ] = None, - progress: callable = None, + progress: Callable = None, progress_args: tuple = () ) -> Optional["types.Message"]: """Send video files. + .. include:: /_includes/usable-by/users-bots.rst + Parameters: chat_id (``int`` | ``str``): Unique identifier (int) or username (str) of the target chat. @@ -74,16 +79,16 @@ async def send_video( caption (``str``, *optional*): Video caption, 0-1024 characters. - parse_mode (``str``, *optional*): + parse_mode (:obj:`~pyrogram.enums.ParseMode`, *optional*): By default, texts are parsed using both Markdown and HTML styles. You can combine both syntaxes together. - Pass "markdown" or "md" to enable Markdown-style parsing only. - Pass "html" to enable HTML-style parsing only. - Pass None to completely disable style parsing. caption_entities (List of :obj:`~pyrogram.types.MessageEntity`): List of special entities that appear in the caption, which can be specified instead of *parse_mode*. + has_spoiler (``bool``, *optional*): + Pass True if the video needs to be covered with a spoiler animation. + ttl_seconds (``int``, *optional*): Self-Destruct Timer. If you set a timer, the video will self-destruct in *ttl_seconds* @@ -119,14 +124,17 @@ async def send_video( reply_to_message_id (``int``, *optional*): If the message is a reply, ID of the original message. - schedule_date (``int``, *optional*): - Date when the message will be automatically sent. Unix time. + schedule_date (:py:obj:`~datetime.datetime`, *optional*): + Date when the message will be automatically sent. + + protect_content (``bool``, *optional*): + Protects the contents of the sent message from forwarding and saving. reply_markup (:obj:`~pyrogram.types.InlineKeyboardMarkup` | :obj:`~pyrogram.types.ReplyKeyboardMarkup` | :obj:`~pyrogram.types.ReplyKeyboardRemove` | :obj:`~pyrogram.types.ForceReply`, *optional*): Additional interface options. An object for an inline keyboard, custom reply keyboard, instructions to remove reply keyboard or to force a reply from the user. - progress (``callable``, *optional*): + progress (``Callable``, *optional*): Pass a callback function to view the file transmission progress. The function must take *(current, total)* as positional arguments (look at Other Parameters below for a detailed description) and will be called back each time a new file chunk has been successfully @@ -156,19 +164,19 @@ async def send_video( .. code-block:: python # Send video by uploading from local file - app.send_video("me", "video.mp4") + await app.send_video("me", "video.mp4") # Add caption to the video - app.send_video("me", "video.mp4", caption="recording") + await app.send_video("me", "video.mp4", caption="video caption") # Send self-destructing video - app.send_video("me", "video.mp4", ttl_seconds=10) + await app.send_video("me", "video.mp4", ttl_seconds=10) # Keep track of the progress while uploading - def progress(current, total): + async def progress(current, total): print(f"{current * 100 / total:.1f}%") - app.send_video("me", "video.mp4", progress=progress) + await app.send_video("me", "video.mp4", progress=progress) """ file = None @@ -181,6 +189,7 @@ def progress(current, total): mime_type=self.guess_mime_type(video) or "video/mp4", file=file, ttl_seconds=ttl_seconds, + spoiler=has_spoiler, thumb=thumb, attributes=[ raw.types.DocumentAttributeVideo( @@ -195,17 +204,19 @@ def progress(current, total): elif re.match("^https?://", video): media = raw.types.InputMediaDocumentExternal( url=video, - ttl_seconds=ttl_seconds + ttl_seconds=ttl_seconds, + spoiler=has_spoiler ) else: - media = utils.get_input_media_from_file_id(video, FileType.VIDEO) + media = utils.get_input_media_from_file_id(video, FileType.VIDEO, ttl_seconds=ttl_seconds) else: thumb = await self.save_file(thumb) file = await self.save_file(video, progress=progress, progress_args=progress_args) media = raw.types.InputMediaUploadedDocument( - mime_type=self.guess_mime_type(video.name) or "video/mp4", + mime_type=self.guess_mime_type(file_name or video.name) or "video/mp4", file=file, ttl_seconds=ttl_seconds, + spoiler=has_spoiler, thumb=thumb, attributes=[ raw.types.DocumentAttributeVideo( @@ -214,26 +225,27 @@ def progress(current, total): w=width, h=height ), - raw.types.DocumentAttributeFilename(file_name=video.name) + raw.types.DocumentAttributeFilename(file_name=file_name or video.name) ] ) while True: try: - r = await self.send( + r = await self.invoke( raw.functions.messages.SendMedia( peer=await self.resolve_peer(chat_id), media=media, silent=disable_notification or None, reply_to_msg_id=reply_to_message_id, random_id=self.rnd_id(), - schedule_date=schedule_date, + schedule_date=utils.datetime_to_timestamp(schedule_date), + noforwards=protect_content, reply_markup=await reply_markup.write(self) if reply_markup else None, **await utils.parse_text_entities(self, caption, parse_mode, caption_entities) ) ) except FilePartMissing as e: - await self.save_file(video, file_id=file.id, file_part=e.x) + await self.save_file(video, file_id=file.id, file_part=e.value) else: for i in r.updates: if isinstance(i, (raw.types.UpdateNewMessage, diff --git a/pyrogram/methods/messages/send_video_note.py b/pyrogram/methods/messages/send_video_note.py index 91ba93fe4c..9c615d3116 100644 --- a/pyrogram/methods/messages/send_video_note.py +++ b/pyrogram/methods/messages/send_video_note.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2021 Dan +# Copyright (C) 2017-present Dan # # This file is part of Pyrogram. # @@ -17,20 +17,21 @@ # along with Pyrogram. If not, see . import os -from typing import Union, BinaryIO, Optional +from datetime import datetime +from typing import Union, BinaryIO, Optional, Callable +import pyrogram from pyrogram import StopTransmission from pyrogram import raw from pyrogram import types from pyrogram import utils from pyrogram.errors import FilePartMissing from pyrogram.file_id import FileType -from pyrogram.scaffold import Scaffold -class SendVideoNote(Scaffold): +class SendVideoNote: async def send_video_note( - self, + self: "pyrogram.Client", chat_id: Union[int, str], video_note: Union[str, BinaryIO], duration: int = 0, @@ -38,18 +39,21 @@ async def send_video_note( thumb: Union[str, BinaryIO] = None, disable_notification: bool = None, reply_to_message_id: int = None, - schedule_date: int = None, + schedule_date: datetime = None, + protect_content: bool = None, reply_markup: Union[ "types.InlineKeyboardMarkup", "types.ReplyKeyboardMarkup", "types.ReplyKeyboardRemove", "types.ForceReply" ] = None, - progress: callable = None, + progress: Callable = None, progress_args: tuple = () ) -> Optional["types.Message"]: """Send video messages. + .. include:: /_includes/usable-by/users-bots.rst + Parameters: chat_id (``int`` | ``str``): Unique identifier (int) or username (str) of the target chat. @@ -82,14 +86,17 @@ async def send_video_note( reply_to_message_id (``int``, *optional*): If the message is a reply, ID of the original message - schedule_date (``int``, *optional*): - Date when the message will be automatically sent. Unix time. + schedule_date (:py:obj:`~datetime.datetime`, *optional*): + Date when the message will be automatically sent. + + protect_content (``bool``, *optional*): + Protects the contents of the sent message from forwarding and saving. reply_markup (:obj:`~pyrogram.types.InlineKeyboardMarkup` | :obj:`~pyrogram.types.ReplyKeyboardMarkup` | :obj:`~pyrogram.types.ReplyKeyboardRemove` | :obj:`~pyrogram.types.ForceReply`, *optional*): Additional interface options. An object for an inline keyboard, custom reply keyboard, instructions to remove reply keyboard or to force a reply from the user. - progress (``callable``, *optional*): + progress (``Callable``, *optional*): Pass a callback function to view the file transmission progress. The function must take *(current, total)* as positional arguments (look at Other Parameters below for a detailed description) and will be called back each time a new file chunk has been successfully @@ -120,10 +127,10 @@ async def send_video_note( .. code-block:: python # Send video note by uploading from local file - app.send_video_note("me", "video_note.mp4") + await app.send_video_note("me", "video_note.mp4") # Set video note length - app.send_video_note("me", "video_note.mp4", length=25) + await app.send_video_note("me", "video_note.mp4", length=25) """ file = None @@ -166,20 +173,21 @@ async def send_video_note( while True: try: - r = await self.send( + r = await self.invoke( raw.functions.messages.SendMedia( peer=await self.resolve_peer(chat_id), media=media, silent=disable_notification or None, reply_to_msg_id=reply_to_message_id, random_id=self.rnd_id(), - schedule_date=schedule_date, + schedule_date=utils.datetime_to_timestamp(schedule_date), + noforwards=protect_content, reply_markup=await reply_markup.write(self) if reply_markup else None, message="" ) ) except FilePartMissing as e: - await self.save_file(video_note, file_id=file.id, file_part=e.x) + await self.save_file(video_note, file_id=file.id, file_part=e.value) else: for i in r.updates: if isinstance(i, (raw.types.UpdateNewMessage, diff --git a/pyrogram/methods/messages/send_voice.py b/pyrogram/methods/messages/send_voice.py index c534767bc4..266702fd83 100644 --- a/pyrogram/methods/messages/send_voice.py +++ b/pyrogram/methods/messages/send_voice.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2021 Dan +# Copyright (C) 2017-present Dan # # This file is part of Pyrogram. # @@ -18,40 +18,44 @@ import os import re -from typing import Union, BinaryIO, List, Optional +from datetime import datetime +from typing import Union, BinaryIO, List, Optional, Callable -from pyrogram import StopTransmission +import pyrogram +from pyrogram import StopTransmission, enums from pyrogram import raw from pyrogram import types from pyrogram import utils from pyrogram.errors import FilePartMissing from pyrogram.file_id import FileType -from pyrogram.scaffold import Scaffold -class SendVoice(Scaffold): +class SendVoice: async def send_voice( - self, + self: "pyrogram.Client", chat_id: Union[int, str], voice: Union[str, BinaryIO], caption: str = "", - parse_mode: Optional[str] = object, + parse_mode: Optional["enums.ParseMode"] = None, caption_entities: List["types.MessageEntity"] = None, duration: int = 0, disable_notification: bool = None, reply_to_message_id: int = None, - schedule_date: int = None, + schedule_date: datetime = None, + protect_content: bool = None, reply_markup: Union[ "types.InlineKeyboardMarkup", "types.ReplyKeyboardMarkup", "types.ReplyKeyboardRemove", "types.ForceReply" ] = None, - progress: callable = None, + progress: Callable = None, progress_args: tuple = () ) -> Optional["types.Message"]: """Send audio files. + .. include:: /_includes/usable-by/users-bots.rst + Parameters: chat_id (``int`` | ``str``): Unique identifier (int) or username (str) of the target chat. @@ -68,12 +72,9 @@ async def send_voice( caption (``str``, *optional*): Voice message caption, 0-1024 characters. - parse_mode (``str``, *optional*): + parse_mode (:obj:`~pyrogram.enums.ParseMode`, *optional*): By default, texts are parsed using both Markdown and HTML styles. You can combine both syntaxes together. - Pass "markdown" or "md" to enable Markdown-style parsing only. - Pass "html" to enable HTML-style parsing only. - Pass None to completely disable style parsing. caption_entities (List of :obj:`~pyrogram.types.MessageEntity`): List of special entities that appear in the caption, which can be specified instead of *parse_mode*. @@ -88,14 +89,17 @@ async def send_voice( reply_to_message_id (``int``, *optional*): If the message is a reply, ID of the original message - schedule_date (``int``, *optional*): - Date when the message will be automatically sent. Unix time. + schedule_date (:py:obj:`~datetime.datetime`, *optional*): + Date when the message will be automatically sent. + + protect_content (``bool``, *optional*): + Protects the contents of the sent message from forwarding and saving. reply_markup (:obj:`~pyrogram.types.InlineKeyboardMarkup` | :obj:`~pyrogram.types.ReplyKeyboardMarkup` | :obj:`~pyrogram.types.ReplyKeyboardRemove` | :obj:`~pyrogram.types.ForceReply`, *optional*): Additional interface options. An object for an inline keyboard, custom reply keyboard, instructions to remove reply keyboard or to force a reply from the user. - progress (``callable``, *optional*): + progress (``Callable``, *optional*): Pass a callback function to view the file transmission progress. The function must take *(current, total)* as positional arguments (look at Other Parameters below for a detailed description) and will be called back each time a new file chunk has been successfully @@ -125,13 +129,13 @@ async def send_voice( .. code-block:: python # Send voice note by uploading from local file - app.send_voice("me", "voice.ogg") + await app.send_voice("me", "voice.ogg") # Add caption to the voice note - app.send_voice("me", "voice.ogg", caption="voice note") + await app.send_voice("me", "voice.ogg", caption="voice caption") # Set voice note duration - app.send_voice("me", "voice.ogg", duration=20) + await app.send_voice("me", "voice.ogg", duration=20) """ file = None @@ -170,20 +174,21 @@ async def send_voice( while True: try: - r = await self.send( + r = await self.invoke( raw.functions.messages.SendMedia( peer=await self.resolve_peer(chat_id), media=media, silent=disable_notification or None, reply_to_msg_id=reply_to_message_id, random_id=self.rnd_id(), - schedule_date=schedule_date, + schedule_date=utils.datetime_to_timestamp(schedule_date), + noforwards=protect_content, reply_markup=await reply_markup.write(self) if reply_markup else None, **await utils.parse_text_entities(self, caption, parse_mode, caption_entities) ) ) except FilePartMissing as e: - await self.save_file(voice, file_id=file.id, file_part=e.x) + await self.save_file(voice, file_id=file.id, file_part=e.value) else: for i in r.updates: if isinstance(i, (raw.types.UpdateNewMessage, diff --git a/pyrogram/methods/messages/stop_poll.py b/pyrogram/methods/messages/stop_poll.py index 8853c5d6b9..89a33434b7 100644 --- a/pyrogram/methods/messages/stop_poll.py +++ b/pyrogram/methods/messages/stop_poll.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2021 Dan +# Copyright (C) 2017-present Dan # # This file is part of Pyrogram. # @@ -18,14 +18,14 @@ from typing import Union +import pyrogram from pyrogram import raw from pyrogram import types -from pyrogram.scaffold import Scaffold -class StopPoll(Scaffold): +class StopPoll: async def stop_poll( - self, + self: "pyrogram.Client", chat_id: Union[int, str], message_id: int, reply_markup: "types.InlineKeyboardMarkup" = None @@ -34,6 +34,8 @@ async def stop_poll( Stopped polls can't be reopened and nobody will be able to vote in it anymore. + .. include:: /_includes/usable-by/users-bots.rst + Parameters: chat_id (``int`` | ``str``): Unique identifier (int) or username (str) of the target chat. @@ -52,11 +54,11 @@ async def stop_poll( Example: .. code-block:: python - app.stop_poll(chat_id, message_id) + await app.stop_poll(chat_id, message_id) """ poll = (await self.get_messages(chat_id, message_id)).poll - r = await self.send( + r = await self.invoke( raw.functions.messages.EditMessage( peer=await self.resolve_peer(chat_id), id=message_id, diff --git a/pyrogram/methods/messages/stream_media.py b/pyrogram/methods/messages/stream_media.py new file mode 100644 index 0000000000..b432badb4c --- /dev/null +++ b/pyrogram/methods/messages/stream_media.py @@ -0,0 +1,106 @@ +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-present Dan +# +# This file is part of Pyrogram. +# +# Pyrogram is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License as published +# by the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Pyrogram is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with Pyrogram. If not, see . + +import math +from typing import Union, Optional, BinaryIO + +import pyrogram +from pyrogram import types +from pyrogram.file_id import FileId + + +class StreamMedia: + async def stream_media( + self: "pyrogram.Client", + message: Union["types.Message", str], + limit: int = 0, + offset: int = 0 + ) -> Optional[Union[str, BinaryIO]]: + """Stream the media from a message chunk by chunk. + + You can use this method to partially download a file into memory or to selectively download chunks of file. + The chunk maximum size is 1 MiB (1024 * 1024 bytes). + + .. include:: /_includes/usable-by/users-bots.rst + + Parameters: + message (:obj:`~pyrogram.types.Message` | ``str``): + Pass a Message containing the media, the media itself (message.audio, message.video, ...) or a file id + as string. + + limit (``int``, *optional*): + Limit the amount of chunks to stream. + Defaults to 0 (stream the whole media). + + offset (``int``, *optional*): + How many chunks to skip before starting to stream. + Defaults to 0 (start from the beginning). + + Returns: + ``Generator``: A generator yielding bytes chunk by chunk + + Example: + .. code-block:: python + + # Stream the whole media + async for chunk in app.stream_media(message): + print(len(chunk)) + + # Stream the first 3 chunks only + async for chunk in app.stream_media(message, limit=3): + print(len(chunk)) + + # Stream the rest of the media by skipping the first 3 chunks + async for chunk in app.stream_media(message, offset=3): + print(len(chunk)) + + # Stream the last 3 chunks only (negative offset) + async for chunk in app.stream_media(message, offset=-3): + print(len(chunk)) + """ + available_media = ("audio", "document", "photo", "sticker", "animation", "video", "voice", "video_note", + "new_chat_photo") + + if isinstance(message, types.Message): + for kind in available_media: + media = getattr(message, kind, None) + + if media is not None: + break + else: + raise ValueError("This message doesn't contain any downloadable media") + else: + media = message + + if isinstance(media, str): + file_id_str = media + else: + file_id_str = media.file_id + + file_id_obj = FileId.decode(file_id_str) + file_size = getattr(media, "file_size", 0) + + if offset < 0: + if file_size == 0: + raise ValueError("Negative offsets are not supported for file ids, pass a Message object instead") + + chunks = math.ceil(file_size / 1024 / 1024) + offset += chunks + + async for chunk in self.get_file(file_id_obj, file_size, limit, offset): + yield chunk diff --git a/pyrogram/methods/messages/vote_poll.py b/pyrogram/methods/messages/vote_poll.py index 0c18bc215a..620fac5a15 100644 --- a/pyrogram/methods/messages/vote_poll.py +++ b/pyrogram/methods/messages/vote_poll.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2021 Dan +# Copyright (C) 2017-present Dan # # This file is part of Pyrogram. # @@ -18,20 +18,22 @@ from typing import Union, List +import pyrogram from pyrogram import raw from pyrogram import types -from pyrogram.scaffold import Scaffold -class VotePoll(Scaffold): +class VotePoll: async def vote_poll( - self, + self: "pyrogram.Client", chat_id: Union[int, str], message_id: id, options: Union[int, List[int]] ) -> "types.Poll": """Vote a poll. + .. include:: /_includes/usable-by/users.rst + Parameters: chat_id (``int`` | ``str``): Unique identifier (int) or username (str) of the target chat. @@ -50,13 +52,13 @@ async def vote_poll( Example: .. code-block:: python - app.vote_poll(chat_id, message_id, 6) + await app.vote_poll(chat_id, message_id, 6) """ poll = (await self.get_messages(chat_id, message_id)).poll options = [options] if not isinstance(options, list) else options - r = await self.send( + r = await self.invoke( raw.functions.messages.SendVote( peer=await self.resolve_peer(chat_id), msg_id=message_id, diff --git a/pyrogram/methods/password/__init__.py b/pyrogram/methods/password/__init__.py index 30f3950c78..e8d42926a4 100644 --- a/pyrogram/methods/password/__init__.py +++ b/pyrogram/methods/password/__init__.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2021 Dan +# Copyright (C) 2017-present Dan # # This file is part of Pyrogram. # diff --git a/pyrogram/methods/password/change_cloud_password.py b/pyrogram/methods/password/change_cloud_password.py index 1f1f4e0364..29540d653b 100644 --- a/pyrogram/methods/password/change_cloud_password.py +++ b/pyrogram/methods/password/change_cloud_password.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2021 Dan +# Copyright (C) 2017-present Dan # # This file is part of Pyrogram. # @@ -18,20 +18,22 @@ import os +import pyrogram from pyrogram import raw -from pyrogram.scaffold import Scaffold from pyrogram.utils import compute_password_hash, compute_password_check, btoi, itob -class ChangeCloudPassword(Scaffold): +class ChangeCloudPassword: async def change_cloud_password( - self, + self: "pyrogram.Client", current_password: str, new_password: str, new_hint: str = "" ) -> bool: """Change your Two-Step Verification password (Cloud Password) with a new one. + .. include:: /_includes/usable-by/users.rst + Parameters: current_password (``str``): Your current password. @@ -52,12 +54,12 @@ async def change_cloud_password( .. code-block:: python # Change password only - app.change_cloud_password("current_password", "new_password") + await app.change_cloud_password("current_password", "new_password") # Change password and hint - app.change_cloud_password("current_password", "new_password", new_hint="hint") + await app.change_cloud_password("current_password", "new_password", new_hint="hint") """ - r = await self.send(raw.functions.account.GetPassword()) + r = await self.invoke(raw.functions.account.GetPassword()) if not r.has_password: raise ValueError("There is no cloud password to change") @@ -66,7 +68,7 @@ async def change_cloud_password( new_hash = btoi(compute_password_hash(r.new_algo, new_password)) new_hash = itob(pow(r.new_algo.g, new_hash, btoi(r.new_algo.p))) - await self.send( + await self.invoke( raw.functions.account.UpdatePasswordSettings( password=compute_password_check(r, current_password), new_settings=raw.types.account.PasswordInputSettings( diff --git a/pyrogram/methods/password/enable_cloud_password.py b/pyrogram/methods/password/enable_cloud_password.py index ad12cdc440..8f5e59c570 100644 --- a/pyrogram/methods/password/enable_cloud_password.py +++ b/pyrogram/methods/password/enable_cloud_password.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2021 Dan +# Copyright (C) 2017-present Dan # # This file is part of Pyrogram. # @@ -18,14 +18,14 @@ import os +import pyrogram from pyrogram import raw -from pyrogram.scaffold import Scaffold from pyrogram.utils import compute_password_hash, btoi, itob -class EnableCloudPassword(Scaffold): +class EnableCloudPassword: async def enable_cloud_password( - self, + self: "pyrogram.Client", password: str, hint: str = "", email: str = None @@ -34,6 +34,8 @@ async def enable_cloud_password( This password will be asked when you log-in on a new device in addition to the SMS code. + .. include:: /_includes/usable-by/users.rst + Parameters: password (``str``): Your password. @@ -54,15 +56,15 @@ async def enable_cloud_password( .. code-block:: python # Enable password without hint and email - app.enable_cloud_password("password") + await app.enable_cloud_password("password") # Enable password with hint and without email - app.enable_cloud_password("password", hint="hint") + await app.enable_cloud_password("password", hint="hint") # Enable password with hint and email - app.enable_cloud_password("password", hint="hint", email="user@email.com") + await app.enable_cloud_password("password", hint="hint", email="user@email.com") """ - r = await self.send(raw.functions.account.GetPassword()) + r = await self.invoke(raw.functions.account.GetPassword()) if r.has_password: raise ValueError("There is already a cloud password enabled") @@ -71,7 +73,7 @@ async def enable_cloud_password( new_hash = btoi(compute_password_hash(r.new_algo, password)) new_hash = itob(pow(r.new_algo.g, new_hash, btoi(r.new_algo.p))) - await self.send( + await self.invoke( raw.functions.account.UpdatePasswordSettings( password=raw.types.InputCheckPasswordEmpty(), new_settings=raw.types.account.PasswordInputSettings( diff --git a/pyrogram/methods/password/remove_cloud_password.py b/pyrogram/methods/password/remove_cloud_password.py index 3d792835eb..0ec16e12cd 100644 --- a/pyrogram/methods/password/remove_cloud_password.py +++ b/pyrogram/methods/password/remove_cloud_password.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2021 Dan +# Copyright (C) 2017-present Dan # # This file is part of Pyrogram. # @@ -16,18 +16,20 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . +import pyrogram from pyrogram import raw -from pyrogram.scaffold import Scaffold from pyrogram.utils import compute_password_check -class RemoveCloudPassword(Scaffold): +class RemoveCloudPassword: async def remove_cloud_password( - self, + self: "pyrogram.Client", password: str ) -> bool: """Turn off the Two-Step Verification security feature (Cloud Password) on your account. + .. include:: /_includes/usable-by/users.rst + Parameters: password (``str``): Your current password. @@ -41,14 +43,14 @@ async def remove_cloud_password( Example: .. code-block:: python - app.remove_cloud_password("password") + await app.remove_cloud_password("password") """ - r = await self.send(raw.functions.account.GetPassword()) + r = await self.invoke(raw.functions.account.GetPassword()) if not r.has_password: raise ValueError("There is no cloud password to remove") - await self.send( + await self.invoke( raw.functions.account.UpdatePasswordSettings( password=compute_password_check(r, password), new_settings=raw.types.account.PasswordInputSettings( diff --git a/pyrogram/methods/users/__init__.py b/pyrogram/methods/users/__init__.py index d9e61e694c..31fda1dc3a 100644 --- a/pyrogram/methods/users/__init__.py +++ b/pyrogram/methods/users/__init__.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2021 Dan +# Copyright (C) 2017-present Dan # # This file is part of Pyrogram. # @@ -18,30 +18,32 @@ from .block_user import BlockUser from .delete_profile_photos import DeleteProfilePhotos +from .get_chat_photos import GetChatPhotos +from .get_chat_photos_count import GetChatPhotosCount from .get_common_chats import GetCommonChats +from .get_default_emoji_statuses import GetDefaultEmojiStatuses from .get_me import GetMe -from .get_profile_photos import GetProfilePhotos -from .get_profile_photos_count import GetProfilePhotosCount from .get_users import GetUsers -from .iter_profile_photos import IterProfilePhotos +from .set_emoji_status import SetEmojiStatus from .set_profile_photo import SetProfilePhoto +from .set_username import SetUsername from .unblock_user import UnblockUser from .update_profile import UpdateProfile -from .update_username import UpdateUsername class Users( BlockUser, GetCommonChats, - GetProfilePhotos, + GetChatPhotos, SetProfilePhoto, DeleteProfilePhotos, GetUsers, GetMe, - UpdateUsername, - GetProfilePhotosCount, - IterProfilePhotos, + SetUsername, + GetChatPhotosCount, UnblockUser, UpdateProfile, + GetDefaultEmojiStatuses, + SetEmojiStatus ): pass diff --git a/pyrogram/methods/users/block_user.py b/pyrogram/methods/users/block_user.py index 6d7c8cdadb..25cd0ce84e 100644 --- a/pyrogram/methods/users/block_user.py +++ b/pyrogram/methods/users/block_user.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2021 Dan +# Copyright (C) 2017-present Dan # # This file is part of Pyrogram. # @@ -18,17 +18,19 @@ from typing import Union +import pyrogram from pyrogram import raw -from pyrogram.scaffold import Scaffold -class BlockUser(Scaffold): +class BlockUser: async def block_user( - self, + self: "pyrogram.Client", user_id: Union[int, str] ) -> bool: """Block a user. + .. include:: /_includes/usable-by/users.rst + Parameters: user_id (``int`` | ``str``):: Unique identifier (int) or username (str) of the target user. @@ -41,10 +43,10 @@ async def block_user( Example: .. code-block:: python - app.block_user(user_id) + await app.block_user(user_id) """ return bool( - await self.send( + await self.invoke( raw.functions.contacts.Block( id=await self.resolve_peer(user_id) ) diff --git a/pyrogram/methods/users/delete_profile_photos.py b/pyrogram/methods/users/delete_profile_photos.py index 7f2c3a729e..2b52d02f59 100644 --- a/pyrogram/methods/users/delete_profile_photos.py +++ b/pyrogram/methods/users/delete_profile_photos.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2021 Dan +# Copyright (C) 2017-present Dan # # This file is part of Pyrogram. # @@ -18,19 +18,21 @@ from typing import List, Union +import pyrogram from pyrogram import raw from pyrogram import utils from pyrogram.file_id import FileType -from pyrogram.scaffold import Scaffold -class DeleteProfilePhotos(Scaffold): +class DeleteProfilePhotos: async def delete_profile_photos( - self, + self: "pyrogram.Client", photo_ids: Union[str, List[str]] ) -> bool: """Delete your own profile photos. + .. include:: /_includes/usable-by/users.rst + Parameters: photo_ids (``str`` | List of ``str``): A single :obj:`~pyrogram.types.Photo` id as string or multiple ids as list of strings for deleting @@ -43,18 +45,18 @@ async def delete_profile_photos( .. code-block:: python # Get the photos to be deleted - photos = app.get_profile_photos("me") + photos = [p async for p in app.get_chat_photos("me")] # Delete one photo - app.delete_profile_photos(photos[0].file_id) + await app.delete_profile_photos(photos[0].file_id) # Delete the rest of the photos - app.delete_profile_photos([p.file_id for p in photos[1:]]) + await app.delete_profile_photos([p.file_id for p in photos[1:]]) """ photo_ids = photo_ids if isinstance(photo_ids, list) else [photo_ids] input_photos = [utils.get_input_media_from_file_id(i, FileType.PHOTO).id for i in photo_ids] - return bool(await self.send( + return bool(await self.invoke( raw.functions.photos.DeletePhotos( id=input_photos ) diff --git a/pyrogram/methods/users/get_profile_photos.py b/pyrogram/methods/users/get_chat_photos.py similarity index 59% rename from pyrogram/methods/users/get_profile_photos.py rename to pyrogram/methods/users/get_chat_photos.py index 0a4686ca2e..d22c68dcd8 100644 --- a/pyrogram/methods/users/get_profile_photos.py +++ b/pyrogram/methods/users/get_chat_photos.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2021 Dan +# Copyright (C) 2017-present Dan # # This file is part of Pyrogram. # @@ -16,22 +16,21 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . -from typing import Union, List +from typing import Union, AsyncGenerator, Optional -from pyrogram import raw -from pyrogram import types -from pyrogram import utils -from pyrogram.scaffold import Scaffold +import pyrogram +from pyrogram import types, raw, utils -class GetProfilePhotos(Scaffold): - async def get_profile_photos( - self, +class GetChatPhotos: + async def get_chat_photos( + self: "pyrogram.Client", chat_id: Union[int, str], - offset: int = 0, - limit: int = 100 - ) -> List["types.Photo"]: - """Get a list of profile pictures for a user or a chat. + limit: int = 0, + ) -> Optional[AsyncGenerator["types.Photo", None]]: + """Get a chat or a user profile photos sequentially. + + .. include:: /_includes/usable-by/users-bots.rst Parameters: chat_id (``int`` | ``str``): @@ -39,33 +38,23 @@ async def get_profile_photos( For your personal cloud (Saved Messages) you can simply use "me" or "self". For a contact that exists in your Telegram address book you can use his phone number (str). - offset (``int``, *optional*): - Sequential number of the first photo to be returned. - By default, all photos are returned. - limit (``int``, *optional*): - Limits the number of photos to be retrieved. - Values between 1—100 are accepted. Defaults to 100. + Limits the number of profile photos to be retrieved. + By default, no limit is applied and all profile photos are returned. Returns: - List of :obj:`~pyrogram.types.Photo`: On success, a list of profile photos is returned. + ``Generator``: A generator yielding :obj:`~pyrogram.types.Photo` objects. Example: .. code-block:: python - # Get the first 100 profile photos of a user - app.get_profile_photos("haskell") - - # Get only the first profile photo of a user - app.get_profile_photos("haskell", limit=1) - - # Get 3 profile photos of a user, skip the first 5 - app.get_profile_photos("haskell", limit=3, offset=5) + async for photo in app.get_chat_photos("me"): + print(photo) """ peer_id = await self.resolve_peer(chat_id) if isinstance(peer_id, raw.types.InputPeerChannel): - r = await self.send( + r = await self.invoke( raw.functions.channels.GetFullChannel( channel=peer_id ) @@ -75,7 +64,7 @@ async def get_profile_photos( r = await utils.parse_messages( self, - await self.send( + await self.invoke( raw.functions.messages.Search( peer=peer_id, q="", @@ -105,15 +94,42 @@ async def get_profile_photos( else: photos = [] - return types.List(photos[offset:limit]) + current = 0 + + for photo in photos: + yield photo + + current += 1 + + if current >= limit: + return else: - r = await self.send( - raw.functions.photos.GetUserPhotos( - user_id=peer_id, - offset=offset, - max_id=0, - limit=limit + current = 0 + total = limit or (1 << 31) + limit = min(100, total) + offset = 0 + + while True: + r = await self.invoke( + raw.functions.photos.GetUserPhotos( + user_id=peer_id, + offset=offset, + max_id=0, + limit=limit + ) ) - ) - return types.List(types.Photo._parse(self, photo) for photo in r.photos) + photos = [types.Photo._parse(self, photo) for photo in r.photos] + + if not photos: + return + + offset += len(photos) + + for photo in photos: + yield photo + + current += 1 + + if current >= total: + return diff --git a/pyrogram/methods/users/get_profile_photos_count.py b/pyrogram/methods/users/get_chat_photos_count.py similarity index 81% rename from pyrogram/methods/users/get_profile_photos_count.py rename to pyrogram/methods/users/get_chat_photos_count.py index 10aca0c51f..d4cf1594f1 100644 --- a/pyrogram/methods/users/get_profile_photos_count.py +++ b/pyrogram/methods/users/get_chat_photos_count.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2021 Dan +# Copyright (C) 2017-present Dan # # This file is part of Pyrogram. # @@ -18,13 +18,18 @@ from typing import Union +import pyrogram from pyrogram import raw -from pyrogram.scaffold import Scaffold -class GetProfilePhotosCount(Scaffold): - async def get_profile_photos_count(self, chat_id: Union[int, str]) -> int: - """Get the total count of profile pictures for a user. +class GetChatPhotosCount: + async def get_chat_photos_count( + self: "pyrogram.Client", + chat_id: Union[int, str] + ) -> int: + """Get the total count of photos for a chat. + + .. include:: /_includes/usable-by/users-bots.rst Parameters: chat_id (``int`` | ``str``): @@ -38,14 +43,14 @@ async def get_profile_photos_count(self, chat_id: Union[int, str]) -> int: Example: .. code-block:: python - count = app.get_profile_photos_count("haskell") + count = await app.get_chat_photos_count("me") print(count) """ peer_id = await self.resolve_peer(chat_id) if isinstance(peer_id, raw.types.InputPeerChannel): - r = await self.send( + r = await self.invoke( raw.functions.messages.GetSearchCounters( peer=peer_id, filters=[raw.types.InputMessagesFilterChatPhotos()], @@ -54,7 +59,7 @@ async def get_profile_photos_count(self, chat_id: Union[int, str]) -> int: return r[0].count else: - r = await self.send( + r = await self.invoke( raw.functions.photos.GetUserPhotos( user_id=peer_id, offset=0, diff --git a/pyrogram/methods/users/get_common_chats.py b/pyrogram/methods/users/get_common_chats.py index 45ab2fe71f..31e2bac38e 100644 --- a/pyrogram/methods/users/get_common_chats.py +++ b/pyrogram/methods/users/get_common_chats.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2021 Dan +# Copyright (C) 2017-present Dan # # This file is part of Pyrogram. # @@ -16,17 +16,22 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . -from typing import Union +from typing import Union, List +import pyrogram from pyrogram import raw from pyrogram import types -from pyrogram.scaffold import Scaffold -class GetCommonChats(Scaffold): - async def get_common_chats(self, user_id: Union[int, str]) -> list: +class GetCommonChats: + async def get_common_chats( + self: "pyrogram.Client", + user_id: Union[int, str] + ) -> List["types.Chat"]: """Get the common chats you have with a user. + .. include:: /_includes/usable-by/users.rst + Parameters: user_id (``int`` | ``str``): Unique identifier (int) or username (str) of the target chat. @@ -42,14 +47,14 @@ async def get_common_chats(self, user_id: Union[int, str]) -> list: Example: .. code-block:: python - common = app.get_common_chats("haskell") + common = await app.get_common_chats(user_id) print(common) """ peer = await self.resolve_peer(user_id) if isinstance(peer, raw.types.InputPeerUser): - r = await self.send( + r = await self.invoke( raw.functions.messages.GetCommonChats( user_id=peer, max_id=0, diff --git a/pyrogram/methods/users/get_default_emoji_statuses.py b/pyrogram/methods/users/get_default_emoji_statuses.py new file mode 100644 index 0000000000..cd3768b9a4 --- /dev/null +++ b/pyrogram/methods/users/get_default_emoji_statuses.py @@ -0,0 +1,47 @@ +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-present Dan +# +# This file is part of Pyrogram. +# +# Pyrogram is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License as published +# by the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Pyrogram is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with Pyrogram. If not, see . + +from typing import List + +import pyrogram +from pyrogram import raw +from pyrogram import types + + +class GetDefaultEmojiStatuses: + async def get_default_emoji_statuses( + self: "pyrogram.Client", + ) -> List["types.EmojiStatus"]: + """Get the default emoji statuses. + + .. include:: /_includes/usable-by/users-bots.rst + + Returns: + List of :obj:`~pyrogram.types.EmojiStatus`: On success, a list of emoji statuses is returned. + + Example: + .. code-block:: python + + default_emoji_statuses = await app.get_default_emoji_statuses() + print(default_emoji_statuses) + """ + r = await self.invoke( + raw.functions.account.GetDefaultEmojiStatuses(hash=0) + ) + + return types.List([types.EmojiStatus._parse(self, i) for i in r.statuses]) diff --git a/pyrogram/methods/users/get_me.py b/pyrogram/methods/users/get_me.py index 3fab57e34d..a8df3ae9f3 100644 --- a/pyrogram/methods/users/get_me.py +++ b/pyrogram/methods/users/get_me.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2021 Dan +# Copyright (C) 2017-present Dan # # This file is part of Pyrogram. # @@ -16,25 +16,29 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . +import pyrogram from pyrogram import raw from pyrogram import types -from pyrogram.scaffold import Scaffold -class GetMe(Scaffold): - async def get_me(self) -> "types.User": +class GetMe: + async def get_me( + self: "pyrogram.Client" + ) -> "types.User": """Get your own user identity. + .. include:: /_includes/usable-by/users-bots.rst + Returns: :obj:`~pyrogram.types.User`: Information about the own logged in user/bot. Example: .. code-block:: python - me = app.get_me() + me = await app.get_me() print(me) """ - r = await self.send( + r = await self.invoke( raw.functions.users.GetFullUser( id=raw.types.InputUserSelf() ) diff --git a/pyrogram/methods/users/get_users.py b/pyrogram/methods/users/get_users.py index 4da15e9b76..39821a8c5d 100644 --- a/pyrogram/methods/users/get_users.py +++ b/pyrogram/methods/users/get_users.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2021 Dan +# Copyright (C) 2017-present Dan # # This file is part of Pyrogram. # @@ -17,46 +17,47 @@ # along with Pyrogram. If not, see . import asyncio -from typing import Iterable, Union, List +from typing import Union, List, Iterable +import pyrogram from pyrogram import raw from pyrogram import types -from pyrogram.scaffold import Scaffold -class GetUsers(Scaffold): +class GetUsers: async def get_users( - self, - user_ids: Union[Iterable[Union[int, str]], int, str] + self: "pyrogram.Client", + user_ids: Union[int, str, Iterable[Union[int, str]]] ) -> Union["types.User", List["types.User"]]: """Get information about a user. You can retrieve up to 200 users at once. + .. include:: /_includes/usable-by/users-bots.rst + Parameters: - user_ids (``iterable``): + user_ids (``int`` | ``str`` | Iterable of ``int`` or ``str``): A list of User identifiers (id or username) or a single user id/username. For a contact that exists in your Telegram address book you can use his phone number (str). - Iterators and Generators are also accepted. Returns: - :obj:`~pyrogram.types.User` | List of :obj:`~pyrogram.types.User`: In case *user_ids* was an integer or - string the single requested user is returned, otherwise, in case *user_ids* was an iterable a list of users - is returned, even if the iterable contained one item only. + :obj:`~pyrogram.types.User` | List of :obj:`~pyrogram.types.User`: In case *user_ids* was not a list, + a single user is returned, otherwise a list of users is returned. Example: .. code-block:: python # Get information about one user - app.get_users("haskell") + await app.get_users("me") # Get information about multiple users at once - app.get_users([user1, user2, user3]) + await app.get_users([user_id1, user_id2, user_id3]) """ + is_iterable = not isinstance(user_ids, (int, str)) user_ids = list(user_ids) if is_iterable else [user_ids] user_ids = await asyncio.gather(*[self.resolve_peer(i) for i in user_ids]) - r = await self.send( + r = await self.invoke( raw.functions.users.GetUsers( id=user_ids ) diff --git a/pyrogram/methods/users/iter_profile_photos.py b/pyrogram/methods/users/iter_profile_photos.py deleted file mode 100644 index 50af4c442d..0000000000 --- a/pyrogram/methods/users/iter_profile_photos.py +++ /dev/null @@ -1,82 +0,0 @@ -# Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2021 Dan -# -# This file is part of Pyrogram. -# -# Pyrogram is free software: you can redistribute it and/or modify -# it under the terms of the GNU Lesser General Public License as published -# by the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# Pyrogram is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public License -# along with Pyrogram. If not, see . - -from typing import Union, AsyncGenerator, Optional - -from pyrogram import types -from pyrogram.scaffold import Scaffold - - -class IterProfilePhotos(Scaffold): - async def iter_profile_photos( - self, - chat_id: Union[int, str], - offset: int = 0, - limit: int = 0, - ) -> Optional[AsyncGenerator["types.Photo", None]]: - """Iterate through a chat or a user profile photos sequentially. - - This convenience method does the same as repeatedly calling :meth:`~pyrogram.Client.get_profile_photos` in a - loop, thus saving you from the hassle of setting up boilerplate code. It is useful for getting all the profile - photos with a single call. - - Parameters: - chat_id (``int`` | ``str``): - Unique identifier (int) or username (str) of the target chat. - For your personal cloud (Saved Messages) you can simply use "me" or "self". - For a contact that exists in your Telegram address book you can use his phone number (str). - - limit (``int``, *optional*): - Limits the number of profile photos to be retrieved. - By default, no limit is applied and all profile photos are returned. - - offset (``int``, *optional*): - Sequential number of the first profile photo to be returned. - - Returns: - ``Generator``: A generator yielding :obj:`~pyrogram.types.Photo` objects. - - Example: - .. code-block:: python - - for photo in app.iter_profile_photos("haskell"): - print(photo.file_id) - """ - current = 0 - total = limit or (1 << 31) - limit = min(100, total) - - while True: - photos = await self.get_profile_photos( - chat_id=chat_id, - offset=offset, - limit=limit - ) - - if not photos: - return - - offset += len(photos) - - for photo in photos: - yield photo - - current += 1 - - if current >= total: - return diff --git a/pyrogram/methods/users/set_emoji_status.py b/pyrogram/methods/users/set_emoji_status.py new file mode 100644 index 0000000000..2d8c77cc02 --- /dev/null +++ b/pyrogram/methods/users/set_emoji_status.py @@ -0,0 +1,58 @@ +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-present Dan +# +# This file is part of Pyrogram. +# +# Pyrogram is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License as published +# by the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Pyrogram is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with Pyrogram. If not, see . + +from typing import Optional + +import pyrogram +from pyrogram import raw, types + + +class SetEmojiStatus: + async def set_emoji_status( + self: "pyrogram.Client", + emoji_status: Optional["types.EmojiStatus"] = None + ) -> bool: + """Set the emoji status. + + .. include:: /_includes/usable-by/users.rst + + Parameters: + emoji_status (:obj:`~pyrogram.types.EmojiStatus`, *optional*): + The emoji status to set. None to remove. + + Returns: + ``bool``: On success, True is returned. + + Example: + .. code-block:: python + + from pyrogram import types + + await app.set_emoji_status(types.EmojiStatus(custom_emoji_id=1234567890987654321)) + """ + await self.invoke( + raw.functions.account.UpdateEmojiStatus( + emoji_status=( + emoji_status.write() + if emoji_status + else raw.types.EmojiStatusEmpty() + ) + ) + ) + + return True diff --git a/pyrogram/methods/users/set_profile_photo.py b/pyrogram/methods/users/set_profile_photo.py index 822500edc5..e08e669c4e 100644 --- a/pyrogram/methods/users/set_profile_photo.py +++ b/pyrogram/methods/users/set_profile_photo.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2021 Dan +# Copyright (C) 2017-present Dan # # This file is part of Pyrogram. # @@ -18,13 +18,13 @@ from typing import Union, BinaryIO +import pyrogram from pyrogram import raw -from pyrogram.scaffold import Scaffold -class SetProfilePhoto(Scaffold): +class SetProfilePhoto: async def set_profile_photo( - self, + self: "pyrogram.Client", *, photo: Union[str, BinaryIO] = None, video: Union[str, BinaryIO] = None @@ -39,6 +39,8 @@ async def set_profile_photo( This method only works for Users. Bots profile photos must be set using BotFather. + .. include:: /_includes/usable-by/users.rst + Parameters: photo (``str`` | ``BinaryIO``, *optional*): Profile photo to set. @@ -57,14 +59,14 @@ async def set_profile_photo( .. code-block:: python # Set a new profile photo - app.set_profile_photo(photo="new_photo.jpg") + await app.set_profile_photo(photo="new_photo.jpg") # Set a new profile video - app.set_profile_photo(video="new_video.mp4") + await app.set_profile_photo(video="new_video.mp4") """ return bool( - await self.send( + await self.invoke( raw.functions.photos.UploadProfilePhoto( file=await self.save_file(photo), video=await self.save_file(video) diff --git a/pyrogram/methods/users/update_username.py b/pyrogram/methods/users/set_username.py similarity index 74% rename from pyrogram/methods/users/update_username.py rename to pyrogram/methods/users/set_username.py index 865f3ed9eb..d336628eec 100644 --- a/pyrogram/methods/users/update_username.py +++ b/pyrogram/methods/users/set_username.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2021 Dan +# Copyright (C) 2017-present Dan # # This file is part of Pyrogram. # @@ -18,20 +18,22 @@ from typing import Optional +import pyrogram from pyrogram import raw -from pyrogram.scaffold import Scaffold -class UpdateUsername(Scaffold): - async def update_username( - self, +class SetUsername: + async def set_username( + self: "pyrogram.Client", username: Optional[str] ) -> bool: - """Update your own username. + """Set your own username. This method only works for users, not bots. Bot usernames must be changed via Bot Support or by recreating - them from scratch using BotFather. To update a channel or supergroup username you can use - :meth:`~pyrogram.Client.update_chat_username`. + them from scratch using BotFather. To set a channel or supergroup username you can use + :meth:`~pyrogram.Client.set_chat_username`. + + .. include:: /_includes/usable-by/users.rst Parameters: username (``str`` | ``None``): @@ -43,11 +45,11 @@ async def update_username( Example: .. code-block:: python - app.update_username("new_username") + await app.set_username("new_username") """ return bool( - await self.send( + await self.invoke( raw.functions.account.UpdateUsername( username=username or "" ) diff --git a/pyrogram/methods/users/unblock_user.py b/pyrogram/methods/users/unblock_user.py index 2492aeba72..db4fdddcff 100644 --- a/pyrogram/methods/users/unblock_user.py +++ b/pyrogram/methods/users/unblock_user.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2021 Dan +# Copyright (C) 2017-present Dan # # This file is part of Pyrogram. # @@ -18,17 +18,19 @@ from typing import Union +import pyrogram from pyrogram import raw -from pyrogram.scaffold import Scaffold -class UnblockUser(Scaffold): +class UnblockUser: async def unblock_user( - self, + self: "pyrogram.Client", user_id: Union[int, str] ) -> bool: """Unblock a user. + .. include:: /_includes/usable-by/users.rst + Parameters: user_id (``int`` | ``str``):: Unique identifier (int) or username (str) of the target user. @@ -41,10 +43,10 @@ async def unblock_user( Example: .. code-block:: python - app.unblock_user(user_id) + await app.unblock_user(user_id) """ return bool( - await self.send( + await self.invoke( raw.functions.contacts.Unblock( id=await self.resolve_peer(user_id) ) diff --git a/pyrogram/methods/users/update_profile.py b/pyrogram/methods/users/update_profile.py index a505c70877..6c10d16c9d 100644 --- a/pyrogram/methods/users/update_profile.py +++ b/pyrogram/methods/users/update_profile.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2021 Dan +# Copyright (C) 2017-present Dan # # This file is part of Pyrogram. # @@ -16,13 +16,13 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . +import pyrogram from pyrogram import raw -from pyrogram.scaffold import Scaffold -class UpdateProfile(Scaffold): +class UpdateProfile: async def update_profile( - self, + self: "pyrogram.Client", first_name: str = None, last_name: str = None, bio: str = None @@ -31,6 +31,8 @@ async def update_profile( You can omit the parameters you don't want to change. + .. include:: /_includes/usable-by/users.rst + Parameters: first_name (``str``, *optional*): The new first name. @@ -50,17 +52,17 @@ async def update_profile( .. code-block:: python # Update your first name only - app.update_profile(first_name="Pyrogram") + await app.update_profile(first_name="Pyrogram") # Update first name and bio - app.update_profile(first_name="Pyrogram", bio="https://docs.pyrogram.org/") + await app.update_profile(first_name="Pyrogram", bio="https://docs.pyrogram.org/") # Remove the last name - app.update_profile(last_name="") + await app.update_profile(last_name="") """ return bool( - await self.send( + await self.invoke( raw.functions.account.UpdateProfile( first_name=first_name, last_name=last_name, diff --git a/pyrogram/methods/utilities/__init__.py b/pyrogram/methods/utilities/__init__.py index 40034e6f0d..80a5f7419d 100644 --- a/pyrogram/methods/utilities/__init__.py +++ b/pyrogram/methods/utilities/__init__.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2021 Dan +# Copyright (C) 2017-present Dan # # This file is part of Pyrogram. # diff --git a/pyrogram/methods/utilities/add_handler.py b/pyrogram/methods/utilities/add_handler.py index c2f852694c..136647d981 100644 --- a/pyrogram/methods/utilities/add_handler.py +++ b/pyrogram/methods/utilities/add_handler.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2021 Dan +# Copyright (C) 2017-present Dan # # This file is part of Pyrogram. # @@ -16,13 +16,17 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . +import pyrogram from pyrogram.handlers import DisconnectHandler from pyrogram.handlers.handler import Handler -from pyrogram.scaffold import Scaffold -class AddHandler(Scaffold): - def add_handler(self, handler: "Handler", group: int = 0): +class AddHandler: + def add_handler( + self: "pyrogram.Client", + handler: "Handler", + group: int = 0 + ): """Register an update handler. You can register multiple handlers, but at most one handler within a group will be used for a single update. @@ -42,17 +46,16 @@ def add_handler(self, handler: "Handler", group: int = 0): Example: .. code-block:: python - :emphasize-lines: 8 from pyrogram import Client from pyrogram.handlers import MessageHandler - def dump(client, message): + async def hello(client, message): print(message) app = Client("my_account") - app.add_handler(MessageHandler(dump)) + app.add_handler(MessageHandler(hello)) app.run() """ diff --git a/pyrogram/methods/utilities/compose.py b/pyrogram/methods/utilities/compose.py new file mode 100644 index 0000000000..e05773b8e1 --- /dev/null +++ b/pyrogram/methods/utilities/compose.py @@ -0,0 +1,78 @@ +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-present Dan +# +# This file is part of Pyrogram. +# +# Pyrogram is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License as published +# by the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Pyrogram is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with Pyrogram. If not, see . + +import asyncio +from typing import List + +import pyrogram +from .idle import idle + + +async def compose( + clients: List["pyrogram.Client"], + sequential: bool = False +): + """Run multiple clients at once. + + This method can be used to run multiple clients at once and can be found directly in the ``pyrogram`` package. + + If you want to run a single client, you can use Client's bound method :meth:`~pyrogram.Client.run`. + + Parameters: + clients (List of :obj:`~pyrogram.Client`): + A list of client objects to run. + + sequential (``bool``, *optional*): + Pass True to run clients sequentially. + Defaults to False (run clients concurrently) + + Example: + .. code-block:: python + + import asyncio + from pyrogram import Client, compose + + + async def main(): + apps = [ + Client("account1"), + Client("account2"), + Client("account3") + ] + + ... + + await compose(apps) + + + asyncio.run(main()) + + """ + if sequential: + for c in clients: + await c.start() + else: + await asyncio.gather(*[c.start() for c in clients]) + + await idle() + + if sequential: + for c in clients: + await c.stop() + else: + await asyncio.gather(*[c.stop() for c in clients]) diff --git a/pyrogram/methods/utilities/export_session_string.py b/pyrogram/methods/utilities/export_session_string.py index 84aceb46bb..6529484d9b 100644 --- a/pyrogram/methods/utilities/export_session_string.py +++ b/pyrogram/methods/utilities/export_session_string.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2021 Dan +# Copyright (C) 2017-present Dan # # This file is part of Pyrogram. # @@ -16,11 +16,13 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . -from pyrogram.scaffold import Scaffold +import pyrogram -class ExportSessionString(Scaffold): - async def export_session_string(self): +class ExportSessionString: + async def export_session_string( + self: "pyrogram.Client" + ): """Export the current authorized session as a serialized string. Session strings are useful for storing in-memory authorized sessions in a portable, serialized string. @@ -32,13 +34,7 @@ async def export_session_string(self): Example: .. code-block:: python - :emphasize-lines: 6 - from pyrogram import Client - - app = Client("my_account") - - with app: - print(app.export_session_string()) + s = await app.export_session_string() """ return await self.storage.export_session_string() diff --git a/pyrogram/methods/utilities/idle.py b/pyrogram/methods/utilities/idle.py index 7b41f7af67..00db22a2e4 100644 --- a/pyrogram/methods/utilities/idle.py +++ b/pyrogram/methods/utilities/idle.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2021 Dan +# Copyright (C) 2017-present Dan # # This file is part of Pyrogram. # @@ -23,8 +23,6 @@ log = logging.getLogger(__name__) -is_idling = False - # Signal number to name signals = { k: v for v, k in signal.__dict__.items() @@ -41,47 +39,49 @@ async def idle(): It is useful for event-driven application only, that are, applications which react upon incoming Telegram updates through handlers, rather than executing a set of methods sequentially. - The way Pyrogram works, it will keep your handlers in a pool of worker threads, which are executed concurrently - outside the main thread; calling idle() will ensure the client(s) will be kept alive by not letting the main - script to end, until you decide to quit. - Once a signal is received (e.g.: from CTRL+C) the function will terminate and your main script will continue. Don't forget to call :meth:`~pyrogram.Client.stop` for each running client before the script ends. Example: .. code-block:: python - :emphasize-lines: 13 + import asyncio from pyrogram import Client, idle - app1 = Client("account1") - app2 = Client("account2") - app3 = Client("account3") - ... # Set handlers up + async def main(): + apps = [ + Client("account1"), + Client("account2"), + Client("account3") + ] + + ... # Set up handlers - app1.start() - app2.start() - app3.start() + for app in apps: + await app.start() - idle() + await idle() - app1.stop() - app2.stop() - app3.stop() + for app in apps: + await app.stop() + + + asyncio.run(main()) """ - global is_idling + task = None def signal_handler(signum, __): - global is_idling - logging.info(f"Stop signal received ({signals[signum]}). Exiting...") - is_idling = False + task.cancel() for s in (SIGINT, SIGTERM, SIGABRT): signal_fn(s, signal_handler) - is_idling = True + while True: + task = asyncio.create_task(asyncio.sleep(600)) - while is_idling: - await asyncio.sleep(1) + try: + await task + except asyncio.CancelledError: + break diff --git a/pyrogram/methods/utilities/remove_handler.py b/pyrogram/methods/utilities/remove_handler.py index 97c7f18358..9f1c974e69 100644 --- a/pyrogram/methods/utilities/remove_handler.py +++ b/pyrogram/methods/utilities/remove_handler.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2021 Dan +# Copyright (C) 2017-present Dan # # This file is part of Pyrogram. # @@ -16,13 +16,17 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . +import pyrogram from pyrogram.handlers import DisconnectHandler from pyrogram.handlers.handler import Handler -from pyrogram.scaffold import Scaffold -class RemoveHandler(Scaffold): - def remove_handler(self, handler: "Handler", group: int = 0): +class RemoveHandler: + def remove_handler( + self: "pyrogram.Client", + handler: "Handler", + group: int = 0 + ): """Remove a previously-registered update handler. Make sure to provide the right group where the handler was added in. You can use the return value of the @@ -37,17 +41,16 @@ def remove_handler(self, handler: "Handler", group: int = 0): Example: .. code-block:: python - :emphasize-lines: 11 from pyrogram import Client from pyrogram.handlers import MessageHandler - def dump(client, message): + async def hello(client, message): print(message) app = Client("my_account") - handler = app.add_handler(MessageHandler(dump)) + handler = app.add_handler(MessageHandler(hello)) # Starred expression to unpack (handler, group) app.remove_handler(*handler) diff --git a/pyrogram/methods/utilities/restart.py b/pyrogram/methods/utilities/restart.py index afd22bb478..44fd6745ef 100644 --- a/pyrogram/methods/utilities/restart.py +++ b/pyrogram/methods/utilities/restart.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2021 Dan +# Copyright (C) 2017-present Dan # # This file is part of Pyrogram. # @@ -16,11 +16,14 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . -from pyrogram.scaffold import Scaffold +import pyrogram -class Restart(Scaffold): - async def restart(self, block: bool = True): +class Restart: + async def restart( + self: "pyrogram.Client", + block: bool = True + ): """Restart the Client. This method will first call :meth:`~pyrogram.Client.stop` and then :meth:`~pyrogram.Client.start` in a row in @@ -29,7 +32,7 @@ async def restart(self, block: bool = True): Parameters: block (``bool``, *optional*): Blocks the code execution until the client has been restarted. It is useful with ``block=False`` in case - you want to restart the own client *within* an handler in order not to cause a deadlock. + you want to restart the own client within an handler in order not to cause a deadlock. Defaults to True. Returns: @@ -40,20 +43,21 @@ async def restart(self, block: bool = True): Example: .. code-block:: python - :emphasize-lines: 8 from pyrogram import Client app = Client("my_account") - app.start() - ... # Call API methods - app.restart() + async def main(): + await app.start() + ... # Invoke API methods + await app.restart() + ... # Invoke other API methods + await app.stop() - ... # Call other API methods - app.stop() + app.run(main()) """ async def do_it(): diff --git a/pyrogram/methods/utilities/run.py b/pyrogram/methods/utilities/run.py index 6a55b28cde..ec1bece320 100644 --- a/pyrogram/methods/utilities/run.py +++ b/pyrogram/methods/utilities/run.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2021 Dan +# Copyright (C) 2017-present Dan # # This file is part of Pyrogram. # @@ -19,33 +19,56 @@ import asyncio import inspect +import pyrogram from pyrogram.methods.utilities.idle import idle -from pyrogram.scaffold import Scaffold -class Run(Scaffold): - def run(self, coroutine=None): +class Run: + def run( + self: "pyrogram.Client", + coroutine=None + ): """Start the client, idle the main script and finally stop the client. - This is a convenience method that calls :meth:`~pyrogram.Client.start`, :meth:`~pyrogram.idle` and - :meth:`~pyrogram.Client.stop` in sequence. It makes running a client less verbose, but is not suitable in case - you want to run more than one client in a single main script, since :meth:`~pyrogram.idle` will block after - starting the own client. + When calling this method without any argument it acts as a convenience method that calls + :meth:`~pyrogram.Client.start`, :meth:`~pyrogram.idle` and :meth:`~pyrogram.Client.stop` in sequence. + It makes running a single client less verbose. + + In case a coroutine is passed as argument, runs the coroutine until it's completed and doesn't do any client + operation. This is almost the same as :py:obj:`asyncio.run` except for the fact that Pyrogram's ``run`` uses the + current event loop instead of a new one. + + If you want to run multiple clients at once, see :meth:`pyrogram.compose`. + + Parameters: + coroutine (``Coroutine``, *optional*): + Pass a coroutine to run it until it completes. Raises: ConnectionError: In case you try to run an already started client. Example: .. code-block:: python - :emphasize-lines: 7 from pyrogram import Client app = Client("my_account") - ... # Set handlers up - app.run() + + .. code-block:: python + + from pyrogram import Client + + app = Client("my_account") + + + async def main(): + async with app: + print(await app.get_me()) + + + app.run(main()) """ loop = asyncio.get_event_loop() run = loop.run_until_complete diff --git a/pyrogram/methods/utilities/start.py b/pyrogram/methods/utilities/start.py index 0f08d223c5..d8314da182 100644 --- a/pyrogram/methods/utilities/start.py +++ b/pyrogram/methods/utilities/start.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2021 Dan +# Copyright (C) 2017-present Dan # # This file is part of Pyrogram. # @@ -18,17 +18,19 @@ import logging +import pyrogram from pyrogram import raw -from pyrogram.scaffold import Scaffold log = logging.getLogger(__name__) -class Start(Scaffold): - async def start(self): +class Start: + async def start( + self: "pyrogram.Client" + ): """Start the client. - This method connects the client to Telegram and, in case of new sessions, automatically manages the full + This method connects the client to Telegram and, in case of new sessions, automatically manages the authorization process using an interactive prompt. Returns: @@ -39,16 +41,19 @@ async def start(self): Example: .. code-block:: python - :emphasize-lines: 4 from pyrogram import Client app = Client("my_account") - app.start() - ... # Call API methods - app.stop() + async def main(): + await app.start() + ... # Invoke API methods + await app.stop() + + + app.run(main()) """ is_authorized = await self.connect() @@ -57,13 +62,15 @@ async def start(self): await self.authorize() if not await self.storage.is_bot() and self.takeout: - self.takeout_id = (await self.send(raw.functions.account.InitTakeoutSession())).id - log.warning(f"Takeout session {self.takeout_id} initiated") + self.takeout_id = (await self.invoke(raw.functions.account.InitTakeoutSession())).id + log.info("Takeout session %s initiated", self.takeout_id) - await self.send(raw.functions.updates.GetState()) + await self.invoke(raw.functions.updates.GetState()) except (Exception, KeyboardInterrupt): await self.disconnect() raise else: + self.me = await self.get_me() await self.initialize() + return self diff --git a/pyrogram/methods/utilities/stop.py b/pyrogram/methods/utilities/stop.py index 38f0d4c474..1b28add046 100644 --- a/pyrogram/methods/utilities/stop.py +++ b/pyrogram/methods/utilities/stop.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2021 Dan +# Copyright (C) 2017-present Dan # # This file is part of Pyrogram. # @@ -16,11 +16,14 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . -from pyrogram.scaffold import Scaffold +import pyrogram -class Stop(Scaffold): - async def stop(self, block: bool = True): +class Stop: + async def stop( + self: "pyrogram.Client", + block: bool = True + ): """Stop the Client. This method disconnects the client from Telegram and stops the underlying tasks. @@ -39,16 +42,19 @@ async def stop(self, block: bool = True): Example: .. code-block:: python - :emphasize-lines: 8 from pyrogram import Client app = Client("my_account") - app.start() - ... # Call API methods - app.stop() + async def main(): + await app.start() + ... # Invoke API methods + await app.stop() + + + app.run(main()) """ async def do_it(): diff --git a/pyrogram/methods/utilities/stop_transmission.py b/pyrogram/methods/utilities/stop_transmission.py index a0d04b4c52..da64569523 100644 --- a/pyrogram/methods/utilities/stop_transmission.py +++ b/pyrogram/methods/utilities/stop_transmission.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2021 Dan +# Copyright (C) 2017-present Dan # # This file is part of Pyrogram. # @@ -17,10 +17,9 @@ # along with Pyrogram. If not, see . import pyrogram -from pyrogram.scaffold import Scaffold -class StopTransmission(Scaffold): +class StopTransmission: def stop_transmission(self): """Stop downloading or uploading a file. @@ -29,19 +28,16 @@ def stop_transmission(self): Example: .. code-block:: python - :emphasize-lines: 9 - from pyrogram import Client - - app = Client("my_account") - - # Example to stop transmission once the upload progress reaches 50% - # Useless in practice, but shows how to stop on command - def progress(current, total, client): + # Stop transmission once the upload progress reaches 50% + async def progress(current, total, client): if (current * 100 / total) > 50: client.stop_transmission() - with app: - app.send_document("me", "files.zip", progress=progress, progress_args=(app,)) + async with app: + await app.send_document( + "me", "file.zip", + progress=progress, + progress_args=(app,)) """ raise pyrogram.StopTransmission diff --git a/pyrogram/mime_types.py b/pyrogram/mime_types.py index 819e07738e..2f6c86aa8b 100644 --- a/pyrogram/mime_types.py +++ b/pyrogram/mime_types.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2021 Dan +# Copyright (C) 2017-present Dan # # This file is part of Pyrogram. # diff --git a/pyrogram/parser/__init__.py b/pyrogram/parser/__init__.py index c1c03d59d7..00c7acae76 100644 --- a/pyrogram/parser/__init__.py +++ b/pyrogram/parser/__init__.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2021 Dan +# Copyright (C) 2017-present Dan # # This file is part of Pyrogram. # diff --git a/pyrogram/parser/html.py b/pyrogram/parser/html.py index f3f55195c2..46722a8c40 100644 --- a/pyrogram/parser/html.py +++ b/pyrogram/parser/html.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2021 Dan +# Copyright (C) 2017-present Dan # # This file is part of Pyrogram. # @@ -24,6 +24,7 @@ import pyrogram from pyrogram import raw +from pyrogram.enums import MessageEntityType from pyrogram.errors import PeerIdInvalid from . import utils @@ -60,7 +61,9 @@ def handle_starttag(self, tag, attrs): entity = raw.types.MessageEntityCode elif tag == "pre": entity = raw.types.MessageEntityPre - extra["language"] = "" + extra["language"] = attrs.get("language", "") + elif tag == "spoiler": + entity = raw.types.MessageEntitySpoiler elif tag == "a": url = attrs.get("href", "") @@ -72,6 +75,10 @@ def handle_starttag(self, tag, attrs): else: entity = raw.types.MessageEntityTextUrl extra["url"] = url + elif tag == "emoji": + entity = raw.types.MessageEntityCustomEmoji + custom_emoji_id = int(attrs.get("id")) + extra["document_id"] = custom_emoji_id else: return @@ -96,7 +103,7 @@ def handle_endtag(self, tag): line, offset = self.getpos() offset += 1 - log.debug(f"Unmatched closing tag at line {line}:{offset}") + log.debug("Unmatched closing tag at line %s:%s", tag, line, offset) else: if not self.tag_entities[tag]: self.tag_entities.pop(tag) @@ -110,7 +117,8 @@ def __init__(self, client: Optional["pyrogram.Client"]): self.client = client async def parse(self, text: str): - # Strip whitespace characters from the end of the message, but preserve closing tags + # Strip whitespaces from the beginning and the end, but preserve closing tags + text = re.sub(r"^\s*(<[\w<>=\s\"]*>)\s*", r"\1", text) text = re.sub(r"\s*(]*>)\s*$", r"\1", text) parser = Parser(self.client) @@ -123,7 +131,7 @@ async def parse(self, text: str): for tag, entities in parser.tag_entities.items(): unclosed_tags.append(f"<{tag}> (x{len(entities)})") - log.warning(f"Unclosed tags: {', '.join(unclosed_tags)}") + log.info("Unclosed tags: %s", ", ".join(unclosed_tags)) entities = [] @@ -137,46 +145,99 @@ async def parse(self, text: str): entities.append(entity) + # Remove zero-length entities + entities = list(filter(lambda x: x.length > 0, entities)) + return { "message": utils.remove_surrogates(parser.text), - "entities": sorted(entities, key=lambda e: e.offset) + "entities": sorted(entities, key=lambda e: e.offset) or None } @staticmethod def unparse(text: str, entities: list): - text = utils.add_surrogates(text) - - entities_offsets = [] - - for entity in entities: + def parse_one(entity): + """ + Parses a single entity and returns (start_tag, start), (end_tag, end) + """ entity_type = entity.type start = entity.offset end = start + entity.length - if entity_type in ("bold", "italic", "underline", "strikethrough"): - start_tag = f"<{entity_type[0]}>" - end_tag = f"" - elif entity_type in ("code", "pre", "blockquote"): - start_tag = f"<{entity_type}>" - end_tag = f"" - elif entity_type == "text_link": + if entity_type in ( + MessageEntityType.BOLD, + MessageEntityType.ITALIC, + MessageEntityType.UNDERLINE, + MessageEntityType.STRIKETHROUGH, + ): + name = entity_type.name[0].lower() + start_tag = f"<{name}>" + end_tag = f"" + elif entity_type == MessageEntityType.PRE: + name = entity_type.name.lower() + language = getattr(entity, "language", "") or "" + start_tag = f'<{name} language="{language}">' if language else f"<{name}>" + end_tag = f"" + elif entity_type in ( + MessageEntityType.CODE, + MessageEntityType.BLOCKQUOTE, + MessageEntityType.SPOILER, + ): + name = entity_type.name.lower() + start_tag = f"<{name}>" + end_tag = f"" + elif entity_type == MessageEntityType.TEXT_LINK: url = entity.url start_tag = f'' end_tag = "" - elif entity_type == "text_mention": + elif entity_type == MessageEntityType.TEXT_MENTION: user = entity.user start_tag = f'' end_tag = "" + elif entity_type == MessageEntityType.CUSTOM_EMOJI: + custom_emoji_id = entity.custom_emoji_id + start_tag = f'' + end_tag = "" else: - continue + return + + return (start_tag, start), (end_tag, end) + + def recursive(entity_i: int) -> int: + """ + Takes the index of the entity to start parsing from, returns the number of parsed entities inside it. + Uses entities_offsets as a stack, pushing (start_tag, start) first, then parsing nested entities, + and finally pushing (end_tag, end) to the stack. + No need to sort at the end. + """ + this = parse_one(entities[entity_i]) + if this is None: + return 1 + (start_tag, start), (end_tag, end) = this + entities_offsets.append((start_tag, start)) + internal_i = entity_i + 1 + # while the next entity is inside the current one, keep parsing + while internal_i < len(entities) and entities[internal_i].offset < end: + internal_i += recursive(internal_i) + entities_offsets.append((end_tag, end)) + return internal_i - entity_i + + text = utils.add_surrogates(text) + + entities_offsets = [] - entities_offsets.append((start_tag, start,)) - entities_offsets.append((end_tag, end,)) + # probably useless because entities are already sorted by telegram + entities.sort(key=lambda e: (e.offset, -e.length)) - # sorting by offset (desc) - entities_offsets.sort(key=lambda x: -x[1]) + # main loop for first-level entities + i = 0 + while i < len(entities): + i += recursive(i) - for entity, offset in entities_offsets: - text = text[:offset] + entity + text[offset:] + if entities_offsets: + last_offset = entities_offsets[-1][1] + # no need to sort, but still add entities starting from the end + for entity, offset in reversed(entities_offsets): + text = text[:offset] + entity + html.escape(text[offset:last_offset]) + text[last_offset:] + last_offset = offset return utils.remove_surrogates(text) diff --git a/pyrogram/parser/markdown.py b/pyrogram/parser/markdown.py index f71503d832..6219d9583d 100644 --- a/pyrogram/parser/markdown.py +++ b/pyrogram/parser/markdown.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2021 Dan +# Copyright (C) 2017-present Dan # # This file is part of Pyrogram. # @@ -21,6 +21,7 @@ from typing import Optional import pyrogram +from pyrogram.enums import MessageEntityType from . import utils from .html import HTML @@ -28,6 +29,7 @@ ITALIC_DELIM = "__" UNDERLINE_DELIM = "--" STRIKE_DELIM = "~~" +SPOILER_DELIM = "||" CODE_DELIM = "`" PRE_DELIM = "```" @@ -41,7 +43,8 @@ STRIKE_DELIM, UNDERLINE_DELIM, ITALIC_DELIM, - BOLD_DELIM + BOLD_DELIM, + SPOILER_DELIM ] ]] ))) @@ -90,6 +93,8 @@ async def parse(self, text: str, strict: bool = False): tag = "code" elif delim == PRE_DELIM: tag = "pre" + elif delim == SPOILER_DELIM: + tag = "spoiler" else: continue @@ -100,6 +105,12 @@ async def parse(self, text: str, strict: bool = False): delims.remove(delim) tag = CLOSING_TAG.format(tag) + if delim == PRE_DELIM and delim in delims: + delim_and_language = text[text.find(PRE_DELIM):].split("\n")[0] + language = delim_and_language[len(PRE_DELIM):] + text = utils.replace_once(text, delim_and_language, f'
', start)
+                continue
+
             text = utils.replace_once(text, delim, tag, start)
 
         return await self.html.parse(text)
@@ -115,23 +126,29 @@ def unparse(text: str, entities: list):
             start = entity.offset
             end = start + entity.length
 
-            if entity_type == "bold":
+            if entity_type == MessageEntityType.BOLD:
                 start_tag = end_tag = BOLD_DELIM
-            elif entity_type == "italic":
+            elif entity_type == MessageEntityType.ITALIC:
                 start_tag = end_tag = ITALIC_DELIM
-            elif entity_type == "underline":
+            elif entity_type == MessageEntityType.UNDERLINE:
                 start_tag = end_tag = UNDERLINE_DELIM
-            elif entity_type == "strikethrough":
+            elif entity_type == MessageEntityType.STRIKETHROUGH:
                 start_tag = end_tag = STRIKE_DELIM
-            elif entity_type == "code":
+            elif entity_type == MessageEntityType.CODE:
                 start_tag = end_tag = CODE_DELIM
-            elif entity_type in ("pre", "blockquote"):
+            elif entity_type == MessageEntityType.PRE:
+                language = getattr(entity, "language", "") or ""
+                start_tag = f"{PRE_DELIM}{language}\n"
+                end_tag = f"\n{PRE_DELIM}"
+            elif entity_type == MessageEntityType.BLOCKQUOTE:
                 start_tag = end_tag = PRE_DELIM
-            elif entity_type == "text_link":
+            elif entity_type == MessageEntityType.SPOILER:
+                start_tag = end_tag = SPOILER_DELIM
+            elif entity_type == MessageEntityType.TEXT_LINK:
                 url = entity.url
                 start_tag = "["
                 end_tag = f"]({url})"
-            elif entity_type == "text_mention":
+            elif entity_type == MessageEntityType.TEXT_MENTION:
                 user = entity.user
                 start_tag = "["
                 end_tag = f"](tg://user?id={user.id})"
@@ -141,8 +158,14 @@ def unparse(text: str, entities: list):
             entities_offsets.append((start_tag, start,))
             entities_offsets.append((end_tag, end,))
 
-        # sorting by offset (desc)
-        entities_offsets.sort(key=lambda x: -x[1])
+        entities_offsets = map(
+            lambda x: x[1],
+            sorted(
+                enumerate(entities_offsets),
+                key=lambda x: (x[1][1], x[0]),
+                reverse=True
+            )
+        )
 
         for entity, offset in entities_offsets:
             text = text[:offset] + entity + text[offset:]
diff --git a/pyrogram/parser/parser.py b/pyrogram/parser/parser.py
index 24663ab6df..0ce2b2375c 100644
--- a/pyrogram/parser/parser.py
+++ b/pyrogram/parser/parser.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
@@ -16,10 +16,10 @@
 #  You should have received a copy of the GNU Lesser General Public License
 #  along with Pyrogram.  If not, see .
 
-from collections import OrderedDict
 from typing import Optional
 
 import pyrogram
+from pyrogram import enums
 from .html import HTML
 from .markdown import Markdown
 
@@ -30,36 +30,28 @@ def __init__(self, client: Optional["pyrogram.Client"]):
         self.html = HTML(client)
         self.markdown = Markdown(client)
 
-    async def parse(self, text: str, mode: Optional[str] = object):
-        text = str(text).strip()
+    async def parse(self, text: str, mode: Optional[enums.ParseMode] = None):
+        text = str(text if text else "").strip()
 
-        if mode == object:
+        if mode is None:
             if self.client:
                 mode = self.client.parse_mode
             else:
-                mode = "combined"
-
-        if mode is None:
-            return OrderedDict([
-                ("message", text),
-                ("entities", [])
-            ])
+                mode = enums.ParseMode.DEFAULT
 
-        mode = mode.lower()
-
-        if mode == "combined":
+        if mode == enums.ParseMode.DEFAULT:
             return await self.markdown.parse(text)
 
-        if mode in ["markdown", "md"]:
+        if mode == enums.ParseMode.MARKDOWN:
             return await self.markdown.parse(text, True)
 
-        if mode == "html":
+        if mode == enums.ParseMode.HTML:
             return await self.html.parse(text)
 
-        raise ValueError('parse_mode must be one of {} or None. Not "{}"'.format(
-            ", ".join(f'"{m}"' for m in pyrogram.Client.PARSE_MODES[:-1]),
-            mode
-        ))
+        if mode == enums.ParseMode.DISABLED:
+            return {"message": text, "entities": None}
+
+        raise ValueError(f'Invalid parse mode "{mode}"')
 
     @staticmethod
     def unparse(text: str, entities: list, is_html: bool):
diff --git a/pyrogram/parser/utils.py b/pyrogram/parser/utils.py
index db0fc1756d..0d60402894 100644
--- a/pyrogram/parser/utils.py
+++ b/pyrogram/parser/utils.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
diff --git a/pyrogram/py.typed b/pyrogram/py.typed
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/pyrogram/raw/__init__.py b/pyrogram/raw/__init__.py
index deef4038fc..a1435c77c6 100644
--- a/pyrogram/raw/__init__.py
+++ b/pyrogram/raw/__init__.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
diff --git a/pyrogram/raw/core/__init__.py b/pyrogram/raw/core/__init__.py
index 8b284c65d0..95c8123104 100644
--- a/pyrogram/raw/core/__init__.py
+++ b/pyrogram/raw/core/__init__.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
diff --git a/pyrogram/raw/core/future_salt.py b/pyrogram/raw/core/future_salt.py
index 85303d1203..ecef5e2ff3 100644
--- a/pyrogram/raw/core/future_salt.py
+++ b/pyrogram/raw/core/future_salt.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
@@ -42,3 +42,12 @@ def read(data: BytesIO, *args: Any) -> "FutureSalt":
         salt = Long.read(data)
 
         return FutureSalt(valid_since, valid_until, salt)
+
+    def write(self, *args: Any) -> bytes:
+        b = BytesIO()
+
+        b.write(Int(self.valid_since))
+        b.write(Int(self.valid_until))
+        b.write(Long(self.salt))
+
+        return b.getvalue()
diff --git a/pyrogram/raw/core/future_salts.py b/pyrogram/raw/core/future_salts.py
index faa4b74163..88216387f2 100644
--- a/pyrogram/raw/core/future_salts.py
+++ b/pyrogram/raw/core/future_salts.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
@@ -45,3 +45,19 @@ def read(data: BytesIO, *args: Any) -> "FutureSalts":
         salts = [FutureSalt.read(data) for _ in range(count)]
 
         return FutureSalts(req_msg_id, now, salts)
+
+    def write(self, *args: Any) -> bytes:
+        b = BytesIO()
+
+        b.write(Int(self.ID, False))
+
+        b.write(Long(self.req_msg_id))
+        b.write(Int(self.now))
+
+        count = len(self.salts)
+        b.write(Int(count))
+
+        for salt in self.salts:
+            b.write(salt.write())
+
+        return b.getvalue()
diff --git a/pyrogram/raw/core/gzip_packed.py b/pyrogram/raw/core/gzip_packed.py
index 8525338ac8..685594d87a 100644
--- a/pyrogram/raw/core/gzip_packed.py
+++ b/pyrogram/raw/core/gzip_packed.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
diff --git a/pyrogram/raw/core/list.py b/pyrogram/raw/core/list.py
index 74d39c03bf..a7bbc16b2e 100644
--- a/pyrogram/raw/core/list.py
+++ b/pyrogram/raw/core/list.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
@@ -23,4 +23,4 @@
 
 class List(TList[Any], TLObject):
     def __repr__(self) -> str:
-        return f"pyrogram.api.core.List([{','.join(TLObject.__repr__(i) for i in self)}])"
+        return f"pyrogram.raw.core.List([{','.join(TLObject.__repr__(i) for i in self)}])"
diff --git a/pyrogram/raw/core/message.py b/pyrogram/raw/core/message.py
index 6d50ecf318..1357cf8690 100644
--- a/pyrogram/raw/core/message.py
+++ b/pyrogram/raw/core/message.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
diff --git a/pyrogram/raw/core/msg_container.py b/pyrogram/raw/core/msg_container.py
index ddebce0c38..454a150741 100644
--- a/pyrogram/raw/core/msg_container.py
+++ b/pyrogram/raw/core/msg_container.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
diff --git a/pyrogram/raw/core/primitives/__init__.py b/pyrogram/raw/core/primitives/__init__.py
index 575b36966e..88f2fa5363 100644
--- a/pyrogram/raw/core/primitives/__init__.py
+++ b/pyrogram/raw/core/primitives/__init__.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
diff --git a/pyrogram/raw/core/primitives/bool.py b/pyrogram/raw/core/primitives/bool.py
index 480a7224ae..02cc12d131 100644
--- a/pyrogram/raw/core/primitives/bool.py
+++ b/pyrogram/raw/core/primitives/bool.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
diff --git a/pyrogram/raw/core/primitives/bytes.py b/pyrogram/raw/core/primitives/bytes.py
index 47f914e08b..eace1feb99 100644
--- a/pyrogram/raw/core/primitives/bytes.py
+++ b/pyrogram/raw/core/primitives/bytes.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
diff --git a/pyrogram/raw/core/primitives/double.py b/pyrogram/raw/core/primitives/double.py
index 232f35c9b7..bb7878bf1e 100644
--- a/pyrogram/raw/core/primitives/double.py
+++ b/pyrogram/raw/core/primitives/double.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
diff --git a/pyrogram/raw/core/primitives/int.py b/pyrogram/raw/core/primitives/int.py
index aed653ad76..5d5ec177d1 100644
--- a/pyrogram/raw/core/primitives/int.py
+++ b/pyrogram/raw/core/primitives/int.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
diff --git a/pyrogram/raw/core/primitives/string.py b/pyrogram/raw/core/primitives/string.py
index c5d19205d6..66f992717b 100644
--- a/pyrogram/raw/core/primitives/string.py
+++ b/pyrogram/raw/core/primitives/string.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
diff --git a/pyrogram/raw/core/primitives/vector.py b/pyrogram/raw/core/primitives/vector.py
index 48b2eed91c..c6c6e8e5d4 100644
--- a/pyrogram/raw/core/primitives/vector.py
+++ b/pyrogram/raw/core/primitives/vector.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
@@ -33,7 +33,7 @@ class Vector(bytes, TLObject):
     def read_bare(b: BytesIO, size: int) -> Union[int, Any]:
         if size == 4:
             return Int.read(b)
-        
+
         if size == 8:
             return Long.read(b)
 
diff --git a/pyrogram/raw/core/tl_object.py b/pyrogram/raw/core/tl_object.py
index f0dd596a3f..ff67566ea8 100644
--- a/pyrogram/raw/core/tl_object.py
+++ b/pyrogram/raw/core/tl_object.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
@@ -53,6 +53,9 @@ def __str__(self) -> str:
         return dumps(self, indent=4, default=TLObject.default, ensure_ascii=False)
 
     def __repr__(self) -> str:
+        if not hasattr(self, "QUALNAME"):
+            return repr(self)
+
         return "pyrogram.raw.{}({})".format(
             self.QUALNAME,
             ", ".join(
@@ -75,11 +78,5 @@ def __eq__(self, other: Any) -> bool:
     def __len__(self) -> int:
         return len(self.write())
 
-    def __getitem__(self, item: Any) -> Any:
-        return getattr(self, item)
-
-    def __setitem__(self, key: Any, value: Any) -> Any:
-        setattr(self, key, value)
-
     def __call__(self, *args: Any, **kwargs: Any) -> Any:
         pass
diff --git a/pyrogram/scaffold.py b/pyrogram/scaffold.py
deleted file mode 100644
index 2bd2308690..0000000000
--- a/pyrogram/scaffold.py
+++ /dev/null
@@ -1,203 +0,0 @@
-#  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
-#
-#  This file is part of Pyrogram.
-#
-#  Pyrogram is free software: you can redistribute it and/or modify
-#  it under the terms of the GNU Lesser General Public License as published
-#  by the Free Software Foundation, either version 3 of the License, or
-#  (at your option) any later version.
-#
-#  Pyrogram is distributed in the hope that it will be useful,
-#  but WITHOUT ANY WARRANTY; without even the implied warranty of
-#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-#  GNU Lesser General Public License for more details.
-#
-#  You should have received a copy of the GNU Lesser General Public License
-#  along with Pyrogram.  If not, see .
-
-import asyncio
-import os
-import platform
-import re
-import sys
-from io import StringIO
-from mimetypes import MimeTypes
-from pathlib import Path
-
-import pyrogram
-from pyrogram import __version__
-from pyrogram.parser import Parser
-from pyrogram.session.internals import MsgId
-from .mime_types import mime_types
-
-
-class Scaffold:
-    APP_VERSION = f"Pyrogram {__version__}"
-    DEVICE_MODEL = f"{platform.python_implementation()} {platform.python_version()}"
-    SYSTEM_VERSION = f"{platform.system()} {platform.release()}"
-
-    LANG_CODE = "en"
-
-    PARENT_DIR = Path(sys.argv[0]).parent
-
-    INVITE_LINK_RE = re.compile(r"^(?:https?://)?(?:www\.)?(?:t(?:elegram)?\.(?:org|me|dog)/(?:joinchat/|\+))([\w-]+)$")
-    WORKERS = min(32, os.cpu_count() + 4)
-    WORKDIR = PARENT_DIR
-    CONFIG_FILE = PARENT_DIR / "config.ini"
-
-    PARSE_MODES = ["combined", "markdown", "md", "html", None]
-
-    mimetypes = MimeTypes()
-    mimetypes.readfp(StringIO(mime_types))
-
-    def __init__(self):
-        try:
-            asyncio.get_event_loop()
-        except RuntimeError:
-            # This happens when creating Client instances inside different threads that don't have an event loop.
-            # Set the main event loop in this thread.
-            asyncio.set_event_loop(pyrogram.main_event_loop)
-
-        self.session_name = None
-        self.api_id = None
-        self.api_hash = None
-        self.app_version = None
-        self.device_model = None
-        self.system_version = None
-        self.lang_code = None
-        self.ipv6 = None
-        self.proxy = None
-        self.test_mode = None
-        self.bot_token = None
-        self.phone_number = None
-        self.phone_code = None
-        self.password = None
-        self.force_sms = None
-        self.workers = None
-        self.workdir = None
-        self.config_file = None
-        self.plugins = None
-        self.parse_mode = None
-        self.no_updates = None
-        self.takeout = None
-        self.sleep_threshold = None
-
-        self.executor = None
-
-        self.storage = None
-
-        self.rnd_id = MsgId
-
-        self.parser = Parser(self)
-        self.parse_mode = "combined"
-
-        self.session = None
-
-        self.media_sessions = {}
-        self.media_sessions_lock = asyncio.Lock()
-
-        self.is_connected = None
-        self.is_initialized = None
-
-        self.no_updates = None
-        self.takeout_id = None
-
-        self.dispatcher = None
-
-        self.disconnect_handler = None
-
-        self.loop = None
-
-    async def send(self, *args, **kwargs):
-        pass
-
-    async def resolve_peer(self, *args, **kwargs):
-        pass
-
-    def fetch_peers(self, *args, **kwargs):
-        pass
-
-    def add_handler(self, *args, **kwargs):
-        pass
-
-    async def save_file(self, *args, **kwargs):
-        pass
-
-    async def get_messages(self, *args, **kwargs):
-        pass
-
-    async def get_history(self, *args, **kwargs):
-        pass
-
-    async def get_dialogs(self, *args, **kwargs):
-        pass
-
-    async def get_chat_members(self, *args, **kwargs):
-        pass
-
-    async def get_chat_members_count(self, *args, **kwargs):
-        pass
-
-    async def answer_inline_query(self, *args, **kwargs):
-        pass
-
-    async def get_profile_photos(self, *args, **kwargs):
-        pass
-
-    async def edit_message_text(self, *args, **kwargs):
-        pass
-
-    async def edit_inline_text(self, *args, **kwargs):
-        pass
-
-    async def edit_message_media(self, *args, **kwargs):
-        pass
-
-    async def edit_inline_media(self, *args, **kwargs):
-        pass
-
-    async def edit_message_reply_markup(self, *args, **kwargs):
-        pass
-
-    async def edit_inline_reply_markup(self, *args, **kwargs):
-        pass
-
-    def guess_mime_type(self, *args, **kwargs):
-        pass
-
-    def guess_extension(self, *args, **kwargs):
-        pass
-
-    def load_config(self, *args, **kwargs):
-        pass
-
-    def load_session(self, *args, **kwargs):
-        pass
-
-    def load_plugins(self, *args, **kwargs):
-        pass
-
-    async def handle_download(self, *args, **kwargs):
-        pass
-
-    async def start(self, *args, **kwargs):
-        pass
-
-    async def stop(self, *args, **kwargs):
-        pass
-
-    async def connect(self, *args, **kwargs):
-        pass
-
-    async def authorize(self, *args, **kwargs):
-        pass
-
-    async def disconnect(self, *args, **kwargs):
-        pass
-
-    async def initialize(self, *args, **kwargs):
-        pass
-
-    async def terminate(self, *args, **kwargs):
-        pass
diff --git a/pyrogram/session/__init__.py b/pyrogram/session/__init__.py
index 96e06e0f58..b414049b2b 100644
--- a/pyrogram/session/__init__.py
+++ b/pyrogram/session/__init__.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
diff --git a/pyrogram/session/auth.py b/pyrogram/session/auth.py
index a3e87ff41c..c5d9cd9a50 100644
--- a/pyrogram/session/auth.py
+++ b/pyrogram/session/auth.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
@@ -27,6 +27,7 @@
 from pyrogram import raw
 from pyrogram.connection import Connection
 from pyrogram.crypto import aes, rsa, prime
+from pyrogram.errors import SecurityCheckMismatch
 from pyrogram.raw.core import TLObject, Long, Int
 from .internals import MsgId
 
@@ -58,7 +59,7 @@ def unpack(b: BytesIO):
         b.seek(20)  # Skip auth_key_id (8), message_id (8) and message_length (4)
         return TLObject.read(b)
 
-    async def send(self, data: TLObject):
+    async def invoke(self, data: TLObject):
         data = self.pack(data)
         await self.connection.send(data)
         response = BytesIO(await self.connection.recv())
@@ -78,34 +79,34 @@ async def create(self):
             self.connection = Connection(self.dc_id, self.test_mode, self.ipv6, self.proxy)
 
             try:
-                log.info(f"Start creating a new auth key on DC{self.dc_id}")
+                log.info("Start creating a new auth key on DC%s", self.dc_id)
 
                 await self.connection.connect()
 
                 # Step 1; Step 2
                 nonce = int.from_bytes(urandom(16), "little", signed=True)
-                log.debug(f"Send req_pq: {nonce}")
-                res_pq = await self.send(raw.functions.ReqPqMulti(nonce=nonce))
-                log.debug(f"Got ResPq: {res_pq.server_nonce}")
-                log.debug(f"Server public key fingerprints: {res_pq.server_public_key_fingerprints}")
+                log.debug("Send req_pq: %s", nonce)
+                res_pq = await self.invoke(raw.functions.ReqPqMulti(nonce=nonce))
+                log.debug("Got ResPq: %s", res_pq.server_nonce)
+                log.debug("Server public key fingerprints: %s", res_pq.server_public_key_fingerprints)
 
                 for i in res_pq.server_public_key_fingerprints:
                     if i in rsa.server_public_keys:
-                        log.debug(f"Using fingerprint: {i}")
+                        log.debug("Using fingerprint: %s", i)
                         public_key_fingerprint = i
                         break
                     else:
-                        log.debug(f"Fingerprint unknown: {i}")
+                        log.debug("Fingerprint unknown: %s", i)
                 else:
                     raise Exception("Public key not found")
 
                 # Step 3
                 pq = int.from_bytes(res_pq.pq, "big")
-                log.debug(f"Start PQ factorization: {pq}")
+                log.debug("Start PQ factorization: %s", pq)
                 start = time.time()
                 g = prime.decompose(pq)
                 p, q = sorted((g, pq // g))  # p < q
-                log.debug(f"Done PQ factorization ({round(time.time() - start, 3)}s): {p} {q}")
+                log.debug("Done PQ factorization (%ss): %s %s", round(time.time() - start, 3), p, q)
 
                 # Step 4
                 server_nonce = res_pq.server_nonce
@@ -129,7 +130,7 @@ async def create(self):
 
                 # Step 5. TODO: Handle "server_DH_params_fail". Code assumes response is ok
                 log.debug("Send req_DH_params")
-                server_dh_params = await self.send(
+                server_dh_params = await self.invoke(
                     raw.functions.ReqDHParams(
                         nonce=nonce,
                         server_nonce=server_nonce,
@@ -167,7 +168,7 @@ async def create(self):
                 dh_prime = int.from_bytes(server_dh_inner_data.dh_prime, "big")
                 delta_time = server_dh_inner_data.server_time - time.time()
 
-                log.debug(f"Delta time: {round(delta_time, 3)}")
+                log.debug("Delta time: %s", round(delta_time, 3))
 
                 # Step 6
                 g = server_dh_inner_data.g
@@ -189,7 +190,7 @@ async def create(self):
                 encrypted_data = aes.ige256_encrypt(data_with_hash, tmp_aes_key, tmp_aes_iv)
 
                 log.debug("Send set_client_DH_params")
-                set_client_dh_params_answer = await self.send(
+                set_client_dh_params_answer = await self.invoke(
                     raw.functions.SetClientDHParams(
                         nonce=nonce,
                         server_nonce=server_nonce,
@@ -210,43 +211,63 @@ async def create(self):
                 # Security checks
                 #######################
 
-                assert dh_prime == prime.CURRENT_DH_PRIME
+                SecurityCheckMismatch.check(dh_prime == prime.CURRENT_DH_PRIME, "dh_prime == prime.CURRENT_DH_PRIME")
                 log.debug("DH parameters check: OK")
 
                 # https://core.telegram.org/mtproto/security_guidelines#g-a-and-g-b-validation
                 g_b = int.from_bytes(g_b, "big")
-                assert 1 < g < dh_prime - 1
-                assert 1 < g_a < dh_prime - 1
-                assert 1 < g_b < dh_prime - 1
-                assert 2 ** (2048 - 64) < g_a < dh_prime - 2 ** (2048 - 64)
-                assert 2 ** (2048 - 64) < g_b < dh_prime - 2 ** (2048 - 64)
+                SecurityCheckMismatch.check(1 < g < dh_prime - 1, "1 < g < dh_prime - 1")
+                SecurityCheckMismatch.check(1 < g_a < dh_prime - 1, "1 < g_a < dh_prime - 1")
+                SecurityCheckMismatch.check(1 < g_b < dh_prime - 1, "1 < g_b < dh_prime - 1")
+                SecurityCheckMismatch.check(
+                    2 ** (2048 - 64) < g_a < dh_prime - 2 ** (2048 - 64),
+                    "2 ** (2048 - 64) < g_a < dh_prime - 2 ** (2048 - 64)"
+                )
+                SecurityCheckMismatch.check(
+                    2 ** (2048 - 64) < g_b < dh_prime - 2 ** (2048 - 64),
+                    "2 ** (2048 - 64) < g_b < dh_prime - 2 ** (2048 - 64)"
+                )
                 log.debug("g_a and g_b validation: OK")
 
                 # https://core.telegram.org/mtproto/security_guidelines#checking-sha1-hash-values
                 answer = server_dh_inner_data.write()  # Call .write() to remove padding
-                assert answer_with_hash[:20] == sha1(answer).digest()
+                SecurityCheckMismatch.check(
+                    answer_with_hash[:20] == sha1(answer).digest(),
+                    "answer_with_hash[:20] == sha1(answer).digest()"
+                )
                 log.debug("SHA1 hash values check: OK")
 
                 # https://core.telegram.org/mtproto/security_guidelines#checking-nonce-server-nonce-and-new-nonce-fields
                 # 1st message
-                assert nonce == res_pq.nonce
+                SecurityCheckMismatch.check(nonce == res_pq.nonce, "nonce == res_pq.nonce")
                 # 2nd message
                 server_nonce = int.from_bytes(server_nonce, "little", signed=True)
-                assert nonce == server_dh_params.nonce
-                assert server_nonce == server_dh_params.server_nonce
+                SecurityCheckMismatch.check(nonce == server_dh_params.nonce, "nonce == server_dh_params.nonce")
+                SecurityCheckMismatch.check(
+                    server_nonce == server_dh_params.server_nonce,
+                    "server_nonce == server_dh_params.server_nonce"
+                )
                 # 3rd message
-                assert nonce == set_client_dh_params_answer.nonce
-                assert server_nonce == set_client_dh_params_answer.server_nonce
+                SecurityCheckMismatch.check(
+                    nonce == set_client_dh_params_answer.nonce,
+                    "nonce == set_client_dh_params_answer.nonce"
+                )
+                SecurityCheckMismatch.check(
+                    server_nonce == set_client_dh_params_answer.server_nonce,
+                    "server_nonce == set_client_dh_params_answer.server_nonce"
+                )
                 server_nonce = server_nonce.to_bytes(16, "little", signed=True)
                 log.debug("Nonce fields check: OK")
 
                 # Step 9
                 server_salt = aes.xor(new_nonce[:8], server_nonce[:8])
 
-                log.debug(f"Server salt: {int.from_bytes(server_salt, 'little')}")
+                log.debug("Server salt: %s", int.from_bytes(server_salt, "little"))
 
-                log.info(f"Done auth key exchange: {set_client_dh_params_answer.__class__.__name__}")
+                log.info("Done auth key exchange: %s", set_client_dh_params_answer.__class__.__name__)
             except Exception as e:
+                log.info("Retrying due to %s: %s", type(e).__name__, e)
+
                 if retries_left:
                     retries_left -= 1
                 else:
@@ -257,4 +278,4 @@ async def create(self):
             else:
                 return auth_key
             finally:
-                self.connection.close()
+                await self.connection.close()
diff --git a/pyrogram/session/internals/__init__.py b/pyrogram/session/internals/__init__.py
index 7f8ef15b82..31dcc80f12 100644
--- a/pyrogram/session/internals/__init__.py
+++ b/pyrogram/session/internals/__init__.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
diff --git a/pyrogram/session/internals/data_center.py b/pyrogram/session/internals/data_center.py
index 3c9e95675c..d314626352 100644
--- a/pyrogram/session/internals/data_center.py
+++ b/pyrogram/session/internals/data_center.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
@@ -31,7 +31,8 @@ class DataCenter:
         2: "149.154.167.51",
         3: "149.154.175.100",
         4: "149.154.167.91",
-        5: "91.108.56.130"
+        5: "91.108.56.130",
+        203: "91.105.192.100"
     }
 
     PROD_MEDIA = {
@@ -50,7 +51,8 @@ class DataCenter:
         2: "2001:67c:4e8:f002::a",
         3: "2001:b28:f23d:f003::a",
         4: "2001:67c:4e8:f004::a",
-        5: "2001:b28:f23f:f005::a"
+        5: "2001:b28:f23f:f005::a",
+        203: "2a0a:f280:0203:000a:5000:0000:0000:0100"
     }
 
     PROD_IPV6_MEDIA = {
diff --git a/pyrogram/session/internals/msg_factory.py b/pyrogram/session/internals/msg_factory.py
index 2adb22b34a..837f17d0e4 100644
--- a/pyrogram/session/internals/msg_factory.py
+++ b/pyrogram/session/internals/msg_factory.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
diff --git a/pyrogram/session/internals/msg_id.py b/pyrogram/session/internals/msg_id.py
index 1af7d42f0a..da2e264ff6 100644
--- a/pyrogram/session/internals/msg_id.py
+++ b/pyrogram/session/internals/msg_id.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
@@ -17,29 +17,19 @@
 #  along with Pyrogram.  If not, see .
 
 import logging
-from datetime import datetime
-from time import perf_counter
+import time
 
 log = logging.getLogger(__name__)
 
 
 class MsgId:
-    reference_clock = 0
     last_time = 0
-    msg_id_offset = 0
-    server_time = 0
+    offset = 0
 
     def __new__(cls) -> int:
-        now = perf_counter() - cls.reference_clock + cls.server_time
-        cls.msg_id_offset = cls.msg_id_offset + 4 if now == cls.last_time else 0
-        msg_id = int(now * 2 ** 32) + cls.msg_id_offset
+        now = int(time.time())
+        cls.offset = (cls.offset + 4) if now == cls.last_time else 0
+        msg_id = (now * 2 ** 32) + cls.offset
         cls.last_time = now
 
         return msg_id
-
-    @classmethod
-    def set_server_time(cls, server_time: int):
-        if not cls.server_time:
-            cls.reference_clock = perf_counter()
-            cls.server_time = server_time
-            log.info(f"Time synced: {datetime.utcfromtimestamp(server_time)} UTC")
diff --git a/pyrogram/session/internals/seq_no.py b/pyrogram/session/internals/seq_no.py
index 0725809c97..79501d9863 100644
--- a/pyrogram/session/internals/seq_no.py
+++ b/pyrogram/session/internals/seq_no.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
@@ -16,19 +16,15 @@
 #  You should have received a copy of the GNU Lesser General Public License
 #  along with Pyrogram.  If not, see .
 
-from threading import Lock
-
 
 class SeqNo:
     def __init__(self):
         self.content_related_messages_sent = 0
-        self.lock = Lock()
 
     def __call__(self, is_content_related: bool) -> int:
-        with self.lock:
-            seq_no = (self.content_related_messages_sent * 2) + (1 if is_content_related else 0)
+        seq_no = (self.content_related_messages_sent * 2) + (1 if is_content_related else 0)
 
-            if is_content_related:
-                self.content_related_messages_sent += 1
+        if is_content_related:
+            self.content_related_messages_sent += 1
 
-            return seq_no
+        return seq_no
diff --git a/pyrogram/session/session.py b/pyrogram/session/session.py
index 721586a01c..0ed967a1a2 100644
--- a/pyrogram/session/session.py
+++ b/pyrogram/session/session.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
@@ -17,21 +17,22 @@
 #  along with Pyrogram.  If not, see .
 
 import asyncio
+import bisect
 import logging
 import os
-import time
-from datetime import datetime, timedelta
 from hashlib import sha1
 from io import BytesIO
 
 import pyrogram
-from pyrogram import __copyright__, __license__, __version__
 from pyrogram import raw
 from pyrogram.connection import Connection
 from pyrogram.crypto import mtproto
-from pyrogram.errors import RPCError, InternalServerError, AuthKeyDuplicated, FloodWait, ServiceUnavailable
+from pyrogram.errors import (
+    RPCError, InternalServerError, AuthKeyDuplicated, FloodWait, ServiceUnavailable, BadMsgNotification,
+    SecurityCheckMismatch
+)
 from pyrogram.raw.all import layer
-from pyrogram.raw.core import TLObject, MsgContainer, Int, FutureSalt, FutureSalts
+from pyrogram.raw.core import TLObject, MsgContainer, Int, FutureSalts
 from .internals import MsgId, MsgFactory
 
 log = logging.getLogger(__name__)
@@ -44,28 +45,18 @@ def __init__(self):
 
 
 class Session:
-    INITIAL_SALT = 0x616e67656c696361
-    START_TIMEOUT = 1
+    START_TIMEOUT = 2
     WAIT_TIMEOUT = 15
     SLEEP_THRESHOLD = 10
-    MAX_RETRIES = 5
-    ACKS_THRESHOLD = 8
+    MAX_RETRIES = 10
+    ACKS_THRESHOLD = 10
     PING_INTERVAL = 5
+    STORED_MSG_IDS_MAX_SIZE = 1000 * 2
 
-    notice_displayed = False
-
-    BAD_MSG_DESCRIPTION = {
-        16: "[16] msg_id too low, the client time has to be synchronized",
-        17: "[17] msg_id too high, the client time has to be synchronized",
-        18: "[18] incorrect two lower order msg_id bits, the server expects client message msg_id to be divisible by 4",
-        19: "[19] container msg_id is the same as msg_id of a previously received message",
-        20: "[20] message too old, it cannot be verified by the server",
-        32: "[32] msg_seqno too low",
-        33: "[33] msg_seqno too high",
-        34: "[34] an even msg_seqno expected, but odd received",
-        35: "[35] odd msg_seqno expected, but even received",
-        48: "[48] incorrect server salt",
-        64: "[64] invalid container"
+    TRANSPORT_ERRORS = {
+        404: "auth key not found",
+        429: "transport flood",
+        444: "invalid DC"
     }
 
     def __init__(
@@ -77,11 +68,6 @@ def __init__(
         is_media: bool = False,
         is_cdn: bool = False
     ):
-        if not Session.notice_displayed:
-            print(f"Pyrogram v{__version__}, {__copyright__}")
-            print(f"Licensed under the terms of the {__license__}", end="\n\n")
-            Session.notice_displayed = True
-
         self.client = client
         self.dc_id = dc_id
         self.auth_key = auth_key
@@ -96,21 +82,20 @@ def __init__(
         self.session_id = os.urandom(8)
         self.msg_factory = MsgFactory()
 
-        self.current_salt = None
+        self.salt = 0
 
         self.pending_acks = set()
 
         self.results = {}
 
+        self.stored_msg_ids = []
+
         self.ping_task = None
         self.ping_task_event = asyncio.Event()
 
-        self.next_salt_task = None
-        self.next_salt_task_event = asyncio.Event()
-
-        self.network_task = None
+        self.recv_task = None
 
-        self.is_connected = asyncio.Event()
+        self.is_started = asyncio.Event()
 
         self.loop = asyncio.get_event_loop()
 
@@ -127,28 +112,16 @@ async def start(self):
             try:
                 await self.connection.connect()
 
-                self.network_task = self.loop.create_task(self.network_worker())
-
-                self.current_salt = FutureSalt(0, 0, Session.INITIAL_SALT)
-                self.current_salt = FutureSalt(
-                    0, 0,
-                    (await self._send(
-                        raw.functions.Ping(ping_id=0),
-                        timeout=self.START_TIMEOUT
-                    )).new_server_salt
-                )
-                self.current_salt = (await self._send(
-                    raw.functions.GetFutureSalts(num=1),
-                    timeout=self.START_TIMEOUT)).salts[0]
+                self.recv_task = self.loop.create_task(self.recv_worker())
 
-                self.next_salt_task = self.loop.create_task(self.next_salt_worker())
+                await self.send(raw.functions.Ping(ping_id=0), timeout=self.START_TIMEOUT)
 
                 if not self.is_cdn:
-                    await self._send(
+                    await self.send(
                         raw.functions.InvokeWithLayer(
                             layer=layer,
                             query=raw.functions.InitConnection(
-                                api_id=self.client.api_id,
+                                api_id=await self.client.storage.api_id(),
                                 app_version=self.client.app_version,
                                 device_model=self.client.device_model,
                                 system_version=self.client.system_version,
@@ -163,14 +136,13 @@ async def start(self):
 
                 self.ping_task = self.loop.create_task(self.ping_worker())
 
-                log.info(f"Session initialized: Layer {layer}")
-                log.info(f"Device: {self.client.device_model} - {self.client.app_version}")
-                log.info(f"System: {self.client.system_version} ({self.client.lang_code.upper()})")
-
+                log.info("Session initialized: Layer %s", layer)
+                log.info("Device: %s - %s", self.client.device_model, self.client.app_version)
+                log.info("System: %s (%s)", self.client.system_version, self.client.lang_code)
             except AuthKeyDuplicated as e:
                 await self.stop()
                 raise e
-            except (OSError, TimeoutError, RPCError):
+            except (OSError, RPCError):
                 await self.stop()
             except Exception as e:
                 await self.stop()
@@ -178,38 +150,32 @@ async def start(self):
             else:
                 break
 
-        self.is_connected.set()
+        self.is_started.set()
 
         log.info("Session started")
 
     async def stop(self):
-        self.is_connected.clear()
+        self.is_started.clear()
+
+        self.stored_msg_ids.clear()
 
         self.ping_task_event.set()
-        self.next_salt_task_event.set()
 
         if self.ping_task is not None:
             await self.ping_task
 
-        if self.next_salt_task is not None:
-            await self.next_salt_task
-
         self.ping_task_event.clear()
-        self.next_salt_task_event.clear()
 
-        self.connection.close()
+        await self.connection.close()
 
-        if self.network_task:
-            await self.network_task
-
-        for i in self.results.values():
-            i.event.set()
+        if self.recv_task:
+            await self.recv_task
 
         if not self.is_media and callable(self.client.disconnect_handler):
             try:
                 await self.client.disconnect_handler(self.client)
             except Exception as e:
-                log.error(e, exc_info=True)
+                log.exception(e)
 
         log.info("Session stopped")
 
@@ -233,21 +199,42 @@ async def handle_packet(self, packet):
             else [data]
         )
 
-        # Call log.debug twice because calling it once by appending "data" to the previous string (i.e. f"Kind: {data}")
-        # will cause "data" to be evaluated as string every time instead of only when debug is actually enabled.
-        log.debug("Received:")
-        log.debug(data)
+        log.debug("Received: %s", data)
 
         for msg in messages:
-            if msg.seq_no == 0:
-                MsgId.set_server_time(msg.msg_id / (2 ** 32))
-
             if msg.seq_no % 2 != 0:
                 if msg.msg_id in self.pending_acks:
                     continue
                 else:
                     self.pending_acks.add(msg.msg_id)
 
+            try:
+                if len(self.stored_msg_ids) > Session.STORED_MSG_IDS_MAX_SIZE:
+                    del self.stored_msg_ids[:Session.STORED_MSG_IDS_MAX_SIZE // 2]
+
+                if self.stored_msg_ids:
+                    if msg.msg_id < self.stored_msg_ids[0]:
+                        raise SecurityCheckMismatch("The msg_id is lower than all the stored values")
+
+                    if msg.msg_id in self.stored_msg_ids:
+                        raise SecurityCheckMismatch("The msg_id is equal to any of the stored values")
+
+                    time_diff = (msg.msg_id - MsgId()) / 2 ** 32
+
+                    if time_diff > 30:
+                        raise SecurityCheckMismatch("The msg_id belongs to over 30 seconds in the future. "
+                                                    "Most likely the client time has to be synchronized.")
+
+                    if time_diff < -300:
+                        raise SecurityCheckMismatch("The msg_id belongs to over 300 seconds in the past. "
+                                                    "Most likely the client time has to be synchronized.")
+            except SecurityCheckMismatch as e:
+                log.info("Discarding packet: %s", e)
+                await self.connection.close()
+                return
+            else:
+                bisect.insort(self.stored_msg_ids, msg.msg_id)
+
             if isinstance(msg.body, (raw.types.MsgDetailedInfo, raw.types.MsgNewDetailedInfo)):
                 self.pending_acks.add(msg.body.answer_msg_id)
                 continue
@@ -272,11 +259,11 @@ async def handle_packet(self, packet):
                 self.results[msg_id].event.set()
 
         if len(self.pending_acks) >= self.ACKS_THRESHOLD:
-            log.debug(f"Send {len(self.pending_acks)} acks")
+            log.debug("Sending %s acks", len(self.pending_acks))
 
             try:
-                await self._send(raw.types.MsgsAck(msg_ids=list(self.pending_acks)), False)
-            except (OSError, TimeoutError):
+                await self.send(raw.types.MsgsAck(msg_ids=list(self.pending_acks)), False)
+            except OSError:
                 pass
             else:
                 self.pending_acks.clear()
@@ -293,46 +280,17 @@ async def ping_worker(self):
                 break
 
             try:
-                await self._send(
+                await self.send(
                     raw.functions.PingDelayDisconnect(
                         ping_id=0, disconnect_delay=self.WAIT_TIMEOUT + 10
                     ), False
                 )
-            except (OSError, TimeoutError, RPCError):
+            except (OSError, RPCError):
                 pass
 
         log.info("PingTask stopped")
 
-    async def next_salt_worker(self):
-        log.info("NextSaltTask started")
-
-        while True:
-            now = datetime.fromtimestamp(time.perf_counter() - MsgId.reference_clock + MsgId.server_time)
-
-            # Seconds to wait until middle-overlap, which is
-            # 15 minutes before/after the current/next salt end/start time
-            valid_until = datetime.fromtimestamp(self.current_salt.valid_until)
-            dt = (valid_until - now).total_seconds() - 900
-
-            minutes, seconds = divmod(int(dt), 60)
-            log.info(f"Next salt in {minutes:.0f}m {seconds:.0f}s (at {now + timedelta(seconds=dt)})")
-
-            try:
-                await asyncio.wait_for(self.next_salt_task_event.wait(), dt)
-            except asyncio.TimeoutError:
-                pass
-            else:
-                break
-
-            try:
-                self.current_salt = (await self._send(raw.functions.GetFutureSalts(num=1))).salts[0]
-            except (OSError, TimeoutError, RPCError):
-                self.connection.close()
-                break
-
-        log.info("NextSaltTask stopped")
-
-    async def network_worker(self):
+    async def recv_worker(self):
         log.info("NetworkTask started")
 
         while True:
@@ -340,9 +298,14 @@ async def network_worker(self):
 
             if packet is None or len(packet) == 4:
                 if packet:
-                    log.warning(f'Server sent "{Int.read(BytesIO(packet))}"')
+                    error_code = -Int.read(BytesIO(packet))
+
+                    log.warning(
+                        "Server sent transport error: %s (%s)",
+                        error_code, Session.TRANSPORT_ERRORS.get(error_code, "unknown error")
+                    )
 
-                if self.is_connected.is_set():
+                if self.is_started.is_set():
                     self.loop.create_task(self.restart())
 
                 break
@@ -351,23 +314,20 @@ async def network_worker(self):
 
         log.info("NetworkTask stopped")
 
-    async def _send(self, data: TLObject, wait_response: bool = True, timeout: float = WAIT_TIMEOUT):
+    async def send(self, data: TLObject, wait_response: bool = True, timeout: float = WAIT_TIMEOUT):
         message = self.msg_factory(data)
         msg_id = message.msg_id
 
         if wait_response:
             self.results[msg_id] = Result()
 
-        # Call log.debug twice because calling it once by appending "data" to the previous string (i.e. f"Kind: {data}")
-        # will cause "data" to be evaluated as string every time instead of only when debug is actually enabled.
-        log.debug(f"Sent:")
-        log.debug(message)
+        log.debug("Sent: %s", message)
 
         payload = await self.loop.run_in_executor(
             pyrogram.crypto_executor,
             mtproto.pack,
             message,
-            self.current_salt.salt,
+            self.salt,
             self.session_id,
             self.auth_key,
             self.auth_key_id
@@ -384,62 +344,69 @@ async def _send(self, data: TLObject, wait_response: bool = True, timeout: float
                 await asyncio.wait_for(self.results[msg_id].event.wait(), timeout)
             except asyncio.TimeoutError:
                 pass
-            finally:
-                result = self.results.pop(msg_id).value
+
+            result = self.results.pop(msg_id).value
 
             if result is None:
-                raise TimeoutError
-            elif isinstance(result, raw.types.RpcError):
+                raise TimeoutError("Request timed out")
+
+            if isinstance(result, raw.types.RpcError):
                 if isinstance(data, (raw.functions.InvokeWithoutUpdates, raw.functions.InvokeWithTakeout)):
                     data = data.query
 
                 RPCError.raise_it(result, type(data))
-            elif isinstance(result, raw.types.BadMsgNotification):
-                raise Exception(self.BAD_MSG_DESCRIPTION.get(
-                    result.error_code,
-                    f"Error code {result.error_code}"
-                ))
-            else:
-                return result
 
-    async def send(
+            if isinstance(result, raw.types.BadMsgNotification):
+                log.warning("%s: %s", BadMsgNotification.__name__, BadMsgNotification(result.error_code))
+
+            if isinstance(result, raw.types.BadServerSalt):
+                self.salt = result.new_server_salt
+                return await self.send(data, wait_response, timeout)
+
+            return result
+
+    async def invoke(
         self,
-        data: TLObject,
+        query: TLObject,
         retries: int = MAX_RETRIES,
         timeout: float = WAIT_TIMEOUT,
         sleep_threshold: float = SLEEP_THRESHOLD
     ):
         try:
-            await asyncio.wait_for(self.is_connected.wait(), self.WAIT_TIMEOUT)
+            await asyncio.wait_for(self.is_started.wait(), self.WAIT_TIMEOUT)
         except asyncio.TimeoutError:
             pass
 
-        if isinstance(data, (raw.functions.InvokeWithoutUpdates, raw.functions.InvokeWithTakeout)):
-            query = data.query
+        if isinstance(query, (raw.functions.InvokeWithoutUpdates, raw.functions.InvokeWithTakeout)):
+            inner_query = query.query
         else:
-            query = data
+            inner_query = query
 
-        query = ".".join(query.QUALNAME.split(".")[1:])
+        query_name = ".".join(inner_query.QUALNAME.split(".")[1:])
 
         while True:
             try:
-                return await self._send(data, timeout=timeout)
+                return await self.send(query, timeout=timeout)
             except FloodWait as e:
-                amount = e.x
+                amount = e.value
 
                 if amount > sleep_threshold >= 0:
                     raise
 
-                log.warning(f'[{self.client.session_name}] Sleeping for {amount}s (required by "{query}")')
+                log.warning('[%s] Waiting for %s seconds before continuing (required by "%s")',
+                            self.client.name, amount, query_name)
 
                 await asyncio.sleep(amount)
-            except (OSError, TimeoutError, InternalServerError, ServiceUnavailable) as e:
+            except (OSError, InternalServerError, ServiceUnavailable) as e:
                 if retries == 0:
                     raise e from None
 
                 (log.warning if retries < 2 else log.info)(
-                    f'[{Session.MAX_RETRIES - retries + 1}] Retrying "{query}" due to {str(e) or repr(e)}')
+                    '[%s] Retrying "%s" due to: %s',
+                    Session.MAX_RETRIES - retries + 1,
+                    query_name, str(e) or repr(e)
+                )
 
                 await asyncio.sleep(0.5)
 
-                return await self.send(data, retries - 1, timeout)
+                return await self.invoke(query, retries - 1, timeout)
diff --git a/pyrogram/storage/__init__.py b/pyrogram/storage/__init__.py
index a73bd5d4df..2a43309a1b 100644
--- a/pyrogram/storage/__init__.py
+++ b/pyrogram/storage/__init__.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
diff --git a/pyrogram/storage/file_storage.py b/pyrogram/storage/file_storage.py
index 8910a2563b..aebe917671 100644
--- a/pyrogram/storage/file_storage.py
+++ b/pyrogram/storage/file_storage.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
@@ -16,8 +16,6 @@
 #  You should have received a copy of the GNU Lesser General Public License
 #  along with Pyrogram.  If not, see .
 
-import base64
-import json
 import logging
 import os
 import sqlite3
@@ -36,74 +34,27 @@ def __init__(self, name: str, workdir: Path):
 
         self.database = workdir / (self.name + self.FILE_EXTENSION)
 
-    def migrate_from_json(self, session_json: dict):
-        self.open()
-
-        self.dc_id(session_json["dc_id"])
-        self.test_mode(session_json["test_mode"])
-        self.auth_key(base64.b64decode("".join(session_json["auth_key"])))
-        self.user_id(session_json["user_id"])
-        self.date(session_json.get("date", 0))
-        self.is_bot(session_json.get("is_bot", False))
-
-        peers_by_id = session_json.get("peers_by_id", {})
-        peers_by_phone = session_json.get("peers_by_phone", {})
-
-        peers = {}
-
-        for k, v in peers_by_id.items():
-            if v is None:
-                type_ = "group"
-            elif k.startswith("-100"):
-                type_ = "channel"
-            else:
-                type_ = "user"
-
-            peers[int(k)] = [int(k), int(v) if v is not None else None, type_, None, None]
-
-        for k, v in peers_by_phone.items():
-            peers[v][4] = k
-
-        # noinspection PyTypeChecker
-        self.update_peers(peers.values())
-
     def update(self):
         version = self.version()
 
         if version == 1:
-            with self.lock, self.conn:
+            with self.conn:
                 self.conn.execute("DELETE FROM peers")
 
             version += 1
 
+        if version == 2:
+            with self.conn:
+                self.conn.execute("ALTER TABLE sessions ADD api_id INTEGER")
+
+            version += 1
+
         self.version(version)
 
     async def open(self):
         path = self.database
         file_exists = path.is_file()
 
-        if file_exists:
-            try:
-                with open(str(path), encoding="utf-8") as f:
-                    session_json = json.load(f)
-            except ValueError:
-                pass
-            else:
-                log.warning("JSON session storage detected! Converting it into an SQLite session storage...")
-
-                path.rename(path.name + ".OLD")
-
-                log.warning(f'The old session file has been renamed to "{path.name}.OLD"')
-
-                self.migrate_from_json(session_json)
-
-                log.warning("Done! The session has been successfully converted from JSON to SQLite storage")
-
-                return
-
-        if Path(path.name + ".OLD").is_file():
-            log.warning(f'Old session file detected: "{path.name}.OLD". You can remove this file now')
-
         self.conn = sqlite3.connect(str(path), timeout=1, check_same_thread=False)
 
         if not file_exists:
@@ -112,10 +63,7 @@ async def open(self):
             self.update()
 
         with self.conn:
-            try:  # Python 3.6.0 (exactly this version) is bugged and won't successfully execute the vacuum
-                self.conn.execute("VACUUM")
-            except sqlite3.OperationalError:
-                pass
+            self.conn.execute("VACUUM")
 
     async def delete(self):
         os.remove(self.database)
diff --git a/pyrogram/storage/memory_storage.py b/pyrogram/storage/memory_storage.py
index 4934354051..2c01f4474d 100644
--- a/pyrogram/storage/memory_storage.py
+++ b/pyrogram/storage/memory_storage.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
@@ -27,22 +27,42 @@
 
 
 class MemoryStorage(SQLiteStorage):
-    def __init__(self, name: str):
+    def __init__(self, name: str, session_string: str = None):
         super().__init__(name)
 
+        self.session_string = session_string
+
     async def open(self):
         self.conn = sqlite3.connect(":memory:", check_same_thread=False)
         self.create()
 
-        if self.name != ":memory:":
-            dc_id, test_mode, auth_key, user_id, is_bot = struct.unpack(
-                self.SESSION_STRING_FORMAT,
-                base64.urlsafe_b64decode(
-                    self.name + "=" * (-len(self.name) % 4)
+        if self.session_string:
+            # Old format
+            if len(self.session_string) in [self.SESSION_STRING_SIZE, self.SESSION_STRING_SIZE_64]:
+                dc_id, test_mode, auth_key, user_id, is_bot = struct.unpack(
+                    (self.OLD_SESSION_STRING_FORMAT
+                     if len(self.session_string) == self.SESSION_STRING_SIZE else
+                     self.OLD_SESSION_STRING_FORMAT_64),
+                    base64.urlsafe_b64decode(self.session_string + "=" * (-len(self.session_string) % 4))
                 )
+
+                await self.dc_id(dc_id)
+                await self.test_mode(test_mode)
+                await self.auth_key(auth_key)
+                await self.user_id(user_id)
+                await self.is_bot(is_bot)
+                await self.date(0)
+
+                log.warning("You are using an old session string format. Use export_session_string to update")
+                return
+
+            dc_id, api_id, test_mode, auth_key, user_id, is_bot = struct.unpack(
+                self.SESSION_STRING_FORMAT,
+                base64.urlsafe_b64decode(self.session_string + "=" * (-len(self.session_string) % 4))
             )
 
             await self.dc_id(dc_id)
+            await self.api_id(api_id)
             await self.test_mode(test_mode)
             await self.auth_key(auth_key)
             await self.user_id(user_id)
diff --git a/pyrogram/storage/sqlite_storage.py b/pyrogram/storage/sqlite_storage.py
index 73bbb53408..e28b9b746e 100644
--- a/pyrogram/storage/sqlite_storage.py
+++ b/pyrogram/storage/sqlite_storage.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
@@ -19,7 +19,6 @@
 import inspect
 import sqlite3
 import time
-from threading import Lock
 from typing import List, Tuple, Any
 
 from pyrogram import raw
@@ -31,6 +30,7 @@
 CREATE TABLE sessions
 (
     dc_id     INTEGER PRIMARY KEY,
+    api_id    INTEGER,
     test_mode INTEGER,
     auth_key  BLOB,
     date      INTEGER NOT NULL,
@@ -90,17 +90,16 @@ def get_input_peer(peer_id: int, access_hash: int, peer_type: str):
 
 
 class SQLiteStorage(Storage):
-    VERSION = 2
+    VERSION = 3
     USERNAME_TTL = 8 * 60 * 60
 
     def __init__(self, name: str):
         super().__init__(name)
 
         self.conn = None  # type: sqlite3.Connection
-        self.lock = Lock()
 
     def create(self):
-        with self.lock, self.conn:
+        with self.conn:
             self.conn.executescript(SCHEMA)
 
             self.conn.execute(
@@ -109,8 +108,8 @@ def create(self):
             )
 
             self.conn.execute(
-                "INSERT INTO sessions VALUES (?, ?, ?, ?, ?, ?)",
-                (2, None, None, 0, None, None)
+                "INSERT INTO sessions VALUES (?, ?, ?, ?, ?, ?, ?)",
+                (2, None, None, None, 0, None, None)
             )
 
     async def open(self):
@@ -118,24 +117,20 @@ async def open(self):
 
     async def save(self):
         await self.date(int(time.time()))
-
-        with self.lock:
-            self.conn.commit()
+        self.conn.commit()
 
     async def close(self):
-        with self.lock:
-            self.conn.close()
+        self.conn.close()
 
     async def delete(self):
         raise NotImplementedError
 
     async def update_peers(self, peers: List[Tuple[int, int, str, str, str]]):
-        with self.lock:
-            self.conn.executemany(
-                "REPLACE INTO peers (id, access_hash, type, username, phone_number)"
-                "VALUES (?, ?, ?, ?, ?)",
-                peers
-            )
+        self.conn.executemany(
+            "REPLACE INTO peers (id, access_hash, type, username, phone_number)"
+            "VALUES (?, ?, ?, ?, ?)",
+            peers
+        )
 
     async def get_peer_by_id(self, peer_id: int):
         r = self.conn.execute(
@@ -150,7 +145,8 @@ async def get_peer_by_id(self, peer_id: int):
 
     async def get_peer_by_username(self, username: str):
         r = self.conn.execute(
-            "SELECT id, access_hash, type, last_update_on FROM peers WHERE username = ?",
+            "SELECT id, access_hash, type, last_update_on FROM peers WHERE username = ?"
+            "ORDER BY last_update_on DESC",
             (username,)
         ).fetchone()
 
@@ -183,7 +179,7 @@ def _get(self):
     def _set(self, value: Any):
         attr = inspect.stack()[2].function
 
-        with self.lock, self.conn:
+        with self.conn:
             self.conn.execute(
                 f"UPDATE sessions SET {attr} = ?",
                 (value,)
@@ -195,6 +191,9 @@ def _accessor(self, value: Any = object):
     async def dc_id(self, value: int = object):
         return self._accessor(value)
 
+    async def api_id(self, value: int = object):
+        return self._accessor(value)
+
     async def test_mode(self, value: bool = object):
         return self._accessor(value)
 
@@ -216,7 +215,7 @@ def version(self, value: int = object):
                 "SELECT number FROM version"
             ).fetchone()[0]
         else:
-            with self.lock, self.conn:
+            with self.conn:
                 self.conn.execute(
                     "UPDATE version SET number = ?",
                     (value,)
diff --git a/pyrogram/storage/storage.py b/pyrogram/storage/storage.py
index c506c8ca04..0689b6826c 100644
--- a/pyrogram/storage/storage.py
+++ b/pyrogram/storage/storage.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
@@ -22,8 +22,12 @@
 
 
 class Storage:
-    SESSION_STRING_FORMAT = ">B?256sI?"
+    OLD_SESSION_STRING_FORMAT = ">B?256sI?"
+    OLD_SESSION_STRING_FORMAT_64 = ">B?256sQ?"
     SESSION_STRING_SIZE = 351
+    SESSION_STRING_SIZE_64 = 356
+
+    SESSION_STRING_FORMAT = ">BI?256sQ?"
 
     def __init__(self, name: str):
         self.name = name
@@ -55,6 +59,9 @@ async def get_peer_by_phone_number(self, phone_number: str):
     async def dc_id(self, value: int = object):
         raise NotImplementedError
 
+    async def api_id(self, value: int = object):
+        raise NotImplementedError
+
     async def test_mode(self, value: bool = object):
         raise NotImplementedError
 
@@ -71,13 +78,14 @@ async def is_bot(self, value: bool = object):
         raise NotImplementedError
 
     async def export_session_string(self):
-        return base64.urlsafe_b64encode(
-            struct.pack(
-                self.SESSION_STRING_FORMAT,
-                await self.dc_id(),
-                await self.test_mode(),
-                await self.auth_key(),
-                await self.user_id(),
-                await self.is_bot()
-            )
-        ).decode().rstrip("=")
+        packed = struct.pack(
+            self.SESSION_STRING_FORMAT,
+            await self.dc_id(),
+            await self.api_id(),
+            await self.test_mode(),
+            await self.auth_key(),
+            await self.user_id(),
+            await self.is_bot()
+        )
+
+        return base64.urlsafe_b64encode(packed).decode().rstrip("=")
diff --git a/pyrogram/sync.py b/pyrogram/sync.py
index d94d490c41..94c82a3dd6 100644
--- a/pyrogram/sync.py
+++ b/pyrogram/sync.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
@@ -23,15 +23,30 @@
 
 from pyrogram import types
 from pyrogram.methods import Methods
-from pyrogram.methods.utilities import idle as idle_module
+from pyrogram.methods.utilities import idle as idle_module, compose as compose_module
 
 
 def async_to_sync(obj, name):
     function = getattr(obj, name)
     main_loop = asyncio.get_event_loop()
 
-    async def consume_generator(coroutine):
-        return types.List([i async for i in coroutine])
+    def async_to_sync_gen(agen, loop, is_main_thread):
+        async def anext(agen):
+            try:
+                return await agen.__anext__(), False
+            except StopAsyncIteration:
+                return None, True
+
+        while True:
+            if is_main_thread:
+                item, done = loop.run_until_complete(anext(agen))
+            else:
+                item, done = asyncio.run_coroutine_threadsafe(anext(agen), loop).result()
+
+            if done:
+                break
+
+            yield item
 
     @functools.wraps(function)
     def async_to_sync_wrap(*args, **kwargs):
@@ -43,7 +58,7 @@ def async_to_sync_wrap(*args, **kwargs):
             loop = asyncio.new_event_loop()
             asyncio.set_event_loop(loop)
 
-        if threading.current_thread() is threading.main_thread():
+        if threading.current_thread() is threading.main_thread() or not main_loop.is_running():
             if loop.is_running():
                 return coroutine
             else:
@@ -51,7 +66,7 @@ def async_to_sync_wrap(*args, **kwargs):
                     return loop.run_until_complete(coroutine)
 
                 if inspect.isasyncgen(coroutine):
-                    return loop.run_until_complete(consume_generator(coroutine))
+                    return async_to_sync_gen(coroutine, loop, True)
         else:
             if inspect.iscoroutine(coroutine):
                 if loop.is_running():
@@ -66,7 +81,7 @@ async def coro_wrapper():
                 if loop.is_running():
                     return coroutine
                 else:
-                    return asyncio.run_coroutine_threadsafe(consume_generator(coroutine), main_loop).result()
+                    return async_to_sync_gen(coroutine, main_loop, False)
 
     setattr(obj, name, async_to_sync_wrap)
 
@@ -90,6 +105,9 @@ def wrap(source):
     if inspect.isclass(cls):
         wrap(cls)
 
-# Special case for idle, because it's not inside Methods
+# Special case for idle and compose, because they are not inside Methods
 async_to_sync(idle_module, "idle")
 idle = getattr(idle_module, "idle")
+
+async_to_sync(compose_module, "compose")
+compose = getattr(compose_module, "compose")
diff --git a/pyrogram/syncer.py b/pyrogram/syncer.py
deleted file mode 100644
index dbd8dc6495..0000000000
--- a/pyrogram/syncer.py
+++ /dev/null
@@ -1,88 +0,0 @@
-#  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
-#
-#  This file is part of Pyrogram.
-#
-#  Pyrogram is free software: you can redistribute it and/or modify
-#  it under the terms of the GNU Lesser General Public License as published
-#  by the Free Software Foundation, either version 3 of the License, or
-#  (at your option) any later version.
-#
-#  Pyrogram is distributed in the hope that it will be useful,
-#  but WITHOUT ANY WARRANTY; without even the implied warranty of
-#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-#  GNU Lesser General Public License for more details.
-#
-#  You should have received a copy of the GNU Lesser General Public License
-#  along with Pyrogram.  If not, see .
-
-import asyncio
-import logging
-import time
-
-log = logging.getLogger(__name__)
-
-
-class Syncer:
-    INTERVAL = 20
-
-    clients = {}
-    event = None
-    lock = None
-
-    @classmethod
-    async def add(cls, client):
-        if cls.event is None:
-            cls.event = asyncio.Event()
-
-        if cls.lock is None:
-            cls.lock = asyncio.Lock()
-
-        async with cls.lock:
-            await cls.sync(client)
-
-            cls.clients[id(client)] = client
-
-            if len(cls.clients) == 1:
-                cls.start()
-
-    @classmethod
-    async def remove(cls, client):
-        async with cls.lock:
-            await cls.sync(client)
-
-            del cls.clients[id(client)]
-
-            if len(cls.clients) == 0:
-                cls.stop()
-
-    @classmethod
-    def start(cls):
-        cls.event.clear()
-        asyncio.get_event_loop().create_task(cls.worker())
-
-    @classmethod
-    def stop(cls):
-        cls.event.set()
-
-    @classmethod
-    async def worker(cls):
-        while True:
-            try:
-                await asyncio.wait_for(cls.event.wait(), cls.INTERVAL)
-            except asyncio.TimeoutError:
-                async with cls.lock:
-                    for client in cls.clients.values():
-                        await cls.sync(client)
-            else:
-                break
-
-    @classmethod
-    async def sync(cls, client):
-        try:
-            start = time.time()
-            await client.storage.save()
-        except Exception as e:
-            log.critical(e, exc_info=True)
-        else:
-            log.debug(f'Synced "{client.storage.name}" in {(time.time() - start) * 1000:.6} ms')
diff --git a/pyrogram/types/__init__.py b/pyrogram/types/__init__.py
index bad120f105..e0859bbabd 100644
--- a/pyrogram/types/__init__.py
+++ b/pyrogram/types/__init__.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
diff --git a/pyrogram/types/authorization/__init__.py b/pyrogram/types/authorization/__init__.py
index d5b491da57..b31ad26a53 100644
--- a/pyrogram/types/authorization/__init__.py
+++ b/pyrogram/types/authorization/__init__.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
diff --git a/pyrogram/types/authorization/sent_code.py b/pyrogram/types/authorization/sent_code.py
index 1f4399013f..2b29bb3af4 100644
--- a/pyrogram/types/authorization/sent_code.py
+++ b/pyrogram/types/authorization/sent_code.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
@@ -16,7 +16,7 @@
 #  You should have received a copy of the GNU Lesser General Public License
 #  along with Pyrogram.  If not, see .
 
-from pyrogram import raw
+from pyrogram import raw, enums
 from ..object import Object
 
 
@@ -24,29 +24,25 @@ class SentCode(Object):
     """Contains info on a sent confirmation code.
 
     Parameters:
-        type (``str``):
+        type (:obj:`~pyrogram.enums.SentCodeType`):
             Type of the current sent code.
-            Can be *"app"* (code sent via Telegram), *"sms"* (code sent via SMS), *"call"* (code sent via voice call) or
-            *"flash_call"* (code is in the last 5 digits of the caller's phone number).
 
         phone_code_hash (``str``):
             Confirmation code identifier useful for the next authorization steps (either
             :meth:`~pyrogram.Client.sign_in` or :meth:`~pyrogram.Client.sign_up`).
 
-        next_type (``str``):
+        next_type (:obj:`~pyrogram.enums.NextCodeType`, *optional*):
             Type of the next code to be sent with :meth:`~pyrogram.Client.resend_code`.
-            Can be *"sms"* (code will be sent via SMS), *"call"* (code will be sent via voice call) or *"flash_call"*
-            (code will be in the last 5 digits of caller's phone number).
 
-        timeout (``int``):
+        timeout (``int``, *optional*):
             Delay in seconds before calling :meth:`~pyrogram.Client.resend_code`.
     """
 
     def __init__(
         self, *,
-        type: str,
+        type: "enums.SentCodeType",
         phone_code_hash: str,
-        next_type: str = None,
+        next_type: "enums.NextCodeType" = None,
         timeout: int = None
     ):
         super().__init__()
@@ -58,29 +54,9 @@ def __init__(
 
     @staticmethod
     def _parse(sent_code: raw.types.auth.SentCode) -> "SentCode":
-        type = sent_code.type
-
-        if isinstance(type, raw.types.auth.SentCodeTypeApp):
-            type = "app"
-        elif isinstance(type, raw.types.auth.SentCodeTypeSms):
-            type = "sms"
-        elif isinstance(type, raw.types.auth.SentCodeTypeCall):
-            type = "call"
-        elif isinstance(type, raw.types.auth.SentCodeTypeFlashCall):
-            type = "flash_call"
-
-        next_type = sent_code.next_type
-
-        if isinstance(next_type, raw.types.auth.CodeTypeSms):
-            next_type = "sms"
-        elif isinstance(next_type, raw.types.auth.CodeTypeCall):
-            next_type = "call"
-        elif isinstance(next_type, raw.types.auth.CodeTypeFlashCall):
-            next_type = "flash_call"
-
         return SentCode(
-            type=type,
+            type=enums.SentCodeType(type(sent_code.type)),
             phone_code_hash=sent_code.phone_code_hash,
-            next_type=next_type,
+            next_type=enums.NextCodeType(type(sent_code.next_type)) if sent_code.next_type else None,
             timeout=sent_code.timeout
         )
diff --git a/pyrogram/types/authorization/terms_of_service.py b/pyrogram/types/authorization/terms_of_service.py
index db465e643a..3c5ffa6c6d 100644
--- a/pyrogram/types/authorization/terms_of_service.py
+++ b/pyrogram/types/authorization/terms_of_service.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
@@ -52,5 +52,5 @@ def _parse(terms_of_service: "raw.types.help.TermsOfService") -> "TermsOfService
             entities=[
                 types.MessageEntity._parse(None, entity, {})
                 for entity in terms_of_service.entities
-            ]
+            ] if terms_of_service.entities else None
         )
diff --git a/pyrogram/types/bots_and_keyboards/__init__.py b/pyrogram/types/bots_and_keyboards/__init__.py
index a1f3a662d8..6f05a3b486 100644
--- a/pyrogram/types/bots_and_keyboards/__init__.py
+++ b/pyrogram/types/bots_and_keyboards/__init__.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
@@ -16,6 +16,15 @@
 #  You should have received a copy of the GNU Lesser General Public License
 #  along with Pyrogram.  If not, see .
 
+from .bot_command import BotCommand
+from .bot_command_scope import BotCommandScope
+from .bot_command_scope_all_chat_administrators import BotCommandScopeAllChatAdministrators
+from .bot_command_scope_all_group_chats import BotCommandScopeAllGroupChats
+from .bot_command_scope_all_private_chats import BotCommandScopeAllPrivateChats
+from .bot_command_scope_chat import BotCommandScopeChat
+from .bot_command_scope_chat_administrators import BotCommandScopeChatAdministrators
+from .bot_command_scope_chat_member import BotCommandScopeChatMember
+from .bot_command_scope_default import BotCommandScopeDefault
 from .callback_game import CallbackGame
 from .callback_query import CallbackQuery
 from .force_reply import ForceReply
@@ -24,9 +33,14 @@
 from .inline_keyboard_markup import InlineKeyboardMarkup
 from .keyboard_button import KeyboardButton
 from .login_url import LoginUrl
+from .menu_button import MenuButton
+from .menu_button_commands import MenuButtonCommands
+from .menu_button_default import MenuButtonDefault
+from .menu_button_web_app import MenuButtonWebApp
 from .reply_keyboard_markup import ReplyKeyboardMarkup
 from .reply_keyboard_remove import ReplyKeyboardRemove
-from .bot_command import BotCommand
+from .sent_web_app_message import SentWebAppMessage
+from .web_app_info import WebAppInfo
 
 __all__ = [
     "CallbackGame",
@@ -40,4 +54,18 @@
     "ReplyKeyboardRemove",
     "LoginUrl",
     "BotCommand",
+    "BotCommandScope",
+    "BotCommandScopeAllChatAdministrators",
+    "BotCommandScopeAllGroupChats",
+    "BotCommandScopeAllPrivateChats",
+    "BotCommandScopeChat",
+    "BotCommandScopeChatAdministrators",
+    "BotCommandScopeChatMember",
+    "BotCommandScopeDefault",
+    "WebAppInfo",
+    "MenuButton",
+    "MenuButtonCommands",
+    "MenuButtonWebApp",
+    "MenuButtonDefault",
+    "SentWebAppMessage"
 ]
diff --git a/pyrogram/types/bots_and_keyboards/bot_command.py b/pyrogram/types/bots_and_keyboards/bot_command.py
index f2db126a53..88f459dcc4 100644
--- a/pyrogram/types/bots_and_keyboards/bot_command.py
+++ b/pyrogram/types/bots_and_keyboards/bot_command.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
@@ -17,6 +17,7 @@
 #  along with Pyrogram.  If not, see .
 
 from pyrogram import raw
+
 from ..object import Object
 
 
@@ -25,10 +26,11 @@ class BotCommand(Object):
 
     Parameters:
         command (``str``):
-            The bot command, for example: "/start".
-            
+            Text of the command; 1-32 characters.
+            Can contain only lowercase English letters, digits and underscores.
+
         description (``str``):
-            Description of the bot command.
+            Description of the command; 1-256 characters.
     """
 
     def __init__(self, command: str, description: str):
@@ -37,8 +39,15 @@ def __init__(self, command: str, description: str):
         self.command = command
         self.description = description
 
-    def write(self):
+    def write(self) -> "raw.types.BotCommand":
         return raw.types.BotCommand(
             command=self.command,
-            description=self.description
+            description=self.description,
+        )
+
+    @staticmethod
+    def read(c: "raw.types.BotCommand") -> "BotCommand":
+        return BotCommand(
+            command=c.command,
+            description=c.description
         )
diff --git a/pyrogram/types/bots_and_keyboards/bot_command_scope.py b/pyrogram/types/bots_and_keyboards/bot_command_scope.py
new file mode 100644
index 0000000000..2db638e9a7
--- /dev/null
+++ b/pyrogram/types/bots_and_keyboards/bot_command_scope.py
@@ -0,0 +1,73 @@
+#  Pyrogram - Telegram MTProto API Client Library for Python
+#  Copyright (C) 2017-present Dan 
+#
+#  This file is part of Pyrogram.
+#
+#  Pyrogram is free software: you can redistribute it and/or modify
+#  it under the terms of the GNU Lesser General Public License as published
+#  by the Free Software Foundation, either version 3 of the License, or
+#  (at your option) any later version.
+#
+#  Pyrogram is distributed in the hope that it will be useful,
+#  but WITHOUT ANY WARRANTY; without even the implied warranty of
+#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#  GNU Lesser General Public License for more details.
+#
+#  You should have received a copy of the GNU Lesser General Public License
+#  along with Pyrogram.  If not, see .
+
+import pyrogram
+from pyrogram import raw
+from ..object import Object
+
+
+class BotCommandScope(Object):
+    """Represents the scope to which bot commands are applied.
+
+    Currently, the following 7 scopes are supported:
+
+    - :obj:`~pyrogram.types.BotCommandScopeDefault`
+    - :obj:`~pyrogram.types.BotCommandScopeAllPrivateChats`
+    - :obj:`~pyrogram.types.BotCommandScopeAllGroupChats`
+    - :obj:`~pyrogram.types.BotCommandScopeAllChatAdministrators`
+    - :obj:`~pyrogram.types.BotCommandScopeChat`
+    - :obj:`~pyrogram.types.BotCommandScopeChatAdministrators`
+    - :obj:`~pyrogram.types.BotCommandScopeChatMember`
+
+    **Determining list of commands**
+
+    The following algorithm is used to determine the list of commands for a particular user viewing the bot menu.
+    The first list of commands which is set is returned:
+
+    **Commands in the chat with the bot**:
+
+    - BotCommandScopeChat + language_code
+    - BotCommandScopeChat
+    - BotCommandScopeAllPrivateChats + language_code
+    - BotCommandScopeAllPrivateChats
+    - BotCommandScopeDefault + language_code
+    - BotCommandScopeDefault
+
+    **Commands in group and supergroup chats**
+
+    - BotCommandScopeChatMember + language_code
+    - BotCommandScopeChatMember
+    - BotCommandScopeChatAdministrators + language_code (administrators only)
+    - BotCommandScopeChatAdministrators (administrators only)
+    - BotCommandScopeChat + language_code
+    - BotCommandScopeChat
+    - BotCommandScopeAllChatAdministrators + language_code (administrators only)
+    - BotCommandScopeAllChatAdministrators (administrators only)
+    - BotCommandScopeAllGroupChats + language_code
+    - BotCommandScopeAllGroupChats
+    - BotCommandScopeDefault + language_code
+    - BotCommandScopeDefault
+    """
+
+    def __init__(self, type: str):
+        super().__init__()
+
+        self.type = type
+
+    async def write(self, client: "pyrogram.Client") -> "raw.base.BotCommandScope":
+        raise NotImplementedError
diff --git a/pyrogram/types/bots_and_keyboards/bot_command_scope_all_chat_administrators.py b/pyrogram/types/bots_and_keyboards/bot_command_scope_all_chat_administrators.py
new file mode 100644
index 0000000000..0cc6339c77
--- /dev/null
+++ b/pyrogram/types/bots_and_keyboards/bot_command_scope_all_chat_administrators.py
@@ -0,0 +1,32 @@
+#  Pyrogram - Telegram MTProto API Client Library for Python
+#  Copyright (C) 2017-present Dan 
+#
+#  This file is part of Pyrogram.
+#
+#  Pyrogram is free software: you can redistribute it and/or modify
+#  it under the terms of the GNU Lesser General Public License as published
+#  by the Free Software Foundation, either version 3 of the License, or
+#  (at your option) any later version.
+#
+#  Pyrogram is distributed in the hope that it will be useful,
+#  but WITHOUT ANY WARRANTY; without even the implied warranty of
+#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#  GNU Lesser General Public License for more details.
+#
+#  You should have received a copy of the GNU Lesser General Public License
+#  along with Pyrogram.  If not, see .
+
+import pyrogram
+from pyrogram import raw
+from .bot_command_scope import BotCommandScope
+
+
+class BotCommandScopeAllChatAdministrators(BotCommandScope):
+    """Represents the scope of bot commands, covering all group and supergroup chat administrators.
+    """
+
+    def __init__(self):
+        super().__init__("all_chat_administrators")
+
+    async def write(self, client: "pyrogram.Client") -> "raw.base.BotCommandScope":
+        return raw.types.BotCommandScopeChatAdmins()
diff --git a/pyrogram/types/bots_and_keyboards/bot_command_scope_all_group_chats.py b/pyrogram/types/bots_and_keyboards/bot_command_scope_all_group_chats.py
new file mode 100644
index 0000000000..5f8457f61d
--- /dev/null
+++ b/pyrogram/types/bots_and_keyboards/bot_command_scope_all_group_chats.py
@@ -0,0 +1,32 @@
+#  Pyrogram - Telegram MTProto API Client Library for Python
+#  Copyright (C) 2017-present Dan 
+#
+#  This file is part of Pyrogram.
+#
+#  Pyrogram is free software: you can redistribute it and/or modify
+#  it under the terms of the GNU Lesser General Public License as published
+#  by the Free Software Foundation, either version 3 of the License, or
+#  (at your option) any later version.
+#
+#  Pyrogram is distributed in the hope that it will be useful,
+#  but WITHOUT ANY WARRANTY; without even the implied warranty of
+#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#  GNU Lesser General Public License for more details.
+#
+#  You should have received a copy of the GNU Lesser General Public License
+#  along with Pyrogram.  If not, see .
+
+import pyrogram
+from pyrogram import raw
+from .bot_command_scope import BotCommandScope
+
+
+class BotCommandScopeAllGroupChats(BotCommandScope):
+    """Represents the scope of bot commands, covering all group and supergroup chats.
+    """
+
+    def __init__(self):
+        super().__init__("all_group_chats")
+
+    async def write(self, client: "pyrogram.Client") -> "raw.base.BotCommandScope":
+        return raw.types.BotCommandScopeChats()
diff --git a/pyrogram/types/bots_and_keyboards/bot_command_scope_all_private_chats.py b/pyrogram/types/bots_and_keyboards/bot_command_scope_all_private_chats.py
new file mode 100644
index 0000000000..14b80f0dcd
--- /dev/null
+++ b/pyrogram/types/bots_and_keyboards/bot_command_scope_all_private_chats.py
@@ -0,0 +1,32 @@
+#  Pyrogram - Telegram MTProto API Client Library for Python
+#  Copyright (C) 2017-present Dan 
+#
+#  This file is part of Pyrogram.
+#
+#  Pyrogram is free software: you can redistribute it and/or modify
+#  it under the terms of the GNU Lesser General Public License as published
+#  by the Free Software Foundation, either version 3 of the License, or
+#  (at your option) any later version.
+#
+#  Pyrogram is distributed in the hope that it will be useful,
+#  but WITHOUT ANY WARRANTY; without even the implied warranty of
+#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#  GNU Lesser General Public License for more details.
+#
+#  You should have received a copy of the GNU Lesser General Public License
+#  along with Pyrogram.  If not, see .
+
+import pyrogram
+from pyrogram import raw
+from .bot_command_scope import BotCommandScope
+
+
+class BotCommandScopeAllPrivateChats(BotCommandScope):
+    """Represents the scope of bot commands, covering all private chats.
+    """
+
+    def __init__(self):
+        super().__init__("all_private_chats")
+
+    async def write(self, client: "pyrogram.Client") -> "raw.base.BotCommandScope":
+        return raw.types.BotCommandScopeUsers()
diff --git a/pyrogram/types/bots_and_keyboards/bot_command_scope_chat.py b/pyrogram/types/bots_and_keyboards/bot_command_scope_chat.py
new file mode 100644
index 0000000000..28912383b7
--- /dev/null
+++ b/pyrogram/types/bots_and_keyboards/bot_command_scope_chat.py
@@ -0,0 +1,43 @@
+#  Pyrogram - Telegram MTProto API Client Library for Python
+#  Copyright (C) 2017-present Dan 
+#
+#  This file is part of Pyrogram.
+#
+#  Pyrogram is free software: you can redistribute it and/or modify
+#  it under the terms of the GNU Lesser General Public License as published
+#  by the Free Software Foundation, either version 3 of the License, or
+#  (at your option) any later version.
+#
+#  Pyrogram is distributed in the hope that it will be useful,
+#  but WITHOUT ANY WARRANTY; without even the implied warranty of
+#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#  GNU Lesser General Public License for more details.
+#
+#  You should have received a copy of the GNU Lesser General Public License
+#  along with Pyrogram.  If not, see .
+
+from typing import Union
+
+import pyrogram
+from pyrogram import raw
+from .bot_command_scope import BotCommandScope
+
+
+class BotCommandScopeChat(BotCommandScope):
+    """Represents the scope of bot commands, covering a specific chat.
+
+    Parameters:
+        chat_id (``int`` | ``str``):
+            Unique identifier for the target chat or username of the target supergroup (in the format
+            @supergroupusername).
+    """
+
+    def __init__(self, chat_id: Union[int, str]):
+        super().__init__("chat")
+
+        self.chat_id = chat_id
+
+    async def write(self, client: "pyrogram.Client") -> "raw.base.BotCommandScope":
+        return raw.types.BotCommandScopePeer(
+            peer=await client.resolve_peer(self.chat_id)
+        )
diff --git a/pyrogram/types/bots_and_keyboards/bot_command_scope_chat_administrators.py b/pyrogram/types/bots_and_keyboards/bot_command_scope_chat_administrators.py
new file mode 100644
index 0000000000..6f42a29f2b
--- /dev/null
+++ b/pyrogram/types/bots_and_keyboards/bot_command_scope_chat_administrators.py
@@ -0,0 +1,43 @@
+#  Pyrogram - Telegram MTProto API Client Library for Python
+#  Copyright (C) 2017-present Dan 
+#
+#  This file is part of Pyrogram.
+#
+#  Pyrogram is free software: you can redistribute it and/or modify
+#  it under the terms of the GNU Lesser General Public License as published
+#  by the Free Software Foundation, either version 3 of the License, or
+#  (at your option) any later version.
+#
+#  Pyrogram is distributed in the hope that it will be useful,
+#  but WITHOUT ANY WARRANTY; without even the implied warranty of
+#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#  GNU Lesser General Public License for more details.
+#
+#  You should have received a copy of the GNU Lesser General Public License
+#  along with Pyrogram.  If not, see .
+
+from typing import Union
+
+import pyrogram
+from pyrogram import raw
+from .bot_command_scope import BotCommandScope
+
+
+class BotCommandScopeChatAdministrators(BotCommandScope):
+    """Represents the scope of bot commands, covering all administrators of a specific group or supergroup chat.
+
+    Parameters:
+        chat_id (``int`` | ``str``):
+            Unique identifier for the target chat or username of the target supergroup (in the format
+            @supergroupusername).
+    """
+
+    def __init__(self, chat_id: Union[int, str]):
+        super().__init__("chat_administrators")
+
+        self.chat_id = chat_id
+
+    async def write(self, client: "pyrogram.Client") -> "raw.base.BotCommandScope":
+        return raw.types.BotCommandScopePeerAdmins(
+            peer=await client.resolve_peer(self.chat_id)
+        )
diff --git a/pyrogram/types/bots_and_keyboards/bot_command_scope_chat_member.py b/pyrogram/types/bots_and_keyboards/bot_command_scope_chat_member.py
new file mode 100644
index 0000000000..8d91df1413
--- /dev/null
+++ b/pyrogram/types/bots_and_keyboards/bot_command_scope_chat_member.py
@@ -0,0 +1,48 @@
+#  Pyrogram - Telegram MTProto API Client Library for Python
+#  Copyright (C) 2017-present Dan 
+#
+#  This file is part of Pyrogram.
+#
+#  Pyrogram is free software: you can redistribute it and/or modify
+#  it under the terms of the GNU Lesser General Public License as published
+#  by the Free Software Foundation, either version 3 of the License, or
+#  (at your option) any later version.
+#
+#  Pyrogram is distributed in the hope that it will be useful,
+#  but WITHOUT ANY WARRANTY; without even the implied warranty of
+#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#  GNU Lesser General Public License for more details.
+#
+#  You should have received a copy of the GNU Lesser General Public License
+#  along with Pyrogram.  If not, see .
+
+from typing import Union
+
+import pyrogram
+from pyrogram import raw
+from .bot_command_scope import BotCommandScope
+
+
+class BotCommandScopeChatMember(BotCommandScope):
+    """Represents the scope of bot commands, covering a specific member of a group or supergroup chat.
+
+    Parameters:
+        chat_id (``int`` | ``str``):
+            Unique identifier for the target chat or username of the target supergroup (in the format
+            @supergroupusername).
+
+        user_id (``int`` | ``str``):
+            Unique identifier of the target user.
+    """
+
+    def __init__(self, chat_id: Union[int, str], user_id: Union[int, str]):
+        super().__init__("chat_member")
+
+        self.chat_id = chat_id
+        self.user_id = user_id
+
+    async def write(self, client: "pyrogram.Client") -> "raw.base.BotCommandScope":
+        return raw.types.BotCommandScopePeerUser(
+            peer=await client.resolve_peer(self.chat_id),
+            user_id=await client.resolve_peer(self.user_id)
+        )
diff --git a/pyrogram/types/bots_and_keyboards/bot_command_scope_default.py b/pyrogram/types/bots_and_keyboards/bot_command_scope_default.py
new file mode 100644
index 0000000000..d11ab012f8
--- /dev/null
+++ b/pyrogram/types/bots_and_keyboards/bot_command_scope_default.py
@@ -0,0 +1,33 @@
+#  Pyrogram - Telegram MTProto API Client Library for Python
+#  Copyright (C) 2017-present Dan 
+#
+#  This file is part of Pyrogram.
+#
+#  Pyrogram is free software: you can redistribute it and/or modify
+#  it under the terms of the GNU Lesser General Public License as published
+#  by the Free Software Foundation, either version 3 of the License, or
+#  (at your option) any later version.
+#
+#  Pyrogram is distributed in the hope that it will be useful,
+#  but WITHOUT ANY WARRANTY; without even the implied warranty of
+#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#  GNU Lesser General Public License for more details.
+#
+#  You should have received a copy of the GNU Lesser General Public License
+#  along with Pyrogram.  If not, see .
+
+import pyrogram
+from pyrogram import raw
+from .bot_command_scope import BotCommandScope
+
+
+class BotCommandScopeDefault(BotCommandScope):
+    """Represents the default scope of bot commands.
+    Default commands are used if no commands with a narrower scope are specified for the user.
+    """
+
+    def __init__(self):
+        super().__init__("default")
+
+    async def write(self, client: "pyrogram.Client") -> "raw.base.BotCommandScope":
+        return raw.types.BotCommandScopeDefault()
diff --git a/pyrogram/types/bots_and_keyboards/callback_game.py b/pyrogram/types/bots_and_keyboards/callback_game.py
index a89cbbd780..3bd89270ff 100644
--- a/pyrogram/types/bots_and_keyboards/callback_game.py
+++ b/pyrogram/types/bots_and_keyboards/callback_game.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
diff --git a/pyrogram/types/bots_and_keyboards/callback_query.py b/pyrogram/types/bots_and_keyboards/callback_query.py
index 8887594eb7..efdd14ca4d 100644
--- a/pyrogram/types/bots_and_keyboards/callback_query.py
+++ b/pyrogram/types/bots_and_keyboards/callback_query.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
@@ -16,12 +16,10 @@
 #  You should have received a copy of the GNU Lesser General Public License
 #  along with Pyrogram.  If not, see .
 
-from base64 import b64encode
-from struct import pack
 from typing import Union, List, Match, Optional
 
 import pyrogram
-from pyrogram import raw
+from pyrogram import raw, enums
 from pyrogram import types
 from ..object import Object
 from ..update import Update
@@ -89,22 +87,20 @@ def __init__(
         self.matches = matches
 
     @staticmethod
-    async def _parse(client, callback_query, users) -> "CallbackQuery":
+    async def _parse(client: "pyrogram.Client", callback_query, users) -> "CallbackQuery":
         message = None
         inline_message_id = None
 
         if isinstance(callback_query, raw.types.UpdateBotCallbackQuery):
-            message = await client.get_messages(utils.get_peer_id(callback_query.peer), callback_query.msg_id)
+            chat_id = utils.get_peer_id(callback_query.peer)
+            message_id = callback_query.msg_id
+
+            message = client.message_cache[(chat_id, message_id)]
+
+            if not message:
+                message = await client.get_messages(chat_id, message_id)
         elif isinstance(callback_query, raw.types.UpdateInlineBotCallbackQuery):
-            inline_message_id = b64encode(
-                pack(
-                    " Union["types.Message", bool]:
@@ -183,12 +179,9 @@ async def edit_message_text(
             text (``str``):
                 New text of the message.
 
-            parse_mode (``str``, *optional*):
+            parse_mode (:obj:`~pyrogram.enums.ParseMode`, *optional*):
                 By default, texts are parsed using both Markdown and HTML styles.
                 You can combine both syntaxes together.
-                Pass "markdown" or "md" to enable Markdown-style parsing only.
-                Pass "html" to enable HTML-style parsing only.
-                Pass None to completely disable style parsing.
 
             disable_web_page_preview (``bool``, *optional*):
                 Disables link previews for links in this message.
@@ -206,7 +199,7 @@ async def edit_message_text(
         if self.inline_message_id is None:
             return await self._client.edit_message_text(
                 chat_id=self.message.chat.id,
-                message_id=self.message.message_id,
+                message_id=self.message.id,
                 text=text,
                 parse_mode=parse_mode,
                 disable_web_page_preview=disable_web_page_preview,
@@ -224,7 +217,7 @@ async def edit_message_text(
     async def edit_message_caption(
         self,
         caption: str,
-        parse_mode: Optional[str] = object,
+        parse_mode: Optional["enums.ParseMode"] = None,
         reply_markup: "types.InlineKeyboardMarkup" = None
     ) -> Union["types.Message", bool]:
         """Edit the caption of media messages attached to callback queries.
@@ -235,12 +228,9 @@ async def edit_message_caption(
             caption (``str``):
                 New caption of the message.
 
-            parse_mode (``str``, *optional*):
+            parse_mode (:obj:`~pyrogram.enums.ParseMode`, *optional*):
                 By default, texts are parsed using both Markdown and HTML styles.
                 You can combine both syntaxes together.
-                Pass "markdown" or "md" to enable Markdown-style parsing only.
-                Pass "html" to enable HTML-style parsing only.
-                Pass None to completely disable style parsing.
 
             reply_markup (:obj:`~pyrogram.types.InlineKeyboardMarkup`, *optional*):
                 An InlineKeyboardMarkup object.
@@ -280,7 +270,7 @@ async def edit_message_media(
         if self.inline_message_id is None:
             return await self._client.edit_message_media(
                 chat_id=self.message.chat.id,
-                message_id=self.message.message_id,
+                message_id=self.message.id,
                 media=media,
                 reply_markup=reply_markup
             )
@@ -313,7 +303,7 @@ async def edit_message_reply_markup(
         if self.inline_message_id is None:
             return await self._client.edit_message_reply_markup(
                 chat_id=self.message.chat.id,
-                message_id=self.message.message_id,
+                message_id=self.message.id,
                 reply_markup=reply_markup
             )
         else:
diff --git a/pyrogram/types/bots_and_keyboards/force_reply.py b/pyrogram/types/bots_and_keyboards/force_reply.py
index c52bb76651..4cb137d845 100644
--- a/pyrogram/types/bots_and_keyboards/force_reply.py
+++ b/pyrogram/types/bots_and_keyboards/force_reply.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
@@ -36,24 +36,31 @@ class ForceReply(Object):
             Use this parameter if you want to force reply from specific users only. Targets:
             1) users that are @mentioned in the text of the Message object;
             2) if the bot's message is a reply (has reply_to_message_id), sender of the original message.
+
+        placeholder (``str``, *optional*):
+            The placeholder to be shown in the input field when the reply is active; 1-64 characters.
     """
 
     def __init__(
         self,
-        selective: bool = None
+        selective: bool = None,
+        placeholder: str = None
     ):
         super().__init__()
 
         self.selective = selective
+        self.placeholder = placeholder
 
     @staticmethod
     def read(b):
         return ForceReply(
-            selective=b.selective
+            selective=b.selective,
+            placeholder=b.placeholder
         )
 
     async def write(self, _: "pyrogram.Client"):
         return raw.types.ReplyKeyboardForceReply(
             single_use=True,
-            selective=self.selective or None
+            selective=self.selective or None,
+            placeholder=self.placeholder or None
         )
diff --git a/pyrogram/types/bots_and_keyboards/game_high_score.py b/pyrogram/types/bots_and_keyboards/game_high_score.py
index b9a77d332c..295b83fb23 100644
--- a/pyrogram/types/bots_and_keyboards/game_high_score.py
+++ b/pyrogram/types/bots_and_keyboards/game_high_score.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
@@ -32,7 +32,7 @@ class GameHighScore(Object):
         score (``int``):
             Score.
 
-        position (``position``, *optional*):
+        position (``int``, *optional*):
             Position in high score table for the game.
     """
 
diff --git a/pyrogram/types/bots_and_keyboards/inline_keyboard_button.py b/pyrogram/types/bots_and_keyboards/inline_keyboard_button.py
index 84d70642bd..a1d8a7adc8 100644
--- a/pyrogram/types/bots_and_keyboards/inline_keyboard_button.py
+++ b/pyrogram/types/bots_and_keyboards/inline_keyboard_button.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
@@ -39,10 +39,19 @@ class InlineKeyboardButton(Object):
         url (``str``, *optional*):
             HTTP url to be opened when button is pressed.
 
+        web_app (:obj:`~pyrogram.types.WebAppInfo`, *optional*):
+            Description of the `Web App `_ that will be launched when the user
+            presses the button. The Web App will be able to send an arbitrary message on behalf of the user using the
+            method :meth:`~pyrogram.Client.answer_web_app_query`. Available only in private chats between a user and the
+            bot.
+
         login_url (:obj:`~pyrogram.types.LoginUrl`, *optional*):
              An HTTP URL used to automatically authorize the user. Can be used as a replacement for
              the `Telegram Login Widget `_.
 
+        user_id (``int``, *optional*):
+            User id, for links to the user profile.
+
         switch_inline_query (``str``, *optional*):
             If set, pressing the button will prompt the user to select one of their chats, open that chat and insert
             the bot's username and the specified inline query in the input field. Can be empty, in which case just
@@ -67,7 +76,9 @@ def __init__(
         text: str,
         callback_data: Union[str, bytes] = None,
         url: str = None,
+        web_app: "types.WebAppInfo" = None,
         login_url: "types.LoginUrl" = None,
+        user_id: int = None,
         switch_inline_query: str = None,
         switch_inline_query_current_chat: str = None,
         callback_game: "types.CallbackGame" = None
@@ -75,9 +86,11 @@ def __init__(
         super().__init__()
 
         self.text = str(text)
+        self.callback_data = callback_data
         self.url = url
+        self.web_app = web_app
         self.login_url = login_url
-        self.callback_data = callback_data
+        self.user_id = user_id
         self.switch_inline_query = switch_inline_query
         self.switch_inline_query_current_chat = switch_inline_query_current_chat
         self.callback_game = callback_game
@@ -110,6 +123,12 @@ def read(b: "raw.base.KeyboardButton"):
                 login_url=types.LoginUrl.read(b)
             )
 
+        if isinstance(b, raw.types.KeyboardButtonUserProfile):
+            return InlineKeyboardButton(
+                text=b.text,
+                user_id=b.user_id
+            )
+
         if isinstance(b, raw.types.KeyboardButtonSwitchInline):
             if b.same_peer:
                 return InlineKeyboardButton(
@@ -128,6 +147,14 @@ def read(b: "raw.base.KeyboardButton"):
                 callback_game=types.CallbackGame()
             )
 
+        if isinstance(b, raw.types.KeyboardButtonWebView):
+            return InlineKeyboardButton(
+                text=b.text,
+                web_app=types.WebAppInfo(
+                    url=b.url
+                )
+            )
+
     async def write(self, client: "pyrogram.Client"):
         if self.callback_data is not None:
             # Telegram only wants bytes, but we are allowed to pass strings too, for convenience.
@@ -147,7 +174,13 @@ async def write(self, client: "pyrogram.Client"):
         if self.login_url is not None:
             return self.login_url.write(
                 text=self.text,
-                bot=await client.resolve_peer(self.login_url.bot_username)
+                bot=await client.resolve_peer(self.login_url.bot_username or "self")
+            )
+
+        if self.user_id is not None:
+            return raw.types.InputKeyboardButtonUserProfile(
+                text=self.text,
+                user_id=await client.resolve_peer(self.user_id)
             )
 
         if self.switch_inline_query is not None:
@@ -167,3 +200,9 @@ async def write(self, client: "pyrogram.Client"):
             return raw.types.KeyboardButtonGame(
                 text=self.text
             )
+
+        if self.web_app is not None:
+            return raw.types.KeyboardButtonWebView(
+                text=self.text,
+                url=self.web_app.url
+            )
diff --git a/pyrogram/types/bots_and_keyboards/inline_keyboard_markup.py b/pyrogram/types/bots_and_keyboards/inline_keyboard_markup.py
index 1571f1ba32..e0fd323059 100644
--- a/pyrogram/types/bots_and_keyboards/inline_keyboard_markup.py
+++ b/pyrogram/types/bots_and_keyboards/inline_keyboard_markup.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
diff --git a/pyrogram/types/bots_and_keyboards/keyboard_button.py b/pyrogram/types/bots_and_keyboards/keyboard_button.py
index 204c696a72..5c8d4b733c 100644
--- a/pyrogram/types/bots_and_keyboards/keyboard_button.py
+++ b/pyrogram/types/bots_and_keyboards/keyboard_button.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
@@ -16,7 +16,7 @@
 #  You should have received a copy of the GNU Lesser General Public License
 #  along with Pyrogram.  If not, see .
 
-from pyrogram import raw
+from pyrogram import raw, types
 from ..object import Object
 
 
@@ -37,19 +37,27 @@ class KeyboardButton(Object):
         request_location (``bool``, *optional*):
             If True, the user's current location will be sent when the button is pressed.
             Available in private chats only.
+
+        web_app (:obj:`~pyrogram.types.WebAppInfo`, *optional*):
+            If specified, the described `Web App `_ will be launched when the
+            button is pressed. The Web App will be able to send a “web_app_data” service message. Available in private
+            chats only.
+
     """
 
     def __init__(
         self,
         text: str,
         request_contact: bool = None,
-        request_location: bool = None
+        request_location: bool = None,
+        web_app: "types.WebAppInfo" = None
     ):
         super().__init__()
 
         self.text = str(text)
         self.request_contact = request_contact
         self.request_location = request_location
+        self.web_app = web_app
 
     @staticmethod
     def read(b):
@@ -68,10 +76,20 @@ def read(b):
                 request_location=True
             )
 
+        if isinstance(b, raw.types.KeyboardButtonSimpleWebView):
+            return KeyboardButton(
+                text=b.text,
+                web_app=types.WebAppInfo(
+                    url=b.url
+                )
+            )
+
     def write(self):
         if self.request_contact:
             return raw.types.KeyboardButtonRequestPhone(text=self.text)
         elif self.request_location:
             return raw.types.KeyboardButtonRequestGeoLocation(text=self.text)
+        elif self.web_app:
+            return raw.types.KeyboardButtonSimpleWebView(text=self.text, url=self.web_app.url)
         else:
             return raw.types.KeyboardButton(text=self.text)
diff --git a/pyrogram/types/bots_and_keyboards/login_url.py b/pyrogram/types/bots_and_keyboards/login_url.py
index 21c7a45dcc..a0af5a1af0 100644
--- a/pyrogram/types/bots_and_keyboards/login_url.py
+++ b/pyrogram/types/bots_and_keyboards/login_url.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
@@ -31,7 +31,8 @@ class LoginUrl(Object):
         url (``str``):
             An HTTP URL to be opened with user authorization data added to the query string when the button is pressed.
             If the user refuses to provide authorization data, the original URL without information about the user will
-            be opened. The data added is the same as described in Receiving authorization data.
+            be opened. The data added is the same as described in
+            `Receiving authorization data `.
 
             **NOTE**: You **must** always check the hash of the received data to verify the authentication and the
             integrity of the data as described in
@@ -42,7 +43,7 @@ class LoginUrl(Object):
 
         bot_username (``str``, *optional*):
             Username of a bot, which will be used for user authorization.
-            See `Setting up `_ a bot for more details.
+            See `Setting up a bot `_ for more details.
             If not specified, the current bot's username will be assumed. The url's domain must be the same as the
             domain linked with the bot.
             See `Linking your domain to the bot `_
diff --git a/pyrogram/types/bots_and_keyboards/menu_button.py b/pyrogram/types/bots_and_keyboards/menu_button.py
new file mode 100644
index 0000000000..e61e7baa29
--- /dev/null
+++ b/pyrogram/types/bots_and_keyboards/menu_button.py
@@ -0,0 +1,44 @@
+#  Pyrogram - Telegram MTProto API Client Library for Python
+#  Copyright (C) 2017-present Dan 
+#
+#  This file is part of Pyrogram.
+#
+#  Pyrogram is free software: you can redistribute it and/or modify
+#  it under the terms of the GNU Lesser General Public License as published
+#  by the Free Software Foundation, either version 3 of the License, or
+#  (at your option) any later version.
+#
+#  Pyrogram is distributed in the hope that it will be useful,
+#  but WITHOUT ANY WARRANTY; without even the implied warranty of
+#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#  GNU Lesser General Public License for more details.
+#
+#  You should have received a copy of the GNU Lesser General Public License
+#  along with Pyrogram.  If not, see .
+
+import pyrogram
+from pyrogram import raw
+from ..object import Object
+
+
+class MenuButton(Object):
+    """Describes the bot's menu button in a private chat.
+
+    It should be one of:
+
+    - :obj:`~pyrogram.types.MenuButtonCommands`
+    - :obj:`~pyrogram.types.MenuButtonWebApp`
+    - :obj:`~pyrogram.types.MenuButtonDefault`
+
+    If a menu button other than :obj:`~pyrogram.types.MenuButtonDefault` is set for a private chat, then it is applied
+    in the chat. Otherwise the default menu button is applied. By default, the menu button opens the list of bot
+    commands.
+    """
+
+    def __init__(self, type: str):
+        super().__init__()
+
+        self.type = type
+
+    async def write(self, client: "pyrogram.Client") -> "raw.base.BotMenuButton":
+        raise NotImplementedError
diff --git a/pyrogram/types/bots_and_keyboards/menu_button_commands.py b/pyrogram/types/bots_and_keyboards/menu_button_commands.py
new file mode 100644
index 0000000000..b2ef77c9de
--- /dev/null
+++ b/pyrogram/types/bots_and_keyboards/menu_button_commands.py
@@ -0,0 +1,32 @@
+#  Pyrogram - Telegram MTProto API Client Library for Python
+#  Copyright (C) 2017-present Dan 
+#
+#  This file is part of Pyrogram.
+#
+#  Pyrogram is free software: you can redistribute it and/or modify
+#  it under the terms of the GNU Lesser General Public License as published
+#  by the Free Software Foundation, either version 3 of the License, or
+#  (at your option) any later version.
+#
+#  Pyrogram is distributed in the hope that it will be useful,
+#  but WITHOUT ANY WARRANTY; without even the implied warranty of
+#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#  GNU Lesser General Public License for more details.
+#
+#  You should have received a copy of the GNU Lesser General Public License
+#  along with Pyrogram.  If not, see .
+
+import pyrogram
+from pyrogram import raw
+from .menu_button import MenuButton
+
+
+class MenuButtonCommands(MenuButton):
+    """A menu button, which opens the bot's list of commands.
+    """
+
+    def __init__(self):
+        super().__init__("commands")
+
+    async def write(self, client: "pyrogram.Client") -> "raw.types.BotMenuButtonCommands":
+        return raw.types.BotMenuButtonCommands()
diff --git a/pyrogram/types/bots_and_keyboards/menu_button_default.py b/pyrogram/types/bots_and_keyboards/menu_button_default.py
new file mode 100644
index 0000000000..a00e67633e
--- /dev/null
+++ b/pyrogram/types/bots_and_keyboards/menu_button_default.py
@@ -0,0 +1,32 @@
+#  Pyrogram - Telegram MTProto API Client Library for Python
+#  Copyright (C) 2017-present Dan 
+#
+#  This file is part of Pyrogram.
+#
+#  Pyrogram is free software: you can redistribute it and/or modify
+#  it under the terms of the GNU Lesser General Public License as published
+#  by the Free Software Foundation, either version 3 of the License, or
+#  (at your option) any later version.
+#
+#  Pyrogram is distributed in the hope that it will be useful,
+#  but WITHOUT ANY WARRANTY; without even the implied warranty of
+#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#  GNU Lesser General Public License for more details.
+#
+#  You should have received a copy of the GNU Lesser General Public License
+#  along with Pyrogram.  If not, see .
+
+import pyrogram
+from pyrogram import raw
+from .menu_button import MenuButton
+
+
+class MenuButtonDefault(MenuButton):
+    """Describes that no specific value for the menu button was set.
+    """
+
+    def __init__(self):
+        super().__init__("default")
+
+    async def write(self, client: "pyrogram.Client") -> "raw.types.BotMenuButtonDefault":
+        return raw.types.BotMenuButtonDefault()
diff --git a/pyrogram/types/bots_and_keyboards/menu_button_web_app.py b/pyrogram/types/bots_and_keyboards/menu_button_web_app.py
new file mode 100644
index 0000000000..109088bbcf
--- /dev/null
+++ b/pyrogram/types/bots_and_keyboards/menu_button_web_app.py
@@ -0,0 +1,51 @@
+#  Pyrogram - Telegram MTProto API Client Library for Python
+#  Copyright (C) 2017-present Dan 
+#
+#  This file is part of Pyrogram.
+#
+#  Pyrogram is free software: you can redistribute it and/or modify
+#  it under the terms of the GNU Lesser General Public License as published
+#  by the Free Software Foundation, either version 3 of the License, or
+#  (at your option) any later version.
+#
+#  Pyrogram is distributed in the hope that it will be useful,
+#  but WITHOUT ANY WARRANTY; without even the implied warranty of
+#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#  GNU Lesser General Public License for more details.
+#
+#  You should have received a copy of the GNU Lesser General Public License
+#  along with Pyrogram.  If not, see .
+
+import pyrogram
+from pyrogram import raw, types
+from .menu_button import MenuButton
+
+
+class MenuButtonWebApp(MenuButton):
+    """A menu button, which launches a `Web App `_.
+
+    Parameters:
+        text (``str``):
+            Text on the button
+
+        web_app (:obj:`~pyrogram.types.WebAppInfo`):
+            Description of the Web App that will be launched when the user presses the button.
+            The Web App will be able to send an arbitrary message on behalf of the user using the method
+            :meth:`~pyrogram.Client.answer_web_app_query`.
+    """
+
+    def __init__(
+        self,
+        text: str,
+        web_app: "types.WebAppInfo"
+    ):
+        super().__init__("web_app")
+
+        self.text = text
+        self.web_app = web_app
+
+    async def write(self, client: "pyrogram.Client") -> "raw.types.BotMenuButton":
+        return raw.types.BotMenuButton(
+            text=self.text,
+            url=self.web_app.url
+        )
diff --git a/pyrogram/types/bots_and_keyboards/reply_keyboard_markup.py b/pyrogram/types/bots_and_keyboards/reply_keyboard_markup.py
index 21fa6ceb0d..2949c3e206 100644
--- a/pyrogram/types/bots_and_keyboards/reply_keyboard_markup.py
+++ b/pyrogram/types/bots_and_keyboards/reply_keyboard_markup.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
@@ -31,6 +31,10 @@ class ReplyKeyboardMarkup(Object):
         keyboard (List of List of :obj:`~pyrogram.types.KeyboardButton`):
             List of button rows, each represented by a List of KeyboardButton objects.
 
+        is_persistent (``bool``, *optional*):
+            Requests clients to always show the keyboard when the regular keyboard is hidden.
+            Defaults to false, in which case the custom keyboard can be hidden and opened with a keyboard icon.
+
         resize_keyboard (``bool``, *optional*):
             Requests clients to resize the keyboard vertically for optimal fit (e.g., make the keyboard smaller if
             there are just two rows of buttons). Defaults to false, in which case the custom keyboard is always of
@@ -47,24 +51,31 @@ class ReplyKeyboardMarkup(Object):
             2) if the bot's message is a reply (has reply_to_message_id), sender of the original message.
             Example: A user requests to change the bot's language, bot replies to the request with a keyboard to
             select the new language. Other users in the group don't see the keyboard.
+
+        placeholder (``str``, *optional*):
+            The placeholder to be shown in the input field when the keyboard is active; 1-64 characters.
     """
 
     def __init__(
         self,
         keyboard: List[List[Union["types.KeyboardButton", str]]],
+        is_persistent: bool = None,
         resize_keyboard: bool = None,
         one_time_keyboard: bool = None,
-        selective: bool = None
+        selective: bool = None,
+        placeholder: str = None
     ):
         super().__init__()
 
         self.keyboard = keyboard
+        self.is_persistent = is_persistent
         self.resize_keyboard = resize_keyboard
         self.one_time_keyboard = one_time_keyboard
         self.selective = selective
+        self.placeholder = placeholder
 
     @staticmethod
-    def read(kb):
+    def read(kb: "raw.base.ReplyMarkup"):
         keyboard = []
 
         for i in kb.rows:
@@ -77,9 +88,11 @@ def read(kb):
 
         return ReplyKeyboardMarkup(
             keyboard=keyboard,
+            is_persistent=kb.persistent,
             resize_keyboard=kb.resize,
             one_time_keyboard=kb.single_use,
-            selective=kb.selective
+            selective=kb.selective,
+            placeholder=kb.placeholder
         )
 
     async def write(self, _: "pyrogram.Client"):
@@ -93,5 +106,7 @@ async def write(self, _: "pyrogram.Client"):
             ) for i in self.keyboard],
             resize=self.resize_keyboard or None,
             single_use=self.one_time_keyboard or None,
-            selective=self.selective or None
+            selective=self.selective or None,
+            persistent=self.is_persistent or None,
+            placeholder=self.placeholder or None
         )
diff --git a/pyrogram/types/bots_and_keyboards/reply_keyboard_remove.py b/pyrogram/types/bots_and_keyboards/reply_keyboard_remove.py
index 9bf16528f9..479efe9027 100644
--- a/pyrogram/types/bots_and_keyboards/reply_keyboard_remove.py
+++ b/pyrogram/types/bots_and_keyboards/reply_keyboard_remove.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
diff --git a/pyrogram/types/bots_and_keyboards/sent_web_app_message.py b/pyrogram/types/bots_and_keyboards/sent_web_app_message.py
new file mode 100644
index 0000000000..8b1df19737
--- /dev/null
+++ b/pyrogram/types/bots_and_keyboards/sent_web_app_message.py
@@ -0,0 +1,42 @@
+#  Pyrogram - Telegram MTProto API Client Library for Python
+#  Copyright (C) 2017-present Dan 
+#
+#  This file is part of Pyrogram.
+#
+#  Pyrogram is free software: you can redistribute it and/or modify
+#  it under the terms of the GNU Lesser General Public License as published
+#  by the Free Software Foundation, either version 3 of the License, or
+#  (at your option) any later version.
+#
+#  Pyrogram is distributed in the hope that it will be useful,
+#  but WITHOUT ANY WARRANTY; without even the implied warranty of
+#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#  GNU Lesser General Public License for more details.
+#
+#  You should have received a copy of the GNU Lesser General Public License
+#  along with Pyrogram.  If not, see .
+
+from pyrogram import raw, utils
+from ..object import Object
+
+
+class SentWebAppMessage(Object):
+    """Contains information about an inline message sent by a `Web App `_ on behalf of a user.
+
+    Parameters:
+        inline_message_id (``str``):
+            Identifier of the sent inline message.
+            Available only if there is an inline keyboard attached to the message.
+    """
+
+    def __init__(
+        self, *,
+        inline_message_id: str,
+    ):
+        super().__init__()
+
+        self.inline_message_id = inline_message_id
+
+    @staticmethod
+    def _parse(obj: "raw.types.WebViewMessageSent"):
+        return SentWebAppMessage(inline_message_id=utils.pack_inline_message_id(obj.msg_id))
diff --git a/pyrogram/types/bots_and_keyboards/web_app_info.py b/pyrogram/types/bots_and_keyboards/web_app_info.py
new file mode 100644
index 0000000000..1dbb0a781c
--- /dev/null
+++ b/pyrogram/types/bots_and_keyboards/web_app_info.py
@@ -0,0 +1,37 @@
+#  Pyrogram - Telegram MTProto API Client Library for Python
+#  Copyright (C) 2017-present Dan 
+#
+#  This file is part of Pyrogram.
+#
+#  Pyrogram is free software: you can redistribute it and/or modify
+#  it under the terms of the GNU Lesser General Public License as published
+#  by the Free Software Foundation, either version 3 of the License, or
+#  (at your option) any later version.
+#
+#  Pyrogram is distributed in the hope that it will be useful,
+#  but WITHOUT ANY WARRANTY; without even the implied warranty of
+#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#  GNU Lesser General Public License for more details.
+#
+#  You should have received a copy of the GNU Lesser General Public License
+#  along with Pyrogram.  If not, see .
+
+from ..object import Object
+
+
+class WebAppInfo(Object):
+    """Contains information about a `Web App `_.
+
+    Parameters:
+        url (``str``):
+            An HTTPS URL of a Web App to be opened with additional data as specified in
+            `Initializing Web Apps `_.
+    """
+
+    def __init__(
+        self, *,
+        url: str,
+    ):
+        super().__init__()
+
+        self.url = url
diff --git a/pyrogram/types/inline_mode/__init__.py b/pyrogram/types/inline_mode/__init__.py
index 9d464eb48e..f7323abf95 100644
--- a/pyrogram/types/inline_mode/__init__.py
+++ b/pyrogram/types/inline_mode/__init__.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
@@ -21,11 +21,27 @@
 from .inline_query_result import InlineQueryResult
 from .inline_query_result_animation import InlineQueryResultAnimation
 from .inline_query_result_article import InlineQueryResultArticle
+from .inline_query_result_audio import InlineQueryResultAudio
+from .inline_query_result_cached_animation import InlineQueryResultCachedAnimation
+from .inline_query_result_cached_document import InlineQueryResultCachedDocument
+from .inline_query_result_cached_photo import InlineQueryResultCachedPhoto
+from .inline_query_result_cached_sticker import InlineQueryResultCachedSticker
+from .inline_query_result_cached_video import InlineQueryResultCachedVideo
+from .inline_query_result_cached_voice import InlineQueryResultCachedVoice
+from .inline_query_result_contact import InlineQueryResultContact
+from .inline_query_result_document import InlineQueryResultDocument
+from .inline_query_result_location import InlineQueryResultLocation
 from .inline_query_result_photo import InlineQueryResultPhoto
+from .inline_query_result_venue import InlineQueryResultVenue
 from .inline_query_result_video import InlineQueryResultVideo
-from .inline_query_result_audio import InlineQueryResultAudio
+from .inline_query_result_voice import InlineQueryResultVoice
+from .inline_query_result_cached_audio import InlineQueryResultCachedAudio
 
 __all__ = [
     "InlineQuery", "InlineQueryResult", "InlineQueryResultArticle", "InlineQueryResultPhoto",
-    "InlineQueryResultAnimation", "InlineQueryResultAudio", "InlineQueryResultVideo", "ChosenInlineResult"
+    "InlineQueryResultAnimation", "InlineQueryResultAudio", "InlineQueryResultVideo", "ChosenInlineResult",
+    "InlineQueryResultContact", "InlineQueryResultDocument", "InlineQueryResultVoice", "InlineQueryResultLocation",
+    "InlineQueryResultVenue", "InlineQueryResultCachedPhoto", "InlineQueryResultCachedAnimation",
+    "InlineQueryResultCachedSticker", "InlineQueryResultCachedDocument", "InlineQueryResultCachedVideo",
+    "InlineQueryResultCachedVoice", "InlineQueryResultCachedAudio"
 ]
diff --git a/pyrogram/types/inline_mode/chosen_inline_result.py b/pyrogram/types/inline_mode/chosen_inline_result.py
index 578ef64a1b..623d737933 100644
--- a/pyrogram/types/inline_mode/chosen_inline_result.py
+++ b/pyrogram/types/inline_mode/chosen_inline_result.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
diff --git a/pyrogram/types/inline_mode/inline_query.py b/pyrogram/types/inline_mode/inline_query.py
index 5c718c33c9..a5f0422e72 100644
--- a/pyrogram/types/inline_mode/inline_query.py
+++ b/pyrogram/types/inline_mode/inline_query.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
@@ -20,7 +20,7 @@
 
 import pyrogram
 from pyrogram import raw
-from pyrogram import types
+from pyrogram import types, enums
 from ..object import Object
 from ..update import Update
 
@@ -43,11 +43,8 @@ class InlineQuery(Object, Update):
         offset (``str``):
             Offset of the results to be returned, can be controlled by the bot.
 
-        chat_type (``str``, *optional*):
+        chat_type (:obj:`~pyrogram.enums.ChatType`, *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.
 
         location (:obj:`~pyrogram.types.Location`. *optional*):
             Sender location, only for bots that request user location.
@@ -65,7 +62,7 @@ def __init__(
         from_user: "types.User",
         query: str,
         offset: str,
-        chat_type: str,
+        chat_type: "enums.ChatType",
         location: "types.Location" = None,
         matches: List[Match] = None
     ):
@@ -85,15 +82,15 @@ def _parse(client, inline_query: raw.types.UpdateBotInlineQuery, users: dict) ->
         chat_type = None
 
         if isinstance(peer_type, raw.types.InlineQueryPeerTypeSameBotPM):
-            chat_type = "sender"
+            chat_type = enums.ChatType.BOT
         elif isinstance(peer_type, raw.types.InlineQueryPeerTypePM):
-            chat_type = "private"
+            chat_type = enums.ChatType.PRIVATE
         elif isinstance(peer_type, raw.types.InlineQueryPeerTypeChat):
-            chat_type = "group"
+            chat_type = enums.ChatType.GROUP
         elif isinstance(peer_type, raw.types.InlineQueryPeerTypeMegagroup):
-            chat_type = "supergroup"
+            chat_type = enums.ChatType.SUPERGROUP
         elif isinstance(peer_type, raw.types.InlineQueryPeerTypeBroadcast):
-            chat_type = "channel"
+            chat_type = enums.ChatType.CHANNEL
 
         return InlineQuery(
             id=str(inline_query.query_id),
@@ -125,7 +122,7 @@ async def answer(
 
         .. code-block:: python
 
-            client.answer_inline_query(
+            await client.answer_inline_query(
                 inline_query.id,
                 results=[...]
             )
@@ -133,7 +130,7 @@ async def answer(
         Example:
             .. code-block:: python
 
-                inline_query.answer([...])
+                await inline_query.answer([...])
 
         Parameters:
             results (List of :obj:`~pyrogram.types.InlineQueryResult`):
diff --git a/pyrogram/types/inline_mode/inline_query_result.py b/pyrogram/types/inline_mode/inline_query_result.py
index 919b7df565..8548e023c7 100644
--- a/pyrogram/types/inline_mode/inline_query_result.py
+++ b/pyrogram/types/inline_mode/inline_query_result.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
@@ -22,35 +22,27 @@
 from pyrogram import types
 from ..object import Object
 
-"""- :obj:`~pyrogram.types.InlineQueryResultCachedAudio`
+
+class InlineQueryResult(Object):
+    """One result of an inline query.
+
+    - :obj:`~pyrogram.types.InlineQueryResultCachedAudio`
     - :obj:`~pyrogram.types.InlineQueryResultCachedDocument`
-    - :obj:`~pyrogram.types.InlineQueryResultCachedGif`
-    - :obj:`~pyrogram.types.InlineQueryResultCachedMpeg4Gif`
+    - :obj:`~pyrogram.types.InlineQueryResultCachedAnimation`
     - :obj:`~pyrogram.types.InlineQueryResultCachedPhoto`
     - :obj:`~pyrogram.types.InlineQueryResultCachedSticker`
     - :obj:`~pyrogram.types.InlineQueryResultCachedVideo`
     - :obj:`~pyrogram.types.InlineQueryResultCachedVoice`
+    - :obj:`~pyrogram.types.InlineQueryResultArticle`
     - :obj:`~pyrogram.types.InlineQueryResultAudio`
     - :obj:`~pyrogram.types.InlineQueryResultContact`
-    - :obj:`~pyrogram.types.InlineQueryResultGame`
     - :obj:`~pyrogram.types.InlineQueryResultDocument`
-    - :obj:`~pyrogram.types.InlineQueryResultGif`
+    - :obj:`~pyrogram.types.InlineQueryResultAnimation`
     - :obj:`~pyrogram.types.InlineQueryResultLocation`
-    - :obj:`~pyrogram.types.InlineQueryResultMpeg4Gif`
     - :obj:`~pyrogram.types.InlineQueryResultPhoto`
     - :obj:`~pyrogram.types.InlineQueryResultVenue`
     - :obj:`~pyrogram.types.InlineQueryResultVideo`
-    - :obj:`~pyrogram.types.InlineQueryResultVoice`"""
-
-
-class InlineQueryResult(Object):
-    """One result of an inline query.
-
-    Pyrogram currently supports results of the following types:
-
-    - :obj:`~pyrogram.types.InlineQueryResultArticle`
-    - :obj:`~pyrogram.types.InlineQueryResultPhoto`
-    - :obj:`~pyrogram.types.InlineQueryResultAnimation`
+    - :obj:`~pyrogram.types.InlineQueryResultVoice`
     """
 
     def __init__(
diff --git a/pyrogram/types/inline_mode/inline_query_result_animation.py b/pyrogram/types/inline_mode/inline_query_result_animation.py
index 45af1ed38a..71d1edf40a 100644
--- a/pyrogram/types/inline_mode/inline_query_result_animation.py
+++ b/pyrogram/types/inline_mode/inline_query_result_animation.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
@@ -19,7 +19,7 @@
 from typing import Optional, List
 
 import pyrogram
-from pyrogram import raw, types, utils
+from pyrogram import raw, types, utils, enums
 from .inline_query_result import InlineQueryResult
 
 
@@ -35,10 +35,23 @@ class InlineQueryResultAnimation(InlineQueryResult):
             A valid URL for the animated GIF file.
             File size must not exceed 1 MB.
 
+        animation_width (``int``, *optional*)
+            Width of the animation.
+
+        animation_height (``int``, *optional*)
+            Height of the animation.
+
+        animation_duration (``int``, *optional*)
+            Duration of the animation in seconds.
+
         thumb_url (``str``, *optional*):
             URL of the static thumbnail for the result (jpeg or gif)
             Defaults to the value passed in *animation_url*.
 
+        thumb_mime_type (``str``, *optional*)
+            MIME type of the thumbnail, must be one of "image/jpeg", "image/gif", or "video/mp4".
+            Defaults to "image/jpeg".
+
         id (``str``, *optional*):
             Unique identifier for this result, 1-64 bytes.
             Defaults to a randomly generated UUID4.
@@ -46,18 +59,12 @@ class InlineQueryResultAnimation(InlineQueryResult):
         title (``str``, *optional*):
             Title for the result.
 
-        description (``str``, *optional*):
-            Short description of the result.
-
         caption (``str``, *optional*):
-            Caption of the photo to be sent, 0-1024 characters.
+            Caption of the animation to be sent, 0-1024 characters.
 
-        parse_mode (``str``, *optional*):
+        parse_mode (:obj:`~pyrogram.enums.ParseMode`, *optional*):
             By default, texts are parsed using both Markdown and HTML styles.
             You can combine both syntaxes together.
-            Pass "markdown" or "md" to enable Markdown-style parsing only.
-            Pass "html" to enable HTML-style parsing only.
-            Pass None to completely disable style parsing.
 
         caption_entities (List of :obj:`~pyrogram.types.MessageEntity`):
             List of special entities that appear in the caption, which can be specified instead of *parse_mode*.
@@ -72,12 +79,16 @@ class InlineQueryResultAnimation(InlineQueryResult):
     def __init__(
         self,
         animation_url: str,
+        animation_width: int = 0,
+        animation_height: int = 0,
+        animation_duration: int = 0,
         thumb_url: str = None,
+        thumb_mime_type: str = "image/jpeg",
         id: str = None,
         title: str = None,
         description: str = None,
         caption: str = "",
-        parse_mode: Optional[str] = object,
+        parse_mode: Optional["enums.ParseMode"] = None,
         caption_entities: List["types.MessageEntity"] = None,
         reply_markup: "types.InlineKeyboardMarkup" = None,
         input_message_content: "types.InputMessageContent" = None
@@ -85,7 +96,11 @@ def __init__(
         super().__init__("gif", id, input_message_content, reply_markup)
 
         self.animation_url = animation_url
+        self.animation_width = animation_width
+        self.animation_height = animation_height
+        self.animation_duration = animation_duration
         self.thumb_url = thumb_url
+        self.thumb_mime_type = thumb_mime_type
         self.title = title
         self.description = description
         self.caption = caption
@@ -99,7 +114,13 @@ async def write(self, client: "pyrogram.Client"):
             url=self.animation_url,
             size=0,
             mime_type="image/gif",
-            attributes=[]
+            attributes=[
+                raw.types.DocumentAttributeVideo(
+                    w=self.animation_width,
+                    h=self.animation_height,
+                    duration=self.animation_duration
+                )
+            ]
         )
 
         if self.thumb_url is None:
@@ -108,7 +129,7 @@ async def write(self, client: "pyrogram.Client"):
             thumb = raw.types.InputWebDocument(
                 url=self.thumb_url,
                 size=0,
-                mime_type="image/gif",
+                mime_type=self.thumb_mime_type,
                 attributes=[]
             )
 
@@ -120,7 +141,6 @@ async def write(self, client: "pyrogram.Client"):
             id=self.id,
             type=self.type,
             title=self.title,
-            description=self.description,
             thumb=thumb,
             content=animation,
             send_message=(
diff --git a/pyrogram/types/inline_mode/inline_query_result_article.py b/pyrogram/types/inline_mode/inline_query_result_article.py
index 0b5e6008b5..096273f15b 100644
--- a/pyrogram/types/inline_mode/inline_query_result_article.py
+++ b/pyrogram/types/inline_mode/inline_query_result_article.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
@@ -43,11 +43,17 @@ class InlineQueryResultArticle(InlineQueryResult):
         description (``str``, *optional*):
             Short description of the result.
 
-        thumb_url (``str``, *optional*):
-            URL of the thumbnail for the result.
-
         reply_markup (:obj:`~pyrogram.types.InlineKeyboardMarkup`, *optional*):
             Inline keyboard attached to the message.
+
+        thumb_url (``str``, *optional*):
+            Url of the thumbnail for the result.
+
+        thumb_width (``int``, *optional*):
+            Thumbnail width.
+
+        thumb_height (``int``, *optional*):
+            Thumbnail height
     """
 
     def __init__(
@@ -55,10 +61,12 @@ def __init__(
         title: str,
         input_message_content: "types.InputMessageContent",
         id: str = None,
-        reply_markup: "types.InlineKeyboardMarkup" = None,
         url: str = None,
         description: str = None,
-        thumb_url: str = None
+        reply_markup: "types.InlineKeyboardMarkup" = None,
+        thumb_url: str = None,
+        thumb_width: int = 0,
+        thumb_height: int = 0
     ):
         super().__init__("article", id, input_message_content, reply_markup)
 
@@ -66,6 +74,8 @@ def __init__(
         self.url = url
         self.description = description
         self.thumb_url = thumb_url
+        self.thumb_width = thumb_width
+        self.thumb_height = thumb_height
 
     async def write(self, client: "pyrogram.Client"):
         return raw.types.InputBotInlineResult(
@@ -79,6 +89,11 @@ async def write(self, client: "pyrogram.Client"):
                 url=self.thumb_url,
                 size=0,
                 mime_type="image/jpeg",
-                attributes=[]
+                attributes=[
+                    raw.types.DocumentAttributeImageSize(
+                        w=self.thumb_width,
+                        h=self.thumb_height
+                    )
+                ]
             ) if self.thumb_url else None
         )
diff --git a/pyrogram/types/inline_mode/inline_query_result_audio.py b/pyrogram/types/inline_mode/inline_query_result_audio.py
index 6ca8c34c19..a39021000f 100644
--- a/pyrogram/types/inline_mode/inline_query_result_audio.py
+++ b/pyrogram/types/inline_mode/inline_query_result_audio.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2020 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
@@ -16,10 +16,10 @@
 #  You should have received a copy of the GNU Lesser General Public License
 #  along with Pyrogram.  If not, see .
 
-from typing import Union, List
+from typing import List, Optional
 
 import pyrogram
-from pyrogram import raw, types, utils
+from pyrogram import raw, types, utils, enums
 from .inline_query_result import InlineQueryResult
 
 
@@ -50,13 +50,10 @@ class InlineQueryResultAudio(InlineQueryResult):
         caption (``str``, *optional*):
             Caption of the audio to be sent, 0-1024 characters.
             
-        parse_mode (``str``, *optional*):
+        parse_mode (:obj:`~pyrogram.enums.ParseMode`, *optional*):
             By default, texts are parsed using both Markdown and HTML styles.
             You can combine both syntaxes together.
-            Pass "markdown" or "md" to enable Markdown-style parsing only.
-            Pass "html" to enable HTML-style parsing only.
-            Pass None to completely disable style parsing.
-            
+
         caption_entities (List of :obj:`~pyrogram.types.MessageEntity`):
             List of special entities that appear in the caption, which can be specified instead of *parse_mode*.
             
@@ -75,7 +72,7 @@ def __init__(
         performer: str = "",
         audio_duration: int = 0,
         caption: str = "",
-        parse_mode: Union[str, None] = object,
+        parse_mode: Optional["enums.ParseMode"] = None,
         caption_entities: List["types.MessageEntity"] = None,
         reply_markup: "types.InlineKeyboardMarkup" = None,
         input_message_content: "types.InputMessageContent" = None
diff --git a/pyrogram/types/inline_mode/inline_query_result_cached_animation.py b/pyrogram/types/inline_mode/inline_query_result_cached_animation.py
new file mode 100644
index 0000000000..63e58ca027
--- /dev/null
+++ b/pyrogram/types/inline_mode/inline_query_result_cached_animation.py
@@ -0,0 +1,108 @@
+#  Pyrogram - Telegram MTProto API Client Library for Python
+#  Copyright (C) 2017-present Dan 
+#
+#  This file is part of Pyrogram.
+#
+#  Pyrogram is free software: you can redistribute it and/or modify
+#  it under the terms of the GNU Lesser General Public License as published
+#  by the Free Software Foundation, either version 3 of the License, or
+#  (at your option) any later version.
+#
+#  Pyrogram is distributed in the hope that it will be useful,
+#  but WITHOUT ANY WARRANTY; without even the implied warranty of
+#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#  GNU Lesser General Public License for more details.
+#
+#  You should have received a copy of the GNU Lesser General Public License
+#  along with Pyrogram.  If not, see .
+
+from typing import Optional, List
+
+import pyrogram
+from pyrogram import raw, types, utils, enums
+from .inline_query_result import InlineQueryResult
+from ...file_id import FileId
+
+
+class InlineQueryResultCachedAnimation(InlineQueryResult):
+    """A link to an animation file stored on the Telegram servers.
+
+    By default, this animation file will be sent by the user with an optional caption.
+    Alternatively, you can use *input_message_content* to send a message with specified content instead of the
+    animation.
+
+    Parameters:
+        animation_file_id (``str``):
+            A valid file identifier for the animation file.
+
+        id (``str``, *optional*):
+            Unique identifier for this result, 1-64 bytes.
+            Defaults to a randomly generated UUID4.
+
+        title (``str``, *optional*):
+            Title for the result.
+
+        caption (``str``, *optional*):
+            Caption of the photo to be sent, 0-1024 characters.
+
+        parse_mode (:obj:`~pyrogram.enums.ParseMode`, *optional*):
+            By default, texts are parsed using both Markdown and HTML styles.
+            You can combine both syntaxes together.
+
+        caption_entities (List of :obj:`~pyrogram.types.MessageEntity`):
+            List of special entities that appear in the caption, which can be specified instead of *parse_mode*.
+
+        reply_markup (:obj:`~pyrogram.types.InlineKeyboardMarkup`, *optional*):
+            An InlineKeyboardMarkup object.
+
+        input_message_content (:obj:`~pyrogram.types.InputMessageContent`):
+            Content of the message to be sent instead of the photo.
+    """
+
+    def __init__(
+        self,
+        animation_file_id: str,
+        id: str = None,
+        title: str = None,
+        caption: str = "",
+        parse_mode: Optional["enums.ParseMode"] = None,
+        caption_entities: List["types.MessageEntity"] = None,
+        reply_markup: "types.InlineKeyboardMarkup" = None,
+        input_message_content: "types.InputMessageContent" = None
+    ):
+        super().__init__("gif", id, input_message_content, reply_markup)
+
+        self.animation_file_id = animation_file_id
+        self.title = title
+        self.caption = caption
+        self.parse_mode = parse_mode
+        self.caption_entities = caption_entities
+        self.reply_markup = reply_markup
+        self.input_message_content = input_message_content
+
+    async def write(self, client: "pyrogram.Client"):
+        message, entities = (await utils.parse_text_entities(
+            client, self.caption, self.parse_mode, self.caption_entities
+        )).values()
+
+        file_id = FileId.decode(self.animation_file_id)
+
+        return raw.types.InputBotInlineResultDocument(
+            id=self.id,
+            type=self.type,
+            title=self.title,
+            document=raw.types.InputDocument(
+                id=file_id.media_id,
+                access_hash=file_id.access_hash,
+                file_reference=file_id.file_reference,
+            ),
+            send_message=(
+                await self.input_message_content.write(client, self.reply_markup)
+                if self.input_message_content
+                else raw.types.InputBotInlineMessageMediaAuto(
+                    reply_markup=await self.reply_markup.write(client) if self.reply_markup else None,
+                    message=message,
+                    entities=entities
+                )
+            )
+        )
diff --git a/pyrogram/types/inline_mode/inline_query_result_cached_audio.py b/pyrogram/types/inline_mode/inline_query_result_cached_audio.py
new file mode 100644
index 0000000000..9535f63343
--- /dev/null
+++ b/pyrogram/types/inline_mode/inline_query_result_cached_audio.py
@@ -0,0 +1,101 @@
+#  Pyrogram - Telegram MTProto API Client Library for Python
+#  Copyright (C) 2017-present Dan 
+#
+#  This file is part of Pyrogram.
+#
+#  Pyrogram is free software: you can redistribute it and/or modify
+#  it under the terms of the GNU Lesser General Public License as published
+#  by the Free Software Foundation, either version 3 of the License, or
+#  (at your option) any later version.
+#
+#  Pyrogram is distributed in the hope that it will be useful,
+#  but WITHOUT ANY WARRANTY; without even the implied warranty of
+#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#  GNU Lesser General Public License for more details.
+#
+#  You should have received a copy of the GNU Lesser General Public License
+#  along with Pyrogram.  If not, see .
+
+from typing import Optional, List
+
+import pyrogram
+from pyrogram import raw, types, utils, enums
+from .inline_query_result import InlineQueryResult
+from ...file_id import FileId
+
+
+class InlineQueryResultCachedAudio(InlineQueryResult):
+    """A link to an MP3 audio file stored on the Telegram servers
+
+    By default, this audio file will be sent by the user. Alternatively, you can use *input_message_content* to send a
+    message with the specified content instead of the audio.
+
+    Parameters:
+        audio_file_id (``str``):
+            A valid file identifier for the audio file.
+
+        id (``str``, *optional*):
+            Unique identifier for this result, 1-64 bytes.
+            Defaults to a randomly generated UUID4.
+
+        caption (``str``, *optional*):
+            Caption of the photo to be sent, 0-1024 characters.
+
+        parse_mode (:obj:`~pyrogram.enums.ParseMode`, *optional*):
+            By default, texts are parsed using both Markdown and HTML styles.
+            You can combine both syntaxes together.
+
+        caption_entities (List of :obj:`~pyrogram.types.MessageEntity`):
+            List of special entities that appear in the caption, which can be specified instead of *parse_mode*.
+
+        reply_markup (:obj:`~pyrogram.types.InlineKeyboardMarkup`, *optional*):
+            An InlineKeyboardMarkup object.
+
+        input_message_content (:obj:`~pyrogram.types.InputMessageContent`):
+            Content of the message to be sent instead of the photo.
+    """
+
+    def __init__(
+        self,
+        audio_file_id: str,
+        id: str = None,
+        caption: str = "",
+        parse_mode: Optional["enums.ParseMode"] = None,
+        caption_entities: List["types.MessageEntity"] = None,
+        reply_markup: "types.InlineKeyboardMarkup" = None,
+        input_message_content: "types.InputMessageContent" = None
+    ):
+        super().__init__("audio", id, input_message_content, reply_markup)
+
+        self.audio_file_id = audio_file_id
+        self.caption = caption
+        self.parse_mode = parse_mode
+        self.caption_entities = caption_entities
+        self.reply_markup = reply_markup
+        self.input_message_content = input_message_content
+
+    async def write(self, client: "pyrogram.Client"):
+        message, entities = (await utils.parse_text_entities(
+            client, self.caption, self.parse_mode, self.caption_entities
+        )).values()
+
+        file_id = FileId.decode(self.audio_file_id)
+
+        return raw.types.InputBotInlineResultDocument(
+            id=self.id,
+            type=self.type,
+            document=raw.types.InputDocument(
+                id=file_id.media_id,
+                access_hash=file_id.access_hash,
+                file_reference=file_id.file_reference,
+            ),
+            send_message=(
+                await self.input_message_content.write(client, self.reply_markup)
+                if self.input_message_content
+                else raw.types.InputBotInlineMessageMediaAuto(
+                    reply_markup=await self.reply_markup.write(client) if self.reply_markup else None,
+                    message=message,
+                    entities=entities
+                )
+            )
+        )
diff --git a/pyrogram/types/inline_mode/inline_query_result_cached_document.py b/pyrogram/types/inline_mode/inline_query_result_cached_document.py
new file mode 100644
index 0000000000..2ab190e7ff
--- /dev/null
+++ b/pyrogram/types/inline_mode/inline_query_result_cached_document.py
@@ -0,0 +1,113 @@
+#  Pyrogram - Telegram MTProto API Client Library for Python
+#  Copyright (C) 2017-present Dan 
+#
+#  This file is part of Pyrogram.
+#
+#  Pyrogram is free software: you can redistribute it and/or modify
+#  it under the terms of the GNU Lesser General Public License as published
+#  by the Free Software Foundation, either version 3 of the License, or
+#  (at your option) any later version.
+#
+#  Pyrogram is distributed in the hope that it will be useful,
+#  but WITHOUT ANY WARRANTY; without even the implied warranty of
+#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#  GNU Lesser General Public License for more details.
+#
+#  You should have received a copy of the GNU Lesser General Public License
+#  along with Pyrogram.  If not, see .
+
+from typing import Optional, List
+
+import pyrogram
+from pyrogram import raw, types, utils, enums
+from .inline_query_result import InlineQueryResult
+from ...file_id import FileId
+
+
+class InlineQueryResultCachedDocument(InlineQueryResult):
+    """A link to a file stored on the Telegram servers.
+
+    By default, this file will be sent by the user with an optional caption. Alternatively, you can use
+    *input_message_content* to send a message with the specified content instead of the file.
+
+    Parameters:
+        document_file_id (``str``):
+            A valid file identifier for the file.
+
+        title (``str``):
+            Title for the result.
+
+        id (``str``, *optional*):
+            Unique identifier for this result, 1-64 bytes.
+            Defaults to a randomly generated UUID4.
+
+        description (``str``, *optional*):
+            Short description of the result.
+
+        caption (``str``, *optional*):
+            Caption of the photo to be sent, 0-1024 characters.
+
+        parse_mode (:obj:`~pyrogram.enums.ParseMode`, *optional*):
+            By default, texts are parsed using both Markdown and HTML styles.
+            You can combine both syntaxes together.
+
+        caption_entities (List of :obj:`~pyrogram.types.MessageEntity`):
+            List of special entities that appear in the caption, which can be specified instead of *parse_mode*.
+
+        reply_markup (:obj:`~pyrogram.types.InlineKeyboardMarkup`, *optional*):
+            An InlineKeyboardMarkup object.
+
+        input_message_content (:obj:`~pyrogram.types.InputMessageContent`):
+            Content of the message to be sent instead of the photo.
+    """
+
+    def __init__(
+        self,
+        document_file_id: str,
+        title: str,
+        id: str = None,
+        description: str = None,
+        caption: str = "",
+        parse_mode: Optional["enums.ParseMode"] = None,
+        caption_entities: List["types.MessageEntity"] = None,
+        reply_markup: "types.InlineKeyboardMarkup" = None,
+        input_message_content: "types.InputMessageContent" = None
+    ):
+        super().__init__("file", id, input_message_content, reply_markup)
+
+        self.document_file_id = document_file_id
+        self.title = title
+        self.description = description
+        self.caption = caption
+        self.parse_mode = parse_mode
+        self.caption_entities = caption_entities
+        self.reply_markup = reply_markup
+        self.input_message_content = input_message_content
+
+    async def write(self, client: "pyrogram.Client"):
+        message, entities = (await utils.parse_text_entities(
+            client, self.caption, self.parse_mode, self.caption_entities
+        )).values()
+
+        file_id = FileId.decode(self.document_file_id)
+
+        return raw.types.InputBotInlineResultDocument(
+            id=self.id,
+            type=self.type,
+            title=self.title,
+            description=self.description,
+            document=raw.types.InputDocument(
+                id=file_id.media_id,
+                access_hash=file_id.access_hash,
+                file_reference=file_id.file_reference,
+            ),
+            send_message=(
+                await self.input_message_content.write(client, self.reply_markup)
+                if self.input_message_content
+                else raw.types.InputBotInlineMessageMediaAuto(
+                    reply_markup=await self.reply_markup.write(client) if self.reply_markup else None,
+                    message=message,
+                    entities=entities
+                )
+            )
+        )
diff --git a/pyrogram/types/inline_mode/inline_query_result_cached_photo.py b/pyrogram/types/inline_mode/inline_query_result_cached_photo.py
new file mode 100644
index 0000000000..2e01d344ec
--- /dev/null
+++ b/pyrogram/types/inline_mode/inline_query_result_cached_photo.py
@@ -0,0 +1,111 @@
+#  Pyrogram - Telegram MTProto API Client Library for Python
+#  Copyright (C) 2017-present Dan 
+#
+#  This file is part of Pyrogram.
+#
+#  Pyrogram is free software: you can redistribute it and/or modify
+#  it under the terms of the GNU Lesser General Public License as published
+#  by the Free Software Foundation, either version 3 of the License, or
+#  (at your option) any later version.
+#
+#  Pyrogram is distributed in the hope that it will be useful,
+#  but WITHOUT ANY WARRANTY; without even the implied warranty of
+#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#  GNU Lesser General Public License for more details.
+#
+#  You should have received a copy of the GNU Lesser General Public License
+#  along with Pyrogram.  If not, see .
+
+from typing import Optional, List
+
+import pyrogram
+from pyrogram import raw, types, utils, enums
+from .inline_query_result import InlineQueryResult
+from ...file_id import FileId
+
+
+class InlineQueryResultCachedPhoto(InlineQueryResult):
+    """A link to a photo stored on the Telegram servers.
+
+    By default, this photo will be sent by the user with an optional caption. Alternatively, you can use
+    *input_message_content* to send a message with the specified content instead of the photo.
+
+    Parameters:
+        photo_file_id (``str``):
+            A valid file identifier of the photo.
+
+        id (``str``, *optional*):
+            Unique identifier for this result, 1-64 bytes.
+            Defaults to a randomly generated UUID4.
+
+        title (``str``, *optional*):
+            Title for the result.
+
+        description (``str``, *optional*):
+            Short description of the result.
+
+        caption (``str``, *optional*):
+            Caption of the photo to be sent, 0-1024 characters.
+
+        parse_mode (:obj:`~pyrogram.enums.ParseMode`, *optional*):
+            By default, texts are parsed using both Markdown and HTML styles.
+            You can combine both syntaxes together.
+
+        caption_entities (List of :obj:`~pyrogram.types.MessageEntity`):
+            List of special entities that appear in the caption, which can be specified instead of *parse_mode*.
+
+        reply_markup (:obj:`~pyrogram.types.InlineKeyboardMarkup`, *optional*):
+            An InlineKeyboardMarkup object.
+
+        input_message_content (:obj:`~pyrogram.types.InputMessageContent`):
+            Content of the message to be sent instead of the photo.
+    """
+
+    def __init__(
+        self,
+        photo_file_id: str,
+        id: str = None,
+        title: str = None,
+        description: str = None,
+        caption: str = "",
+        parse_mode: Optional["enums.ParseMode"] = None,
+        caption_entities: List["types.MessageEntity"] = None,
+        reply_markup: "types.InlineKeyboardMarkup" = None,
+        input_message_content: "types.InputMessageContent" = None
+    ):
+        super().__init__("photo", id, input_message_content, reply_markup)
+
+        self.photo_file_id = photo_file_id
+        self.title = title
+        self.description = description
+        self.caption = caption
+        self.parse_mode = parse_mode
+        self.caption_entities = caption_entities
+        self.reply_markup = reply_markup
+        self.input_message_content = input_message_content
+
+    async def write(self, client: "pyrogram.Client"):
+        message, entities = (await utils.parse_text_entities(
+            client, self.caption, self.parse_mode, self.caption_entities
+        )).values()
+
+        file_id = FileId.decode(self.photo_file_id)
+
+        return raw.types.InputBotInlineResultPhoto(
+            id=self.id,
+            type=self.type,
+            photo=raw.types.InputPhoto(
+                id=file_id.media_id,
+                access_hash=file_id.access_hash,
+                file_reference=file_id.file_reference,
+            ),
+            send_message=(
+                await self.input_message_content.write(client, self.reply_markup)
+                if self.input_message_content
+                else raw.types.InputBotInlineMessageMediaAuto(
+                    reply_markup=await self.reply_markup.write(client) if self.reply_markup else None,
+                    message=message,
+                    entities=entities
+                )
+            )
+        )
diff --git a/pyrogram/types/inline_mode/inline_query_result_cached_sticker.py b/pyrogram/types/inline_mode/inline_query_result_cached_sticker.py
new file mode 100644
index 0000000000..06d012fbe6
--- /dev/null
+++ b/pyrogram/types/inline_mode/inline_query_result_cached_sticker.py
@@ -0,0 +1,78 @@
+#  Pyrogram - Telegram MTProto API Client Library for Python
+#  Copyright (C) 2017-present Dan 
+#
+#  This file is part of Pyrogram.
+#
+#  Pyrogram is free software: you can redistribute it and/or modify
+#  it under the terms of the GNU Lesser General Public License as published
+#  by the Free Software Foundation, either version 3 of the License, or
+#  (at your option) any later version.
+#
+#  Pyrogram is distributed in the hope that it will be useful,
+#  but WITHOUT ANY WARRANTY; without even the implied warranty of
+#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#  GNU Lesser General Public License for more details.
+#
+#  You should have received a copy of the GNU Lesser General Public License
+#  along with Pyrogram.  If not, see .
+
+import pyrogram
+from pyrogram import raw, types
+from .inline_query_result import InlineQueryResult
+from ...file_id import FileId
+
+
+class InlineQueryResultCachedSticker(InlineQueryResult):
+    """A link to a sticker stored on the Telegram servers
+
+    By default, this sticker will be sent by the user. Alternatively, you can use *input_message_content* to send a
+    message with the specified content instead of the sticker.
+
+    Parameters:
+        sticker_file_id (``str``):
+            A valid file identifier of the sticker.
+
+        id (``str``, *optional*):
+            Unique identifier for this result, 1-64 bytes.
+            Defaults to a randomly generated UUID4.
+
+        reply_markup (:obj:`~pyrogram.types.InlineKeyboardMarkup`, *optional*):
+            An InlineKeyboardMarkup object.
+
+        input_message_content (:obj:`~pyrogram.types.InputMessageContent`):
+            Content of the message to be sent instead of the photo.
+    """
+
+    def __init__(
+        self,
+        sticker_file_id: str,
+        id: str = None,
+        reply_markup: "types.InlineKeyboardMarkup" = None,
+        input_message_content: "types.InputMessageContent" = None
+    ):
+        super().__init__("sticker", id, input_message_content, reply_markup)
+
+        self.sticker_file_id = sticker_file_id
+        self.reply_markup = reply_markup
+        self.input_message_content = input_message_content
+
+    async def write(self, client: "pyrogram.Client"):
+        file_id = FileId.decode(self.sticker_file_id)
+
+        return raw.types.InputBotInlineResultDocument(
+            id=self.id,
+            type=self.type,
+            document=raw.types.InputDocument(
+                id=file_id.media_id,
+                access_hash=file_id.access_hash,
+                file_reference=file_id.file_reference,
+            ),
+            send_message=(
+                await self.input_message_content.write(client, self.reply_markup)
+                if self.input_message_content
+                else raw.types.InputBotInlineMessageMediaAuto(
+                    reply_markup=await self.reply_markup.write(client) if self.reply_markup else None,
+                    message="",
+                )
+            )
+        )
diff --git a/pyrogram/types/inline_mode/inline_query_result_cached_video.py b/pyrogram/types/inline_mode/inline_query_result_cached_video.py
new file mode 100644
index 0000000000..00ea32ecdb
--- /dev/null
+++ b/pyrogram/types/inline_mode/inline_query_result_cached_video.py
@@ -0,0 +1,114 @@
+#  Pyrogram - Telegram MTProto API Client Library for Python
+#  Copyright (C) 2017-present Dan 
+#
+#  This file is part of Pyrogram.
+#
+#  Pyrogram is free software: you can redistribute it and/or modify
+#  it under the terms of the GNU Lesser General Public License as published
+#  by the Free Software Foundation, either version 3 of the License, or
+#  (at your option) any later version.
+#
+#  Pyrogram is distributed in the hope that it will be useful,
+#  but WITHOUT ANY WARRANTY; without even the implied warranty of
+#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#  GNU Lesser General Public License for more details.
+#
+#  You should have received a copy of the GNU Lesser General Public License
+#  along with Pyrogram.  If not, see .
+
+from typing import Optional, List
+
+import pyrogram
+from pyrogram import raw, types, utils, enums
+from .inline_query_result import InlineQueryResult
+from ...file_id import FileId
+
+
+class InlineQueryResultCachedVideo(InlineQueryResult):
+    """A link to a video file stored on the Telegram servers.
+
+    By default, this video file will be sent by the user with an optional caption.
+    Alternatively, you can use *input_message_content* to send a message with the specified content instead of the
+    video.
+
+    Parameters:
+        video_file_id (``str``):
+            A valid file identifier for the video file.
+
+        title (``str``):
+            Title for the result.
+
+        id (``str``, *optional*):
+            Unique identifier for this result, 1-64 bytes.
+            Defaults to a randomly generated UUID4.
+
+        description (``str``, *optional*):
+            Short description of the result.
+
+        caption (``str``, *optional*):
+            Caption of the photo to be sent, 0-1024 characters.
+
+        parse_mode (:obj:`~pyrogram.enums.ParseMode`, *optional*):
+            By default, texts are parsed using both Markdown and HTML styles.
+            You can combine both syntaxes together.
+
+        caption_entities (List of :obj:`~pyrogram.types.MessageEntity`):
+            List of special entities that appear in the caption, which can be specified instead of *parse_mode*.
+
+        reply_markup (:obj:`~pyrogram.types.InlineKeyboardMarkup`, *optional*):
+            An InlineKeyboardMarkup object.
+
+        input_message_content (:obj:`~pyrogram.types.InputMessageContent`):
+            Content of the message to be sent instead of the photo.
+    """
+
+    def __init__(
+        self,
+        video_file_id: str,
+        title: str,
+        id: str = None,
+        description: str = None,
+        caption: str = "",
+        parse_mode: Optional["enums.ParseMode"] = None,
+        caption_entities: List["types.MessageEntity"] = None,
+        reply_markup: "types.InlineKeyboardMarkup" = None,
+        input_message_content: "types.InputMessageContent" = None
+    ):
+        super().__init__("video", id, input_message_content, reply_markup)
+
+        self.video_file_id = video_file_id
+        self.title = title
+        self.description = description
+        self.caption = caption
+        self.parse_mode = parse_mode
+        self.caption_entities = caption_entities
+        self.reply_markup = reply_markup
+        self.input_message_content = input_message_content
+
+    async def write(self, client: "pyrogram.Client"):
+        message, entities = (await utils.parse_text_entities(
+            client, self.caption, self.parse_mode, self.caption_entities
+        )).values()
+
+        file_id = FileId.decode(self.video_file_id)
+
+        return raw.types.InputBotInlineResultDocument(
+            id=self.id,
+            type=self.type,
+            title=self.title,
+            description=self.description,
+            document=raw.types.InputDocument(
+                id=file_id.media_id,
+                access_hash=file_id.access_hash,
+                file_reference=file_id.file_reference,
+            ),
+            send_message=(
+                await self.input_message_content.write(client, self.reply_markup)
+                if self.input_message_content
+                else raw.types.InputBotInlineMessageMediaAuto(
+                    reply_markup=await self.reply_markup.write(client) if self.reply_markup else None,
+                    message=message,
+                    entities=entities
+                )
+            )
+        )
diff --git a/pyrogram/types/inline_mode/inline_query_result_cached_voice.py b/pyrogram/types/inline_mode/inline_query_result_cached_voice.py
new file mode 100644
index 0000000000..cc2bd76855
--- /dev/null
+++ b/pyrogram/types/inline_mode/inline_query_result_cached_voice.py
@@ -0,0 +1,108 @@
+#  Pyrogram - Telegram MTProto API Client Library for Python
+#  Copyright (C) 2017-present Dan 
+#
+#  This file is part of Pyrogram.
+#
+#  Pyrogram is free software: you can redistribute it and/or modify
+#  it under the terms of the GNU Lesser General Public License as published
+#  by the Free Software Foundation, either version 3 of the License, or
+#  (at your option) any later version.
+#
+#  Pyrogram is distributed in the hope that it will be useful,
+#  but WITHOUT ANY WARRANTY; without even the implied warranty of
+#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#  GNU Lesser General Public License for more details.
+#
+#  You should have received a copy of the GNU Lesser General Public License
+#  along with Pyrogram.  If not, see .
+
+from typing import Optional, List
+
+import pyrogram
+from pyrogram import raw, types, utils, enums
+from .inline_query_result import InlineQueryResult
+from ...file_id import FileId
+
+
+class InlineQueryResultCachedVoice(InlineQueryResult):
+    """A link to a voice message stored on the Telegram servers.
+
+    By default, this voice message will be sent by the user.
+    Alternatively, you can use *input_message_content* to send a message with the specified content instead of the voice
+    message.
+
+    Parameters:
+        voice_file_id (``str``):
+            A valid file identifier for the voice message.
+
+        id (``str``, *optional*):
+            Unique identifier for this result, 1-64 bytes.
+            Defaults to a randomly generated UUID4.
+
+        title (``str``, *optional*):
+            Title for the result.
+
+        caption (``str``, *optional*):
+            Caption of the photo to be sent, 0-1024 characters.
+
+        parse_mode (:obj:`~pyrogram.enums.ParseMode`, *optional*):
+            By default, texts are parsed using both Markdown and HTML styles.
+            You can combine both syntaxes together.
+
+        caption_entities (List of :obj:`~pyrogram.types.MessageEntity`):
+            List of special entities that appear in the caption, which can be specified instead of *parse_mode*.
+
+        reply_markup (:obj:`~pyrogram.types.InlineKeyboardMarkup`, *optional*):
+            An InlineKeyboardMarkup object.
+
+        input_message_content (:obj:`~pyrogram.types.InputMessageContent`):
+            Content of the message to be sent instead of the photo.
+    """
+
+    def __init__(
+        self,
+        voice_file_id: str,
+        id: str = None,
+        title: str = None,
+        caption: str = "",
+        parse_mode: Optional["enums.ParseMode"] = None,
+        caption_entities: List["types.MessageEntity"] = None,
+        reply_markup: "types.InlineKeyboardMarkup" = None,
+        input_message_content: "types.InputMessageContent" = None
+    ):
+        super().__init__("voice", id, input_message_content, reply_markup)
+
+        self.voice_file_id = voice_file_id
+        self.title = title
+        self.caption = caption
+        self.parse_mode = parse_mode
+        self.caption_entities = caption_entities
+        self.reply_markup = reply_markup
+        self.input_message_content = input_message_content
+
+    async def write(self, client: "pyrogram.Client"):
+        message, entities = (await utils.parse_text_entities(
+            client, self.caption, self.parse_mode, self.caption_entities
+        )).values()
+
+        file_id = FileId.decode(self.voice_file_id)
+
+        return raw.types.InputBotInlineResultDocument(
+            id=self.id,
+            type=self.type,
+            title=self.title,
+            document=raw.types.InputDocument(
+                id=file_id.media_id,
+                access_hash=file_id.access_hash,
+                file_reference=file_id.file_reference,
+            ),
+            send_message=(
+                await self.input_message_content.write(client, self.reply_markup)
+                if self.input_message_content
+                else raw.types.InputBotInlineMessageMediaAuto(
+                    reply_markup=await self.reply_markup.write(client) if self.reply_markup else None,
+                    message=message,
+                    entities=entities
+                )
+            )
+        )
diff --git a/pyrogram/types/inline_mode/inline_query_result_contact.py b/pyrogram/types/inline_mode/inline_query_result_contact.py
new file mode 100644
index 0000000000..d55a624450
--- /dev/null
+++ b/pyrogram/types/inline_mode/inline_query_result_contact.py
@@ -0,0 +1,114 @@
+#  Pyrogram - Telegram MTProto API Client Library for Python
+#  Copyright (C) 2017-present Dan 
+#
+#  This file is part of Pyrogram.
+#
+#  Pyrogram is free software: you can redistribute it and/or modify
+#  it under the terms of the GNU Lesser General Public License as published
+#  by the Free Software Foundation, either version 3 of the License, or
+#  (at your option) any later version.
+#
+#  Pyrogram is distributed in the hope that it will be useful,
+#  but WITHOUT ANY WARRANTY; without even the implied warranty of
+#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#  GNU Lesser General Public License for more details.
+#
+#  You should have received a copy of the GNU Lesser General Public License
+#  along with Pyrogram.  If not, see .
+
+import pyrogram
+from pyrogram import raw, types
+from .inline_query_result import InlineQueryResult
+
+
+class InlineQueryResultContact(InlineQueryResult):
+    """Contact with a phone number
+    
+    By default, this contact will be sent by the user.
+    Alternatively, you can use *input_message_content* to send a message with the specified content instead of the
+    contact.
+    
+    Parameters:
+        phone_number (``str``):
+            Contact's phone number.
+
+        first_name (``str``):
+            Contact's first name.
+
+        last_name (``str``, *optional*):
+            Contact's last name.
+
+        vcard (``str``, *optional*):
+            Additional data about the contact in the form of a `vCard `_.
+
+        id (``str``, *optional*):
+            Unique identifier for this result, 1-64 bytes.
+            Defaults to a randomly generated UUID4.
+
+        reply_markup (:obj:`~pyrogram.types.InlineKeyboardMarkup`, *optional*):
+            Inline keyboard attached to the message.
+            
+        input_message_content (:obj:`~pyrogram.types.InputMessageContent`, *optional*):
+            Content of the message to be sent instead of the contact.
+
+        thumb_url (``str``, *optional*):
+            Url of the thumbnail for the result.
+
+        thumb_width (``int``, *optional*):
+            Thumbnail width.
+
+        thumb_height (``int``, *optional*):
+            Thumbnail height.
+    """
+
+    def __init__(
+        self,
+        phone_number: str,
+        first_name: str,
+        last_name: str = "",
+        vcard: str = "",
+        id: str = None,
+        reply_markup: "types.InlineKeyboardMarkup" = None,
+        input_message_content: "types.InputMessageContent" = None,
+        thumb_url: str = None,
+        thumb_width: int = 0,
+        thumb_height: int = 0
+    ):
+        super().__init__("contact", id, input_message_content, reply_markup)
+
+        self.phone_number = phone_number
+        self.first_name = first_name
+        self.last_name = last_name
+        self.vcard = vcard
+        self.thumb_url = thumb_url
+        self.thumb_width = thumb_width
+        self.thumb_height = thumb_height
+
+    async def write(self, client: "pyrogram.Client"):
+        return raw.types.InputBotInlineResult(
+            id=self.id,
+            type=self.type,
+            title=self.first_name,
+            send_message=(
+                await self.input_message_content.write(client, self.reply_markup)
+                if self.input_message_content
+                else raw.types.InputBotInlineMessageMediaContact(
+                    phone_number=self.phone_number,
+                    first_name=self.first_name,
+                    last_name=self.last_name,
+                    vcard=self.vcard,
+                    reply_markup=await self.reply_markup.write(client) if self.reply_markup else None,
+                )
+            ),
+            thumb=raw.types.InputWebDocument(
+                url=self.thumb_url,
+                size=0,
+                mime_type="image/jpg",
+                attributes=[
+                    raw.types.DocumentAttributeImageSize(
+                        w=self.thumb_width,
+                        h=self.thumb_height
+                    )
+                ]
+            ) if self.thumb_url else None
+        )
diff --git a/pyrogram/types/inline_mode/inline_query_result_document.py b/pyrogram/types/inline_mode/inline_query_result_document.py
new file mode 100644
index 0000000000..eac7901b14
--- /dev/null
+++ b/pyrogram/types/inline_mode/inline_query_result_document.py
@@ -0,0 +1,145 @@
+#  Pyrogram - Telegram MTProto API Client Library for Python
+#  Copyright (C) 2017-present Dan 
+#
+#  This file is part of Pyrogram.
+#
+#  Pyrogram is free software: you can redistribute it and/or modify
+#  it under the terms of the GNU Lesser General Public License as published
+#  by the Free Software Foundation, either version 3 of the License, or
+#  (at your option) any later version.
+#
+#  Pyrogram is distributed in the hope that it will be useful,
+#  but WITHOUT ANY WARRANTY; without even the implied warranty of
+#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#  GNU Lesser General Public License for more details.
+#
+#  You should have received a copy of the GNU Lesser General Public License
+#  along with Pyrogram.  If not, see .
+
+from typing import Optional, List
+
+import pyrogram
+from pyrogram import raw, types, utils, enums
+from .inline_query_result import InlineQueryResult
+
+
+class InlineQueryResultDocument(InlineQueryResult):
+    """Link to a file.
+
+    By default, this file will be sent by the user with an optional caption.
+    Alternatively, you can use *input_message_content* to send a message with the specified content instead of the file.
+
+    Parameters:
+        document_url (``str``):
+            A valid URL for the file.
+
+        title (``str``):
+            Title for the result.
+
+        mime_type (``str``, *optional*):
+            Mime type of the content of the file, either “application/pdf” or “application/zip”.
+            Defaults to "application/zip".
+
+        id (``str``, *optional*):
+            Unique identifier for this result, 1-64 bytes.
+            Defaults to a randomly generated UUID4.
+
+        caption (``str``, *optional*):
+            Caption of the video to be sent, 0-1024 characters.
+
+        parse_mode (:obj:`~pyrogram.enums.ParseMode`, *optional*):
+            By default, texts are parsed using both Markdown and HTML styles.
+            You can combine both syntaxes together.
+
+        caption_entities (List of :obj:`~pyrogram.types.MessageEntity`):
+            List of special entities that appear in the caption, which can be specified instead of *parse_mode*.
+
+        description (``str``, *optional*):
+            Short description of the result.
+
+        reply_markup (:obj:`~pyrogram.types.InlineKeyboardMarkup`, *optional*):
+            Inline keyboard attached to the message.
+
+        input_message_content (:obj:`~pyrogram.types.InputMessageContent`):
+            Content of the message to be sent instead of the file.
+
+        thumb_url (``str``, *optional*):
+            Url of the thumbnail for the result.
+
+        thumb_width (``int``, *optional*):
+            Thumbnail width.
+
+        thumb_height (``int``, *optional*):
+            Thumbnail height.
+    """
+
+    def __init__(
+        self,
+        document_url: str,
+        title: str,
+        mime_type: str = "application/zip",
+        id: str = None,
+        caption: str = "",
+        parse_mode: Optional["enums.ParseMode"] = None,
+        caption_entities: List["types.MessageEntity"] = None,
+        description: str = "",
+        reply_markup: "types.InlineKeyboardMarkup" = None,
+        input_message_content: "types.InputMessageContent" = None,
+        thumb_url: str = None,
+        thumb_width: int = 0,
+        thumb_height: int = 0
+    ):
+        super().__init__("file", id, input_message_content, reply_markup)
+
+        self.document_url = document_url
+        self.title = title
+        self.mime_type = mime_type
+        self.caption = caption
+        self.parse_mode = parse_mode
+        self.caption_entities = caption_entities
+        self.description = description
+        self.thumb_url = thumb_url
+        self.thumb_width = thumb_width
+        self.thumb_height = thumb_height
+
+    async def write(self, client: "pyrogram.Client"):
+        document = raw.types.InputWebDocument(
+            url=self.document_url,
+            size=0,
+            mime_type=self.mime_type,
+            attributes=[]
+        )
+
+        thumb = raw.types.InputWebDocument(
+            url=self.thumb_url,
+            size=0,
+            mime_type="image/jpeg",
+            attributes=[
+                raw.types.DocumentAttributeImageSize(
+                    w=self.thumb_width,
+                    h=self.thumb_height
+                )
+            ]
+        ) if self.thumb_url else None
+
+        message, entities = (await utils.parse_text_entities(
+            client, self.caption, self.parse_mode, self.caption_entities
+        )).values()
+
+        return raw.types.InputBotInlineResult(
+            id=self.id,
+            type=self.type,
+            title=self.title,
+            description=self.description,
+            thumb=thumb,
+            content=document,
+            send_message=(
+                await self.input_message_content.write(client, self.reply_markup)
+                if self.input_message_content
+                else raw.types.InputBotInlineMessageMediaAuto(
+                    reply_markup=await self.reply_markup.write(client) if self.reply_markup else None,
+                    message=message,
+                    entities=entities
+                )
+            )
+        )
diff --git a/pyrogram/types/inline_mode/inline_query_result_location.py b/pyrogram/types/inline_mode/inline_query_result_location.py
new file mode 100644
index 0000000000..236f39a624
--- /dev/null
+++ b/pyrogram/types/inline_mode/inline_query_result_location.py
@@ -0,0 +1,122 @@
+#  Pyrogram - Telegram MTProto API Client Library for Python
+#  Copyright (C) 2017-present Dan 
+#
+#  This file is part of Pyrogram.
+#
+#  Pyrogram is free software: you can redistribute it and/or modify
+#  it under the terms of the GNU Lesser General Public License as published
+#  by the Free Software Foundation, either version 3 of the License, or
+#  (at your option) any later version.
+#
+#  Pyrogram is distributed in the hope that it will be useful,
+#  but WITHOUT ANY WARRANTY; without even the implied warranty of
+#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#  GNU Lesser General Public License for more details.
+#
+#  You should have received a copy of the GNU Lesser General Public License
+#  along with Pyrogram.  If not, see .
+
+import pyrogram
+from pyrogram import raw, types
+from .inline_query_result import InlineQueryResult
+
+
+class InlineQueryResultLocation(InlineQueryResult):
+    """A location on a map.
+
+    By default, the location will be sent by the user. Alternatively, you can use *input_message_content* to send a
+    message with the specified content instead of the location.
+
+    Parameters:
+        title (``str``):
+            Title for the result.
+
+        latitude (``float``):
+            Location latitude in degrees.
+
+        longitude (``float``):
+            Location longitude in degrees.
+
+        id (``str``, *optional*):
+            Unique identifier for this result, 1-64 bytes.
+            Defaults to a randomly generated UUID4.
+
+        horizontal_accuracy (``float``, *optional*)
+            The radius of uncertainty for the location, measured in meters; 0-1500.
+
+        live_period (``int``, *optional*):
+            Period in seconds for which the location can be updated, should be between 60 and 86400.
+
+        heading (``int``, *optional*):
+            For live locations, a direction in which the user is moving, in degrees.
+            Must be between 1 and 360 if specified.
+
+        proximity_alert_radius (``int``, *optional*):
+            For live locations, a maximum distance for proximity alerts about approaching another chat member,
+            in meters. Must be between 1 and 100000 if specified.
+
+        reply_markup (:obj:`~pyrogram.types.InlineKeyboardMarkup`, *optional*):
+            Inline keyboard attached to the message.
+
+        input_message_content (:obj:`~pyrogram.types.InputMessageContent`):
+            Content of the message to be sent instead of the file.
+
+        thumb_url (``str``, *optional*):
+            Url of the thumbnail for the result.
+
+        thumb_width (``int``, *optional*):
+            Thumbnail width.
+
+        thumb_height (``int``, *optional*):
+            Thumbnail height.
+    """
+
+    def __init__(
+        self,
+        title: str,
+        latitude: float,
+        longitude: float,
+        horizontal_accuracy: float = None,
+        live_period: int = None,
+        heading: int = None,
+        proximity_alert_radius: int = None,
+        id: str = None,
+        reply_markup: "types.InlineKeyboardMarkup" = None,
+        input_message_content: "types.InputMessageContent" = None,
+        thumb_url: str = None,
+        thumb_width: int = 0,
+        thumb_height: int = 0
+    ):
+        super().__init__("location", id, input_message_content, reply_markup)
+
+        self.title = title
+        self.latitude = latitude
+        self.longitude = longitude
+        self.horizontal_accuracy = horizontal_accuracy
+        self.live_period = live_period
+        self.heading = heading
+        self.proximity_alert_radius = proximity_alert_radius
+        self.thumb_url = thumb_url
+        self.thumb_width = thumb_width
+        self.thumb_height = thumb_height
+
+    async def write(self, client: "pyrogram.Client"):
+        return raw.types.InputBotInlineResult(
+            id=self.id,
+            type=self.type,
+            title=self.title,
+            send_message=(
+                await self.input_message_content.write(client, self.reply_markup)
+                if self.input_message_content
+                else raw.types.InputBotInlineMessageMediaGeo(
+                    geo_point=raw.types.InputGeoPoint(
+                        lat=self.latitude,
+                        long=self.longitude
+                    ),
+                    heading=self.heading,
+                    period=self.live_period,
+                    proximity_notification_radius=self.proximity_alert_radius,
+                    reply_markup=await self.reply_markup.write(client) if self.reply_markup else None
+                )
+            )
+        )
diff --git a/pyrogram/types/inline_mode/inline_query_result_photo.py b/pyrogram/types/inline_mode/inline_query_result_photo.py
index f86c671898..d75ccac2a5 100644
--- a/pyrogram/types/inline_mode/inline_query_result_photo.py
+++ b/pyrogram/types/inline_mode/inline_query_result_photo.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
@@ -19,7 +19,7 @@
 from typing import Optional, List
 
 import pyrogram
-from pyrogram import raw, types, utils
+from pyrogram import raw, types, utils, enums
 from .inline_query_result import InlineQueryResult
 
 
@@ -39,6 +39,12 @@ class InlineQueryResultPhoto(InlineQueryResult):
             URL of the thumbnail for the photo.
             Defaults to the value passed in *photo_url*.
 
+        photo_width (``int``, *optional*):
+            Width of the photo.
+
+        photo_height (``int``, *optional*):
+            Height of the photo
+
         id (``str``, *optional*):
             Unique identifier for this result, 1-64 bytes.
             Defaults to a randomly generated UUID4.
@@ -52,15 +58,12 @@ class InlineQueryResultPhoto(InlineQueryResult):
         caption (``str``, *optional*):
             Caption of the photo to be sent, 0-1024 characters.
 
-        parse_mode (``str``, *optional*):
+        parse_mode (:obj:`~pyrogram.enums.ParseMode`, *optional*):
             By default, texts are parsed using both Markdown and HTML styles.
             You can combine both syntaxes together.
-            Pass "markdown" or "md" to enable Markdown-style parsing only.
-            Pass "html" to enable HTML-style parsing only.
-            Pass None to completely disable style parsing.
 
         caption_entities (List of :obj:`~pyrogram.types.MessageEntity`):
-                List of special entities that appear in the caption, which can be specified instead of *parse_mode*.
+            List of special entities that appear in the caption, which can be specified instead of *parse_mode*.
 
         reply_markup (:obj:`~pyrogram.types.InlineKeyboardMarkup`, *optional*):
             An InlineKeyboardMarkup object.
@@ -73,11 +76,13 @@ def __init__(
         self,
         photo_url: str,
         thumb_url: str = None,
+        photo_width: int = 0,
+        photo_height: int = 0,
         id: str = None,
         title: str = None,
         description: str = None,
         caption: str = "",
-        parse_mode: Optional[str] = object,
+        parse_mode: Optional["enums.ParseMode"] = None,
         caption_entities: List["types.MessageEntity"] = None,
         reply_markup: "types.InlineKeyboardMarkup" = None,
         input_message_content: "types.InputMessageContent" = None
@@ -86,6 +91,8 @@ def __init__(
 
         self.photo_url = photo_url
         self.thumb_url = thumb_url
+        self.photo_width = photo_width
+        self.photo_height = photo_height
         self.title = title
         self.description = description
         self.caption = caption
@@ -99,7 +106,12 @@ async def write(self, client: "pyrogram.Client"):
             url=self.photo_url,
             size=0,
             mime_type="image/jpeg",
-            attributes=[]
+            attributes=[
+                raw.types.DocumentAttributeImageSize(
+                    w=self.photo_width,
+                    h=self.photo_height
+                )
+            ]
         )
 
         if self.thumb_url is None:
diff --git a/pyrogram/types/inline_mode/inline_query_result_venue.py b/pyrogram/types/inline_mode/inline_query_result_venue.py
new file mode 100644
index 0000000000..b3b513a55d
--- /dev/null
+++ b/pyrogram/types/inline_mode/inline_query_result_venue.py
@@ -0,0 +1,131 @@
+#  Pyrogram - Telegram MTProto API Client Library for Python
+#  Copyright (C) 2017-present Dan 
+#
+#  This file is part of Pyrogram.
+#
+#  Pyrogram is free software: you can redistribute it and/or modify
+#  it under the terms of the GNU Lesser General Public License as published
+#  by the Free Software Foundation, either version 3 of the License, or
+#  (at your option) any later version.
+#
+#  Pyrogram is distributed in the hope that it will be useful,
+#  but WITHOUT ANY WARRANTY; without even the implied warranty of
+#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#  GNU Lesser General Public License for more details.
+#
+#  You should have received a copy of the GNU Lesser General Public License
+#  along with Pyrogram.  If not, see .
+
+import pyrogram
+from pyrogram import raw, types
+from .inline_query_result import InlineQueryResult
+
+
+class InlineQueryResultVenue(InlineQueryResult):
+    """A venue.
+
+    By default, the venue will be sent by the user. Alternatively, you can use *input_message_content* to send a message
+    with the specified content instead of the venue.
+
+    Parameters:
+        title (``str``):
+            Title for the result.
+
+        address (``str``):
+            Address of the venue.
+
+        latitude (``float``):
+            Location latitude in degrees.
+
+        longitude (``float``):
+            Location longitude in degrees.
+
+        id (``str``, *optional*):
+            Unique identifier for this result, 1-64 bytes.
+            Defaults to a randomly generated UUID4.
+
+        foursquare_id (``str``, *optional*):
+            Foursquare identifier of the venue if known.
+
+        foursquare_type (``str``, *optional*):
+            Foursquare type of the venue, if known.
+
+        google_place_id (``str``, *optional*):
+            Google Places identifier of the venue.
+
+        google_place_type (``str``, *optional*):
+            Google Places type of the venue.
+
+        reply_markup (:obj:`~pyrogram.types.InlineKeyboardMarkup`, *optional*):
+            Inline keyboard attached to the message.
+
+        input_message_content (:obj:`~pyrogram.types.InputMessageContent`):
+            Content of the message to be sent instead of the file.
+
+        thumb_url (``str``, *optional*):
+            Url of the thumbnail for the result.
+
+        thumb_width (``int``, *optional*):
+            Thumbnail width.
+
+        thumb_height (``int``, *optional*):
+            Thumbnail height.
+    """
+
+    def __init__(
+        self,
+        title: str,
+        address: str,
+        latitude: float,
+        longitude: float,
+        id: str = None,
+        foursquare_id: str = None,
+        foursquare_type: str = None,
+        google_place_id: str = None,
+        google_place_type: str = None,
+        reply_markup: "types.InlineKeyboardMarkup" = None,
+        input_message_content: "types.InputMessageContent" = None,
+        thumb_url: str = None,
+        thumb_width: int = 0,
+        thumb_height: int = 0
+    ):
+        super().__init__("venue", id, input_message_content, reply_markup)
+
+        self.title = title
+        self.address = address
+        self.latitude = latitude
+        self.longitude = longitude
+        self.foursquare_id = foursquare_id
+        self.foursquare_type = foursquare_type
+        self.google_place_id = google_place_id
+        self.google_place_type = google_place_type
+        self.thumb_url = thumb_url
+        self.thumb_width = thumb_width
+        self.thumb_height = thumb_height
+
+    async def write(self, client: "pyrogram.Client"):
+        return raw.types.InputBotInlineResult(
+            id=self.id,
+            type=self.type,
+            title=self.title,
+            send_message=(
+                await self.input_message_content.write(client, self.reply_markup)
+                if self.input_message_content
+                else raw.types.InputBotInlineMessageMediaVenue(
+                    geo_point=raw.types.InputGeoPoint(
+                        lat=self.latitude,
+                        long=self.longitude
+                    ),
+                    title=self.title,
+                    address=self.address,
+                    provider=(
+                        "foursquare" if self.foursquare_id or self.foursquare_type
+                        else "google" if self.google_place_id or self.google_place_type
+                        else ""
+                    ),
+                    venue_id=self.foursquare_id or self.google_place_id or "",
+                    venue_type=self.foursquare_type or self.google_place_type or "",
+                    reply_markup=await self.reply_markup.write(client) if self.reply_markup else None
+                )
+            )
+        )
diff --git a/pyrogram/types/inline_mode/inline_query_result_video.py b/pyrogram/types/inline_mode/inline_query_result_video.py
index cf035408df..5f71111f4f 100644
--- a/pyrogram/types/inline_mode/inline_query_result_video.py
+++ b/pyrogram/types/inline_mode/inline_query_result_video.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
@@ -19,7 +19,7 @@
 from typing import Optional, List
 
 import pyrogram
-from pyrogram import raw, types, utils
+from pyrogram import raw, types, utils, enums
 from .inline_query_result import InlineQueryResult
 
 
@@ -63,12 +63,9 @@ class InlineQueryResultVideo(InlineQueryResult):
         caption (``str``, *optional*):
             Caption of the video to be sent, 0-1024 characters.
 
-        parse_mode (``str``, *optional*):
+        parse_mode (:obj:`~pyrogram.enums.ParseMode`, *optional*):
             By default, texts are parsed using both Markdown and HTML styles.
             You can combine both syntaxes together.
-            Pass "markdown" or "md" to enable Markdown-style parsing only.
-            Pass "html" to enable HTML-style parsing only.
-            Pass None to completely disable style parsing.
 
         caption_entities (List of :obj:`~pyrogram.types.MessageEntity`):
             List of special entities that appear in the caption, which can be specified instead of *parse_mode*.
@@ -93,7 +90,7 @@ def __init__(
         video_duration: int = 0,
         description: str = None,
         caption: str = "",
-        parse_mode: Optional[str] = object,
+        parse_mode: Optional["enums.ParseMode"] = None,
         caption_entities: List["types.MessageEntity"] = None,
         reply_markup: "types.InlineKeyboardMarkup" = None,
         input_message_content: "types.InputMessageContent" = None
diff --git a/pyrogram/types/inline_mode/inline_query_result_voice.py b/pyrogram/types/inline_mode/inline_query_result_voice.py
new file mode 100644
index 0000000000..31b422f8d3
--- /dev/null
+++ b/pyrogram/types/inline_mode/inline_query_result_voice.py
@@ -0,0 +1,114 @@
+#  Pyrogram - Telegram MTProto API Client Library for Python
+#  Copyright (C) 2017-present Dan 
+#
+#  This file is part of Pyrogram.
+#
+#  Pyrogram is free software: you can redistribute it and/or modify
+#  it under the terms of the GNU Lesser General Public License as published
+#  by the Free Software Foundation, either version 3 of the License, or
+#  (at your option) any later version.
+#
+#  Pyrogram is distributed in the hope that it will be useful,
+#  but WITHOUT ANY WARRANTY; without even the implied warranty of
+#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#  GNU Lesser General Public License for more details.
+#
+#  You should have received a copy of the GNU Lesser General Public License
+#  along with Pyrogram.  If not, see .
+
+from typing import List, Optional
+
+import pyrogram
+from pyrogram import raw, types, utils, enums
+from .inline_query_result import InlineQueryResult
+
+
+class InlineQueryResultVoice(InlineQueryResult):
+    """Link to a voice recording in an .OGG container encoded with OPUS.
+    
+    By default, this voice recording will be sent by the user.
+    Alternatively, you can use *input_message_content* to send a message with the specified content instead of the
+    voice message.
+    
+    Parameters:
+        voice_url (``str``):
+            A valid URL for the voice recording.
+            
+        title (``str``):
+            Title for the result.
+            
+        id (``str``, *optional*):
+            Unique identifier for this result, 1-64 bytes.
+            Defaults to a randomly generated UUID4.
+            
+        voice_duration (``int``, *optional*):
+            Recording duration in seconds.
+
+        caption (``str``, *optional*):
+            Caption of the audio to be sent, 0-1024 characters.
+            
+        parse_mode (:obj:`~pyrogram.enums.ParseMode`, *optional*):
+            By default, texts are parsed using both Markdown and HTML styles.
+            You can combine both syntaxes together.
+
+        caption_entities (List of :obj:`~pyrogram.types.MessageEntity`):
+            List of special entities that appear in the caption, which can be specified instead of *parse_mode*.
+            
+        reply_markup (:obj:`~pyrogram.types.InlineKeyboardMarkup`, *optional*):
+            Inline keyboard attached to the message.
+            
+        input_message_content (:obj:`~pyrogram.types.InputMessageContent`, *optional*):
+            Content of the message to be sent instead of the audio.
+    """
+
+    def __init__(
+        self,
+        voice_url: str,
+        title: str,
+        id: str = None,
+        voice_duration: int = 0,
+        caption: str = "",
+        parse_mode: Optional["enums.ParseMode"] = None,
+        caption_entities: List["types.MessageEntity"] = None,
+        reply_markup: "types.InlineKeyboardMarkup" = None,
+        input_message_content: "types.InputMessageContent" = None
+    ):
+        super().__init__("voice", id, input_message_content, reply_markup)
+
+        self.voice_url = voice_url
+        self.title = title
+        self.voice_duration = voice_duration
+        self.caption = caption
+        self.parse_mode = parse_mode
+        self.caption_entities = caption_entities
+
+    async def write(self, client: "pyrogram.Client"):
+        audio = raw.types.InputWebDocument(
+            url=self.voice_url,
+            size=0,
+            mime_type="audio/mpeg",
+            attributes=[raw.types.DocumentAttributeAudio(
+                duration=self.voice_duration,
+                title=self.title,
+            )]
+        )
+
+        message, entities = (await utils.parse_text_entities(
+            client, self.caption, self.parse_mode, self.caption_entities
+        )).values()
+
+        return raw.types.InputBotInlineResult(
+            id=self.id,
+            type=self.type,
+            title=self.title,
+            content=audio,
+            send_message=(
+                await self.input_message_content.write(client, self.reply_markup)
+                if self.input_message_content
+                else raw.types.InputBotInlineMessageMediaAuto(
+                    reply_markup=await self.reply_markup.write(client) if self.reply_markup else None,
+                    message=message,
+                    entities=entities
+                )
+            )
+        )
diff --git a/pyrogram/types/input_media/__init__.py b/pyrogram/types/input_media/__init__.py
index 655a968c73..a03d1e21f4 100644
--- a/pyrogram/types/input_media/__init__.py
+++ b/pyrogram/types/input_media/__init__.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
diff --git a/pyrogram/types/input_media/input_media.py b/pyrogram/types/input_media/input_media.py
index 16a72bdadd..bd60aeb3e1 100644
--- a/pyrogram/types/input_media/input_media.py
+++ b/pyrogram/types/input_media/input_media.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
@@ -16,7 +16,7 @@
 #  You should have received a copy of the GNU Lesser General Public License
 #  along with Pyrogram.  If not, see .
 
-from typing import List
+from typing import List, Union, BinaryIO
 
 from ..messages_and_media import MessageEntity
 from ..object import Object
@@ -36,7 +36,7 @@ class InputMedia(Object):
 
     def __init__(
         self,
-        media: str,
+        media: Union[str, BinaryIO],
         caption: str = "",
         parse_mode: str = None,
         caption_entities: List[MessageEntity] = None
diff --git a/pyrogram/types/input_media/input_media_animation.py b/pyrogram/types/input_media/input_media_animation.py
index 9c5767fe78..2e91a2147b 100644
--- a/pyrogram/types/input_media/input_media_animation.py
+++ b/pyrogram/types/input_media/input_media_animation.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
@@ -20,13 +20,14 @@
 
 from .input_media import InputMedia
 from ..messages_and_media import MessageEntity
+from ... import enums
 
 
 class InputMediaAnimation(InputMedia):
     """An animation file (GIF or H.264/MPEG-4 AVC video without sound) to be sent inside an album.
 
     Parameters:
-        media (``str``):
+        media (``str`` | ``BinaryIO``):
             Animation to send.
             Pass a file_id as string to send a file that exists on the Telegram servers or
             pass a file path as string to upload a new file that exists on your local machine or
@@ -43,12 +44,9 @@ class InputMediaAnimation(InputMedia):
             Caption of the animation to be sent, 0-1024 characters.
             If not specified, the original caption is kept. Pass "" (empty string) to remove the caption.
 
-        parse_mode (``str``, *optional*):
+        parse_mode (:obj:`~pyrogram.enums.ParseMode`, *optional*):
             By default, texts are parsed using both Markdown and HTML styles.
             You can combine both syntaxes together.
-            Pass "markdown" or "md" to enable Markdown-style parsing only.
-            Pass "html" to enable HTML-style parsing only.
-            Pass None to completely disable style parsing.
 
         caption_entities (List of :obj:`~pyrogram.types.MessageEntity`):
             List of special entities that appear in the caption, which can be specified instead of *parse_mode*.
@@ -61,6 +59,9 @@ class InputMediaAnimation(InputMedia):
 
         duration (``int``, *optional*):
             Animation duration.
+
+        has_spoiler (``bool``, *optional*):
+            Pass True if the photo needs to be covered with a spoiler animation.
     """
 
     def __init__(
@@ -68,11 +69,12 @@ def __init__(
         media: Union[str, BinaryIO],
         thumb: str = None,
         caption: str = "",
-        parse_mode: Optional[str] = object,
+        parse_mode: Optional["enums.ParseMode"] = None,
         caption_entities: List[MessageEntity] = None,
         width: int = 0,
         height: int = 0,
-        duration: int = 0
+        duration: int = 0,
+        has_spoiler: bool = None
     ):
         super().__init__(media, caption, parse_mode, caption_entities)
 
@@ -80,3 +82,4 @@ def __init__(
         self.width = width
         self.height = height
         self.duration = duration
+        self.has_spoiler = has_spoiler
diff --git a/pyrogram/types/input_media/input_media_audio.py b/pyrogram/types/input_media/input_media_audio.py
index 3a66b5ee58..cc91e7bd5c 100644
--- a/pyrogram/types/input_media/input_media_audio.py
+++ b/pyrogram/types/input_media/input_media_audio.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
@@ -20,6 +20,7 @@
 
 from .input_media import InputMedia
 from ..messages_and_media import MessageEntity
+from ... import enums
 
 
 class InputMediaAudio(InputMedia):
@@ -28,7 +29,7 @@ class InputMediaAudio(InputMedia):
     It is intended to be used with :meth:`~pyrogram.Client.send_media_group`.
 
     Parameters:
-        media (``str``):
+        media (``str`` | ``BinaryIO``):
             Audio to send.
             Pass a file_id as string to send an audio that exists on the Telegram servers or
             pass a file path as string to upload a new audio that exists on your local machine or
@@ -45,12 +46,9 @@ class InputMediaAudio(InputMedia):
             Caption of the audio to be sent, 0-1024 characters.
             If not specified, the original caption is kept. Pass "" (empty string) to remove the caption.
 
-        parse_mode (``str``, *optional*):
+        parse_mode (:obj:`~pyrogram.enums.ParseMode`, *optional*):
             By default, texts are parsed using both Markdown and HTML styles.
             You can combine both syntaxes together.
-            Pass "markdown" or "md" to enable Markdown-style parsing only.
-            Pass "html" to enable HTML-style parsing only.
-            Pass None to completely disable style parsing.
 
         caption_entities (List of :obj:`~pyrogram.types.MessageEntity`):
             List of special entities that appear in the caption, which can be specified instead of *parse_mode*.
@@ -70,7 +68,7 @@ def __init__(
         media: Union[str, BinaryIO],
         thumb: str = None,
         caption: str = "",
-        parse_mode: Optional[str] = object,
+        parse_mode: Optional["enums.ParseMode"] = None,
         caption_entities: List[MessageEntity] = None,
         duration: int = 0,
         performer: str = "",
diff --git a/pyrogram/types/input_media/input_media_document.py b/pyrogram/types/input_media/input_media_document.py
index 3146006048..3e4d510b95 100644
--- a/pyrogram/types/input_media/input_media_document.py
+++ b/pyrogram/types/input_media/input_media_document.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
@@ -20,13 +20,14 @@
 
 from .input_media import InputMedia
 from ..messages_and_media import MessageEntity
+from ... import enums
 
 
 class InputMediaDocument(InputMedia):
     """A generic file to be sent inside an album.
 
     Parameters:
-        media (``str``):
+        media (``str`` | ``BinaryIO``):
             File to send.
             Pass a file_id as string to send a file that exists on the Telegram servers or
             pass a file path as string to upload a new file that exists on your local machine or
@@ -43,12 +44,9 @@ class InputMediaDocument(InputMedia):
             Caption of the document to be sent, 0-1024 characters.
             If not specified, the original caption is kept. Pass "" (empty string) to remove the caption.
 
-        parse_mode (``str``, *optional*):
+        parse_mode (:obj:`~pyrogram.enums.ParseMode`, *optional*):
             By default, texts are parsed using both Markdown and HTML styles.
             You can combine both syntaxes together.
-            Pass "markdown" or "md" to enable Markdown-style parsing only.
-            Pass "html" to enable HTML-style parsing only.
-            Pass None to completely disable style parsing.
 
         caption_entities (List of :obj:`~pyrogram.types.MessageEntity`):
             List of special entities that appear in the caption, which can be specified instead of *parse_mode*.
@@ -59,7 +57,7 @@ def __init__(
         media: Union[str, BinaryIO],
         thumb: str = None,
         caption: str = "",
-        parse_mode: Optional[str] = object,
+        parse_mode: Optional["enums.ParseMode"] = None,
         caption_entities: List[MessageEntity] = None
     ):
         super().__init__(media, caption, parse_mode, caption_entities)
diff --git a/pyrogram/types/input_media/input_media_photo.py b/pyrogram/types/input_media/input_media_photo.py
index e84a7616c0..f4fd0e0305 100644
--- a/pyrogram/types/input_media/input_media_photo.py
+++ b/pyrogram/types/input_media/input_media_photo.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
@@ -20,6 +20,7 @@
 
 from .input_media import InputMedia
 from ..messages_and_media import MessageEntity
+from ... import enums
 
 
 class InputMediaPhoto(InputMedia):
@@ -38,22 +39,25 @@ class InputMediaPhoto(InputMedia):
             Caption of the photo to be sent, 0-1024 characters.
             If not specified, the original caption is kept. Pass "" (empty string) to remove the caption.
 
-        parse_mode (``str``, *optional*):
+        parse_mode (:obj:`~pyrogram.enums.ParseMode`, *optional*):
             By default, texts are parsed using both Markdown and HTML styles.
             You can combine both syntaxes together.
-            Pass "markdown" or "md" to enable Markdown-style parsing only.
-            Pass "html" to enable HTML-style parsing only.
-            Pass None to completely disable style parsing.
 
         caption_entities (List of :obj:`~pyrogram.types.MessageEntity`):
             List of special entities that appear in the caption, which can be specified instead of *parse_mode*.
+
+        has_spoiler (``bool``, *optional*):
+            Pass True if the photo needs to be covered with a spoiler animation.
     """
 
     def __init__(
         self,
         media: Union[str, BinaryIO],
         caption: str = "",
-        parse_mode: Optional[str] = object,
-        caption_entities: List[MessageEntity] = None
+        parse_mode: Optional["enums.ParseMode"] = None,
+        caption_entities: List[MessageEntity] = None,
+        has_spoiler: bool = None
     ):
         super().__init__(media, caption, parse_mode, caption_entities)
+
+        self.has_spoiler = has_spoiler
diff --git a/pyrogram/types/input_media/input_media_video.py b/pyrogram/types/input_media/input_media_video.py
index 1199d86265..ab1823d339 100644
--- a/pyrogram/types/input_media/input_media_video.py
+++ b/pyrogram/types/input_media/input_media_video.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
@@ -20,6 +20,7 @@
 
 from .input_media import InputMedia
 from ..messages_and_media import MessageEntity
+from ... import enums
 
 
 class InputMediaVideo(InputMedia):
@@ -27,7 +28,7 @@ class InputMediaVideo(InputMedia):
     It is intended to be used with :obj:`~pyrogram.Client.send_media_group`.
 
     Parameters:
-        media (``str``):
+        media (``str`` | ``BinaryIO``):
             Video to send.
             Pass a file_id as string to send a video that exists on the Telegram servers or
             pass a file path as string to upload a new video that exists on your local machine or
@@ -44,12 +45,9 @@ class InputMediaVideo(InputMedia):
             Caption of the video to be sent, 0-1024 characters.
             If not specified, the original caption is kept. Pass "" (empty string) to remove the caption.
 
-        parse_mode (``str``, *optional*):
+        parse_mode (:obj:`~pyrogram.enums.ParseMode`, *optional*):
             By default, texts are parsed using both Markdown and HTML styles.
             You can combine both syntaxes together.
-            Pass "markdown" or "md" to enable Markdown-style parsing only.
-            Pass "html" to enable HTML-style parsing only.
-            Pass None to completely disable style parsing.
 
         caption_entities (List of :obj:`~pyrogram.types.MessageEntity`):
             List of special entities that appear in the caption, which can be specified instead of *parse_mode*.
@@ -65,6 +63,9 @@ class InputMediaVideo(InputMedia):
 
         supports_streaming (``bool``, *optional*):
             Pass True, if the uploaded video is suitable for streaming.
+
+        has_spoiler (``bool``, *optional*):
+            Pass True if the photo needs to be covered with a spoiler animation.
     """
 
     def __init__(
@@ -72,12 +73,13 @@ def __init__(
         media: Union[str, BinaryIO],
         thumb: str = None,
         caption: str = "",
-        parse_mode: Optional[str] = object,
+        parse_mode: Optional["enums.ParseMode"] = None,
         caption_entities: List[MessageEntity] = None,
         width: int = 0,
         height: int = 0,
         duration: int = 0,
-        supports_streaming: bool = True
+        supports_streaming: bool = True,
+        has_spoiler: bool = None,
     ):
         super().__init__(media, caption, parse_mode, caption_entities)
 
@@ -86,3 +88,4 @@ def __init__(
         self.height = height
         self.duration = duration
         self.supports_streaming = supports_streaming
+        self.has_spoiler = has_spoiler
diff --git a/pyrogram/types/input_media/input_phone_contact.py b/pyrogram/types/input_media/input_phone_contact.py
index e0169a5d81..0608cf2179 100644
--- a/pyrogram/types/input_media/input_phone_contact.py
+++ b/pyrogram/types/input_media/input_phone_contact.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
diff --git a/pyrogram/types/input_message_content/__init__.py b/pyrogram/types/input_message_content/__init__.py
index 233f53b07a..b445f3ba6a 100644
--- a/pyrogram/types/input_message_content/__init__.py
+++ b/pyrogram/types/input_message_content/__init__.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
diff --git a/pyrogram/types/input_message_content/input_message_content.py b/pyrogram/types/input_message_content/input_message_content.py
index 99bed67de9..2b46393259 100644
--- a/pyrogram/types/input_message_content/input_message_content.py
+++ b/pyrogram/types/input_message_content/input_message_content.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
diff --git a/pyrogram/types/input_message_content/input_text_message_content.py b/pyrogram/types/input_message_content/input_text_message_content.py
index a053d818bf..7c88f996ba 100644
--- a/pyrogram/types/input_message_content/input_text_message_content.py
+++ b/pyrogram/types/input_message_content/input_text_message_content.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
@@ -19,7 +19,7 @@
 from typing import Optional, List
 
 import pyrogram
-from pyrogram import raw, types, utils
+from pyrogram import raw, types, utils, enums
 from .input_message_content import InputMessageContent
 
 
@@ -30,12 +30,9 @@ class InputTextMessageContent(InputMessageContent):
         message_text (``str``):
             Text of the message to be sent, 1-4096 characters.
 
-        parse_mode (``str``, *optional*):
+        parse_mode (:obj:`~pyrogram.enums.ParseMode`, *optional*):
             By default, texts are parsed using both Markdown and HTML styles.
             You can combine both syntaxes together.
-            Pass "markdown" or "md" to enable Markdown-style parsing only.
-            Pass "html" to enable HTML-style parsing only.
-            Pass None to completely disable style parsing.
 
         entities (List of :obj:`~pyrogram.types.MessageEntity`):
             List of special entities that appear in message text, which can be specified instead of *parse_mode*.
@@ -47,7 +44,7 @@ class InputTextMessageContent(InputMessageContent):
     def __init__(
         self,
         message_text: str,
-        parse_mode: Optional[str] = object,
+        parse_mode: Optional["enums.ParseMode"] = None,
         entities: List["types.MessageEntity"] = None,
         disable_web_page_preview: bool = None
     ):
diff --git a/pyrogram/types/list.py b/pyrogram/types/list.py
index fe91c4c6d0..da87d5275f 100644
--- a/pyrogram/types/list.py
+++ b/pyrogram/types/list.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
diff --git a/pyrogram/types/messages_and_media/__init__.py b/pyrogram/types/messages_and_media/__init__.py
index 864019fe24..54dfd56043 100644
--- a/pyrogram/types/messages_and_media/__init__.py
+++ b/pyrogram/types/messages_and_media/__init__.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
@@ -28,6 +28,7 @@
 from .photo import Photo
 from .poll import Poll
 from .poll_option import PollOption
+from .reaction import Reaction
 from .sticker import Sticker
 from .stripped_thumbnail import StrippedThumbnail
 from .thumbnail import Thumbnail
@@ -35,9 +36,12 @@
 from .video import Video
 from .video_note import VideoNote
 from .voice import Voice
-from .webpage import WebPage
+from .web_app_data import WebAppData
+from .web_page import WebPage
+from .message_reactions import MessageReactions
 
 __all__ = [
     "Animation", "Audio", "Contact", "Document", "Game", "Location", "Message", "MessageEntity", "Photo", "Thumbnail",
-    "StrippedThumbnail", "Poll", "PollOption", "Sticker", "Venue", "Video", "VideoNote", "Voice", "WebPage", "Dice"
+    "StrippedThumbnail", "Poll", "PollOption", "Sticker", "Venue", "Video", "VideoNote", "Voice", "WebPage", "Dice",
+    "Reaction", "WebAppData", "MessageReactions"
 ]
diff --git a/pyrogram/types/messages_and_media/animation.py b/pyrogram/types/messages_and_media/animation.py
index 96a57469e1..1e7bf4cf39 100644
--- a/pyrogram/types/messages_and_media/animation.py
+++ b/pyrogram/types/messages_and_media/animation.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
@@ -16,10 +16,11 @@
 #  You should have received a copy of the GNU Lesser General Public License
 #  along with Pyrogram.  If not, see .
 
+from datetime import datetime
 from typing import List
 
 import pyrogram
-from pyrogram import raw
+from pyrogram import raw, utils
 from pyrogram import types
 from pyrogram.file_id import FileId, FileType, FileUniqueId, FileUniqueType
 from ..object import Object
@@ -54,8 +55,8 @@ class Animation(Object):
         file_size (``int``, *optional*):
             File size.
 
-        date (``int``, *optional*):
-            Date the animation was sent in Unix time.
+        date (:py:obj:`~datetime.datetime`, *optional*):
+            Date the animation was sent.
 
         thumbs (List of :obj:`~pyrogram.types.Thumbnail`, *optional*):
             Animation thumbnails.
@@ -73,7 +74,7 @@ def __init__(
         file_name: str = None,
         mime_type: str = None,
         file_size: int = None,
-        date: int = None,
+        date: datetime = None,
         thumbs: List["types.Thumbnail"] = None
     ):
         super().__init__(client)
@@ -114,7 +115,7 @@ def _parse(
             mime_type=animation.mime_type,
             file_size=animation.size,
             file_name=file_name,
-            date=animation.date,
+            date=utils.timestamp_to_datetime(animation.date),
             thumbs=types.Thumbnail._parse(client, animation),
             client=client
         )
diff --git a/pyrogram/types/messages_and_media/audio.py b/pyrogram/types/messages_and_media/audio.py
index e590d2e367..e474282f0b 100644
--- a/pyrogram/types/messages_and_media/audio.py
+++ b/pyrogram/types/messages_and_media/audio.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
@@ -16,10 +16,11 @@
 #  You should have received a copy of the GNU Lesser General Public License
 #  along with Pyrogram.  If not, see .
 
+from datetime import datetime
 from typing import List
 
 import pyrogram
-from pyrogram import raw
+from pyrogram import raw, utils
 from pyrogram import types
 from pyrogram.file_id import FileId, FileType, FileUniqueId, FileUniqueType
 from ..object import Object
@@ -54,8 +55,8 @@ class Audio(Object):
         file_size (``int``, *optional*):
             File size.
 
-        date (``int``, *optional*):
-            Date the audio was originally sent, in Unix time.
+        date (:py:obj:`~datetime.datetime`, *optional*):
+            Date the audio was originally sent.
 
         thumbs (List of :obj:`~pyrogram.types.Thumbnail`, *optional*):
             Thumbnails of the music file album cover.
@@ -73,7 +74,7 @@ def __init__(
         file_name: str = None,
         mime_type: str = None,
         file_size: int = None,
-        date: int = None,
+        date: datetime = None,
         thumbs: List["types.Thumbnail"] = None
     ):
         super().__init__(client)
@@ -114,7 +115,7 @@ def _parse(
             mime_type=audio.mime_type,
             file_size=audio.size,
             file_name=file_name,
-            date=audio.date,
+            date=utils.timestamp_to_datetime(audio.date),
             thumbs=types.Thumbnail._parse(client, audio),
             client=client
         )
diff --git a/pyrogram/types/messages_and_media/contact.py b/pyrogram/types/messages_and_media/contact.py
index fe6f55fd60..cec03329a5 100644
--- a/pyrogram/types/messages_and_media/contact.py
+++ b/pyrogram/types/messages_and_media/contact.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
diff --git a/pyrogram/types/messages_and_media/dice.py b/pyrogram/types/messages_and_media/dice.py
index aa67470cc4..2c683ec828 100644
--- a/pyrogram/types/messages_and_media/dice.py
+++ b/pyrogram/types/messages_and_media/dice.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
diff --git a/pyrogram/types/messages_and_media/document.py b/pyrogram/types/messages_and_media/document.py
index 76ebed9b0f..95ebe3f289 100644
--- a/pyrogram/types/messages_and_media/document.py
+++ b/pyrogram/types/messages_and_media/document.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
@@ -16,10 +16,11 @@
 #  You should have received a copy of the GNU Lesser General Public License
 #  along with Pyrogram.  If not, see .
 
+from datetime import datetime
 from typing import List
 
 import pyrogram
-from pyrogram import raw
+from pyrogram import raw, utils
 from pyrogram import types
 from pyrogram.file_id import FileId, FileType, FileUniqueId, FileUniqueType
 from ..object import Object
@@ -45,8 +46,8 @@ class Document(Object):
         file_size (``int``, *optional*):
             File size.
 
-        date (``int``, *optional*):
-            Date the document was sent in Unix time.
+        date (:py:obj:`~datetime.datetime`, *optional*):
+            Date the document was sent.
 
         thumbs (List of :obj:`~pyrogram.types.Thumbnail`, *optional*):
             Document thumbnails as defined by sender.
@@ -61,7 +62,7 @@ def __init__(
         file_name: str = None,
         mime_type: str = None,
         file_size: int = None,
-        date: int = None,
+        date: datetime = None,
         thumbs: List["types.Thumbnail"] = None
     ):
         super().__init__(client)
@@ -91,7 +92,7 @@ def _parse(client, document: "raw.types.Document", file_name: str) -> "Document"
             file_name=file_name,
             mime_type=document.mime_type,
             file_size=document.size,
-            date=document.date,
+            date=utils.timestamp_to_datetime(document.date),
             thumbs=types.Thumbnail._parse(client, document),
             client=client
         )
diff --git a/pyrogram/types/messages_and_media/game.py b/pyrogram/types/messages_and_media/game.py
index 5e76f5b6a3..1452d79eb3 100644
--- a/pyrogram/types/messages_and_media/game.py
+++ b/pyrogram/types/messages_and_media/game.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
diff --git a/pyrogram/types/messages_and_media/location.py b/pyrogram/types/messages_and_media/location.py
index 6cd0e9f189..664890cb76 100644
--- a/pyrogram/types/messages_and_media/location.py
+++ b/pyrogram/types/messages_and_media/location.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
diff --git a/pyrogram/types/messages_and_media/message.py b/pyrogram/types/messages_and_media/message.py
index 2fa21c4eb7..62a85b7593 100644
--- a/pyrogram/types/messages_and_media/message.py
+++ b/pyrogram/types/messages_and_media/message.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
@@ -17,11 +17,12 @@
 #  along with Pyrogram.  If not, see .
 
 import logging
+from datetime import datetime
 from functools import partial
-from typing import List, Match, Union, BinaryIO, Optional
+from typing import List, Match, Union, BinaryIO, Optional, Callable
 
 import pyrogram
-from pyrogram import raw
+from pyrogram import raw, enums
 from pyrogram import types
 from pyrogram import utils
 from pyrogram.errors import MessageIdsEmpty, PeerIdInvalid
@@ -59,7 +60,7 @@ class Message(Object, Update):
     """A message.
 
     Parameters:
-        message_id (``int``):
+        id (``int``):
             Unique message identifier inside this chat.
 
         from_user (:obj:`~pyrogram.types.User`, *optional*):
@@ -71,8 +72,8 @@ class Message(Object, Update):
             The supergroup itself for messages from anonymous group administrators.
             The linked channel for messages automatically forwarded to the discussion group.
 
-        date (``int``, *optional*):
-            Date the message was sent in Unix time.
+        date (:py:obj:`~datetime.datetime`, *optional*):
+            Date the message was sent.
 
         chat (:obj:`~pyrogram.types.Chat`, *optional*):
             Conversation the message belongs to.
@@ -92,8 +93,14 @@ class Message(Object, Update):
         forward_signature (``str``, *optional*):
             For messages forwarded from channels, signature of the post author if present.
 
-        forward_date (``int``, *optional*):
-            For forwarded messages, date the original message was sent in Unix time.
+        forward_date (:py:obj:`~datetime.datetime`, *optional*):
+            For forwarded messages, date the original message was sent.
+
+        reply_to_message_id (``int``, *optional*):
+            The id of the message which this message directly replied to.
+
+        reply_to_top_message_id (``int``, *optional*):
+            The id of the first message which started this message thread.
 
         reply_to_message (:obj:`~pyrogram.types.Message`, *optional*):
             For replies, the original message. Note that the Message object in this field will not contain
@@ -106,20 +113,18 @@ class Message(Object, Update):
             The message is empty.
             A message can be empty in case it was deleted or you tried to retrieve a message that doesn't exist yet.
 
-        service (``str``, *optional*):
-            The message is a service message. This field will contain the name of the service message.
-            A service message has one and only one of these fields set: new_chat_members, left_chat_member,
-            new_chat_title, new_chat_photo, delete_chat_photo, group_chat_created, channel_chat_created,
-            migrate_to_chat_id, migrate_from_chat_id, pinned_message, game_high_score, voice_chat_started,
-            voice_chat_ended, voice_chat_scheduled, voice_chat_members_invited.
+        service (:obj:`~pyrogram.enums.MessageServiceType`, *optional*):
+            The message is a service message.
+            This field will contain the enumeration type of the service message.
+            You can use ``service = getattr(message, message.service.value)`` to access the service message.
 
-        media (``str``, *optional*):
-            The message is a media message. This field will contain the name of the media message.
-            A media message has one and only one of these fields set: audio, document, photo, sticker, video, animation,
-            voice, video_note, contact, location, venue, poll, web_page, dice, game.
+        media (:obj:`~pyrogram.enums.MessageMediaType`, *optional*):
+            The message is a media message.
+            This field will contain the enumeration type of the media message.
+            You can use ``media = getattr(message, message.media.value)`` to access the media message.
 
-        edit_date (``int``, *optional*):
-            Date the message was last edited in Unix time.
+        edit_date (:py:obj:`~datetime.datetime`, *optional*):
+            Date the message was last edited.
 
         media_group_id (``str``, *optional*):
             The unique identifier of a media message group this message belongs to.
@@ -128,6 +133,12 @@ class Message(Object, Update):
             Signature of the post author for messages in channels, or the custom title of an anonymous group
             administrator.
 
+        has_protected_content (``bool``, *optional*):
+            True, if the message can't be forwarded.
+
+        has_media_spoiler (``bool``, *optional*):
+            True, if the message media is covered by a spoiler animation.
+
         text (``str``, *optional*):
             For text messages, the actual UTF-8 text of the message, 0-4096 characters.
             If the message contains entities (bold, italic, ...) you can access *text.markdown* or
@@ -246,6 +257,9 @@ class Message(Object, Update):
 
         views (``int``, *optional*):
             Channel post views.
+	    
+	forwards (``int``, *optional*):
+            Channel post forwards.
 
         via_bot (:obj:`~pyrogram.types.User`):
             The information of the bot that generated the message from an inline query of a user.
@@ -265,22 +279,28 @@ class Message(Object, Update):
             E.g.: "/start 1 2 3" would produce ["start", "1", "2", "3"].
             Only applicable when using :obj:`~pyrogram.filters.command`.
 
-        voice_chat_scheduled (:obj:`~pyrogram.types.VoiceChatScheduled`, *optional*):
+        video_chat_scheduled (:obj:`~pyrogram.types.VideoChatScheduled`, *optional*):
             Service message: voice chat scheduled.
 
-        voice_chat_started (:obj:`~pyrogram.types.VoiceChatStarted`, *optional*):
+        video_chat_started (:obj:`~pyrogram.types.VideoChatStarted`, *optional*):
             Service message: the voice chat started.
 
-        voice_chat_ended (:obj:`~pyrogram.types.VoiceChatEnded`, *optional*):
+        video_chat_ended (:obj:`~pyrogram.types.VideoChatEnded`, *optional*):
             Service message: the voice chat has ended.
 
-        voice_chat_members_invited (:obj:`~pyrogram.types.VoiceChatParticipantsInvited`, *optional*):
+        video_chat_members_invited (:obj:`~pyrogram.types.VoiceChatParticipantsInvited`, *optional*):
             Service message: new members were invited to the voice chat.
 
+        web_app_data (:obj:`~pyrogram.types.WebAppData`, *optional*):
+            Service message: web app data sent to the bot.
+
         reply_markup (:obj:`~pyrogram.types.InlineKeyboardMarkup` | :obj:`~pyrogram.types.ReplyKeyboardMarkup` | :obj:`~pyrogram.types.ReplyKeyboardRemove` | :obj:`~pyrogram.types.ForceReply`, *optional*):
             Additional interface options. An object for an inline keyboard, custom reply keyboard,
             instructions to remove reply keyboard or to force a reply from the user.
 
+        reactions (List of :obj:`~pyrogram.types.Reaction`):
+            List of the reactions to this message.
+
         link (``str``, *property*):
             Generate a link to this message, only for groups and channels.
     """
@@ -291,27 +311,31 @@ def __init__(
         self,
         *,
         client: "pyrogram.Client" = None,
-        message_id: int,
+        id: int,
         from_user: "types.User" = None,
         sender_chat: "types.Chat" = None,
-        date: int = None,
+        date: datetime = None,
         chat: "types.Chat" = None,
         forward_from: "types.User" = None,
         forward_sender_name: str = None,
         forward_from_chat: "types.Chat" = None,
         forward_from_message_id: int = None,
         forward_signature: str = None,
-        forward_date: int = None,
+        forward_date: datetime = None,
+        reply_to_message_id: int = None,
+        reply_to_top_message_id: int = None,
         reply_to_message: "Message" = None,
         mentioned: bool = None,
         empty: bool = None,
-        service: str = None,
+        service: "enums.MessageServiceType" = None,
         scheduled: bool = None,
         from_scheduled: bool = None,
-        media: str = None,
-        edit_date: int = None,
+        media: "enums.MessageMediaType" = None,
+        edit_date: datetime = None,
         media_group_id: str = None,
         author_signature: str = None,
+        has_protected_content: bool = None,
+        has_media_spoiler: bool = None,
         text: Str = None,
         entities: List["types.MessageEntity"] = None,
         caption_entities: List["types.MessageEntity"] = None,
@@ -344,24 +368,27 @@ def __init__(
         pinned_message: "Message" = None,
         game_high_score: int = None,
         views: int = None,
+        forwards: int = None,
         via_bot: "types.User" = None,
         outgoing: bool = None,
         matches: List[Match] = None,
         command: List[str] = None,
-        voice_chat_scheduled: "types.VoiceChatScheduled" = None,
-        voice_chat_started: "types.VoiceChatStarted" = None,
-        voice_chat_ended: "types.VoiceChatEnded" = None,
-        voice_chat_members_invited: "types.VoiceChatMembersInvited" = None,
+        video_chat_scheduled: "types.VideoChatScheduled" = None,
+        video_chat_started: "types.VideoChatStarted" = None,
+        video_chat_ended: "types.VideoChatEnded" = None,
+        video_chat_members_invited: "types.VideoChatMembersInvited" = None,
+        web_app_data: "types.WebAppData" = None,
         reply_markup: Union[
             "types.InlineKeyboardMarkup",
             "types.ReplyKeyboardMarkup",
             "types.ReplyKeyboardRemove",
             "types.ForceReply"
-        ] = None
+        ] = None,
+        reactions: List["types.Reaction"] = None
     ):
         super().__init__(client)
 
-        self.message_id = message_id
+        self.id = id
         self.from_user = from_user
         self.sender_chat = sender_chat
         self.date = date
@@ -372,6 +399,8 @@ def __init__(
         self.forward_from_message_id = forward_from_message_id
         self.forward_signature = forward_signature
         self.forward_date = forward_date
+        self.reply_to_message_id = reply_to_message_id
+        self.reply_to_top_message_id = reply_to_top_message_id
         self.reply_to_message = reply_to_message
         self.mentioned = mentioned
         self.empty = empty
@@ -382,6 +411,8 @@ def __init__(
         self.edit_date = edit_date
         self.media_group_id = media_group_id
         self.author_signature = author_signature
+        self.has_protected_content = has_protected_content
+        self.has_media_spoiler = has_media_spoiler
         self.text = text
         self.entities = entities
         self.caption_entities = caption_entities
@@ -414,19 +445,22 @@ def __init__(
         self.pinned_message = pinned_message
         self.game_high_score = game_high_score
         self.views = views
+        self.forwards = forwards
         self.via_bot = via_bot
         self.outgoing = outgoing
         self.matches = matches
         self.command = command
         self.reply_markup = reply_markup
-        self.voice_chat_scheduled = voice_chat_scheduled
-        self.voice_chat_started = voice_chat_started
-        self.voice_chat_ended = voice_chat_ended
-        self.voice_chat_members_invited = voice_chat_members_invited
+        self.video_chat_scheduled = video_chat_scheduled
+        self.video_chat_started = video_chat_started
+        self.video_chat_ended = video_chat_ended
+        self.video_chat_members_invited = video_chat_members_invited
+        self.web_app_data = web_app_data
+        self.reactions = reactions
 
     @staticmethod
     async def _parse(
-        client,
+        client: "pyrogram.Client",
         message: raw.base.Message,
         users: dict,
         chats: dict,
@@ -434,7 +468,7 @@ async def _parse(
         replies: int = 1
     ):
         if isinstance(message, raw.types.MessageEmpty):
-            return Message(message_id=message.id, empty=True, client=client)
+            return Message(id=message.id, empty=True, client=client)
 
         from_id = utils.get_raw_peer_id(message.from_id)
         peer_id = utils.get_raw_peer_id(message.peer_id)
@@ -443,7 +477,7 @@ async def _parse(
         if isinstance(message.from_id, raw.types.PeerUser) and isinstance(message.peer_id, raw.types.PeerUser):
             if from_id not in users or peer_id not in users:
                 try:
-                    r = await client.send(
+                    r = await client.invoke(
                         raw.functions.users.GetUsers(
                             id=[
                                 await client.resolve_peer(from_id),
@@ -468,63 +502,67 @@ async def _parse(
             group_chat_created = None
             channel_chat_created = None
             new_chat_photo = None
-            voice_chat_scheduled = None
-            voice_chat_started = None
-            voice_chat_ended = None
-            voice_chat_members_invited = None
+            video_chat_scheduled = None
+            video_chat_started = None
+            video_chat_ended = None
+            video_chat_members_invited = None
+            web_app_data = None
 
             service_type = None
 
             if isinstance(action, raw.types.MessageActionChatAddUser):
                 new_chat_members = [types.User._parse(client, users[i]) for i in action.users]
-                service_type = "new_chat_members"
+                service_type = enums.MessageServiceType.NEW_CHAT_MEMBERS
             elif isinstance(action, raw.types.MessageActionChatJoinedByLink):
                 new_chat_members = [types.User._parse(client, users[utils.get_raw_peer_id(message.from_id)])]
-                service_type = "new_chat_members"
+                service_type = enums.MessageServiceType.NEW_CHAT_MEMBERS
             elif isinstance(action, raw.types.MessageActionChatDeleteUser):
                 left_chat_member = types.User._parse(client, users[action.user_id])
-                service_type = "left_chat_member"
+                service_type = enums.MessageServiceType.LEFT_CHAT_MEMBERS
             elif isinstance(action, raw.types.MessageActionChatEditTitle):
                 new_chat_title = action.title
-                service_type = "new_chat_title"
+                service_type = enums.MessageServiceType.NEW_CHAT_TITLE
             elif isinstance(action, raw.types.MessageActionChatDeletePhoto):
                 delete_chat_photo = True
-                service_type = "delete_chat_photo"
+                service_type = enums.MessageServiceType.DELETE_CHAT_PHOTO
             elif isinstance(action, raw.types.MessageActionChatMigrateTo):
                 migrate_to_chat_id = action.channel_id
-                service_type = "migrate_to_chat_id"
+                service_type = enums.MessageServiceType.MIGRATE_TO_CHAT_ID
             elif isinstance(action, raw.types.MessageActionChannelMigrateFrom):
                 migrate_from_chat_id = action.chat_id
-                service_type = "migrate_from_chat_id"
+                service_type = enums.MessageServiceType.MIGRATE_FROM_CHAT_ID
             elif isinstance(action, raw.types.MessageActionChatCreate):
                 group_chat_created = True
-                service_type = "group_chat_created"
+                service_type = enums.MessageServiceType.GROUP_CHAT_CREATED
             elif isinstance(action, raw.types.MessageActionChannelCreate):
                 channel_chat_created = True
-                service_type = "channel_chat_created"
+                service_type = enums.MessageServiceType.CHANNEL_CHAT_CREATED
             elif isinstance(action, raw.types.MessageActionChatEditPhoto):
                 new_chat_photo = types.Photo._parse(client, action.photo)
-                service_type = "new_chat_photo"
+                service_type = enums.MessageServiceType.NEW_CHAT_PHOTO
             elif isinstance(action, raw.types.MessageActionGroupCallScheduled):
-                voice_chat_scheduled = types.VoiceChatScheduled._parse(action)
-                service_type = "voice_chat_scheduled"
+                video_chat_scheduled = types.VideoChatScheduled._parse(action)
+                service_type = enums.MessageServiceType.VIDEO_CHAT_SCHEDULED
             elif isinstance(action, raw.types.MessageActionGroupCall):
                 if action.duration:
-                    voice_chat_ended = types.VoiceChatEnded._parse(action)
-                    service_type = "voice_chat_ended"
+                    video_chat_ended = types.VideoChatEnded._parse(action)
+                    service_type = enums.MessageServiceType.VIDEO_CHAT_ENDED
                 else:
-                    voice_chat_started = types.VoiceChatStarted()
-                    service_type = "voice_chat_started"
+                    video_chat_started = types.VideoChatStarted()
+                    service_type = enums.MessageServiceType.VIDEO_CHAT_STARTED
             elif isinstance(action, raw.types.MessageActionInviteToGroupCall):
-                voice_chat_members_invited = types.VoiceChatMembersInvited._parse(client, action, users)
-                service_type = "voice_chat_members_invited"
+                video_chat_members_invited = types.VideoChatMembersInvited._parse(client, action, users)
+                service_type = enums.MessageServiceType.VIDEO_CHAT_MEMBERS_INVITED
+            elif isinstance(action, raw.types.MessageActionWebViewDataSentMe):
+                web_app_data = types.WebAppData._parse(action)
+                service_type = enums.MessageServiceType.WEB_APP_DATA
 
             from_user = types.User._parse(client, users.get(user_id, None))
             sender_chat = types.Chat._parse(client, message, users, chats, is_chat=False) if not from_user else None
 
             parsed_message = Message(
-                message_id=message.id,
-                date=message.date,
+                id=message.id,
+                date=utils.timestamp_to_datetime(message.date),
                 chat=types.Chat._parse(client, message, users, chats, is_chat=True),
                 from_user=from_user,
                 sender_chat=sender_chat,
@@ -538,10 +576,11 @@ async def _parse(
                 migrate_from_chat_id=-migrate_from_chat_id if migrate_from_chat_id else None,
                 group_chat_created=group_chat_created,
                 channel_chat_created=channel_chat_created,
-                voice_chat_scheduled=voice_chat_scheduled,
-                voice_chat_started=voice_chat_started,
-                voice_chat_ended=voice_chat_ended,
-                voice_chat_members_invited=voice_chat_members_invited,
+                video_chat_scheduled=video_chat_scheduled,
+                video_chat_started=video_chat_started,
+                video_chat_ended=video_chat_ended,
+                video_chat_members_invited=video_chat_members_invited,
+                web_app_data=web_app_data,
                 client=client
                 # TODO: supergroup_chat_created
             )
@@ -554,7 +593,7 @@ async def _parse(
                         replies=0
                     )
 
-                    parsed_message.service = "pinned_message"
+                    parsed_message.service = enums.MessageServiceType.PINNED_MESSAGE
                 except MessageIdsEmpty:
                     pass
 
@@ -569,10 +608,12 @@ async def _parse(
                             replies=0
                         )
 
-                        parsed_message.service = "game_high_score"
+                        parsed_message.service = enums.MessageServiceType.GAME_HIGH_SCORE
                     except MessageIdsEmpty:
                         pass
 
+            client.message_cache[(parsed_message.chat.id, parsed_message.id)] = parsed_message
+
             return parsed_message
 
         if isinstance(message, raw.types.Message):
@@ -589,7 +630,7 @@ async def _parse(
             forward_header = message.fwd_from  # type: raw.types.MessageFwdHeader
 
             if forward_header:
-                forward_date = forward_header.date
+                forward_date = utils.timestamp_to_datetime(forward_header.date)
 
                 if forward_header.from_id:
                     raw_peer_id = utils.get_raw_peer_id(forward_header.from_id)
@@ -622,23 +663,25 @@ async def _parse(
 
             media = message.media
             media_type = None
+            has_media_spoiler = None
 
             if media:
                 if isinstance(media, raw.types.MessageMediaPhoto):
                     photo = types.Photo._parse(client, media.photo, media.ttl_seconds)
-                    media_type = "photo"
+                    media_type = enums.MessageMediaType.PHOTO
+                    has_media_spoiler = media.spoiler
                 elif isinstance(media, raw.types.MessageMediaGeo):
                     location = types.Location._parse(client, media.geo)
-                    media_type = "location"
+                    media_type = enums.MessageMediaType.LOCATION
                 elif isinstance(media, raw.types.MessageMediaContact):
                     contact = types.Contact._parse(client, media)
-                    media_type = "contact"
+                    media_type = enums.MessageMediaType.CONTACT
                 elif isinstance(media, raw.types.MessageMediaVenue):
                     venue = types.Venue._parse(client, media)
-                    media_type = "venue"
+                    media_type = enums.MessageMediaType.VENUE
                 elif isinstance(media, raw.types.MessageMediaGame):
                     game = types.Game._parse(client, message)
-                    media_type = "game"
+                    media_type = enums.MessageMediaType.GAME
                 elif isinstance(media, raw.types.MessageMediaDocument):
                     doc = media.document
 
@@ -651,51 +694,48 @@ async def _parse(
                             ), "file_name", None
                         )
 
-                        if raw.types.DocumentAttributeAudio in attributes:
-                            audio_attributes = attributes[raw.types.DocumentAttributeAudio]
-
-                            if audio_attributes.voice:
-                                voice = types.Voice._parse(client, doc, audio_attributes)
-                                media_type = "voice"
-                            else:
-                                audio = types.Audio._parse(client, doc, audio_attributes, file_name)
-                                media_type = "audio"
-                        elif raw.types.DocumentAttributeAnimated in attributes:
+                        if raw.types.DocumentAttributeAnimated in attributes:
                             video_attributes = attributes.get(raw.types.DocumentAttributeVideo, None)
                             animation = types.Animation._parse(client, doc, video_attributes, file_name)
-                            media_type = "animation"
+                            media_type = enums.MessageMediaType.ANIMATION
+                            has_media_spoiler = media.spoiler
+                        elif raw.types.DocumentAttributeSticker in attributes:
+                            sticker = await types.Sticker._parse(client, doc, attributes)
+                            media_type = enums.MessageMediaType.STICKER
                         elif raw.types.DocumentAttributeVideo in attributes:
                             video_attributes = attributes[raw.types.DocumentAttributeVideo]
 
                             if video_attributes.round_message:
                                 video_note = types.VideoNote._parse(client, doc, video_attributes)
-                                media_type = "video_note"
+                                media_type = enums.MessageMediaType.VIDEO_NOTE
                             else:
                                 video = types.Video._parse(client, doc, video_attributes, file_name, media.ttl_seconds)
-                                media_type = "video"
-                        elif raw.types.DocumentAttributeSticker in attributes:
-                            sticker = await types.Sticker._parse(
-                                client, doc,
-                                attributes.get(raw.types.DocumentAttributeImageSize, None),
-                                attributes[raw.types.DocumentAttributeSticker],
-                                file_name
-                            )
-                            media_type = "sticker"
+                                media_type = enums.MessageMediaType.VIDEO
+                                has_media_spoiler = media.spoiler
+                        elif raw.types.DocumentAttributeAudio in attributes:
+                            audio_attributes = attributes[raw.types.DocumentAttributeAudio]
+
+                            if audio_attributes.voice:
+                                voice = types.Voice._parse(client, doc, audio_attributes)
+                                media_type = enums.MessageMediaType.VOICE
+                            else:
+                                audio = types.Audio._parse(client, doc, audio_attributes, file_name)
+                                media_type = enums.MessageMediaType.AUDIO
                         else:
                             document = types.Document._parse(client, doc, file_name)
-                            media_type = "document"
+                            media_type = enums.MessageMediaType.DOCUMENT
                 elif isinstance(media, raw.types.MessageMediaWebPage):
                     if isinstance(media.webpage, raw.types.WebPage):
                         web_page = types.WebPage._parse(client, media.webpage)
-                        media_type = "web_page"
+                        media_type = enums.MessageMediaType.WEB_PAGE
                     else:
                         media = None
                 elif isinstance(media, raw.types.MessageMediaPoll):
                     poll = types.Poll._parse(client, media)
-                    media_type = "poll"
+                    media_type = enums.MessageMediaType.POLL
                 elif isinstance(media, raw.types.MessageMediaDice):
                     dice = types.Dice._parse(client, media)
-                    media_type = "dice"
+                    media_type = enums.MessageMediaType.DICE
                 else:
                     media = None
 
@@ -716,9 +756,11 @@ async def _parse(
             from_user = types.User._parse(client, users.get(user_id, None))
             sender_chat = types.Chat._parse(client, message, users, chats, is_chat=False) if not from_user else None
 
+            reactions = types.MessageReactions._parse(client, message.reactions)
+
             parsed_message = Message(
-                message_id=message.id,
-                date=message.date,
+                id=message.id,
+                date=utils.timestamp_to_datetime(message.date),
                 chat=types.Chat._parse(client, message, users, chats, is_chat=True),
                 from_user=from_user,
                 sender_chat=sender_chat,
@@ -743,6 +785,8 @@ async def _parse(
                     else None
                 ),
                 author_signature=message.post_author,
+                has_protected_content=message.noforwards,
+                has_media_spoiler=has_media_spoiler,
                 forward_from=forward_from,
                 forward_sender_name=forward_sender_name,
                 forward_from_chat=forward_from_chat,
@@ -753,7 +797,7 @@ async def _parse(
                 scheduled=is_scheduled,
                 from_scheduled=message.from_scheduled,
                 media=media_type,
-                edit_date=message.edit_date,
+                edit_date=utils.timestamp_to_datetime(message.edit_date),
                 media_group_id=message.grouped_id,
                 photo=photo,
                 location=location,
@@ -771,30 +815,48 @@ async def _parse(
                 poll=poll,
                 dice=dice,
                 views=message.views,
+                forwards=message.forwards,
                 via_bot=types.User._parse(client, users.get(message.via_bot_id, None)),
                 outgoing=message.out,
                 reply_markup=reply_markup,
+                reactions=reactions,
                 client=client
             )
 
-            if message.reply_to and replies:
-                try:
-                    parsed_message.reply_to_message = await client.get_messages(
-                        parsed_message.chat.id,
-                        reply_to_message_ids=message.id,
-                        replies=replies - 1
-                    )
-                except MessageIdsEmpty:
-                    pass
+            if message.reply_to:
+                parsed_message.reply_to_message_id = message.reply_to.reply_to_msg_id
+                parsed_message.reply_to_top_message_id = message.reply_to.reply_to_top_id
+
+                if replies:
+                    try:
+                        key = (parsed_message.chat.id, parsed_message.reply_to_message_id)
+                        reply_to_message = client.message_cache[key]
+
+                        if not reply_to_message:
+                            reply_to_message = await client.get_messages(
+                                parsed_message.chat.id,
+                                reply_to_message_ids=message.id,
+                                replies=replies - 1
+                            )
+
+                        parsed_message.reply_to_message = reply_to_message
+                    except MessageIdsEmpty:
+                        pass
+
+            if not parsed_message.poll:  # Do not cache poll messages
+                client.message_cache[(parsed_message.chat.id, parsed_message.id)] = parsed_message
 
             return parsed_message
 
     @property
     def link(self) -> str:
-        if self.chat.type in ("group", "supergroup", "channel") and self.chat.username:
-            return f"https://t.me/{self.chat.username}/{self.message_id}"
+        if (
+            self.chat.type in (enums.ChatType.GROUP, enums.ChatType.SUPERGROUP, enums.ChatType.CHANNEL)
+            and self.chat.username
+        ):
+            return f"https://t.me/{self.chat.username}/{self.id}"
         else:
-            return f"https://t.me/c/{utils.get_channel_id(self.chat.id)}/{self.message_id}"
+            return f"https://t.me/c/{utils.get_channel_id(self.chat.id)}/{self.id}"
 
     async def get_media_group(self) -> List["types.Message"]:
         """Bound method *get_media_group* of :obj:`~pyrogram.types.Message`.
@@ -803,15 +865,15 @@ async def get_media_group(self) -> List["types.Message"]:
         
         .. code-block:: python
 
-            client.get_media_group(
+            await client.get_media_group(
                 chat_id=message.chat.id,
-                message_id=message.message_id
+                message_id=message.id
             )
             
         Example:
             .. code-block:: python
 
-                message.get_media_group()
+                await message.get_media_group()
                 
         Returns:
             List of :obj:`~pyrogram.types.Message`: On success, a list of messages of the media group is returned.
@@ -822,18 +884,20 @@ async def get_media_group(self) -> List["types.Message"]:
 
         return await self._client.get_media_group(
             chat_id=self.chat.id,
-            message_id=self.message_id
+            message_id=self.id
         )
 
     async def reply_text(
         self,
         text: str,
         quote: bool = None,
-        parse_mode: Optional[str] = object,
+        parse_mode: Optional["enums.ParseMode"] = None,
         entities: List["types.MessageEntity"] = None,
         disable_web_page_preview: bool = None,
         disable_notification: bool = None,
         reply_to_message_id: int = None,
+        schedule_date: datetime = None,
+        protect_content: bool = None,
         reply_markup=None
     ) -> "Message":
         """Bound method *reply_text* of :obj:`~pyrogram.types.Message`.
@@ -844,16 +908,16 @@ async def reply_text(
 
         .. code-block:: python
 
-            client.send_message(
+            await client.send_message(
                 chat_id=message.chat.id,
                 text="hello",
-                reply_to_message_id=message.message_id
+                reply_to_message_id=message.id
             )
 
         Example:
             .. code-block:: python
 
-                message.reply_text("hello", quote=True)
+                await message.reply_text("hello", quote=True)
 
         Parameters:
             text (``str``):
@@ -864,12 +928,9 @@ async def reply_text(
                 If *reply_to_message_id* is passed, this parameter will be ignored.
                 Defaults to ``True`` in group chats and ``False`` in private chats.
 
-            parse_mode (``str``, *optional*):
+            parse_mode (:obj:`~pyrogram.enums.ParseMode`, *optional*):
                 By default, texts are parsed using both Markdown and HTML styles.
                 You can combine both syntaxes together.
-                Pass "markdown" or "md" to enable Markdown-style parsing only.
-                Pass "html" to enable HTML-style parsing only.
-                Pass None to completely disable style parsing.
 
             entities (List of :obj:`~pyrogram.types.MessageEntity`):
                 List of special entities that appear in message text, which can be specified instead of *parse_mode*.
@@ -884,6 +945,12 @@ async def reply_text(
             reply_to_message_id (``int``, *optional*):
                 If the message is a reply, ID of the original message.
 
+            schedule_date (:py:obj:`~datetime.datetime`, *optional*):
+                Date when the message will be automatically sent.
+
+            protect_content (``bool``, *optional*):
+                Protects the contents of the sent message from forwarding and saving.
+
             reply_markup (:obj:`~pyrogram.types.InlineKeyboardMarkup` | :obj:`~pyrogram.types.ReplyKeyboardMarkup` | :obj:`~pyrogram.types.ReplyKeyboardRemove` | :obj:`~pyrogram.types.ForceReply`, *optional*):
                 Additional interface options. An object for an inline keyboard, custom reply keyboard,
                 instructions to remove reply keyboard or to force a reply from the user.
@@ -895,10 +962,10 @@ async def reply_text(
             RPCError: In case of a Telegram RPC error.
         """
         if quote is None:
-            quote = self.chat.type != "private"
+            quote = self.chat.type != enums.ChatType.PRIVATE
 
         if reply_to_message_id is None and quote:
-            reply_to_message_id = self.message_id
+            reply_to_message_id = self.id
 
         return await self._client.send_message(
             chat_id=self.chat.id,
@@ -908,6 +975,8 @@ async def reply_text(
             disable_web_page_preview=disable_web_page_preview,
             disable_notification=disable_notification,
             reply_to_message_id=reply_to_message_id,
+            schedule_date=schedule_date,
+            protect_content=protect_content,
             reply_markup=reply_markup
         )
 
@@ -918,8 +987,9 @@ async def reply_animation(
         animation: Union[str, BinaryIO],
         quote: bool = None,
         caption: str = "",
-        parse_mode: Optional[str] = object,
+        parse_mode: Optional["enums.ParseMode"] = None,
         caption_entities: List["types.MessageEntity"] = None,
+        has_spoiler: bool = None,
         duration: int = 0,
         width: int = 0,
         height: int = 0,
@@ -932,7 +1002,7 @@ async def reply_animation(
             "types.ForceReply"
         ] = None,
         reply_to_message_id: int = None,
-        progress: callable = None,
+        progress: Callable = None,
         progress_args: tuple = ()
     ) -> "Message":
         """Bound method *reply_animation* :obj:`~pyrogram.types.Message`.
@@ -941,7 +1011,7 @@ async def reply_animation(
 
         .. code-block:: python
 
-            client.send_animation(
+            await client.send_animation(
                 chat_id=message.chat.id,
                 animation=animation
             )
@@ -949,7 +1019,7 @@ async def reply_animation(
         Example:
             .. code-block:: python
 
-                message.reply_animation(animation)
+                await message.reply_animation(animation)
 
         Parameters:
             animation (``str``):
@@ -966,16 +1036,16 @@ async def reply_animation(
             caption (``str``, *optional*):
                 Animation caption, 0-1024 characters.
 
-            parse_mode (``str``, *optional*):
+            parse_mode (:obj:`~pyrogram.enums.ParseMode`, *optional*):
                 By default, texts are parsed using both Markdown and HTML styles.
                 You can combine both syntaxes together.
-                Pass "markdown" or "md" to enable Markdown-style parsing only.
-                Pass "html" to enable HTML-style parsing only.
-                Pass None to completely disable style parsing.
 
             caption_entities (List of :obj:`~pyrogram.types.MessageEntity`):
                 List of special entities that appear in the caption, which can be specified instead of *parse_mode*.
 
+            has_spoiler (``bool``, *optional*):
+                Pass True if the animation needs to be covered with a spoiler animation.
+
             duration (``int``, *optional*):
                 Duration of sent animation in seconds.
 
@@ -1002,7 +1072,7 @@ async def reply_animation(
                 Additional interface options. An object for an inline keyboard, custom reply keyboard,
                 instructions to remove reply keyboard or to force a reply from the user.
 
-            progress (``callable``, *optional*):
+            progress (``Callable``, *optional*):
                 Pass a callback function to view the file transmission progress.
                 The function must take *(current, total)* as positional arguments (look at Other Parameters below for a
                 detailed description) and will be called back each time a new file chunk has been successfully
@@ -1033,10 +1103,10 @@ async def reply_animation(
             RPCError: In case of a Telegram RPC error.
         """
         if quote is None:
-            quote = self.chat.type != "private"
+            quote = self.chat.type != enums.ChatType.PRIVATE
 
         if reply_to_message_id is None and quote:
-            reply_to_message_id = self.message_id
+            reply_to_message_id = self.id
 
         return await self._client.send_animation(
             chat_id=self.chat.id,
@@ -1044,6 +1114,7 @@ async def reply_animation(
             caption=caption,
             parse_mode=parse_mode,
             caption_entities=caption_entities,
+            has_spoiler=has_spoiler,
             duration=duration,
             width=width,
             height=height,
@@ -1060,7 +1131,7 @@ async def reply_audio(
         audio: Union[str, BinaryIO],
         quote: bool = None,
         caption: str = "",
-        parse_mode: Optional[str] = object,
+        parse_mode: Optional["enums.ParseMode"] = None,
         caption_entities: List["types.MessageEntity"] = None,
         duration: int = 0,
         performer: str = None,
@@ -1074,7 +1145,7 @@ async def reply_audio(
             "types.ReplyKeyboardRemove",
             "types.ForceReply"
         ] = None,
-        progress: callable = None,
+        progress: Callable = None,
         progress_args: tuple = ()
     ) -> "Message":
         """Bound method *reply_audio* of :obj:`~pyrogram.types.Message`.
@@ -1083,7 +1154,7 @@ async def reply_audio(
 
         .. code-block:: python
 
-            client.send_audio(
+            await client.send_audio(
                 chat_id=message.chat.id,
                 audio=audio
             )
@@ -1091,7 +1162,7 @@ async def reply_audio(
         Example:
             .. code-block:: python
 
-                message.reply_audio(audio)
+                await message.reply_audio(audio)
 
         Parameters:
             audio (``str``):
@@ -1108,12 +1179,9 @@ async def reply_audio(
             caption (``str``, *optional*):
                 Audio caption, 0-1024 characters.
 
-            parse_mode (``str``, *optional*):
+            parse_mode (:obj:`~pyrogram.enums.ParseMode`, *optional*):
                 By default, texts are parsed using both Markdown and HTML styles.
                 You can combine both syntaxes together.
-                Pass "markdown" or "md" to enable Markdown-style parsing only.
-                Pass "html" to enable HTML-style parsing only.
-                Pass None to completely disable style parsing.
 
             caption_entities (List of :obj:`~pyrogram.types.MessageEntity`):
                 List of special entities that appear in the caption, which can be specified instead of *parse_mode*.
@@ -1144,7 +1212,7 @@ async def reply_audio(
                 Additional interface options. An object for an inline keyboard, custom reply keyboard,
                 instructions to remove reply keyboard or to force a reply from the user.
 
-            progress (``callable``, *optional*):
+            progress (``Callable``, *optional*):
                 Pass a callback function to view the file transmission progress.
                 The function must take *(current, total)* as positional arguments (look at Other Parameters below for a
                 detailed description) and will be called back each time a new file chunk has been successfully
@@ -1175,10 +1243,10 @@ async def reply_audio(
             RPCError: In case of a Telegram RPC error.
         """
         if quote is None:
-            quote = self.chat.type != "private"
+            quote = self.chat.type != enums.ChatType.PRIVATE
 
         if reply_to_message_id is None and quote:
-            reply_to_message_id = self.message_id
+            reply_to_message_id = self.id
 
         return await self._client.send_audio(
             chat_id=self.chat.id,
@@ -1202,7 +1270,7 @@ async def reply_cached_media(
         file_id: str,
         quote: bool = None,
         caption: str = "",
-        parse_mode: Optional[str] = object,
+        parse_mode: Optional["enums.ParseMode"] = None,
         caption_entities: List["types.MessageEntity"] = None,
         disable_notification: bool = None,
         reply_to_message_id: int = None,
@@ -1219,7 +1287,7 @@ async def reply_cached_media(
 
         .. code-block:: python
 
-            client.send_cached_media(
+            await client.send_cached_media(
                 chat_id=message.chat.id,
                 file_id=file_id
             )
@@ -1227,7 +1295,7 @@ async def reply_cached_media(
         Example:
             .. code-block:: python
 
-                message.reply_cached_media(file_id)
+                await message.reply_cached_media(file_id)
 
         Parameters:
             file_id (``str``):
@@ -1242,12 +1310,9 @@ async def reply_cached_media(
             caption (``bool``, *optional*):
                 Media caption, 0-1024 characters.
 
-            parse_mode (``str``, *optional*):
+            parse_mode (:obj:`~pyrogram.enums.ParseMode`, *optional*):
                 By default, texts are parsed using both Markdown and HTML styles.
                 You can combine both syntaxes together.
-                Pass "markdown" or "md" to enable Markdown-style parsing only.
-                Pass "html" to enable HTML-style parsing only.
-                Pass None to completely disable style parsing.
 
             caption_entities (List of :obj:`~pyrogram.types.MessageEntity`):
                 List of special entities that appear in the caption, which can be specified instead of *parse_mode*.
@@ -1270,10 +1335,10 @@ async def reply_cached_media(
             RPCError: In case of a Telegram RPC error.
         """
         if quote is None:
-            quote = self.chat.type != "private"
+            quote = self.chat.type != enums.ChatType.PRIVATE
 
         if reply_to_message_id is None and quote:
-            reply_to_message_id = self.message_id
+            reply_to_message_id = self.id
 
         return await self._client.send_cached_media(
             chat_id=self.chat.id,
@@ -1286,31 +1351,30 @@ async def reply_cached_media(
             reply_markup=reply_markup
         )
 
-    async def reply_chat_action(self, action: str) -> bool:
+    async def reply_chat_action(self, action: "enums.ChatAction") -> bool:
         """Bound method *reply_chat_action* of :obj:`~pyrogram.types.Message`.
 
         Use as a shortcut for:
 
         .. code-block:: python
 
-            client.send_chat_action(
+            from pyrogram import enums
+
+            await client.send_chat_action(
                 chat_id=message.chat.id,
-                action="typing"
+                action=enums.ChatAction.TYPING
             )
 
         Example:
             .. code-block:: python
 
-                message.reply_chat_action("typing")
+                from pyrogram import enums
+
+                await message.reply_chat_action(enums.ChatAction.TYPING)
 
         Parameters:
-            action (``str``):
-                Type of action to broadcast. Choose one, depending on what the user is about to receive: *"typing"* for
-                text messages, *"upload_photo"* for photos, *"record_video"* or *"upload_video"* for videos,
-                *"record_audio"* or *"upload_audio"* for audio files, *"upload_document"* for general files,
-                *"find_location"* for location data, *"record_video_note"* or *"upload_video_note"* for video notes,
-                *"choose_contact"* for contacts, *"playing"* for games or *"cancel"* to cancel any chat action currently
-                displayed.
+            action (:obj:`~pyrogram.enums.ChatAction`):
+                Type of action to broadcast.
 
         Returns:
             ``bool``: On success, True is returned.
@@ -1346,7 +1410,7 @@ async def reply_contact(
 
         .. code-block:: python
 
-            client.send_contact(
+            await client.send_contact(
                 chat_id=message.chat.id,
                 phone_number=phone_number,
                 first_name=first_name
@@ -1355,7 +1419,7 @@ async def reply_contact(
         Example:
             .. code-block:: python
 
-                message.reply_contact(phone_number, "Dan")
+                await message.reply_contact("+1-123-456-7890", "Name")
 
         Parameters:
             phone_number (``str``):
@@ -1393,10 +1457,10 @@ async def reply_contact(
             RPCError: In case of a Telegram RPC error.
         """
         if quote is None:
-            quote = self.chat.type != "private"
+            quote = self.chat.type != enums.ChatType.PRIVATE
 
         if reply_to_message_id is None and quote:
-            reply_to_message_id = self.message_id
+            reply_to_message_id = self.id
 
         return await self._client.send_contact(
             chat_id=self.chat.id,
@@ -1415,20 +1479,20 @@ async def reply_document(
         quote: bool = None,
         thumb: str = None,
         caption: str = "",
-        parse_mode: Optional[str] = object,
+        parse_mode: Optional["enums.ParseMode"] = None,
         caption_entities: List["types.MessageEntity"] = None,
         file_name: str = None,
         force_document: bool = None,
         disable_notification: bool = None,
         reply_to_message_id: int = None,
-        schedule_date: int = None,
+        schedule_date: datetime = None,
         reply_markup: Union[
             "types.InlineKeyboardMarkup",
             "types.ReplyKeyboardMarkup",
             "types.ReplyKeyboardRemove",
             "types.ForceReply"
         ] = None,
-        progress: callable = None,
+        progress: Callable = None,
         progress_args: tuple = ()
     ) -> "Message":
         """Bound method *reply_document* of :obj:`~pyrogram.types.Message`.
@@ -1437,7 +1501,7 @@ async def reply_document(
 
         .. code-block:: python
 
-            client.send_document(
+            await client.send_document(
                 chat_id=message.chat.id,
                 document=document
             )
@@ -1445,7 +1509,7 @@ async def reply_document(
         Example:
             .. code-block:: python
 
-                message.reply_document(document)
+                await message.reply_document(document)
 
         Parameters:
             document (``str``):
@@ -1468,12 +1532,9 @@ async def reply_document(
             caption (``str``, *optional*):
                 Document caption, 0-1024 characters.
 
-            parse_mode (``str``, *optional*):
+            parse_mode (:obj:`~pyrogram.enums.ParseMode`, *optional*):
                 By default, texts are parsed using both Markdown and HTML styles.
                 You can combine both syntaxes together.
-                Pass "markdown" or "md" to enable Markdown-style parsing only.
-                Pass "html" to enable HTML-style parsing only.
-                Pass None to completely disable style parsing.
 
             caption_entities (List of :obj:`~pyrogram.types.MessageEntity`):
                 List of special entities that appear in the caption, which can be specified instead of *parse_mode*.
@@ -1494,14 +1555,14 @@ async def reply_document(
             reply_to_message_id (``int``, *optional*):
                 If the message is a reply, ID of the original message.
             
-            schedule_date (``int``, *optional*):
-                Date when the message will be automatically sent. Unix time.
+            schedule_date (:py:obj:`~datetime.datetime`, *optional*):
+                Date when the message will be automatically sent.
 
             reply_markup (:obj:`~pyrogram.types.InlineKeyboardMarkup` | :obj:`~pyrogram.types.ReplyKeyboardMarkup` | :obj:`~pyrogram.types.ReplyKeyboardRemove` | :obj:`~pyrogram.types.ForceReply`, *optional*):
                 Additional interface options. An object for an inline keyboard, custom reply keyboard,
                 instructions to remove reply keyboard or to force a reply from the user.
 
-            progress (``callable``, *optional*):
+            progress (``Callable``, *optional*):
                 Pass a callback function to view the file transmission progress.
                 The function must take *(current, total)* as positional arguments (look at Other Parameters below for a
                 detailed description) and will be called back each time a new file chunk has been successfully
@@ -1532,10 +1593,10 @@ async def reply_document(
             RPCError: In case of a Telegram RPC error.
         """
         if quote is None:
-            quote = self.chat.type != "private"
+            quote = self.chat.type != enums.ChatType.PRIVATE
 
         if reply_to_message_id is None and quote:
-            reply_to_message_id = self.message_id
+            reply_to_message_id = self.id
 
         return await self._client.send_document(
             chat_id=self.chat.id,
@@ -1573,7 +1634,7 @@ async def reply_game(
 
         .. code-block:: python
 
-            client.send_game(
+            await client.send_game(
                 chat_id=message.chat.id,
                 game_short_name="lumberjack"
             )
@@ -1581,7 +1642,7 @@ async def reply_game(
         Example:
             .. code-block:: python
 
-                message.reply_game("lumberjack")
+                await message.reply_game("lumberjack")
 
         Parameters:
             game_short_name (``str``):
@@ -1610,10 +1671,10 @@ async def reply_game(
             RPCError: In case of a Telegram RPC error.
         """
         if quote is None:
-            quote = self.chat.type != "private"
+            quote = self.chat.type != enums.ChatType.PRIVATE
 
         if reply_to_message_id is None and quote:
-            reply_to_message_id = self.message_id
+            reply_to_message_id = self.id
 
         return await self._client.send_game(
             chat_id=self.chat.id,
@@ -1629,8 +1690,7 @@ async def reply_inline_bot_result(
         result_id: str,
         quote: bool = None,
         disable_notification: bool = None,
-        reply_to_message_id: int = None,
-        hide_via: bool = None
+        reply_to_message_id: int = None
     ) -> "Message":
         """Bound method *reply_inline_bot_result* of :obj:`~pyrogram.types.Message`.
 
@@ -1638,7 +1698,7 @@ async def reply_inline_bot_result(
 
         .. code-block:: python
 
-            client.send_inline_bot_result(
+            await client.send_inline_bot_result(
                 chat_id=message.chat.id,
                 query_id=query_id,
                 result_id=result_id
@@ -1647,7 +1707,7 @@ async def reply_inline_bot_result(
         Example:
             .. code-block:: python
 
-                message.reply_inline_bot_result(query_id, result_id)
+                await message.reply_inline_bot_result(query_id, result_id)
 
         Parameters:
             query_id (``int``):
@@ -1668,9 +1728,6 @@ async def reply_inline_bot_result(
             reply_to_message_id (``bool``, *optional*):
                 If the message is a reply, ID of the original message.
 
-            hide_via (``bool``):
-                Sends the message with *via @bot* hidden.
-
         Returns:
             On success, the sent Message is returned.
 
@@ -1678,18 +1735,17 @@ async def reply_inline_bot_result(
             RPCError: In case of a Telegram RPC error.
         """
         if quote is None:
-            quote = self.chat.type != "private"
+            quote = self.chat.type != enums.ChatType.PRIVATE
 
         if reply_to_message_id is None and quote:
-            reply_to_message_id = self.message_id
+            reply_to_message_id = self.id
 
         return await self._client.send_inline_bot_result(
             chat_id=self.chat.id,
             query_id=query_id,
             result_id=result_id,
             disable_notification=disable_notification,
-            reply_to_message_id=reply_to_message_id,
-            hide_via=hide_via
+            reply_to_message_id=reply_to_message_id
         )
 
     async def reply_location(
@@ -1712,16 +1768,16 @@ async def reply_location(
 
         .. code-block:: python
 
-            client.send_location(
+            await client.send_location(
                 chat_id=message.chat.id,
-                latitude=41.890251,
-                longitude=12.492373
+                latitude=latitude,
+                longitude=longitude
             )
 
         Example:
             .. code-block:: python
 
-                message.reply_location(41.890251, 12.492373)
+                await message.reply_location(latitude, longitude)
 
         Parameters:
             latitude (``float``):
@@ -1753,10 +1809,10 @@ async def reply_location(
             RPCError: In case of a Telegram RPC error.
         """
         if quote is None:
-            quote = self.chat.type != "private"
+            quote = self.chat.type != enums.ChatType.PRIVATE
 
         if reply_to_message_id is None and quote:
-            reply_to_message_id = self.message_id
+            reply_to_message_id = self.id
 
         return await self._client.send_location(
             chat_id=self.chat.id,
@@ -1780,7 +1836,7 @@ async def reply_media_group(
 
         .. code-block:: python
 
-            client.send_media_group(
+            await client.send_media_group(
                 chat_id=message.chat.id,
                 media=list_of_media
             )
@@ -1788,7 +1844,7 @@ async def reply_media_group(
         Example:
             .. code-block:: python
 
-                message.reply_media_group(list_of_media)
+                await message.reply_media_group(list_of_media)
 
         Parameters:
             media (``list``):
@@ -1816,10 +1872,10 @@ async def reply_media_group(
             RPCError: In case of a Telegram RPC error.
         """
         if quote is None:
-            quote = self.chat.type != "private"
+            quote = self.chat.type != enums.ChatType.PRIVATE
 
         if reply_to_message_id is None and quote:
-            reply_to_message_id = self.message_id
+            reply_to_message_id = self.id
 
         return await self._client.send_media_group(
             chat_id=self.chat.id,
@@ -1833,8 +1889,9 @@ async def reply_photo(
         photo: Union[str, BinaryIO],
         quote: bool = None,
         caption: str = "",
-        parse_mode: Optional[str] = object,
+        parse_mode: Optional["enums.ParseMode"] = None,
         caption_entities: List["types.MessageEntity"] = None,
+        has_spoiler: bool = None,
         ttl_seconds: int = None,
         disable_notification: bool = None,
         reply_to_message_id: int = None,
@@ -1844,7 +1901,7 @@ async def reply_photo(
             "types.ReplyKeyboardRemove",
             "types.ForceReply"
         ] = None,
-        progress: callable = None,
+        progress: Callable = None,
         progress_args: tuple = ()
     ) -> "Message":
         """Bound method *reply_photo* of :obj:`~pyrogram.types.Message`.
@@ -1853,7 +1910,7 @@ async def reply_photo(
 
         .. code-block:: python
 
-            client.send_photo(
+            await client.send_photo(
                 chat_id=message.chat.id,
                 photo=photo
             )
@@ -1861,7 +1918,7 @@ async def reply_photo(
         Example:
             .. code-block:: python
 
-                message.reply_photo(photo)
+                await message.reply_photo(photo)
 
         Parameters:
             photo (``str``):
@@ -1875,19 +1932,19 @@ async def reply_photo(
                 If *reply_to_message_id* is passed, this parameter will be ignored.
                 Defaults to ``True`` in group chats and ``False`` in private chats.
 
-            caption (``bool``, *optional*):
+            caption (``str``, *optional*):
                 Photo caption, 0-1024 characters.
 
-            parse_mode (``str``, *optional*):
+            parse_mode (:obj:`~pyrogram.enums.ParseMode`, *optional*):
                 By default, texts are parsed using both Markdown and HTML styles.
                 You can combine both syntaxes together.
-                Pass "markdown" or "md" to enable Markdown-style parsing only.
-                Pass "html" to enable HTML-style parsing only.
-                Pass None to completely disable style parsing.
 
             caption_entities (List of :obj:`~pyrogram.types.MessageEntity`):
                 List of special entities that appear in the caption, which can be specified instead of *parse_mode*.
 
+            has_spoiler (``bool``, *optional*):
+                Pass True if the photo needs to be covered with a spoiler animation.
+
             ttl_seconds (``int``, *optional*):
                 Self-Destruct Timer.
                 If you set a timer, the photo will self-destruct in *ttl_seconds*
@@ -1904,7 +1961,7 @@ async def reply_photo(
                 Additional interface options. An object for an inline keyboard, custom reply keyboard,
                 instructions to remove reply keyboard or to force a reply from the user.
 
-            progress (``callable``, *optional*):
+            progress (``Callable``, *optional*):
                 Pass a callback function to view the file transmission progress.
                 The function must take *(current, total)* as positional arguments (look at Other Parameters below for a
                 detailed description) and will be called back each time a new file chunk has been successfully
@@ -1935,10 +1992,10 @@ async def reply_photo(
             RPCError: In case of a Telegram RPC error.
         """
         if quote is None:
-            quote = self.chat.type != "private"
+            quote = self.chat.type != enums.ChatType.PRIVATE
 
         if reply_to_message_id is None and quote:
-            reply_to_message_id = self.message_id
+            reply_to_message_id = self.id
 
         return await self._client.send_photo(
             chat_id=self.chat.id,
@@ -1946,6 +2003,7 @@ async def reply_photo(
             caption=caption,
             parse_mode=parse_mode,
             caption_entities=caption_entities,
+            has_spoiler=has_spoiler,
             ttl_seconds=ttl_seconds,
             disable_notification=disable_notification,
             reply_to_message_id=reply_to_message_id,
@@ -1958,14 +2016,21 @@ async def reply_poll(
         self,
         question: str,
         options: List[str],
-        quote: bool = None,
         is_anonymous: bool = True,
+        type: "enums.PollType" = enums.PollType.REGULAR,
         allows_multiple_answers: bool = None,
-        type: str = "regular",
         correct_option_id: int = None,
+        explanation: str = None,
+        explanation_parse_mode: "enums.ParseMode" = None,
+        explanation_entities: List["types.MessageEntity"] = None,
+        open_period: int = None,
+        close_date: datetime = None,
+        is_closed: bool = None,
+        quote: bool = None,
         disable_notification: bool = None,
+        protect_content: bool = None,
         reply_to_message_id: int = None,
-        schedule_date: int = None,
+        schedule_date: datetime = None,
         reply_markup: Union[
             "types.InlineKeyboardMarkup",
             "types.ReplyKeyboardMarkup",
@@ -1979,54 +2044,81 @@ async def reply_poll(
 
         .. code-block:: python
 
-            client.send_poll(
+            await client.send_poll(
                 chat_id=message.chat.id,
-                question="Is Pyrogram the best?",
-                options=["Yes", "Yes"]
+                question="This is a poll",
+                options=["A", "B", "C]
             )
 
         Example:
             .. code-block:: python
 
-                message.reply_poll("Is Pyrogram the best?", ["Yes", "Yes"])
+                await message.reply_poll("This is a poll", ["A", "B", "C"])
 
         Parameters:
             question (``str``):
-                The poll question, as string.
+                Poll question, 1-255 characters.
 
             options (List of ``str``):
-                The poll options, as list of strings (2 to 10 options are allowed).
+                List of answer options, 2-10 strings 1-100 characters each.
 
-            quote (``bool``, *optional*):
-                If ``True``, the message will be sent as a reply to this message.
-                If *reply_to_message_id* is passed, this parameter will be ignored.
-                Defaults to ``True`` in group chats and ``False`` in private chats.
-            
             is_anonymous (``bool``, *optional*):
                 True, if the poll needs to be anonymous.
                 Defaults to True.
 
-            type (``str``, *optional*):
-                Poll type, "quiz" or "regular".
-                Defaults to "regular"
+            type (:obj`~pyrogram.enums.PollType`, *optional*):
+                Poll type, :obj:`~pyrogram.enums.PollType.QUIZ` or :obj:`~pyrogram.enums.PollType.REGULAR`.
+                Defaults to :obj:`~pyrogram.enums.PollType.REGULAR`.
 
             allows_multiple_answers (``bool``, *optional*):
                 True, if the poll allows multiple answers, ignored for polls in quiz mode.
-                Defaults to False
-            
+                Defaults to False.
+
             correct_option_id (``int``, *optional*):
-                0-based identifier of the correct answer option (the index of the correct option)
-                Required for polls in quiz mode.
+                0-based identifier of the correct answer option, required for polls in quiz mode.
+
+            explanation (``str``, *optional*):
+                Text that is shown when a user chooses an incorrect answer or taps on the lamp icon in a quiz-style
+                poll, 0-200 characters with at most 2 line feeds after entities parsing.
+
+            explanation_parse_mode (:obj:`~pyrogram.enums.ParseMode`, *optional*):
+                By default, texts are parsed using both Markdown and HTML styles.
+                You can combine both syntaxes together.
+
+            explanation_entities (List of :obj:`~pyrogram.types.MessageEntity`):
+                List of special entities that appear in the poll explanation, which can be specified instead of
+                *parse_mode*.
+
+            open_period (``int``, *optional*):
+                Amount of time in seconds the poll will be active after creation, 5-600.
+                Can't be used together with *close_date*.
+
+            close_date (:py:obj:`~datetime.datetime`, *optional*):
+                Point in time when the poll will be automatically closed.
+                Must be at least 5 and no more than 600 seconds in the future.
+                Can't be used together with *open_period*.
+
+            is_closed (``bool``, *optional*):
+                Pass True, if the poll needs to be immediately closed.
+                This can be useful for poll preview.
+
+            quote (``bool``, *optional*):
+                If ``True``, the message will be sent as a reply to this message.
+                If *reply_to_message_id* is passed, this parameter will be ignored.
+                Defaults to ``True`` in group chats and ``False`` in private chats.
 
             disable_notification (``bool``, *optional*):
                 Sends the message silently.
                 Users will receive a notification with no sound.
 
+            protect_content (``bool``, *optional*):
+                Protects the contents of the sent message from forwarding and saving.
+
             reply_to_message_id (``int``, *optional*):
                 If the message is a reply, ID of the original message.
-            
-            schedule_date (``int``, *optional*):
-                Date when the message will be automatically sent. Unix time.
+
+            schedule_date (:py:obj:`~datetime.datetime`, *optional*):
+                Date when the message will be automatically sent.
 
             reply_markup (:obj:`~pyrogram.types.InlineKeyboardMarkup` | :obj:`~pyrogram.types.ReplyKeyboardMarkup` | :obj:`~pyrogram.types.ReplyKeyboardRemove` | :obj:`~pyrogram.types.ForceReply`, *optional*):
                 Additional interface options. An object for an inline keyboard, custom reply keyboard,
@@ -2039,20 +2131,27 @@ async def reply_poll(
             RPCError: In case of a Telegram RPC error.
         """
         if quote is None:
-            quote = self.chat.type != "private"
+            quote = self.chat.type != enums.ChatType.PRIVATE
 
         if reply_to_message_id is None and quote:
-            reply_to_message_id = self.message_id
+            reply_to_message_id = self.id
 
         return await self._client.send_poll(
             chat_id=self.chat.id,
             question=question,
             options=options,
             is_anonymous=is_anonymous,
-            allows_multiple_answers=allows_multiple_answers,
             type=type,
+            allows_multiple_answers=allows_multiple_answers,
             correct_option_id=correct_option_id,
+            explanation=explanation,
+            explanation_parse_mode=explanation_parse_mode,
+            explanation_entities=explanation_entities,
+            open_period=open_period,
+            close_date=close_date,
+            is_closed=is_closed,
             disable_notification=disable_notification,
+            protect_content=protect_content,
             reply_to_message_id=reply_to_message_id,
             schedule_date=schedule_date,
             reply_markup=reply_markup
@@ -2070,7 +2169,7 @@ async def reply_sticker(
             "types.ReplyKeyboardRemove",
             "types.ForceReply"
         ] = None,
-        progress: callable = None,
+        progress: Callable = None,
         progress_args: tuple = ()
     ) -> "Message":
         """Bound method *reply_sticker* of :obj:`~pyrogram.types.Message`.
@@ -2079,7 +2178,7 @@ async def reply_sticker(
 
         .. code-block:: python
 
-            client.send_sticker(
+            await client.send_sticker(
                 chat_id=message.chat.id,
                 sticker=sticker
             )
@@ -2087,7 +2186,7 @@ async def reply_sticker(
         Example:
             .. code-block:: python
 
-                message.reply_sticker(sticker)
+                await message.reply_sticker(sticker)
 
         Parameters:
             sticker (``str``):
@@ -2112,7 +2211,7 @@ async def reply_sticker(
                 Additional interface options. An object for an inline keyboard, custom reply keyboard,
                 instructions to remove reply keyboard or to force a reply from the user.
 
-            progress (``callable``, *optional*):
+            progress (``Callable``, *optional*):
                 Pass a callback function to view the file transmission progress.
                 The function must take *(current, total)* as positional arguments (look at Other Parameters below for a
                 detailed description) and will be called back each time a new file chunk has been successfully
@@ -2143,10 +2242,10 @@ async def reply_sticker(
             RPCError: In case of a Telegram RPC error.
         """
         if quote is None:
-            quote = self.chat.type != "private"
+            quote = self.chat.type != enums.ChatType.PRIVATE
 
         if reply_to_message_id is None and quote:
-            reply_to_message_id = self.message_id
+            reply_to_message_id = self.id
 
         return await self._client.send_sticker(
             chat_id=self.chat.id,
@@ -2182,18 +2281,18 @@ async def reply_venue(
 
         .. code-block:: python
 
-            client.send_venue(
+            await client.send_venue(
                 chat_id=message.chat.id,
-                latitude=41.890251,
-                longitude=12.492373,
-                title="Coliseum",
-                address="Piazza del Colosseo, 1, 00184 Roma RM"
+                latitude=latitude,
+                longitude=longitude,
+                title="Venue title",
+                address="Venue address"
             )
 
         Example:
             .. code-block:: python
 
-                message.reply_venue(41.890251, 12.492373, "Coliseum", "Piazza del Colosseo, 1, 00184 Roma RM")
+                await message.reply_venue(latitude, longitude, "Venue title", "Venue address")
 
         Parameters:
             latitude (``float``):
@@ -2238,10 +2337,10 @@ async def reply_venue(
             RPCError: In case of a Telegram RPC error.
         """
         if quote is None:
-            quote = self.chat.type != "private"
+            quote = self.chat.type != enums.ChatType.PRIVATE
 
         if reply_to_message_id is None and quote:
-            reply_to_message_id = self.message_id
+            reply_to_message_id = self.id
 
         return await self._client.send_venue(
             chat_id=self.chat.id,
@@ -2261,8 +2360,9 @@ async def reply_video(
         video: Union[str, BinaryIO],
         quote: bool = None,
         caption: str = "",
-        parse_mode: Optional[str] = object,
+        parse_mode: Optional["enums.ParseMode"] = None,
         caption_entities: List["types.MessageEntity"] = None,
+        has_spoiler: bool = None,
         ttl_seconds: int = None,
         duration: int = 0,
         width: int = 0,
@@ -2277,7 +2377,7 @@ async def reply_video(
             "types.ReplyKeyboardRemove",
             "types.ForceReply"
         ] = None,
-        progress: callable = None,
+        progress: Callable = None,
         progress_args: tuple = ()
     ) -> "Message":
         """Bound method *reply_video* of :obj:`~pyrogram.types.Message`.
@@ -2286,7 +2386,7 @@ async def reply_video(
 
         .. code-block:: python
 
-            client.send_video(
+            await client.send_video(
                 chat_id=message.chat.id,
                 video=video
             )
@@ -2294,7 +2394,7 @@ async def reply_video(
         Example:
             .. code-block:: python
 
-                message.reply_video(video)
+                await message.reply_video(video)
 
         Parameters:
             video (``str``):
@@ -2311,16 +2411,16 @@ async def reply_video(
             caption (``str``, *optional*):
                 Video caption, 0-1024 characters.
 
-            parse_mode (``str``, *optional*):
+            parse_mode (:obj:`~pyrogram.enums.ParseMode`, *optional*):
                 By default, texts are parsed using both Markdown and HTML styles.
                 You can combine both syntaxes together.
-                Pass "markdown" or "md" to enable Markdown-style parsing only.
-                Pass "html" to enable HTML-style parsing only.
-                Pass None to completely disable style parsing.
 
             caption_entities (List of :obj:`~pyrogram.types.MessageEntity`):
                 List of special entities that appear in the caption, which can be specified instead of *parse_mode*.
 
+            has_spoiler (``bool``, *optional*):
+                Pass True if the video needs to be covered with a spoiler animation.
+
             ttl_seconds (``int``, *optional*):
                 Self-Destruct Timer.
                 If you set a timer, the video will self-destruct in *ttl_seconds*
@@ -2355,7 +2455,7 @@ async def reply_video(
                 Additional interface options. An object for an inline keyboard, custom reply keyboard,
                 instructions to remove reply keyboard or to force a reply from the user.
 
-            progress (``callable``, *optional*):
+            progress (``Callable``, *optional*):
                 Pass a callback function to view the file transmission progress.
                 The function must take *(current, total)* as positional arguments (look at Other Parameters below for a
                 detailed description) and will be called back each time a new file chunk has been successfully
@@ -2386,10 +2486,10 @@ async def reply_video(
             RPCError: In case of a Telegram RPC error.
         """
         if quote is None:
-            quote = self.chat.type != "private"
+            quote = self.chat.type != enums.ChatType.PRIVATE
 
         if reply_to_message_id is None and quote:
-            reply_to_message_id = self.message_id
+            reply_to_message_id = self.id
 
         return await self._client.send_video(
             chat_id=self.chat.id,
@@ -2397,6 +2497,7 @@ async def reply_video(
             caption=caption,
             parse_mode=parse_mode,
             caption_entities=caption_entities,
+            has_spoiler=has_spoiler,
             ttl_seconds=ttl_seconds,
             duration=duration,
             width=width,
@@ -2425,7 +2526,7 @@ async def reply_video_note(
             "types.ReplyKeyboardRemove",
             "types.ForceReply"
         ] = None,
-        progress: callable = None,
+        progress: Callable = None,
         progress_args: tuple = ()
     ) -> "Message":
         """Bound method *reply_video_note* of :obj:`~pyrogram.types.Message`.
@@ -2434,7 +2535,7 @@ async def reply_video_note(
 
         .. code-block:: python
 
-            client.send_video_note(
+            await client.send_video_note(
                 chat_id=message.chat.id,
                 video_note=video_note
             )
@@ -2442,7 +2543,7 @@ async def reply_video_note(
         Example:
             .. code-block:: python
 
-                message.reply_video_note(video_note)
+                await message.reply_video_note(video_note)
 
         Parameters:
             video_note (``str``):
@@ -2479,7 +2580,7 @@ async def reply_video_note(
                 Additional interface options. An object for an inline keyboard, custom reply keyboard,
                 instructions to remove reply keyboard or to force a reply from the user.
 
-            progress (``callable``, *optional*):
+            progress (``Callable``, *optional*):
                 Pass a callback function to view the file transmission progress.
                 The function must take *(current, total)* as positional arguments (look at Other Parameters below for a
                 detailed description) and will be called back each time a new file chunk has been successfully
@@ -2510,10 +2611,10 @@ async def reply_video_note(
             RPCError: In case of a Telegram RPC error.
         """
         if quote is None:
-            quote = self.chat.type != "private"
+            quote = self.chat.type != enums.ChatType.PRIVATE
 
         if reply_to_message_id is None and quote:
-            reply_to_message_id = self.message_id
+            reply_to_message_id = self.id
 
         return await self._client.send_video_note(
             chat_id=self.chat.id,
@@ -2533,7 +2634,7 @@ async def reply_voice(
         voice: Union[str, BinaryIO],
         quote: bool = None,
         caption: str = "",
-        parse_mode: Optional[str] = object,
+        parse_mode: Optional["enums.ParseMode"] = None,
         caption_entities: List["types.MessageEntity"] = None,
         duration: int = 0,
         disable_notification: bool = None,
@@ -2544,7 +2645,7 @@ async def reply_voice(
             "types.ReplyKeyboardRemove",
             "types.ForceReply"
         ] = None,
-        progress: callable = None,
+        progress: Callable = None,
         progress_args: tuple = ()
     ) -> "Message":
         """Bound method *reply_voice* of :obj:`~pyrogram.types.Message`.
@@ -2553,7 +2654,7 @@ async def reply_voice(
 
         .. code-block:: python
 
-            client.send_voice(
+            await client.send_voice(
                 chat_id=message.chat.id,
                 voice=voice
             )
@@ -2561,7 +2662,7 @@ async def reply_voice(
         Example:
             .. code-block:: python
 
-                message.reply_voice(voice)
+                await message.reply_voice(voice)
 
         Parameters:
             voice (``str``):
@@ -2578,12 +2679,9 @@ async def reply_voice(
             caption (``str``, *optional*):
                 Voice message caption, 0-1024 characters.
 
-            parse_mode (``str``, *optional*):
+            parse_mode (:obj:`~pyrogram.enums.ParseMode`, *optional*):
                 By default, texts are parsed using both Markdown and HTML styles.
                 You can combine both syntaxes together.
-                Pass "markdown" or "md" to enable Markdown-style parsing only.
-                Pass "html" to enable HTML-style parsing only.
-                Pass None to completely disable style parsing.
 
             caption_entities (List of :obj:`~pyrogram.types.MessageEntity`):
                 List of special entities that appear in the caption, which can be specified instead of *parse_mode*.
@@ -2602,7 +2700,7 @@ async def reply_voice(
                 Additional interface options. An object for an inline keyboard, custom reply keyboard,
                 instructions to remove reply keyboard or to force a reply from the user.
 
-            progress (``callable``, *optional*):
+            progress (``Callable``, *optional*):
                 Pass a callback function to view the file transmission progress.
                 The function must take *(current, total)* as positional arguments (look at Other Parameters below for a
                 detailed description) and will be called back each time a new file chunk has been successfully
@@ -2633,10 +2731,10 @@ async def reply_voice(
             RPCError: In case of a Telegram RPC error.
         """
         if quote is None:
-            quote = self.chat.type != "private"
+            quote = self.chat.type != enums.ChatType.PRIVATE
 
         if reply_to_message_id is None and quote:
-            reply_to_message_id = self.message_id
+            reply_to_message_id = self.id
 
         return await self._client.send_voice(
             chat_id=self.chat.id,
@@ -2655,7 +2753,7 @@ async def reply_voice(
     async def edit_text(
         self,
         text: str,
-        parse_mode: Optional[str] = object,
+        parse_mode: Optional["enums.ParseMode"] = None,
         entities: List["types.MessageEntity"] = None,
         disable_web_page_preview: bool = None,
         reply_markup: "types.InlineKeyboardMarkup" = None
@@ -2668,27 +2766,24 @@ async def edit_text(
 
         .. code-block:: python
 
-            client.edit_message_text(
+            await client.edit_message_text(
                 chat_id=message.chat.id,
-                message_id=message.message_id,
+                message_id=message.id,
                 text="hello"
             )
 
         Example:
             .. code-block:: python
 
-                message.edit_text("hello")
+                await message.edit_text("hello")
 
         Parameters:
             text (``str``):
                 New text of the message.
 
-            parse_mode (``str``, *optional*):
+            parse_mode (:obj:`~pyrogram.enums.ParseMode`, *optional*):
                 By default, texts are parsed using both Markdown and HTML styles.
                 You can combine both syntaxes together.
-                Pass "markdown" or "md" to enable Markdown-style parsing only.
-                Pass "html" to enable HTML-style parsing only.
-                Pass None to completely disable style parsing.
 
             entities (List of :obj:`~pyrogram.types.MessageEntity`):
                 List of special entities that appear in message text, which can be specified instead of *parse_mode*.
@@ -2707,7 +2802,7 @@ async def edit_text(
         """
         return await self._client.edit_message_text(
             chat_id=self.chat.id,
-            message_id=self.message_id,
+            message_id=self.id,
             text=text,
             parse_mode=parse_mode,
             entities=entities,
@@ -2720,7 +2815,7 @@ async def edit_text(
     async def edit_caption(
         self,
         caption: str,
-        parse_mode: Optional[str] = object,
+        parse_mode: Optional["enums.ParseMode"] = None,
         caption_entities: List["types.MessageEntity"] = None,
         reply_markup: "types.InlineKeyboardMarkup" = None
     ) -> "Message":
@@ -2730,27 +2825,24 @@ async def edit_caption(
 
         .. code-block:: python
 
-            client.edit_message_caption(
+            await client.edit_message_caption(
                 chat_id=message.chat.id,
-                message_id=message.message_id,
+                message_id=message.id,
                 caption="hello"
             )
 
         Example:
             .. code-block:: python
 
-                message.edit_caption("hello")
+                await message.edit_caption("hello")
 
         Parameters:
             caption (``str``):
                 New caption of the message.
 
-            parse_mode (``str``, *optional*):
+            parse_mode (:obj:`~pyrogram.enums.ParseMode`, *optional*):
                 By default, texts are parsed using both Markdown and HTML styles.
                 You can combine both syntaxes together.
-                Pass "markdown" or "md" to enable Markdown-style parsing only.
-                Pass "html" to enable HTML-style parsing only.
-                Pass None to completely disable style parsing.
 
             caption_entities (List of :obj:`~pyrogram.types.MessageEntity`):
                 List of special entities that appear in the caption, which can be specified instead of *parse_mode*.
@@ -2766,7 +2858,7 @@ async def edit_caption(
         """
         return await self._client.edit_message_caption(
             chat_id=self.chat.id,
-            message_id=self.message_id,
+            message_id=self.id,
             caption=caption,
             parse_mode=parse_mode,
             caption_entities=caption_entities,
@@ -2784,16 +2876,16 @@ async def edit_media(
 
         .. code-block:: python
 
-            client.edit_message_media(
+            await client.edit_message_media(
                 chat_id=message.chat.id,
-                message_id=message.message_id,
+                message_id=message.id,
                 media=media
             )
 
         Example:
             .. code-block:: python
 
-                message.edit_media(media)
+                await message.edit_media(media)
 
         Parameters:
             media (:obj:`~pyrogram.types.InputMedia`):
@@ -2810,7 +2902,7 @@ async def edit_media(
         """
         return await self._client.edit_message_media(
             chat_id=self.chat.id,
-            message_id=self.message_id,
+            message_id=self.id,
             media=media,
             reply_markup=reply_markup
         )
@@ -2822,16 +2914,16 @@ async def edit_reply_markup(self, reply_markup: "types.InlineKeyboardMarkup" = N
 
         .. code-block:: python
 
-            client.edit_message_reply_markup(
+            await client.edit_message_reply_markup(
                 chat_id=message.chat.id,
-                message_id=message.message_id,
+                message_id=message.id,
                 reply_markup=inline_reply_markup
             )
 
         Example:
             .. code-block:: python
 
-                message.edit_reply_markup(inline_reply_markup)
+                await message.edit_reply_markup(inline_reply_markup)
 
         Parameters:
             reply_markup (:obj:`~pyrogram.types.InlineKeyboardMarkup`):
@@ -2846,7 +2938,7 @@ async def edit_reply_markup(self, reply_markup: "types.InlineKeyboardMarkup" = N
         """
         return await self._client.edit_message_reply_markup(
             chat_id=self.chat.id,
-            message_id=self.message_id,
+            message_id=self.id,
             reply_markup=reply_markup
         )
 
@@ -2854,7 +2946,7 @@ async def forward(
         self,
         chat_id: Union[int, str],
         disable_notification: bool = None,
-        schedule_date: int = None
+        schedule_date: datetime = None
     ) -> Union["types.Message", List["types.Message"]]:
         """Bound method *forward* of :obj:`~pyrogram.types.Message`.
 
@@ -2862,16 +2954,16 @@ async def forward(
 
         .. code-block:: python
 
-            client.forward_messages(
+            await client.forward_messages(
                 chat_id=chat_id,
                 from_chat_id=message.chat.id,
-                message_ids=message.message_id
+                message_ids=message.id
             )
 
         Example:
             .. code-block:: python
 
-                message.forward(chat_id)
+                await message.forward(chat_id)
 
         Parameters:
             chat_id (``int`` | ``str``):
@@ -2883,8 +2975,8 @@ async def forward(
                 Sends the message silently.
                 Users will receive a notification with no sound.
 
-            schedule_date (``int``, *optional*):
-                Date when the message will be automatically sent. Unix time.
+            schedule_date (:py:obj:`~datetime.datetime`, *optional*):
+                Date when the message will be automatically sent.
 
         Returns:
             On success, the forwarded Message is returned.
@@ -2895,7 +2987,7 @@ async def forward(
         return await self._client.forward_messages(
             chat_id=chat_id,
             from_chat_id=self.chat.id,
-            message_ids=self.message_id,
+            message_ids=self.id,
             disable_notification=disable_notification,
             schedule_date=schedule_date
         )
@@ -2904,11 +2996,12 @@ async def copy(
         self,
         chat_id: Union[int, str],
         caption: str = None,
-        parse_mode: Optional[str] = object,
+        parse_mode: Optional["enums.ParseMode"] = None,
         caption_entities: List["types.MessageEntity"] = None,
         disable_notification: bool = None,
         reply_to_message_id: int = None,
-        schedule_date: int = None,
+        schedule_date: datetime = None,
+        protect_content: bool = None,
         reply_markup: Union[
             "types.InlineKeyboardMarkup",
             "types.ReplyKeyboardMarkup",
@@ -2922,16 +3015,16 @@ async def copy(
 
         .. code-block:: python
 
-            client.copy_message(
+            await client.copy_message(
                 chat_id=chat_id,
                 from_chat_id=message.chat.id,
-                message_id=message.message_id
+                message_id=message.id
             )
 
         Example:
             .. code-block:: python
 
-                message.copy(chat_id)
+                await message.copy(chat_id)
 
         Parameters:
             chat_id (``int`` | ``str``):
@@ -2944,12 +3037,9 @@ async def copy(
                 If not specified, the original caption is kept.
                 Pass "" (empty string) to remove the caption.
 
-            parse_mode (``str``, *optional*):
+            parse_mode (:obj:`~pyrogram.enums.ParseMode`, *optional*):
                 By default, texts are parsed using both Markdown and HTML styles.
                 You can combine both syntaxes together.
-                Pass "markdown" or "md" to enable Markdown-style parsing only.
-                Pass "html" to enable HTML-style parsing only.
-                Pass None to completely disable style parsing.
 
             caption_entities (List of :obj:`~pyrogram.types.MessageEntity`):
                 List of special entities that appear in the new caption, which can be specified instead of *parse_mode*.
@@ -2961,8 +3051,11 @@ async def copy(
             reply_to_message_id (``int``, *optional*):
                 If the message is a reply, ID of the original message.
 
-            schedule_date (``int``, *optional*):
-                Date when the message will be automatically sent. Unix time.
+            schedule_date (:py:obj:`~datetime.datetime`, *optional*):
+                Date when the message will be automatically sent.
+
+            protect_content (``bool``, *optional*):
+                Protects the contents of the sent message from forwarding and saving.
 
             reply_markup (:obj:`~pyrogram.types.InlineKeyboardMarkup` | :obj:`~pyrogram.types.ReplyKeyboardMarkup` | :obj:`~pyrogram.types.ReplyKeyboardRemove` | :obj:`~pyrogram.types.ForceReply`, *optional*):
                 Additional interface options. An object for an inline keyboard, custom reply keyboard,
@@ -2977,22 +3070,24 @@ async def copy(
             RPCError: In case of a Telegram RPC error.
         """
         if self.service:
-            log.warning(f"Service messages cannot be copied. "
-                        f"chat_id: {self.chat.id}, message_id: {self.message_id}")
+            log.warning("Service messages cannot be copied. chat_id: %s, message_id: %s",
+                        self.chat.id, self.id)
         elif self.game and not await self._client.storage.is_bot():
-            log.warning(f"Users cannot send messages with Game media type. "
-                        f"chat_id: {self.chat.id}, message_id: {self.message_id}")
+            log.warning("Users cannot send messages with Game media type. chat_id: %s, message_id: %s",
+                        self.chat.id, self.id)
         elif self.empty:
-            log.warning(f"Empty messages cannot be copied. ")
+            log.warning("Empty messages cannot be copied.")
         elif self.text:
             return await self._client.send_message(
                 chat_id,
                 text=self.text,
                 entities=self.entities,
+                parse_mode=enums.ParseMode.DISABLED,
                 disable_web_page_preview=not self.web_page,
                 disable_notification=disable_notification,
                 reply_to_message_id=reply_to_message_id,
                 schedule_date=schedule_date,
+                protect_content=protect_content,
                 reply_markup=self.reply_markup if reply_markup is object else reply_markup
             )
         elif self.media:
@@ -3002,6 +3097,7 @@ async def copy(
                 disable_notification=disable_notification,
                 reply_to_message_id=reply_to_message_id,
                 schedule_date=schedule_date,
+                protect_content=protect_content,
                 reply_markup=self.reply_markup if reply_markup is object else reply_markup
             )
 
@@ -3091,15 +3187,15 @@ async def delete(self, revoke: bool = True):
 
         .. code-block:: python
 
-            client.delete_messages(
+            await client.delete_messages(
                 chat_id=chat_id,
-                message_ids=message.message_id
+                message_ids=message.id
             )
 
         Example:
             .. code-block:: python
 
-                message.delete()
+                await message.delete()
 
         Parameters:
             revoke (``bool``, *optional*):
@@ -3116,7 +3212,7 @@ async def delete(self, revoke: bool = True):
         """
         return await self._client.delete_messages(
             chat_id=self.chat.id,
-            message_ids=self.message_id,
+            message_ids=self.id,
             revoke=revoke
         )
 
@@ -3129,9 +3225,9 @@ async def click(self, x: Union[int, str] = 0, y: int = None, quote: bool = None,
 
         .. code-block:: python
 
-            client.request_callback_answer(
+            await client.request_callback_answer(
                 chat_id=message.chat.id,
-                message_id=message.message_id,
+                message_id=message.id,
                 callback_data=message.reply_markup[i][j].callback_data
             )
 
@@ -3139,7 +3235,7 @@ async def click(self, x: Union[int, str] = 0, y: int = None, quote: bool = None,
 
         .. code-block:: python
 
-            client.send_message(
+            await client.send_message(
                 chat_id=message.chat.id,
                 text=message.reply_markup[i][j].text
             )
@@ -3226,7 +3322,7 @@ async def click(self, x: Union[int, str] = 0, y: int = None, quote: bool = None,
             if button.callback_data:
                 return await self._client.request_callback_answer(
                     chat_id=self.chat.id,
-                    message_id=self.message_id,
+                    message_id=self.id,
                     callback_data=button.callback_data,
                     timeout=timeout
                 )
@@ -3241,6 +3337,47 @@ async def click(self, x: Union[int, str] = 0, y: int = None, quote: bool = None,
         else:
             await self.reply(button, quote=quote)
 
+    async def react(self, emoji: str = "", big: bool = False) -> bool:
+        """Bound method *react* of :obj:`~pyrogram.types.Message`.
+
+        Use as a shortcut for:
+
+        .. code-block:: python
+
+            await client.send_reaction(
+                chat_id=chat_id,
+                message_id=message.id,
+                emoji="🔥"
+            )
+
+        Example:
+            .. code-block:: python
+
+                await message.react(emoji="🔥")
+
+        Parameters:
+            emoji (``str``, *optional*):
+                Reaction emoji.
+                Pass "" as emoji (default) to retract the reaction.
+             
+            big (``bool``, *optional*):
+                Pass True to show a bigger and longer reaction.
+                Defaults to False.
+
+        Returns:
+            ``bool``: On success, True is returned.
+
+        Raises:
+            RPCError: In case of a Telegram RPC error.
+        """
+
+        return await self._client.send_reaction(
+            chat_id=self.chat.id,
+            message_id=self.id,
+            emoji=emoji,
+            big=big
+        )
+
     async def retract_vote(
         self,
     ) -> "types.Poll":
@@ -3269,14 +3406,15 @@ async def retract_vote(
 
         return await self._client.retract_vote(
             chat_id=self.chat.id,
-            message_id=self.message_id
+            message_id=self.id
         )
 
     async def download(
         self,
         file_name: str = "",
+        in_memory: bool = False,
         block: bool = True,
-        progress: callable = None,
+        progress: Callable = None,
         progress_args: tuple = ()
     ) -> str:
         """Bound method *download* of :obj:`~pyrogram.types.Message`.
@@ -3285,12 +3423,12 @@ async def download(
 
         .. code-block:: python
 
-            client.download_media(message)
+            await client.download_media(message)
 
         Example:
             .. code-block:: python
 
-                message.download()
+                await message.download()
 
         Parameters:
             file_name (``str``, *optional*):
@@ -3299,11 +3437,16 @@ async def download(
                 You can also specify a path for downloading files in a custom location: paths that end with "/"
                 are considered directories. All non-existent folders will be created automatically.
 
+            in_memory (``bool``, *optional*):
+                Pass True to download the media in-memory.
+                A binary file-like object with its attribute ".name" set will be returned.
+                Defaults to False.
+
             block (``bool``, *optional*):
                 Blocks the code execution until the file has been downloaded.
                 Defaults to True.
 
-            progress (``callable``, *optional*):
+            progress (``Callable``, *optional*):
                 Pass a callback function to view the file transmission progress.
                 The function must take *(current, total)* as positional arguments (look at Other Parameters below for a
                 detailed description) and will be called back each time a new file chunk has been successfully
@@ -3335,6 +3478,7 @@ async def download(
         return await self._client.download_media(
             message=self,
             file_name=file_name,
+            in_memory=in_memory,
             block=block,
             progress=progress,
             progress_args=progress_args,
@@ -3352,7 +3496,7 @@ async def vote(
 
             client.vote_poll(
                 chat_id=message.chat.id,
-                message_id=message.message_id,
+                message_id=message.id,
                 option=1
             )
 
@@ -3374,18 +3518,18 @@ async def vote(
 
         return await self._client.vote_poll(
             chat_id=self.chat.id,
-            message_id=self.message_id,
+            message_id=self.id,
             options=option
         )
 
-    async def pin(self, disable_notification: bool = False, both_sides: bool = False) -> bool:
+    async def pin(self, disable_notification: bool = False, both_sides: bool = False) -> "types.Message":
         """Bound method *pin* of :obj:`~pyrogram.types.Message`.
 
         Use as a shortcut for:
 
         .. code-block:: python
 
-            client.pin_chat_message(
+            await client.pin_chat_message(
                 chat_id=message.chat.id,
                 message_id=message_id
             )
@@ -3393,7 +3537,7 @@ async def pin(self, disable_notification: bool = False, both_sides: bool = False
         Example:
             .. code-block:: python
 
-                message.pin()
+                await message.pin()
 
         Parameters:
             disable_notification (``bool``):
@@ -3405,14 +3549,14 @@ async def pin(self, disable_notification: bool = False, both_sides: bool = False
                 Applicable to private chats only. Defaults to False.
 
         Returns:
-            True on success.
+            :obj:`~pyrogram.types.Message`: On success, the service message is returned.
 
         Raises:
             RPCError: In case of a Telegram RPC error.
         """
         return await self._client.pin_chat_message(
             chat_id=self.chat.id,
-            message_id=self.message_id,
+            message_id=self.id,
             disable_notification=disable_notification,
             both_sides=both_sides
         )
@@ -3424,7 +3568,7 @@ async def unpin(self) -> bool:
 
         .. code-block:: python
 
-            client.unpin_chat_message(
+            await client.unpin_chat_message(
                 chat_id=message.chat.id,
                 message_id=message_id
             )
@@ -3432,7 +3576,7 @@ async def unpin(self) -> bool:
         Example:
             .. code-block:: python
 
-                message.unpin()
+                await message.unpin()
 
         Returns:
             True on success.
@@ -3442,5 +3586,5 @@ async def unpin(self) -> bool:
         """
         return await self._client.unpin_chat_message(
             chat_id=self.chat.id,
-            message_id=self.message_id
+            message_id=self.id
         )
diff --git a/pyrogram/types/messages_and_media/message_entity.py b/pyrogram/types/messages_and_media/message_entity.py
index a0fc86a922..d2bf654dcd 100644
--- a/pyrogram/types/messages_and_media/message_entity.py
+++ b/pyrogram/types/messages_and_media/message_entity.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
@@ -16,84 +16,22 @@
 #  You should have received a copy of the GNU Lesser General Public License
 #  along with Pyrogram.  If not, see .
 
-from enum import Enum, auto
 from typing import Optional
 
 import pyrogram
-from pyrogram import raw
+from pyrogram import raw, enums
 from pyrogram import types
 from ..object import Object
 
 
-class AutoName(Enum):
-    def _generate_next_value_(self, *args):
-        return self.lower()
-
-
-class MessageEntityType(AutoName):
-    MENTION = auto()
-    HASHTAG = auto()
-    CASHTAG = auto()
-    BOT_COMMAND = auto()
-    URL = auto()
-    EMAIL = auto()
-    PHONE_NUMBER = auto()
-    BOLD = auto()
-    ITALIC = auto()
-    UNDERLINE = auto()
-    STRIKETHROUGH = auto()
-    CODE = auto()
-    PRE = auto()
-    TEXT_LINK = auto()
-    TEXT_MENTION = auto()
-    BLOCKQUOTE = auto()
-
-
-RAW_ENTITIES_TO_TYPE = {
-    raw.types.MessageEntityMention: MessageEntityType.MENTION,
-    raw.types.MessageEntityHashtag: MessageEntityType.HASHTAG,
-    raw.types.MessageEntityCashtag: MessageEntityType.CASHTAG,
-    raw.types.MessageEntityBotCommand: MessageEntityType.BOT_COMMAND,
-    raw.types.MessageEntityUrl: MessageEntityType.URL,
-    raw.types.MessageEntityEmail: MessageEntityType.EMAIL,
-    raw.types.MessageEntityBold: MessageEntityType.BOLD,
-    raw.types.MessageEntityItalic: MessageEntityType.ITALIC,
-    raw.types.MessageEntityCode: MessageEntityType.CODE,
-    raw.types.MessageEntityPre: MessageEntityType.PRE,
-    raw.types.MessageEntityUnderline: MessageEntityType.UNDERLINE,
-    raw.types.MessageEntityStrike: MessageEntityType.STRIKETHROUGH,
-    raw.types.MessageEntityBlockquote: MessageEntityType.BLOCKQUOTE,
-    raw.types.MessageEntityTextUrl: MessageEntityType.TEXT_LINK,
-    raw.types.MessageEntityMentionName: MessageEntityType.TEXT_MENTION,
-    raw.types.MessageEntityPhone: MessageEntityType.PHONE_NUMBER
-}
-
-TYPE_TO_RAW_ENTITIES = {v.value: k for k, v in RAW_ENTITIES_TO_TYPE.items()}
-
-
 class MessageEntity(Object):
     """One special entity in a text message.
+    
     For example, hashtags, usernames, URLs, etc.
 
     Parameters:
-        type (``str``):
-            Type of the entity. Can be:
-
-            - "mention": ``@username``.
-            - "hashtag": ``#hashtag``.
-            - "cashtag": ``$PYRO``.
-            - "bot_command": ``/start@pyrogrambot``.
-            - "url": ``https://pyrogram.org`` (see *url* below).
-            - "email": ``do-not-reply@pyrogram.org``.
-            - "phone_number": ``+69-420-1337``.
-            - "bold": **bold text**.
-            - "italic": *italic text*.
-            - "underline": underlined text.
-            - "strikethrough": strikethrough text.
-            - "code": monowidth string.
-            - "pre": monowidth block (see *language* below).
-            - "text_link": for clickable text URLs.
-            - "text_mention": for users without usernames (see *user* below).
+        type (:obj:`~pyrogram.enums.MessageEntityType`):
+            Type of the entity.
 
         offset (``int``):
             Offset in UTF-16 code units to the start of the entity.
@@ -102,25 +40,30 @@ class MessageEntity(Object):
             Length of the entity in UTF-16 code units.
 
         url (``str``, *optional*):
-            For "text_link" only, url that will be opened after user taps on the text.
+            For :obj:`~pyrogram.enums.MessageEntityType.TEXT_LINK` only, url that will be opened after user taps on the text.
 
         user (:obj:`~pyrogram.types.User`, *optional*):
-            For "text_mention" only, the mentioned user.
+            For :obj:`~pyrogram.enums.MessageEntityType.TEXT_MENTION` only, the mentioned user.
 
-        language (``str``. *optional*):
+        language (``str``, *optional*):
             For "pre" only, the programming language of the entity text.
+
+        custom_emoji_id (``int``, *optional*):
+            For :obj:`~pyrogram.enums.MessageEntityType.CUSTOM_EMOJI` only, unique identifier of the custom emoji.
+            Use :meth:`~pyrogram.Client.get_custom_emoji_stickers` to get full information about the sticker.
     """
 
     def __init__(
         self,
         *,
         client: "pyrogram.Client" = None,
-        type: str,
+        type: "enums.MessageEntityType",
         offset: int,
         length: int,
         url: str = None,
         user: "types.User" = None,
-        language: str = None
+        language: str = None,
+        custom_emoji_id: int = None
     ):
         super().__init__(client)
 
@@ -130,21 +73,27 @@ def __init__(
         self.url = url
         self.user = user
         self.language = language
+        self.custom_emoji_id = custom_emoji_id
 
     @staticmethod
-    def _parse(client, entity, users: dict) -> Optional["MessageEntity"]:
-        type = RAW_ENTITIES_TO_TYPE.get(entity.__class__, None)
-
-        if type is None:
-            return None
+    def _parse(client, entity: "raw.base.MessageEntity", users: dict) -> Optional["MessageEntity"]:
+        # Special case for InputMessageEntityMentionName -> MessageEntityType.TEXT_MENTION
+        # This happens in case of UpdateShortSentMessage inside send_message() where entities are parsed from the input
+        if isinstance(entity, raw.types.InputMessageEntityMentionName):
+            entity_type = enums.MessageEntityType.TEXT_MENTION
+            user_id = entity.user_id.user_id
+        else:
+            entity_type = enums.MessageEntityType(entity.__class__)
+            user_id = getattr(entity, "user_id", None)
 
         return MessageEntity(
-            type=type.value,
+            type=entity_type,
             offset=entity.offset,
             length=entity.length,
             url=getattr(entity, "url", None),
-            user=types.User._parse(client, users.get(getattr(entity, "user_id", None), None)),
+            user=types.User._parse(client, users.get(user_id, None)),
             language=getattr(entity, "language", None),
+            custom_emoji_id=getattr(entity, "document_id", None),
             client=client
         )
 
@@ -163,15 +112,13 @@ async def write(self):
         if self.language is None:
             args.pop("language")
 
-        try:
-            entity = TYPE_TO_RAW_ENTITIES[self.type]
+        args.pop("custom_emoji_id")
+        if self.custom_emoji_id is not None:
+            args["document_id"] = self.custom_emoji_id
 
-            if entity is raw.types.MessageEntityMentionName:
-                entity = raw.types.InputMessageEntityMentionName
-        except KeyError as e:
-            raise ValueError(f"Invalid message entity type {e}")
-        else:
-            try:
-                return entity(**args)
-            except TypeError as e:
-                raise TypeError(f"{entity.QUALNAME}'s {e}")
+        entity = self.type.value
+
+        if entity is raw.types.MessageEntityMentionName:
+            entity = raw.types.InputMessageEntityMentionName
+
+        return entity(**args)
diff --git a/pyrogram/types/messages_and_media/message_reactions.py b/pyrogram/types/messages_and_media/message_reactions.py
new file mode 100644
index 0000000000..8f057cf559
--- /dev/null
+++ b/pyrogram/types/messages_and_media/message_reactions.py
@@ -0,0 +1,56 @@
+#  Pyrogram - Telegram MTProto API Client Library for Python
+#  Copyright (C) 2017-present Dan 
+#
+#  This file is part of Pyrogram.
+#
+#  Pyrogram is free software: you can redistribute it and/or modify
+#  it under the terms of the GNU Lesser General Public License as published
+#  by the Free Software Foundation, either version 3 of the License, or
+#  (at your option) any later version.
+#
+#  Pyrogram is distributed in the hope that it will be useful,
+#  but WITHOUT ANY WARRANTY; without even the implied warranty of
+#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#  GNU Lesser General Public License for more details.
+#
+#  You should have received a copy of the GNU Lesser General Public License
+#  along with Pyrogram.  If not, see .
+
+from typing import Optional, List
+
+import pyrogram
+from pyrogram import raw, types
+from ..object import Object
+
+
+class MessageReactions(Object):
+    """Contains information about a message reactions.
+
+    Parameters:
+        reactions (List of :obj:`~pyrogram.types.Reaction`):
+            Reactions list.
+    """
+
+    def __init__(
+        self,
+        *,
+        client: "pyrogram.Client" = None,
+        reactions: Optional[List["types.Reaction"]] = None,
+    ):
+        super().__init__(client)
+
+        self.reactions = reactions
+
+    @staticmethod
+    def _parse(
+        client: "pyrogram.Client",
+        message_reactions: Optional["raw.base.MessageReactions"] = None
+    ) -> Optional["MessageReactions"]:
+        if not message_reactions:
+            return None
+
+        return MessageReactions(
+            client=client,
+            reactions=[types.Reaction._parse_count(client, reaction)
+                       for reaction in message_reactions.results]
+        )
diff --git a/pyrogram/types/messages_and_media/photo.py b/pyrogram/types/messages_and_media/photo.py
index a512d17b82..d6ad92263d 100644
--- a/pyrogram/types/messages_and_media/photo.py
+++ b/pyrogram/types/messages_and_media/photo.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
@@ -16,10 +16,11 @@
 #  You should have received a copy of the GNU Lesser General Public License
 #  along with Pyrogram.  If not, see .
 
+from datetime import datetime
 from typing import List
 
 import pyrogram
-from pyrogram import raw
+from pyrogram import raw, utils
 from pyrogram import types
 from pyrogram.file_id import FileId, FileType, FileUniqueId, FileUniqueType, ThumbnailSource
 from ..object import Object
@@ -45,8 +46,8 @@ class Photo(Object):
         file_size (``int``):
             File size.
 
-        date (``int``):
-            Date the photo was sent in Unix time.
+        date (:py:obj:`~datetime.datetime`):
+            Date the photo was sent.
 
         ttl_seconds (``int``, *optional*):
             Time-to-live seconds, for secret photos.
@@ -64,7 +65,7 @@ def __init__(
         width: int,
         height: int,
         file_size: int,
-        date: int,
+        date: datetime,
         ttl_seconds: int = None,
         thumbs: List["types.Thumbnail"] = None
     ):
@@ -122,7 +123,7 @@ def _parse(client, photo: "raw.types.Photo", ttl_seconds: int = None) -> "Photo"
                 width=main.w,
                 height=main.h,
                 file_size=main.size,
-                date=photo.date,
+                date=utils.timestamp_to_datetime(photo.date),
                 ttl_seconds=ttl_seconds,
                 thumbs=types.Thumbnail._parse(client, photo),
                 client=client
diff --git a/pyrogram/types/messages_and_media/poll.py b/pyrogram/types/messages_and_media/poll.py
index 832b81e258..cdb97ea100 100644
--- a/pyrogram/types/messages_and_media/poll.py
+++ b/pyrogram/types/messages_and_media/poll.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
@@ -16,10 +16,11 @@
 #  You should have received a copy of the GNU Lesser General Public License
 #  along with Pyrogram.  If not, see .
 
-from typing import List, Union
+from datetime import datetime
+from typing import List, Union, Optional
 
 import pyrogram
-from pyrogram import raw
+from pyrogram import raw, enums, utils
 from pyrogram import types
 from ..object import Object
 from ..update import Update
@@ -47,14 +48,32 @@ class Poll(Object, Update):
         is_anonymous (``bool``, *optional*):
             True, if the poll is anonymous
 
-        type (``str``, *optional*):
-            Poll type, currently can be "regular" or "quiz".
+        type (:obj:`~pyrogram.enums.PollType`, *optional*):
+            Poll type.
 
         allows_multiple_answers (``bool``, *optional*):
             True, if the poll allows multiple answers.
 
-        chosen_option (``int``, *optional*):
-            Index of your chosen option (0-9), None in case you haven't voted yet.
+        chosen_option_id (``int``, *optional*):
+            0-based index of the chosen option), None in case of no vote yet.
+
+        correct_option_id (``int``, *optional*):
+            0-based identifier of the correct answer option.
+            Available only for polls in the quiz mode, which are closed, or was sent (not forwarded) by the bot or to
+            the private chat with the bot.
+
+        explanation (``str``, *optional*):
+            Text that is shown when a user chooses an incorrect answer or taps on the lamp icon in a quiz-style poll,
+            0-200 characters.
+
+        explanation_entities (List of :obj:`~pyrogram.types.MessageEntity`, *optional*):
+            Special entities like usernames, URLs, bot commands, etc. that appear in the explanation.
+
+        open_period (``int``, *optional*):
+            Amount of time in seconds the poll will be active after creation.
+
+        close_date (:py:obj:`~datetime.datetime`, *optional*):
+            Point in time when the poll will be automatically closed.
     """
 
     def __init__(
@@ -67,10 +86,14 @@ def __init__(
         total_voter_count: int,
         is_closed: bool,
         is_anonymous: bool = None,
-        type: str = None,
+        type: "enums.PollType" = None,
         allows_multiple_answers: bool = None,
-        # correct_option_id: int,
-        chosen_option: int = None
+        chosen_option_id: Optional[int] = None,
+        correct_option_id: Optional[int] = None,
+        explanation: Optional[str] = None,
+        explanation_entities: Optional[List["types.MessageEntity"]] = None,
+        open_period: Optional[int] = None,
+        close_date: Optional[datetime] = None
     ):
         super().__init__(client)
 
@@ -82,14 +105,21 @@ def __init__(
         self.is_anonymous = is_anonymous
         self.type = type
         self.allows_multiple_answers = allows_multiple_answers
-        # self.correct_option_id = correct_option_id
-        self.chosen_option = chosen_option
+        self.chosen_option_id = chosen_option_id
+        self.correct_option_id = correct_option_id
+        self.explanation = explanation
+        self.explanation_entities = explanation_entities
+        self.open_period = open_period
+        self.close_date = close_date
 
     @staticmethod
     def _parse(client, media_poll: Union["raw.types.MessageMediaPoll", "raw.types.UpdateMessagePoll"]) -> "Poll":
-        poll = media_poll.poll  # type: raw.types.Poll
-        results = media_poll.results.results
-        chosen_option = None
+        poll: raw.types.Poll = media_poll.poll
+        poll_results: raw.types.PollResults = media_poll.results
+        results: List[raw.types.PollAnswerVoters] = poll_results.results
+
+        chosen_option_id = None
+        correct_option_id = None
         options = []
 
         for i, answer in enumerate(poll.answers):
@@ -100,7 +130,10 @@ def _parse(client, media_poll: Union["raw.types.MessageMediaPoll", "raw.types.Up
                 voter_count = result.voters
 
                 if result.chosen:
-                    chosen_option = i
+                    chosen_option_id = i
+
+                if result.correct:
+                    correct_option_id = i
 
             options.append(
                 types.PollOption(
@@ -118,9 +151,17 @@ def _parse(client, media_poll: Union["raw.types.MessageMediaPoll", "raw.types.Up
             total_voter_count=media_poll.results.total_voters,
             is_closed=poll.closed,
             is_anonymous=not poll.public_voters,
-            type="quiz" if poll.quiz else "regular",
+            type=enums.PollType.QUIZ if poll.quiz else enums.PollType.REGULAR,
             allows_multiple_answers=poll.multiple_choice,
-            chosen_option=chosen_option,
+            chosen_option_id=chosen_option_id,
+            correct_option_id=correct_option_id,
+            explanation=poll_results.solution,
+            explanation_entities=[
+                types.MessageEntity._parse(client, i, {})
+                for i in poll_results.solution_entities
+            ] if poll_results.solution_entities else None,
+            open_period=poll.close_period,
+            close_date=utils.timestamp_to_datetime(poll.close_date),
             client=client
         )
 
@@ -130,12 +171,16 @@ def _parse_update(client, update: "raw.types.UpdateMessagePoll"):
             return Poll._parse(client, update)
 
         results = update.results.results
-        chosen_option = None
+        chosen_option_id = None
+        correct_option_id = None
         options = []
 
         for i, result in enumerate(results):
             if result.chosen:
-                chosen_option = i
+                chosen_option_id = i
+
+            if result.correct:
+                correct_option_id = i
 
             options.append(
                 types.PollOption(
@@ -152,6 +197,7 @@ def _parse_update(client, update: "raw.types.UpdateMessagePoll"):
             options=options,
             total_voter_count=update.results.total_voters,
             is_closed=False,
-            chosen_option=chosen_option,
+            chosen_option_id=chosen_option_id,
+            correct_option_id=correct_option_id,
             client=client
         )
diff --git a/pyrogram/types/messages_and_media/poll_option.py b/pyrogram/types/messages_and_media/poll_option.py
index 25eff66387..a40926b077 100644
--- a/pyrogram/types/messages_and_media/poll_option.py
+++ b/pyrogram/types/messages_and_media/poll_option.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
diff --git a/pyrogram/types/messages_and_media/reaction.py b/pyrogram/types/messages_and_media/reaction.py
new file mode 100644
index 0000000000..17e08ff578
--- /dev/null
+++ b/pyrogram/types/messages_and_media/reaction.py
@@ -0,0 +1,86 @@
+#  Pyrogram - Telegram MTProto API Client Library for Python
+#  Copyright (C) 2017-present Dan 
+#
+#  This file is part of Pyrogram.
+#
+#  Pyrogram is free software: you can redistribute it and/or modify
+#  it under the terms of the GNU Lesser General Public License as published
+#  by the Free Software Foundation, either version 3 of the License, or
+#  (at your option) any later version.
+#
+#  Pyrogram is distributed in the hope that it will be useful,
+#  but WITHOUT ANY WARRANTY; without even the implied warranty of
+#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#  GNU Lesser General Public License for more details.
+#
+#  You should have received a copy of the GNU Lesser General Public License
+#  along with Pyrogram.  If not, see .
+
+from typing import Optional
+
+import pyrogram
+from pyrogram import raw
+from ..object import Object
+
+
+class Reaction(Object):
+    """Contains information about a reaction.
+
+    Parameters:
+        emoji (``str``, *optional*):
+            Reaction emoji.
+
+        custom_emoji_id (``int``, *optional*):
+            Custom emoji id.
+
+        count (``int``, *optional*):
+            Reaction count.
+
+        chosen_order (``int``, *optional*):
+            Chosen reaction order.
+            Available for chosen reactions.
+    """
+
+    def __init__(
+        self,
+        *,
+        client: "pyrogram.Client" = None,
+        emoji: Optional[str] = None,
+        custom_emoji_id: Optional[int] = None,
+        count: Optional[int] = None,
+        chosen_order: Optional[int] = None
+    ):
+        super().__init__(client)
+
+        self.emoji = emoji
+        self.custom_emoji_id = custom_emoji_id
+        self.count = count
+        self.chosen_order = chosen_order
+
+    @staticmethod
+    def _parse(
+        client: "pyrogram.Client",
+        reaction: "raw.base.Reaction"
+    ) -> "Reaction":
+        if isinstance(reaction, raw.types.ReactionEmoji):
+            return Reaction(
+                client=client,
+                emoji=reaction.emoticon
+            )
+
+        if isinstance(reaction, raw.types.ReactionCustomEmoji):
+            return Reaction(
+                client=client,
+                custom_emoji_id=reaction.document_id
+            )
+
+    @staticmethod
+    def _parse_count(
+        client: "pyrogram.Client",
+        reaction_count: "raw.base.ReactionCount"
+    ) -> "Reaction":
+        reaction = Reaction._parse(client, reaction_count.reaction)
+        reaction.count = reaction_count.count
+        reaction.chosen_order = reaction_count.chosen_order
+
+        return reaction
diff --git a/pyrogram/types/messages_and_media/sticker.py b/pyrogram/types/messages_and_media/sticker.py
index b7e4ecb5f4..de266b2fd2 100644
--- a/pyrogram/types/messages_and_media/sticker.py
+++ b/pyrogram/types/messages_and_media/sticker.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
@@ -16,10 +16,11 @@
 #  You should have received a copy of the GNU Lesser General Public License
 #  along with Pyrogram.  If not, see .
 
-from typing import List
+from datetime import datetime
+from typing import List, Dict, Type
 
 import pyrogram
-from pyrogram import raw
+from pyrogram import raw, utils
 from pyrogram import types
 from pyrogram.errors import StickersetInvalid
 from pyrogram.file_id import FileId, FileType, FileUniqueId, FileUniqueType
@@ -46,6 +47,9 @@ class Sticker(Object):
         is_animated (``bool``):
             True, if the sticker is animated
 
+        is_video (``bool``):
+            True, if the sticker is a video sticker
+
         file_name (``str``, *optional*):
             Sticker file name.
 
@@ -55,8 +59,8 @@ class Sticker(Object):
         file_size (``int``, *optional*):
             File size.
 
-        date (``int``, *optional*):
-            Date the sticker was sent in Unix time.
+        date (:py:obj:`~datetime.datetime`, *optional*):
+            Date the sticker was sent.
 
         emoji (``str``, *optional*):
             Emoji associated with the sticker.
@@ -79,10 +83,11 @@ def __init__(
         width: int,
         height: int,
         is_animated: bool,
+        is_video: bool,
         file_name: str = None,
         mime_type: str = None,
         file_size: int = None,
-        date: int = None,
+        date: datetime = None,
         emoji: str = None,
         set_name: str = None,
         thumbs: List["types.Thumbnail"] = None
@@ -98,6 +103,7 @@ def __init__(
         self.width = width
         self.height = height
         self.is_animated = is_animated
+        self.is_video = is_video
         self.emoji = emoji
         self.set_name = set_name
         self.thumbs = thumbs
@@ -106,7 +112,7 @@ def __init__(
     cache = {}
 
     @staticmethod
-    async def _get_sticker_set_name(send, input_sticker_set_id):
+    async def _get_sticker_set_name(invoke, input_sticker_set_id):
         try:
             set_id = input_sticker_set_id[0]
             set_access_hash = input_sticker_set_id[1]
@@ -116,7 +122,7 @@ async def _get_sticker_set_name(send, input_sticker_set_id):
             if name is not None:
                 return name
 
-            name = (await send(
+            name = (await invoke(
                 raw.functions.messages.GetStickerSet(
                     stickerset=raw.types.InputStickerSetID(
                         id=set_id,
@@ -140,15 +146,23 @@ async def _get_sticker_set_name(send, input_sticker_set_id):
     async def _parse(
         client,
         sticker: "raw.types.Document",
-        image_size_attributes: "raw.types.DocumentAttributeImageSize",
-        sticker_attributes: "raw.types.DocumentAttributeSticker",
-        file_name: str
+        document_attributes: Dict[Type["raw.base.DocumentAttribute"], "raw.base.DocumentAttribute"],
     ) -> "Sticker":
+        sticker_attributes = (
+            document_attributes[raw.types.DocumentAttributeSticker]
+            if raw.types.DocumentAttributeSticker in document_attributes
+            else document_attributes[raw.types.DocumentAttributeCustomEmoji]
+        )
+
+        image_size_attributes = document_attributes.get(raw.types.DocumentAttributeImageSize, None)
+        file_name = getattr(document_attributes.get(raw.types.DocumentAttributeFilename, None), "file_name", None)
+        video_attributes = document_attributes.get(raw.types.DocumentAttributeVideo, None)
+
         sticker_set = sticker_attributes.stickerset
 
         if isinstance(sticker_set, raw.types.InputStickerSetID):
             input_sticker_set_id = (sticker_set.id, sticker_set.access_hash)
-            set_name = await Sticker._get_sticker_set_name(client.send, input_sticker_set_id)
+            set_name = await Sticker._get_sticker_set_name(client.invoke, input_sticker_set_id)
         else:
             set_name = None
 
@@ -164,16 +178,29 @@ async def _parse(
                 file_unique_type=FileUniqueType.DOCUMENT,
                 media_id=sticker.id
             ).encode(),
-            width=image_size_attributes.w if image_size_attributes else 512,
-            height=image_size_attributes.h if image_size_attributes else 512,
+            width=(
+                image_size_attributes.w
+                if image_size_attributes
+                else video_attributes.w
+                if video_attributes
+                else 512
+            ),
+            height=(
+                image_size_attributes.h
+                if image_size_attributes
+                else video_attributes.h
+                if video_attributes
+                else 512
+            ),
             is_animated=sticker.mime_type == "application/x-tgsticker",
+            is_video=sticker.mime_type == "video/webm",
             # TODO: mask_position
             set_name=set_name,
             emoji=sticker_attributes.alt or None,
             file_size=sticker.size,
             mime_type=sticker.mime_type,
             file_name=file_name,
-            date=sticker.date,
+            date=utils.timestamp_to_datetime(sticker.date),
             thumbs=types.Thumbnail._parse(client, sticker),
             client=client
         )
diff --git a/pyrogram/types/messages_and_media/stripped_thumbnail.py b/pyrogram/types/messages_and_media/stripped_thumbnail.py
index 9c832ffb80..e9756607b9 100644
--- a/pyrogram/types/messages_and_media/stripped_thumbnail.py
+++ b/pyrogram/types/messages_and_media/stripped_thumbnail.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
diff --git a/pyrogram/types/messages_and_media/thumbnail.py b/pyrogram/types/messages_and_media/thumbnail.py
index 1a55c18ce4..b9cb93e127 100644
--- a/pyrogram/types/messages_and_media/thumbnail.py
+++ b/pyrogram/types/messages_and_media/thumbnail.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
diff --git a/pyrogram/types/messages_and_media/venue.py b/pyrogram/types/messages_and_media/venue.py
index bdf385e504..8a26f60061 100644
--- a/pyrogram/types/messages_and_media/venue.py
+++ b/pyrogram/types/messages_and_media/venue.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
diff --git a/pyrogram/types/messages_and_media/video.py b/pyrogram/types/messages_and_media/video.py
index b64dcfb6b9..b50f9fca78 100644
--- a/pyrogram/types/messages_and_media/video.py
+++ b/pyrogram/types/messages_and_media/video.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
@@ -16,10 +16,11 @@
 #  You should have received a copy of the GNU Lesser General Public License
 #  along with Pyrogram.  If not, see .
 
+from datetime import datetime
 from typing import List
 
 import pyrogram
-from pyrogram import raw
+from pyrogram import raw, utils
 from pyrogram import types
 from pyrogram.file_id import FileId, FileType, FileUniqueId, FileUniqueType
 from ..object import Object
@@ -60,8 +61,8 @@ class Video(Object):
         ttl_seconds (``int``. *optional*):
             Time-to-live seconds, for secret photos.
 
-        date (``int``, *optional*):
-            Date the video was sent in Unix time.
+        date (:py:obj:`~datetime.datetime`, *optional*):
+            Date the video was sent.
 
         thumbs (List of :obj:`~pyrogram.types.Thumbnail`, *optional*):
             Video thumbnails.
@@ -81,7 +82,7 @@ def __init__(
         file_size: int = None,
         supports_streaming: bool = None,
         ttl_seconds: int = None,
-        date: int = None,
+        date: datetime = None,
         thumbs: List["types.Thumbnail"] = None
     ):
         super().__init__(client)
@@ -126,7 +127,7 @@ def _parse(
             mime_type=video.mime_type,
             supports_streaming=video_attributes.supports_streaming,
             file_size=video.size,
-            date=video.date,
+            date=utils.timestamp_to_datetime(video.date),
             ttl_seconds=ttl_seconds,
             thumbs=types.Thumbnail._parse(client, video),
             client=client
diff --git a/pyrogram/types/messages_and_media/video_note.py b/pyrogram/types/messages_and_media/video_note.py
index 9a5685fc8a..3e6b40d0c9 100644
--- a/pyrogram/types/messages_and_media/video_note.py
+++ b/pyrogram/types/messages_and_media/video_note.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
@@ -16,10 +16,11 @@
 #  You should have received a copy of the GNU Lesser General Public License
 #  along with Pyrogram.  If not, see .
 
+from datetime import datetime
 from typing import List
 
 import pyrogram
-from pyrogram import raw
+from pyrogram import raw, utils
 from pyrogram import types
 from pyrogram.file_id import FileId, FileType, FileUniqueId, FileUniqueType
 from ..object import Object
@@ -48,8 +49,8 @@ class VideoNote(Object):
         file_size (``int``, *optional*):
             File size.
 
-        date (``int``, *optional*):
-            Date the video note was sent in Unix time.
+        date (:py:obj:`~datetime.datetime`, *optional*):
+            Date the video note was sent.
 
         thumbs (List of :obj:`~pyrogram.types.Thumbnail`, *optional*):
             Video thumbnails.
@@ -66,7 +67,7 @@ def __init__(
         thumbs: List["types.Thumbnail"] = None,
         mime_type: str = None,
         file_size: int = None,
-        date: int = None
+        date: datetime = None
     ):
         super().__init__(client)
 
@@ -101,7 +102,7 @@ def _parse(
             duration=video_attributes.duration,
             file_size=video_note.size,
             mime_type=video_note.mime_type,
-            date=video_note.date,
+            date=utils.timestamp_to_datetime(video_note.date),
             thumbs=types.Thumbnail._parse(client, video_note),
             client=client
         )
diff --git a/pyrogram/types/messages_and_media/voice.py b/pyrogram/types/messages_and_media/voice.py
index 640ad8ccf6..8d1c15f657 100644
--- a/pyrogram/types/messages_and_media/voice.py
+++ b/pyrogram/types/messages_and_media/voice.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
@@ -16,8 +16,10 @@
 #  You should have received a copy of the GNU Lesser General Public License
 #  along with Pyrogram.  If not, see .
 
+from datetime import datetime
+
 import pyrogram
-from pyrogram import raw
+from pyrogram import raw, utils
 from pyrogram.file_id import FileId, FileType, FileUniqueId, FileUniqueType
 from ..object import Object
 
@@ -45,8 +47,8 @@ class Voice(Object):
         file_size (``int``, *optional*):
             File size.
 
-        date (``int``, *optional*):
-            Date the voice was sent in Unix time.
+        date (:py:obj:`~datetime.datetime`, *optional*):
+            Date the voice was sent.
     """
 
     def __init__(
@@ -59,7 +61,7 @@ def __init__(
         waveform: bytes = None,
         mime_type: str = None,
         file_size: int = None,
-        date: int = None
+        date: datetime = None
     ):
         super().__init__(client)
 
@@ -89,6 +91,6 @@ def _parse(client, voice: "raw.types.Document", attributes: "raw.types.DocumentA
             mime_type=voice.mime_type,
             file_size=voice.size,
             waveform=attributes.waveform,
-            date=voice.date,
+            date=utils.timestamp_to_datetime(voice.date),
             client=client
         )
diff --git a/pyrogram/types/messages_and_media/web_app_data.py b/pyrogram/types/messages_and_media/web_app_data.py
new file mode 100644
index 0000000000..b9a471fd89
--- /dev/null
+++ b/pyrogram/types/messages_and_media/web_app_data.py
@@ -0,0 +1,51 @@
+#  Pyrogram - Telegram MTProto API Client Library for Python
+#  Copyright (C) 2017-present Dan 
+#
+#  This file is part of Pyrogram.
+#
+#  Pyrogram is free software: you can redistribute it and/or modify
+#  it under the terms of the GNU Lesser General Public License as published
+#  by the Free Software Foundation, either version 3 of the License, or
+#  (at your option) any later version.
+#
+#  Pyrogram is distributed in the hope that it will be useful,
+#  but WITHOUT ANY WARRANTY; without even the implied warranty of
+#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#  GNU Lesser General Public License for more details.
+#
+#  You should have received a copy of the GNU Lesser General Public License
+#  along with Pyrogram.  If not, see .
+
+from pyrogram import raw
+from ..object import Object
+
+
+class WebAppData(Object):
+    """Contains data sent from a `Web App `_ to the bot.
+
+    Parameters:
+        data (``str``):
+            The data.
+
+        button_text (``str``):
+            Text of the *web_app* keyboard button, from which the Web App was opened.
+
+    """
+
+    def __init__(
+        self,
+        *,
+        data: str,
+        button_text: str,
+    ):
+        super().__init__()
+
+        self.data = data
+        self.button_text = button_text
+
+    @staticmethod
+    def _parse(action: "raw.types.MessageActionWebViewDataSentMe"):
+        return WebAppData(
+            data=action.data,
+            button_text=action.text
+        )
diff --git a/pyrogram/types/messages_and_media/webpage.py b/pyrogram/types/messages_and_media/web_page.py
similarity index 98%
rename from pyrogram/types/messages_and_media/webpage.py
rename to pyrogram/types/messages_and_media/web_page.py
index 08432eabfc..34e51d88cc 100644
--- a/pyrogram/types/messages_and_media/webpage.py
+++ b/pyrogram/types/messages_and_media/web_page.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
diff --git a/pyrogram/types/object.py b/pyrogram/types/object.py
index daeb89af74..3253c8ecff 100644
--- a/pyrogram/types/object.py
+++ b/pyrogram/types/object.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
@@ -18,22 +18,18 @@
 
 import typing
 from datetime import datetime
+from enum import Enum
 from json import dumps
 
 import pyrogram
 
 
-class Meta(type, metaclass=type("", (type,), {"__str__": lambda _: "~hi"})):
-    def __str__(self):
-        return f""
-
-
-class Object(metaclass=Meta):
+class Object:
     def __init__(self, client: "pyrogram.Client" = None):
         self._client = client
 
     def bind(self, client: "pyrogram.Client"):
-        """Bind a Client instance to this Pyrogram Object
+        """Bind a Client instance to this and to all nested Pyrogram objects.
 
         Parameters:
             client (:obj:`~pyrogram.types.Client`):
@@ -42,6 +38,12 @@ def bind(self, client: "pyrogram.Client"):
         """
         self._client = client
 
+        for i in self.__dict__:
+            o = getattr(self, i)
+
+            if isinstance(o, Object):
+                o.bind(client)
+
     @staticmethod
     def default(obj: "Object"):
         if isinstance(obj, bytes):
@@ -52,14 +54,17 @@ def default(obj: "Object"):
         if isinstance(obj, typing.Match):
             return repr(obj)
 
+        if isinstance(obj, Enum):
+            return str(obj)
+
+        if isinstance(obj, datetime):
+            return str(obj)
+
         return {
             "_": obj.__class__.__name__,
             **{
                 attr: (
-                    "*" * 9
-                    if attr == "phone_number" else
-                    str(datetime.fromtimestamp(getattr(obj, attr)))
-                    if attr.endswith("date") else
+                    "*" * 9 if attr == "phone_number" else
                     getattr(obj, attr)
                 )
                 for attr in filter(lambda x: not x.startswith("_"), obj.__dict__)
@@ -83,6 +88,9 @@ def __repr__(self) -> str:
     def __eq__(self, other: "Object") -> bool:
         for attr in self.__dict__:
             try:
+                if attr.startswith("_"):
+                    continue
+
                 if getattr(self, attr) != getattr(other, attr):
                     return False
             except AttributeError:
@@ -90,13 +98,24 @@ def __eq__(self, other: "Object") -> bool:
 
         return True
 
-    def __getitem__(self, item):
-        return getattr(self, item)
+    def __setstate__(self, state):
+        for attr in state:
+            obj = state[attr]
+
+            # Maybe a better alternative would be https://docs.python.org/3/library/inspect.html#inspect.signature
+            if isinstance(obj, tuple) and len(obj) == 2 and obj[0] == "dt":
+                state[attr] = datetime.fromtimestamp(obj[1])
 
-    def __setitem__(self, key, value):
-        setattr(self, key, value)
+        self.__dict__ = state
 
     def __getstate__(self):
-        new_dict = self.__dict__.copy()
-        new_dict.pop("_client", None)
-        return new_dict
+        state = self.__dict__.copy()
+        state.pop("_client", None)
+
+        for attr in state:
+            obj = state[attr]
+
+            if isinstance(obj, datetime):
+                state[attr] = ("dt", obj.timestamp())
+
+        return state
diff --git a/pyrogram/types/update.py b/pyrogram/types/update.py
index 1937c94b93..d3e45b4abd 100644
--- a/pyrogram/types/update.py
+++ b/pyrogram/types/update.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
diff --git a/pyrogram/types/user_and_chats/__init__.py b/pyrogram/types/user_and_chats/__init__.py
index 39e5d786ac..348ecd9f3c 100644
--- a/pyrogram/types/user_and_chats/__init__.py
+++ b/pyrogram/types/user_and_chats/__init__.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
@@ -21,19 +21,24 @@
 from .chat_event import ChatEvent
 from .chat_event_filter import ChatEventFilter
 from .chat_invite_link import ChatInviteLink
+from .chat_join_request import ChatJoinRequest
+from .chat_joiner import ChatJoiner
 from .chat_member import ChatMember
 from .chat_member_updated import ChatMemberUpdated
 from .chat_permissions import ChatPermissions
 from .chat_photo import ChatPhoto
 from .chat_preview import ChatPreview
+from .chat_privileges import ChatPrivileges
+from .chat_reactions import ChatReactions
 from .dialog import Dialog
+from .emoji_status import EmojiStatus
 from .invite_link_importer import InviteLinkImporter
 from .restriction import Restriction
 from .user import User
-from .voice_chat_ended import VoiceChatEnded
-from .voice_chat_members_invited import VoiceChatMembersInvited
-from .voice_chat_scheduled import VoiceChatScheduled
-from .voice_chat_started import VoiceChatStarted
+from .video_chat_ended import VideoChatEnded
+from .video_chat_members_invited import VideoChatMembersInvited
+from .video_chat_scheduled import VideoChatScheduled
+from .video_chat_started import VideoChatStarted
 
 __all__ = [
     "Chat",
@@ -49,9 +54,14 @@
     "ChatInviteLink",
     "InviteLinkImporter",
     "ChatAdminWithInviteLinks",
-    "VoiceChatStarted",
-    "VoiceChatEnded",
-    "VoiceChatMembersInvited",
+    "VideoChatStarted",
+    "VideoChatEnded",
+    "VideoChatMembersInvited",
     "ChatMemberUpdated",
-    "VoiceChatScheduled"
+    "VideoChatScheduled",
+    "ChatJoinRequest",
+    "ChatPrivileges",
+    "ChatJoiner",
+    "EmojiStatus",
+    "ChatReactions"
 ]
diff --git a/pyrogram/types/user_and_chats/chat.py b/pyrogram/types/user_and_chats/chat.py
index cf607c66df..f37542fb0c 100644
--- a/pyrogram/types/user_and_chats/chat.py
+++ b/pyrogram/types/user_and_chats/chat.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
@@ -16,10 +16,11 @@
 #  You should have received a copy of the GNU Lesser General Public License
 #  along with Pyrogram.  If not, see .
 
-from typing import Union, List, Generator, Optional
+from datetime import datetime
+from typing import Union, List, Optional, AsyncGenerator, BinaryIO
 
 import pyrogram
-from pyrogram import raw
+from pyrogram import raw, enums
 from pyrogram import types
 from pyrogram import utils
 from ..object import Object
@@ -32,8 +33,8 @@ class Chat(Object):
         id (``int``):
             Unique identifier for this chat.
 
-        type (``str``):
-            Type of chat, can be either "private", "bot", "group", "supergroup" or "channel".
+        type (:obj:`~pyrogram.enums.ChatType`):
+            Type of chat.
 
         is_verified (``bool``, *optional*):
             True, if this chat has been verified by Telegram. Supergroups, channels and bots only.
@@ -83,6 +84,9 @@ class Chat(Object):
             It is accurate only in case the owner has set the chat photo, otherwise the dc_id will be the one assigned
             to the administrator who set the current chat photo.
 
+        has_protected_content (``bool``, *optional*):
+            True, if messages from the chat can't be forwarded to other chats.
+
         invite_link (``str``, *optional*):
             Chat invite link, for groups, supergroups and channels.
             Returned only in :meth:`~pyrogram.Client.get_chat`.
@@ -117,6 +121,14 @@ class Chat(Object):
         linked_chat (:obj:`~pyrogram.types.Chat`, *optional*):
             The linked discussion group (in case of channels) or the linked channel (in case of supergroups).
             Returned only in :meth:`~pyrogram.Client.get_chat`.
+
+        send_as_chat (:obj:`~pyrogram.types.Chat`, *optional*):
+            The default "send_as" chat.
+            Returned only in :meth:`~pyrogram.Client.get_chat`.
+
+        available_reactions (:obj:`~pyrogram.types.ChatReactions`, *optional*):
+            Available reactions in the chat.
+            Returned only in :meth:`~pyrogram.Client.get_chat`.
     """
 
     def __init__(
@@ -124,7 +136,7 @@ def __init__(
         *,
         client: "pyrogram.Client" = None,
         id: int,
-        type: str,
+        type: "enums.ChatType",
         is_verified: bool = None,
         is_restricted: bool = None,
         is_creator: bool = None,
@@ -139,6 +151,7 @@ def __init__(
         bio: str = None,
         description: str = None,
         dc_id: int = None,
+        has_protected_content: bool = None,
         invite_link: str = None,
         pinned_message=None,
         sticker_set_name: str = None,
@@ -147,7 +160,9 @@ def __init__(
         restrictions: List["types.Restriction"] = None,
         permissions: "types.ChatPermissions" = None,
         distance: int = None,
-        linked_chat: "types.Chat" = None
+        linked_chat: "types.Chat" = None,
+        send_as_chat: "types.Chat" = None,
+        available_reactions: Optional["types.ChatReactions"] = None
     ):
         super().__init__(client)
 
@@ -167,6 +182,7 @@ def __init__(
         self.bio = bio
         self.description = description
         self.dc_id = dc_id
+        self.has_protected_content = has_protected_content
         self.invite_link = invite_link
         self.pinned_message = pinned_message
         self.sticker_set_name = sticker_set_name
@@ -176,6 +192,8 @@ def __init__(
         self.permissions = permissions
         self.distance = distance
         self.linked_chat = linked_chat
+        self.send_as_chat = send_as_chat
+        self.available_reactions = available_reactions
 
     @staticmethod
     def _parse_user_chat(client, user: raw.types.User) -> "Chat":
@@ -183,7 +201,7 @@ def _parse_user_chat(client, user: raw.types.User) -> "Chat":
 
         return Chat(
             id=peer_id,
-            type="bot" if user.bot else "private",
+            type=enums.ChatType.BOT if user.bot else enums.ChatType.PRIVATE,
             is_verified=getattr(user, "verified", None),
             is_restricted=getattr(user, "restricted", None),
             is_scam=getattr(user, "scam", None),
@@ -204,13 +222,14 @@ def _parse_chat_chat(client, chat: raw.types.Chat) -> "Chat":
 
         return Chat(
             id=peer_id,
-            type="group",
+            type=enums.ChatType.GROUP,
             title=chat.title,
             is_creator=getattr(chat, "creator", None),
             photo=types.ChatPhoto._parse(client, getattr(chat, "photo", None), peer_id, 0),
             permissions=types.ChatPermissions._parse(getattr(chat, "default_banned_rights", None)),
             members_count=getattr(chat, "participants_count", None),
             dc_id=getattr(getattr(chat, "photo", None), "dc_id", None),
+            has_protected_content=getattr(chat, "noforwards", None),
             client=client
         )
 
@@ -221,7 +240,7 @@ def _parse_channel_chat(client, channel: raw.types.Channel) -> "Chat":
 
         return Chat(
             id=peer_id,
-            type="supergroup" if channel.megagroup else "channel",
+            type=enums.ChatType.SUPERGROUP if getattr(channel, "megagroup", None) else enums.ChatType.CHANNEL,
             is_verified=getattr(channel, "verified", None),
             is_restricted=getattr(channel, "restricted", None),
             is_creator=getattr(channel, "creator", None),
@@ -229,11 +248,13 @@ def _parse_channel_chat(client, channel: raw.types.Channel) -> "Chat":
             is_fake=getattr(channel, "fake", None),
             title=channel.title,
             username=getattr(channel, "username", None),
-            photo=types.ChatPhoto._parse(client, getattr(channel, "photo", None), peer_id, channel.access_hash),
+            photo=types.ChatPhoto._parse(client, getattr(channel, "photo", None), peer_id,
+                                         getattr(channel, "access_hash", 0)),
             restrictions=types.List([types.Restriction._parse(r) for r in restriction_reason]) or None,
             permissions=types.ChatPermissions._parse(getattr(channel, "default_banned_rights", None)),
             members_count=getattr(channel, "participants_count", None),
             dc_id=getattr(getattr(channel, "photo", None), "dc_id", None),
+            has_protected_content=getattr(channel, "noforwards", None),
             client=client
         )
 
@@ -268,43 +289,52 @@ def _parse_dialog(client, peer, users: dict, chats: dict):
 
     @staticmethod
     async def _parse_full(client, chat_full: Union[raw.types.messages.ChatFull, raw.types.users.UserFull]) -> "Chat":
+        users = {u.id: u for u in chat_full.users}
+        chats = {c.id: c for c in chat_full.chats}
+
         if isinstance(chat_full, raw.types.users.UserFull):
-            parsed_chat = Chat._parse_user_chat(client, chat_full.users[0])
-            parsed_chat.bio = chat_full.full_user.about
+            full_user = chat_full.full_user
+
+            parsed_chat = Chat._parse_user_chat(client, users[full_user.id])
+            parsed_chat.bio = full_user.about
 
-            if chat_full.full_user.pinned_msg_id:
+            if full_user.pinned_msg_id:
                 parsed_chat.pinned_message = await client.get_messages(
                     parsed_chat.id,
-                    message_ids=chat_full.full_user.pinned_msg_id
+                    message_ids=full_user.pinned_msg_id
                 )
         else:
             full_chat = chat_full.full_chat
-            chat = None
-            linked_chat = None
-
-            for c in chat_full.chats:
-                if full_chat.id == c.id:
-                    chat = c
-
-                if isinstance(full_chat, raw.types.ChannelFull):
-                    if full_chat.linked_chat_id == c.id:
-                        linked_chat = c
+            chat_raw = chats[full_chat.id]
 
             if isinstance(full_chat, raw.types.ChatFull):
-                parsed_chat = Chat._parse_chat_chat(client, chat)
+                parsed_chat = Chat._parse_chat_chat(client, chat_raw)
                 parsed_chat.description = full_chat.about or None
 
                 if isinstance(full_chat.participants, raw.types.ChatParticipants):
                     parsed_chat.members_count = len(full_chat.participants.participants)
             else:
-                parsed_chat = Chat._parse_channel_chat(client, chat)
+                parsed_chat = Chat._parse_channel_chat(client, chat_raw)
                 parsed_chat.members_count = full_chat.participants_count
                 parsed_chat.description = full_chat.about or None
                 # TODO: Add StickerSet type
                 parsed_chat.can_set_sticker_set = full_chat.can_set_stickers
                 parsed_chat.sticker_set_name = getattr(full_chat.stickerset, "short_name", None)
-                if linked_chat:
-                    parsed_chat.linked_chat = Chat._parse_channel_chat(client, linked_chat)
+
+                linked_chat_raw = chats.get(full_chat.linked_chat_id, None)
+
+                if linked_chat_raw:
+                    parsed_chat.linked_chat = Chat._parse_channel_chat(client, linked_chat_raw)
+
+                default_send_as = full_chat.default_send_as
+
+                if default_send_as:
+                    if isinstance(default_send_as, raw.types.PeerUser):
+                        send_as_raw = users[default_send_as.user_id]
+                    else:
+                        send_as_raw = chats[default_send_as.channel_id]
+
+                    parsed_chat.send_as_chat = Chat._parse_chat(client, send_as_raw)
 
             if full_chat.pinned_msg_id:
                 parsed_chat.pinned_message = await client.get_messages(
@@ -315,6 +345,8 @@ async def _parse_full(client, chat_full: Union[raw.types.messages.ChatFull, raw.
             if isinstance(full_chat.exported_invite, raw.types.ChatInviteExported):
                 parsed_chat.invite_link = full_chat.exported_invite.link
 
+            parsed_chat.available_reactions = types.ChatReactions._parse(client, full_chat.available_reactions)
+
         return parsed_chat
 
     @staticmethod
@@ -333,12 +365,12 @@ async def archive(self):
 
         .. code-block:: python
 
-            client.archive_chats(-100123456789)
+            await client.archive_chats(-100123456789)
 
         Example:
             .. code-block:: python
 
-                chat.archive()
+                await chat.archive()
 
         Returns:
             True on success.
@@ -356,12 +388,12 @@ async def unarchive(self):
 
         .. code-block:: python
 
-            client.unarchive_chats(-100123456789)
+            await client.unarchive_chats(-100123456789)
 
         Example:
             .. code-block:: python
 
-                chat.unarchive()
+                await chat.unarchive()
 
         Returns:
             True on success.
@@ -380,7 +412,7 @@ async def set_title(self, title: str) -> bool:
 
         .. code-block:: python
 
-            client.set_chat_title(
+            await client.set_chat_title(
                 chat_id=chat_id,
                 title=title
             )
@@ -388,7 +420,7 @@ async def set_title(self, title: str) -> bool:
         Example:
             .. code-block:: python
 
-                chat.set_title("Lounge")
+                await chat.set_title("Lounge")
 
         Note:
             In regular groups (non-supergroups), this method will only work if the "All Members Are Admins"
@@ -418,7 +450,7 @@ async def set_description(self, description: str) -> bool:
 
         .. code-block:: python
 
-            client.set_chat_description(
+            await client.set_chat_description(
                 chat_id=chat_id,
                 description=description
             )
@@ -426,7 +458,7 @@ async def set_description(self, description: str) -> bool:
         Example:
             .. code-block:: python
 
-                chat.set_chat_description("Don't spam!")
+                await chat.set_chat_description("Don't spam!")
 
         Parameters:
             description (``str``):
@@ -445,14 +477,20 @@ async def set_description(self, description: str) -> bool:
             description=description
         )
 
-    async def set_photo(self, photo: str) -> bool:
+    async def set_photo(
+        self,
+        *,
+        photo: Union[str, BinaryIO] = None,
+        video: Union[str, BinaryIO] = None,
+        video_start_ts: float = None,
+    ) -> bool:
         """Bound method *set_photo* of :obj:`~pyrogram.types.Chat`.
 
         Use as a shortcut for:
 
         .. code-block:: python
 
-            client.set_chat_photo(
+            await client.set_chat_photo(
                 chat_id=chat_id,
                 photo=photo
             )
@@ -460,11 +498,32 @@ async def set_photo(self, photo: str) -> bool:
         Example:
             .. code-block:: python
 
-                chat.set_photo("photo.png")
+                # Set chat photo using a local file
+                await chat.set_photo(photo="photo.jpg")
+
+                # Set chat photo using an existing Photo file_id
+                await chat.set_photo(photo=photo.file_id)
+
+
+                # Set chat video using a local file
+                await chat.set_photo(video="video.mp4")
+
+                # Set chat photo using an existing Video file_id
+                await chat.set_photo(video=video.file_id)
 
         Parameters:
-            photo (``str``):
-                New chat photo. You can pass a :obj:`~pyrogram.types.Photo` id or a file path to upload a new photo.
+            photo (``str`` | ``BinaryIO``, *optional*):
+                New chat photo. You can pass a :obj:`~pyrogram.types.Photo` file_id, a file path to upload a new photo
+                from your local machine or a binary file-like object with its attribute
+                ".name" set for in-memory uploads.
+
+            video (``str`` | ``BinaryIO``, *optional*):
+                New chat video. You can pass a :obj:`~pyrogram.types.Video` file_id, a file path to upload a new video
+                from your local machine or a binary file-like object with its attribute
+                ".name" set for in-memory uploads.
+
+            video_start_ts (``float``, *optional*):
+                The timestamp in seconds of the video frame to use as photo profile preview.
 
         Returns:
             ``bool``: True on success.
@@ -476,21 +535,23 @@ async def set_photo(self, photo: str) -> bool:
 
         return await self._client.set_chat_photo(
             chat_id=self.id,
-            photo=photo
+            photo=photo,
+            video=video,
+            video_start_ts=video_start_ts
         )
 
-    async def kick_member(
+    async def ban_member(
         self,
         user_id: Union[int, str],
-        until_date: int = 0
+        until_date: datetime = utils.zero_datetime()
     ) -> Union["types.Message", bool]:
-        """Bound method *kick_member* of :obj:`~pyrogram.types.Chat`.
+        """Bound method *ban_member* of :obj:`~pyrogram.types.Chat`.
 
         Use as a shortcut for:
 
         .. code-block:: python
 
-            client.kick_chat_member(
+            await client.ban_chat_member(
                 chat_id=chat_id,
                 user_id=user_id
             )
@@ -498,7 +559,7 @@ async def kick_member(
         Example:
             .. code-block:: python
 
-                chat.kick_member(123456789)
+                await chat.ban_member(123456789)
 
         Note:
             In regular groups (non-supergroups), this method will only work if the "All Members Are Admins" setting is
@@ -510,10 +571,10 @@ async def kick_member(
                 Unique identifier (int) or username (str) of the target user.
                 For a contact that exists in your Telegram address book you can use his phone number (str).
 
-            until_date (``int``, *optional*):
-                Date when the user will be unbanned, unix time.
+            until_date (:py:obj:`~datetime.datetime`, *optional*):
+                Date when the user will be unbanned.
                 If user is banned for more than 366 days or less than 30 seconds from the current time they are
-                considered to be banned forever. Defaults to 0 (ban forever).
+                considered to be banned forever. Defaults to epoch (ban forever).
 
         Returns:
             :obj:`~pyrogram.types.Message` | ``bool``: On success, a service message will be returned (when applicable), otherwise, in
@@ -523,7 +584,7 @@ async def kick_member(
             RPCError: In case of a Telegram RPC error.
         """
 
-        return await self._client.kick_chat_member(
+        return await self._client.ban_chat_member(
             chat_id=self.id,
             user_id=user_id,
             until_date=until_date
@@ -539,7 +600,7 @@ async def unban_member(
 
         .. code-block:: python
 
-            client.unban_chat_member(
+            await client.unban_chat_member(
                 chat_id=chat_id,
                 user_id=user_id
             )
@@ -547,7 +608,7 @@ async def unban_member(
         Example:
             .. code-block:: python
 
-                chat.unban_member(123456789)
+                await chat.unban_member(123456789)
 
         Parameters:
             user_id (``int`` | ``str``):
@@ -570,7 +631,7 @@ async def restrict_member(
         self,
         user_id: Union[int, str],
         permissions: "types.ChatPermissions",
-        until_date: int = 0,
+        until_date: datetime = utils.zero_datetime(),
     ) -> "types.Chat":
         """Bound method *unban_member* of :obj:`~pyrogram.types.Chat`.
 
@@ -578,7 +639,7 @@ async def restrict_member(
 
         .. code-block:: python
 
-            client.restrict_chat_member(
+            await client.restrict_chat_member(
                 chat_id=chat_id,
                 user_id=user_id,
                 permissions=ChatPermissions()
@@ -587,7 +648,7 @@ async def restrict_member(
         Example:
             .. code-block:: python
 
-                chat.restrict_member(user_id, ChatPermissions())
+                await chat.restrict_member(user_id, ChatPermissions())
 
         Parameters:
             user_id (``int`` | ``str``):
@@ -597,10 +658,10 @@ async def restrict_member(
             permissions (:obj:`~pyrogram.types.ChatPermissions`):
                 New user permissions.
 
-            until_date (``int``, *optional*):
-                Date when the user will be unbanned, unix time.
+            until_date (:py:obj:`~datetime.datetime`, *optional*):
+                Date when the user will be unbanned.
                 If user is banned for more than 366 days or less than 30 seconds from the current time they are
-                considered to be banned forever. Defaults to 0 (ban forever).
+                considered to be banned forever. Defaults to epoch (ban forever).
 
         Returns:
             :obj:`~pyrogram.types.Chat`: On success, a chat object is returned.
@@ -616,19 +677,12 @@ async def restrict_member(
             until_date=until_date,
         )
 
+    # Set None as privileges default due to issues with partially initialized module, because at the time Chat
+    # is being initialized, ChatPrivileges would be required here, but was not initialized yet.
     async def promote_member(
         self,
         user_id: Union[int, str],
-        can_manage_chat: bool = True,
-        can_change_info: bool = True,
-        can_post_messages: bool = False,
-        can_edit_messages: bool = False,
-        can_delete_messages: bool = True,
-        can_restrict_members: bool = True,
-        can_invite_users: bool = True,
-        can_pin_messages: bool = False,
-        can_promote_members: bool = False,
-        can_manage_voice_chats: bool = False
+        privileges: "types.ChatPrivileges" = None
     ) -> bool:
         """Bound method *promote_member* of :obj:`~pyrogram.types.Chat`.
 
@@ -636,7 +690,7 @@ async def promote_member(
 
         .. code-block:: python
 
-            client.promote_chat_member(
+            await client.promote_chat_member(
                 chat_id=chat_id,
                 user_id=user_id
             )
@@ -645,46 +699,15 @@ async def promote_member(
 
             .. code-block:: python
 
-                chat.promote_member(123456789)
+                await chat.promote_member(123456789)
 
         Parameters:
             user_id (``int`` | ``str``):
                 Unique identifier (int) or username (str) of the target user.
                 For a contact that exists in your Telegram address book you can use his phone number (str).
 
-            can_manage_chat (``bool``, *optional*):
-                Pass True, if the administrator can access the chat event log, chat statistics, message statistics
-                in channels, see channel members, see anonymous administrators in supergroups and ignore slow mode.
-                Implied by any other administrator privilege.
-
-            can_change_info (``bool``, *optional*):
-                Pass True, if the administrator can change chat title, photo and other settings.
-
-            can_post_messages (``bool``, *optional*):
-                Pass True, if the administrator can create channel posts, channels only.
-
-            can_edit_messages (``bool``, *optional*):
-                Pass True, if the administrator can edit messages of other users and can pin messages, channels only.
-
-            can_delete_messages (``bool``, *optional*):
-                Pass True, if the administrator can delete messages of other users.
-
-            can_restrict_members (``bool``, *optional*):
-                Pass True, if the administrator can restrict, ban or unban chat members.
-
-            can_invite_users (``bool``, *optional*):
-                Pass True, if the administrator can invite new users to the chat.
-
-            can_pin_messages (``bool``, *optional*):
-                Pass True, if the administrator can pin messages, supergroups only.
-
-            can_promote_members (``bool``, *optional*):
-                Pass True, if the administrator can add new administrators with a subset of his own privileges or
-                demote administrators that he has promoted, directly or indirectly (promoted by administrators that
-                were appointed by him).
-
-            can_manage_voice_chats (``bool``, *optional*):
-                Pass True, if the administration can manage voice chats (also called group calls).
+            privileges (:obj:`~pyrogram.types.ChatPrivileges`, *optional*):
+                New user privileges.
 
         Returns:
             ``bool``: True on success.
@@ -696,16 +719,7 @@ async def promote_member(
         return await self._client.promote_chat_member(
             chat_id=self.id,
             user_id=user_id,
-            can_manage_chat=can_manage_chat,
-            can_change_info=can_change_info,
-            can_post_messages=can_post_messages,
-            can_edit_messages=can_edit_messages,
-            can_delete_messages=can_delete_messages,
-            can_restrict_members=can_restrict_members,
-            can_invite_users=can_invite_users,
-            can_pin_messages=can_pin_messages,
-            can_promote_members=can_promote_members,
-            can_manage_voice_chats=can_manage_voice_chats
+            privileges=privileges
         )
 
     async def join(self):
@@ -715,12 +729,12 @@ async def join(self):
 
         .. code-block:: python
 
-            client.join_chat(123456789)
+            await client.join_chat(123456789)
 
         Example:
             .. code-block:: python
 
-                chat.join()
+                await chat.join()
 
         Note:
             This only works for public groups, channels that have set a username or linked chats.
@@ -741,12 +755,12 @@ async def leave(self):
 
         .. code-block:: python
 
-            client.leave_chat(123456789)
+            await client.leave_chat(123456789)
 
         Example:
             .. code-block:: python
 
-                chat.leave()
+                await chat.leave()
 
         Raises:
             RPCError: In case of a Telegram RPC error.
@@ -787,7 +801,7 @@ async def get_member(
 
         .. code-block:: python
 
-            client.get_chat_member(
+            await client.get_chat_member(
                 chat_id=chat_id,
                 user_id=user_id
             )
@@ -795,7 +809,7 @@ async def get_member(
         Example:
             .. code-block:: python
 
-                chat.get_member(user_id)
+                await chat.get_member(user_id)
 
         Returns:
             :obj:`~pyrogram.types.ChatMember`: On success, a chat member is returned.
@@ -806,186 +820,144 @@ async def get_member(
             user_id=user_id
         )
 
-    async def get_members(
+    def get_members(
         self,
-        offset: int = 0,
-        limit: int = 200,
         query: str = "",
-        filter: str = "all"
-    ) -> List["types.ChatMember"]:
+        limit: int = 0,
+        filter: "enums.ChatMembersFilter" = enums.ChatMembersFilter.SEARCH
+    ) -> Optional[AsyncGenerator["types.ChatMember", None]]:
         """Bound method *get_members* of :obj:`~pyrogram.types.Chat`.
 
         Use as a shortcut for:
 
         .. code-block:: python
 
-            client.get_chat_members(chat_id)
+            async for member in client.get_chat_members(chat_id):
+                print(member)
 
+        Example:
+            .. code-block:: python
+
+                async for member in chat.get_members():
+                    print(member)
 
         Parameters:
-            offset (``int``, *optional*):
-                Sequential number of the first member to be returned.
-                Only applicable to supergroups and channels. Defaults to 0 [1]_.
+            query (``str``, *optional*):
+                Query string to filter members based on their display names and usernames.
+                Only applicable to supergroups and channels. Defaults to "" (empty string).
+                A query string is applicable only for :obj:`~pyrogram.enums.ChatMembersFilter.SEARCH`,
+                :obj:`~pyrogram.enums.ChatMembersFilter.BANNED` and :obj:`~pyrogram.enums.ChatMembersFilter.RESTRICTED`
+                filters only.
 
             limit (``int``, *optional*):
                 Limits the number of members to be retrieved.
-                Only applicable to supergroups and channels.
-                Defaults to 200, which is also the maximum server limit allowed per method call.
-
-            query (``str``, *optional*):
-                Query string to filter members based on their display names and usernames.
-                Only applicable to supergroups and channels. Defaults to "" (empty string) [2]_.
 
-            filter (``str``, *optional*):
+            filter (:obj:`~pyrogram.enums.ChatMembersFilter`, *optional*):
                 Filter used to select the kind of members you want to retrieve. Only applicable for supergroups
-                and channels. It can be any of the followings:
-                *"all"* - all kind of members,
-                *"kicked"* - kicked (banned) members only,
-                *"restricted"* - restricted members only,
-                *"bots"* - bots only,
-                *"recent"* - recent members only,
-                *"administrators"* - chat administrators only.
-                Only applicable to supergroups and channels.
-                Defaults to *"recent"*.
-
-        .. [1] Server limit: on supergroups, you can get up to 10,000 members for a single query and up to 200 members
-            on channels.
-
-        .. [2] A query string is applicable only for *"all"*, *"kicked"* and *"restricted"* filters only.
-
-        Example:
-            .. code-block:: python
-
-                # Get first 200 recent members
-                chat.get_members()
-
-                # Get all administrators
-                chat.get_members(filter="administrators")
-
-                # Get all bots
-                chat.get_members(filter="bots")
+                and channels.
 
         Returns:
-            List of :obj:`~pyrogram.types.ChatMember`: On success, a list of chat members is returned.
+            ``Generator``: On success, a generator yielding :obj:`~pyrogram.types.ChatMember` objects is returned.
         """
 
-        return await self._client.get_chat_members(
+        return self._client.get_chat_members(
             self.id,
-            offset=offset,
-            limit=limit,
             query=query,
+            limit=limit,
             filter=filter
         )
 
-    def iter_members(
+    async def add_members(
         self,
-        limit: int = 0,
-        query: str = "",
-        filter: str = "all"
-    ) -> Optional[Generator["types.ChatMember", None, None]]:
-        """Bound method *iter_members* of :obj:`~pyrogram.types.Chat`.
+        user_ids: Union[Union[int, str], List[Union[int, str]]],
+        forward_limit: int = 100
+    ) -> bool:
+        """Bound method *add_members* of :obj:`~pyrogram.types.Chat`.
 
         Use as a shortcut for:
 
         .. code-block:: python
 
-        Parameters:
-            limit (``int``, *optional*):
-                Limits the number of members to be retrieved.
-                Only applicable to supergroups and channels.
-                Defaults to 200, which is also the maximum server limit allowed per method call [1]_.
+            await client.add_chat_members(chat_id, user_id)
 
-            query (``str``, *optional*):
-                Query string to filter members based on their display names and usernames.
-                Only applicable to supergroups and channels. Defaults to "" (empty string) [2]_.
+        Example:
+            .. code-block:: python
 
-            filter (``str``, *optional*):
-                Filter used to select the kind of members you want to retrieve. Only applicable for supergroups
-                and channels. It can be any of the followings:
-                *"all"* - all kind of members,
-                *"kicked"* - kicked (banned) members only,
-                *"restricted"* - restricted members only,
-                *"bots"* - bots only,
-                *"recent"* - recent members only,
-                *"administrators"* - chat administrators only.
-                Only applicable to supergroups and channels.
-                Defaults to *"recent"*.
+                await chat.add_members(user_id)
 
-        .. [1] Server limit: on supergroups, you can get up to 10,000 members for a single query and up to 200 members
-            on channels.
+        Returns:
+            ``bool``: On success, True is returned.
+        """
 
-        .. [2] A query string is applicable only for *"all"*, *"kicked"* and *"restricted"* filters only.
+        return await self._client.add_chat_members(
+            self.id,
+            user_ids=user_ids,
+            forward_limit=forward_limit
+        )
 
-        Example:
-            .. code-block:: python
+    async def mark_unread(self, ) -> bool:
+        """Bound method *mark_unread* of :obj:`~pyrogram.types.Chat`.
 
-                # Get first 200 recent members
-                for member in chat.get_members():
-                    print(member.user.first_name)
+        Use as a shortcut for:
 
-                # Get all administrators
-                for member in chat.iter_members(filter="administrators"):
-                    print(member.user.first_name)
+        .. code-block:: python
+
+            await client.mark_unread(chat_id)
 
-                # Get first 3 bots
-                for member in chat.iter_members(filter="bots", limit=3):
-                    print(member.user.first_name)
+        Example:
+            .. code-block:: python
+
+                await chat.mark_unread()
 
         Returns:
-            ``Generator``: A generator yielding :obj:`~pyrogram.types.ChatMember` objects.
+            ``bool``: On success, True is returned.
         """
 
-        return self._client.iter_chat_members(
-            self.id,
-            limit=limit,
-            query=query,
-            filter=filter
-        )
+        return await self._client.mark_chat_unread(self.id)
 
-    async def add_members(
-        self,
-        user_ids: Union[Union[int, str], List[Union[int, str]]],
-        forward_limit: int = 100
-    ) -> bool:
-        """Bound method *add_members* of :obj:`~pyrogram.types.Chat`.
+    async def set_protected_content(self, enabled: bool) -> bool:
+        """Bound method *set_protected_content* of :obj:`~pyrogram.types.Chat`.
 
         Use as a shortcut for:
 
         .. code-block:: python
 
-            client.add_chat_members(chat_id, user_id)
+            await client.set_chat_protected_content(chat_id, enabled)
+
+        Parameters:
+            enabled (``bool``):
+                Pass True to enable the protected content setting, False to disable.
 
         Example:
             .. code-block:: python
 
-                chat.add_members(user_id)
+                await chat.set_protected_content(enabled)
 
         Returns:
             ``bool``: On success, True is returned.
         """
 
-        return await self._client.add_chat_members(
+        return await self._client.set_chat_protected_content(
             self.id,
-            user_ids=user_ids,
-            forward_limit=forward_limit
+            enabled=enabled
         )
 
-    async def mark_unread(self, ) -> bool:
-        """Bound method *mark_unread* of :obj:`~pyrogram.types.Chat`.
+    async def unpin_all_messages(self) -> bool:
+        """Bound method *unpin_all_messages* of :obj:`~pyrogram.types.Chat`.
 
         Use as a shortcut for:
 
         .. code-block:: python
 
-            client.mark_unread(chat_id)
+            client.unpin_all_chat_messages(chat_id)
 
         Example:
             .. code-block:: python
 
-                chat.mark_unread()
+                chat.unpin_all_messages()
 
         Returns:
             ``bool``: On success, True is returned.
         """
 
-        return await self._client.mark_chat_unread(self.id)
+        return await self._client.unpin_all_chat_messages(self.id)
diff --git a/pyrogram/types/user_and_chats/chat_admin_with_invite_links.py b/pyrogram/types/user_and_chats/chat_admin_with_invite_links.py
index c7e617e941..385a38da43 100644
--- a/pyrogram/types/user_and_chats/chat_admin_with_invite_links.py
+++ b/pyrogram/types/user_and_chats/chat_admin_with_invite_links.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
diff --git a/pyrogram/types/user_and_chats/chat_event.py b/pyrogram/types/user_and_chats/chat_event.py
index 3d0bbe19d1..88ff42f343 100644
--- a/pyrogram/types/user_and_chats/chat_event.py
+++ b/pyrogram/types/user_and_chats/chat_event.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
@@ -16,238 +16,126 @@
 #  You should have received a copy of the GNU Lesser General Public License
 #  along with Pyrogram.  If not, see .
 
-from enum import Enum, auto
+from datetime import datetime
 from typing import List, Optional
 
 import pyrogram
 from pyrogram import raw
-from pyrogram import types
+from pyrogram import types, utils, enums
 from ..object import Object
 
 
-class AutoName(Enum):
-    def _generate_next_value_(self, *args):
-        return self.lower()
-
-
-class ChatEventAction(AutoName):
-    DESCRIPTION_CHANGED = auto()
-    HISTORY_TTL_CHANGED = auto()
-    LINKED_CHAT_CHANGED = auto()
-    # LOCATION_CHANGED = auto()
-    PHOTO_CHANGED = auto()
-    # STICKER_SET_CHANGED = auto()
-    TITLE_CHANGED = auto()
-    USERNAME_CHANGED = auto()
-    CHAT_PERMISSIONS_CHANGED = auto()
-    MESSAGE_DELETED = auto()
-    # VOICE_CHAT_DISCARDED = auto()
-    MESSAGE_EDITED = auto()
-    INVITE_LINK_EDITED = auto()
-    INVITE_LINK_REVOKED = auto()
-    INVITE_LINK_DELETED = auto()
-    MEMBER_INVITED = auto()
-    MEMBER_JOINED = auto()
-    # MEMBER_JOINED_BY_LINK = auto()
-    MEMBER_LEFT = auto()
-    # MEMBER_MUTED = auto()
-    ADMIN_RIGHTS_CHANGED = auto()
-    MEMBER_PERMISSIONS_CHANGED = auto()
-    # MEMBER_UNMUTED = auto()
-    # MEMBER_VOLUME_CHANGED = auto()
-    # VOICE_CHAT_STARTED = auto()
-    POLL_STOPPED = auto()
-    # VOICE_CHAT_SETTINGS_CHANGED = auto()
-    INVITES_ENABLED = auto()
-    HISTORY_HIDDEN = auto()
-    SIGNATURES_ENABLED = auto()
-    SLOW_MODE_CHANGED = auto()
-    MESSAGE_PINNED = auto()
-    MESSAGE_UNPINNED = auto()
-    UNKNOWN = auto()
-
-
 class ChatEvent(Object):
     """A chat event from the recent actions log (also known as admin log).
 
+    See ``action`` to know which kind of event this is and the relative attributes to get the event content.
+
     Parameters:
         id (``int``):
             Chat event identifier.
 
-        date (``int``):
-            Date of the event. Unix time.
-
-        action (``str``):
-            Event action. Can be:
-
-            - "description_changed": the chat description has been changed
-              (see *old_description* and *new_description* below).
-
-            - "history_ttl_changed": the history time-to-live has been changed
-              (see *old_history_ttl* and *new_history_ttl* below).
-
-            - "linked_chat_changed": the linked chat has been changed
-              (see *old_linked_chat* and *new_linked_chat* below).
-
-            - "photo_changed": the chat photo has been changed
-              (see *old_photo* and *new_photo* below).
-
-            - "title_changed": the chat title has been changed
-              (see *old_title* and *new_title* below).
-
-            - "username_changed": the chat username has been changed
-              (see *old_username* and *new_username* below).
-
-            - "chat_permissions_changed": the default chat permissions has been changed
-              (see *old_chat_permissions* and *new_chat_permissions* below).
-
-            - "message_deleted": a message has been deleted
-              (see *deleted_message* below).
-
-            - "message_edited": a message has been edited
-              (see *old_message* and *new_message* below).
-
-            - "member_invited": a member has been invited by someone
-              (see *invited_member* below).
-
-            - "member_joined": a member joined by themselves.
-              (see *user* below)
-
-            - "member_left": a member left by themselves.
-              (see *user* below).
-
-            - "admin_rights_changed": a chat member has been promoted/demoted or their administrator rights has changed
-              (see *old_admin_rights* and *new_admin_rights* below).
-
-            - "member_permissions_changed": a chat member has been restricted/unrestricted or banned/unbanned, or their
-              permissions has changed (see *old_member_permissions* and *new_member_permissions* below).
-
-            - "poll_stopped": a poll has been stopped
-              (see *stopped_poll* below).
-
-            - "invites_enabled": the chat invitation has been enabled or disabled
-              (see *invites_enabled* below).
-
-            - "history_hidden": the chat history has been hidden or unhidden
-              (see *history_hidden* below).
-
-            - "signatures_enabled": the message signatures have been enabled or disabled
-              (see *signatures_enabled* below).
-
-            - "slow_mode_changed": the slow mode has been changes
-              (see *old_slow_mode* and *new_slow_mode* below).
-
-            - "message_pinned": a message has been pinned
-              (see *pinned_message* below).
-
-            - "message_unpinned": a message has been unpinned
-              (see *unpinned_message* below).
-
-            - "invite_link_edited": an invite link has been edited
-              (see *edited_invite_link* below).
-
-            - "invite_link_revoked": an invite link has been revoked
-              (see *revoked_invite_link* below).
+        date (:py:obj:`~datetime.datetime`):
+            Date of the event.
 
-            - "invite_link_deleted": an invite link has been deleted
-              (see *deleted_invite_link* below).
+        action (:obj:`~pyrogram.enums.ChatEventAction`):
+            Event action.
 
         user (:obj:`~pyrogram.types.User`):
             User that triggered the event.
 
         old_description, new_description (``str``, *optional*):
             Previous and new chat description.
-            For "description_changed" only.
+            For :obj:`~pyrogram.enums.ChatEventAction.DESCRIPTION_CHANGED` action only.
 
         old_history_ttl, new_history_ttl (``int``, *optional*):
             Previous and new chat history TTL.
-            For "history_ttl_changed" only.
+            For :obj:`~pyrogram.enums.ChatEventAction.HISTORY_TTL_CHANGED` action only.
 
         old_linked_chat, new_linked_chat (:obj:`~pyrogram.types.Chat`, *optional*):
             Previous and new linked chat.
-            For "linked_chat_changed" only.
+            For :obj:`~pyrogram.enums.ChatEventAction.LINKED_CHAT_CHANGED` action only.
 
         old_photo, new_photo (:obj:`~pyrogram.types.Photo`, *optional*):
             Previous and new chat photo.
-            For "photo_changed" only.
+            For :obj:`~pyrogram.enums.ChatEventAction.PHOTO_CHANGED` action only.
 
         old_title, new_title (``str``, *optional*):
             Previous and new chat title.
-            For "title_changed" only.
+            For :obj:`~pyrogram.enums.ChatEventAction.TITLE_CHANGED` action only.
 
         old_username, new_username (``str``, *optional*):
             Previous and new chat username.
-            For "username_changed" only.
+            For :obj:`~pyrogram.enums.ChatEventAction.USERNAME_CHANGED` action only.
 
         old_chat_permissions, new_chat_permissions (:obj:`~pyrogram.types.ChatPermissions`, *optional*):
             Previous and new default chat permissions.
-            For "chat_permissions_changed" only.
+            For :obj:`~pyrogram.enums.ChatEventAction.CHAT_PERMISSIONS_CHANGED` action only.
 
         deleted_message (:obj:`~pyrogram.types.Message`, *optional*):
             Deleted message.
-            For "deleted_message" only.
+            For :obj:`~pyrogram.enums.ChatEventAction.MESSAGE_DELETED` action only.
 
         old_message, new_message (:obj:`~pyrogram.types.Message`, *optional*):
             Previous and new message before it has been edited.
-            For "message_edited" only.
+            For :obj:`~pyrogram.enums.ChatEventAction.MESSAGE_EDITED` action only.
 
         invited_member (:obj:`~pyrogram.types.ChatMember`, *optional*):
             New invited chat member.
-            For "member_invited" only.
+            For :obj:`~pyrogram.enums.ChatEventAction.MEMBER_INVITED` action only.
 
-        old_admin_rights, new_admin_rights (:obj:`~pyrogram.types.ChatMember`, *optional*):
-            Previous and new administrator rights.
-            For "admin_rights_changed" only.
+        old_administrator_privileges, new_administrator_privileges (:obj:`~pyrogram.types.ChatMember`, *optional*):
+            Previous and new administrator privileges.
+            For :obj:`~pyrogram.enums.ChatEventAction.ADMINISTRATOR_PRIVILEGES_CHANGED` action only.
 
         old_member_permissions, new_member_permissions (:obj:`~pyrogram.types.ChatMember`, *optional*):
             Previous and new member permissions.
-            For "member_permissions_changed" only.
+            For :obj:`~pyrogram.enums.ChatEventAction.MEMBER_PERMISSIONS_CHANGED` action only.
 
         stopped_poll (:obj:`~pyrogram.types.Message`, *optional*):
             Message containing the stopped poll.
-            For "poll_stopped" only.
+            For :obj:`~pyrogram.enums.ChatEventAction.POLL_STOPPED` action only.
 
         invites_enabled (``bool``, *optional*):
             If chat invites were enabled (True) or disabled (False).
-            For "invites_enabled" only.
+            For :obj:`~pyrogram.enums.ChatEventAction.INVITES_ENABLED` action only.
 
         history_hidden (``bool``, *optional*):
             If chat history has been hidden (True) or unhidden (False).
-            For "history_hidden" only.
+            For :obj:`~pyrogram.enums.ChatEventAction.HISTORY_HIDDEN` action only.
 
         signatures_enabled (``bool``, *optional*):
             If message signatures were enabled (True) or disabled (False).
-            For "signatures_enabled" only.
+            For :obj:`~pyrogram.enums.ChatEventAction.SIGNATURES_ENABLED` action only.
 
         old_slow_mode, new_slow_mode (``int``, *optional*):
             Previous and new slow mode value in seconds.
-            For "slow_mode_changed" only.
+            For :obj:`~pyrogram.enums.ChatEventAction.SLOW_MODE_CHANGED` action only.
 
         pinned_message (:obj:`~pyrogram.types.Message`, *optional*):
             Pinned message.
-            For "message_pinned" only.
+            For :obj:`~pyrogram.enums.ChatEventAction.MESSAGE_PINNED` action only.
 
         unpinned_message (:obj:`~pyrogram.types.Message`, *optional*):
             Unpinned message.
-            For "unpinned_message" only.
+            For :obj:`~pyrogram.enums.ChatEventAction.MESSAGE_UNPINNED` action only.
 
         old_invite_link, new_invite_link (:obj:`~pyrogram.types.ChatInviteLink`, *optional*):
             Previous and new edited invite link.
-            For "invite_link_edited" only.
+            For :obj:`~pyrogram.enums.ChatEventAction.INVITE_LINK_EDITED` action only.
 
         revoked_invite_link (:obj:`~pyrogram.types.ChatInviteLink`, *optional*):
             Revoked invite link.
-            For "invite_link_revoked" only.
+            For :obj:`~pyrogram.enums.ChatEventAction.INVITE_LINK_REVOKED` action only.
 
         deleted_invite_link (:obj:`~pyrogram.types.ChatInviteLink`, *optional*):
             Deleted invite link.
-            For "invite_link_deleted" only.
+            For :obj:`~pyrogram.enums.ChatEventAction.INVITE_LINK_DELETED` action only.
     """
 
     def __init__(
         self, *,
         id: int,
-        date: int,
+        date: datetime,
         user: "types.User",
         action: str,
 
@@ -279,8 +167,8 @@ def __init__(
 
         invited_member: "types.ChatMember" = None,
 
-        old_admin_rights: "types.ChatMember" = None,
-        new_admin_rights: "types.ChatMember" = None,
+        old_administrator_privileges: "types.ChatMember" = None,
+        new_administrator_privileges: "types.ChatMember" = None,
 
         old_member_permissions: "types.ChatMember" = None,
         new_member_permissions: "types.ChatMember" = None,
@@ -302,7 +190,7 @@ def __init__(
         old_invite_link: "types.ChatInviteLink" = None,
         new_invite_link: "types.ChatInviteLink" = None,
         revoked_invite_link: "types.ChatInviteLink" = None,
-        deleted_invite_link: "types.ChatInviteLink" = None,
+        deleted_invite_link: "types.ChatInviteLink" = None
     ):
         super().__init__()
 
@@ -339,8 +227,8 @@ def __init__(
 
         self.invited_member = invited_member
 
-        self.old_admin_rights = old_admin_rights
-        self.new_admin_rights = new_admin_rights
+        self.old_administrator_privileges = old_administrator_privileges
+        self.new_administrator_privileges = new_administrator_privileges
 
         self.old_member_permissions = old_member_permissions
         self.new_member_permissions = new_member_permissions
@@ -405,8 +293,8 @@ async def _parse(
 
         invited_member: Optional[types.ChatMember] = None
 
-        old_admin_rights: Optional[types.ChatMember] = None
-        new_admin_rights: Optional[types.ChatMember] = None
+        old_administrator_privileges: Optional[types.ChatMember] = None
+        new_administrator_privileges: Optional[types.ChatMember] = None
 
         old_member_permissions: Optional[types.ChatMember] = None
         new_member_permissions: Optional[types.ChatMember] = None
@@ -433,117 +321,117 @@ async def _parse(
         if isinstance(action, raw.types.ChannelAdminLogEventActionChangeAbout):
             old_description = action.prev_value
             new_description = action.new_value
-            action = ChatEventAction.DESCRIPTION_CHANGED.value
+            action = enums.ChatEventAction.DESCRIPTION_CHANGED
 
         elif isinstance(action, raw.types.ChannelAdminLogEventActionChangeHistoryTTL):
             old_history_ttl = action.prev_value
             new_history_ttl = action.new_value
-            action = ChatEventAction.HISTORY_TTL_CHANGED.value
+            action = enums.ChatEventAction.HISTORY_TTL_CHANGED
 
         elif isinstance(action, raw.types.ChannelAdminLogEventActionChangeLinkedChat):
             old_linked_chat = types.Chat._parse_chat(client, chats[action.prev_value])
             new_linked_chat = types.Chat._parse_chat(client, chats[action.new_value])
-            action = ChatEventAction.LINKED_CHAT_CHANGED.value
+            action = enums.ChatEventAction.LINKED_CHAT_CHANGED
 
         elif isinstance(action, raw.types.ChannelAdminLogEventActionChangePhoto):
             old_photo = types.Photo._parse(client, action.prev_photo)
             new_photo = types.Photo._parse(client, action.new_photo)
-            action = ChatEventAction.PHOTO_CHANGED.value
+            action = enums.ChatEventAction.PHOTO_CHANGED
 
         elif isinstance(action, raw.types.ChannelAdminLogEventActionChangeTitle):
             old_title = action.prev_value
             new_title = action.new_value
-            action = ChatEventAction.TITLE_CHANGED.value
+            action = enums.ChatEventAction.TITLE_CHANGED
 
         elif isinstance(action, raw.types.ChannelAdminLogEventActionChangeUsername):
             old_username = action.prev_value
             new_username = action.new_value
-            action = ChatEventAction.USERNAME_CHANGED.value
+            action = enums.ChatEventAction.USERNAME_CHANGED
 
         elif isinstance(action, raw.types.ChannelAdminLogEventActionDefaultBannedRights):
             old_chat_permissions = types.ChatPermissions._parse(action.prev_banned_rights)
             new_chat_permissions = types.ChatPermissions._parse(action.new_banned_rights)
-            action = ChatEventAction.CHAT_PERMISSIONS_CHANGED.value
+            action = enums.ChatEventAction.CHAT_PERMISSIONS_CHANGED
 
         elif isinstance(action, raw.types.ChannelAdminLogEventActionDeleteMessage):
             deleted_message = await types.Message._parse(client, action.message, users, chats)
-            action = ChatEventAction.MESSAGE_DELETED.value
+            action = enums.ChatEventAction.MESSAGE_DELETED
 
         elif isinstance(action, raw.types.ChannelAdminLogEventActionEditMessage):
             old_message = await types.Message._parse(client, action.prev_message, users, chats)
             new_message = await types.Message._parse(client, action.new_message, users, chats)
-            action = ChatEventAction.MESSAGE_EDITED.value
+            action = enums.ChatEventAction.MESSAGE_EDITED
 
         elif isinstance(action, raw.types.ChannelAdminLogEventActionParticipantInvite):
             invited_member = types.ChatMember._parse(client, action.participant, users, chats)
-            action = ChatEventAction.MEMBER_INVITED.value
+            action = enums.ChatEventAction.MEMBER_INVITED
 
         elif isinstance(action, raw.types.ChannelAdminLogEventActionParticipantToggleAdmin):
-            old_admin_rights = types.ChatMember._parse(client, action.prev_participant, users, chats)
-            new_admin_rights = types.ChatMember._parse(client, action.new_participant, users, chats)
-            action = ChatEventAction.ADMIN_RIGHTS_CHANGED.value
+            old_administrator_privileges = types.ChatMember._parse(client, action.prev_participant, users, chats)
+            new_administrator_privileges = types.ChatMember._parse(client, action.new_participant, users, chats)
+            action = enums.ChatEventAction.ADMINISTRATOR_PRIVILEGES_CHANGED
 
         elif isinstance(action, raw.types.ChannelAdminLogEventActionParticipantToggleBan):
             old_member_permissions = types.ChatMember._parse(client, action.prev_participant, users, chats)
             new_member_permissions = types.ChatMember._parse(client, action.new_participant, users, chats)
-            action = ChatEventAction.MEMBER_PERMISSIONS_CHANGED.value
+            action = enums.ChatEventAction.MEMBER_PERMISSIONS_CHANGED
 
         elif isinstance(action, raw.types.ChannelAdminLogEventActionStopPoll):
             stopped_poll = await types.Message._parse(client, action.message, users, chats)
-            action = ChatEventAction.POLL_STOPPED.value
+            action = enums.ChatEventAction.POLL_STOPPED
 
         elif isinstance(action, raw.types.ChannelAdminLogEventActionParticipantJoin):
-            action = ChatEventAction.MEMBER_JOINED.value
+            action = enums.ChatEventAction.MEMBER_JOINED
 
         elif isinstance(action, raw.types.ChannelAdminLogEventActionParticipantLeave):
-            action = ChatEventAction.MEMBER_LEFT.value
+            action = enums.ChatEventAction.MEMBER_LEFT
 
         elif isinstance(action, raw.types.ChannelAdminLogEventActionToggleInvites):
             invites_enabled = action.new_value
-            action = ChatEventAction.INVITES_ENABLED.value
+            action = enums.ChatEventAction.INVITES_ENABLED
 
         elif isinstance(action, raw.types.ChannelAdminLogEventActionTogglePreHistoryHidden):
             history_hidden = action.new_value
-            action = ChatEventAction.HISTORY_HIDDEN.value
+            action = enums.ChatEventAction.HISTORY_HIDDEN
 
         elif isinstance(action, raw.types.ChannelAdminLogEventActionToggleSignatures):
             signatures_enabled = action.new_value
-            action = ChatEventAction.SIGNATURES_ENABLED.value
+            action = enums.ChatEventAction.SIGNATURES_ENABLED
 
         elif isinstance(action, raw.types.ChannelAdminLogEventActionToggleSlowMode):
             old_slow_mode = action.prev_value
             new_slow_mode = action.new_value
-            action = ChatEventAction.SLOW_MODE_CHANGED.value
+            action = enums.ChatEventAction.SLOW_MODE_CHANGED
 
         elif isinstance(action, raw.types.ChannelAdminLogEventActionUpdatePinned):
             message = action.message
 
             if message.pinned:
                 pinned_message = await types.Message._parse(client, message, users, chats)
-                action = ChatEventAction.MESSAGE_PINNED.value
+                action = enums.ChatEventAction.MESSAGE_PINNED
             else:
                 unpinned_message = await types.Message._parse(client, message, users, chats)
-                action = ChatEventAction.MESSAGE_UNPINNED.value
+                action = enums.ChatEventAction.MESSAGE_UNPINNED
 
         elif isinstance(action, raw.types.ChannelAdminLogEventActionExportedInviteEdit):
             old_invite_link = types.ChatInviteLink._parse(client, action.prev_invite, users)
             new_invite_link = types.ChatInviteLink._parse(client, action.new_invite, users)
-            action = ChatEventAction.INVITE_LINK_EDITED.value
+            action = enums.ChatEventAction.INVITE_LINK_EDITED
 
         elif isinstance(action, raw.types.ChannelAdminLogEventActionExportedInviteRevoke):
             revoked_invite_link = types.ChatInviteLink._parse(client, action.invite, users)
-            action = ChatEventAction.INVITE_LINK_REVOKED
+            action = enums.ChatEventAction.INVITE_LINK_REVOKED
 
         elif isinstance(action, raw.types.ChannelAdminLogEventActionExportedInviteDelete):
             deleted_invite_link = types.ChatInviteLink._parse(client, action.invite, users)
-            action = ChatEventAction.INVITE_LINK_DELETED.value
+            action = enums.ChatEventAction.INVITE_LINK_DELETED
 
         else:
-            action = f"{ChatEventAction.UNKNOWN.value}-{action.QUALNAME}"
+            action = f"{enums.ChatEventAction.UNKNOWN}-{action.QUALNAME}"
 
         return ChatEvent(
             id=event.id,
-            date=event.date,
+            date=utils.timestamp_to_datetime(event.date),
             user=user,
             action=action,
             old_description=old_description,
@@ -574,8 +462,8 @@ async def _parse(
 
             invited_member=invited_member,
 
-            old_admin_rights=old_admin_rights,
-            new_admin_rights=new_admin_rights,
+            old_administrator_privileges=old_administrator_privileges,
+            new_administrator_privileges=new_administrator_privileges,
 
             old_member_permissions=old_member_permissions,
             new_member_permissions=new_member_permissions,
diff --git a/pyrogram/types/user_and_chats/chat_event_filter.py b/pyrogram/types/user_and_chats/chat_event_filter.py
index edd003eba8..92298ea3be 100644
--- a/pyrogram/types/user_and_chats/chat_event_filter.py
+++ b/pyrogram/types/user_and_chats/chat_event_filter.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
@@ -28,7 +28,7 @@ class ChatEventFilter(Object):
             True, if member restricted/unrestricted/banned/unbanned events should be returned.
             Defaults to False.
 
-        admin_rights (``bool``, *optional*):
+        new_privileges (``bool``, *optional*):
             True, if member promotion/demotion events should be returned.
             Defaults to False.
 
@@ -66,15 +66,15 @@ class ChatEventFilter(Object):
             True, if members leaving events should be returned.
             Defaults to False.
 
-        voice_chats (``bool``, *optional*):
-            True, if voice chats events should be returned.
+        video_chats (``bool``, *optional*):
+            True, if video chats events should be returned.
             Defaults to False.
     """
 
     def __init__(
         self, *,
         new_restrictions: bool = False,
-        admin_rights: bool = False,
+        new_privileges: bool = False,
         new_members: bool = False,
         chat_info: bool = False,
         chat_settings: bool = False,
@@ -83,12 +83,12 @@ def __init__(
         edited_messages: bool = False,
         pinned_messages: bool = False,
         leaving_members: bool = False,
-        voice_chats: bool = False
+        video_chats: bool = False
     ):
         super().__init__()
 
         self.new_restrictions = new_restrictions
-        self.admin_rights = admin_rights
+        self.new_privileges = new_privileges
         self.new_members = new_members
         self.chat_info = chat_info
         self.chat_settings = chat_settings
@@ -97,7 +97,7 @@ def __init__(
         self.edited_messages = edited_messages
         self.pinned_messages = pinned_messages
         self.leaving_members = leaving_members
-        self.voice_chats = voice_chats
+        self.video_chats = video_chats
 
     def write(self) -> "raw.base.ChannelAdminLogEventsFilter":
         join = False
@@ -123,7 +123,7 @@ def write(self) -> "raw.base.ChannelAdminLogEventsFilter":
             kick = True
             unkick = True
 
-        if self.admin_rights:
+        if self.new_privileges:
             promote = True
             demote = True
 
@@ -152,7 +152,7 @@ def write(self) -> "raw.base.ChannelAdminLogEventsFilter":
         if self.leaving_members:
             leave = True
 
-        if self.voice_chats:
+        if self.video_chats:
             group_call = True
 
         return raw.types.ChannelAdminLogEventsFilter(
diff --git a/pyrogram/types/user_and_chats/chat_invite_link.py b/pyrogram/types/user_and_chats/chat_invite_link.py
index f4803b6a73..59f6315ba5 100644
--- a/pyrogram/types/user_and_chats/chat_invite_link.py
+++ b/pyrogram/types/user_and_chats/chat_invite_link.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
@@ -16,10 +16,12 @@
 #  You should have received a copy of the GNU Lesser General Public License
 #  along with Pyrogram.  If not, see .
 
+from datetime import datetime
 from typing import Dict
+from typing import Optional
 
 import pyrogram
-from pyrogram import raw
+from pyrogram import raw, utils
 from pyrogram import types
 from ..object import Object
 
@@ -32,8 +34,8 @@ class ChatInviteLink(Object):
             The invite link. If the link was created by another chat administrator, then the second part of the
             link will be replaced with "...".
 
-        date (``int``):
-            The date in Unix timestamp when the link was created.
+        date (:py:obj:`~datetime.datetime`):
+            The date when the link was created.
 
         is_primary (``bool``):
             True, if the link is primary.
@@ -44,8 +46,17 @@ class ChatInviteLink(Object):
         creator (:obj:`~pyrogram.types.User`, *optional*):
             Creator of the link.
 
-        expire_date (``int``, *optional*):
-            Point in time (Unix timestamp) when the link will expire or has been expired.
+        name (``str``, *optional*):
+            Invite link name
+
+        creates_join_request (``bool``, *optional*):
+            True, if users joining the chat via the link need to be approved by chat administrators.
+
+        start_date (:py:obj:`~datetime.datetime`, *optional*):
+            Point in time when the link has been edited.
+
+        expire_date (:py:obj:`~datetime.datetime`, *optional*):
+            Point in time when the link will expire or has been expired.
 
         member_limit (``int``, *optional*):
             Maximum number of users that can be members of the chat simultaneously after joining the chat via this
@@ -53,38 +64,50 @@ class ChatInviteLink(Object):
 
         member_count (``int``, *optional*):
             Number of users that joined via this link and are currently member of the chat.
+
+        pending_join_request_count (``int``, *optional*):
+            Number of pending join requests created using this link
     """
 
     def __init__(
         self, *,
         invite_link: str,
-        creator: "types.User",
-        date: int,
+        date: datetime,
         is_primary: bool = None,
         is_revoked: bool = None,
-        start_date: int = None,
-        expire_date: int = None,
+        creator: "types.User" = None,
+        name: str = None,
+        creates_join_request: bool = None,
+        start_date: datetime = None,
+        expire_date: datetime = None,
         member_limit: int = None,
-        member_count: int = None
+        member_count: int = None,
+        pending_join_request_count: int = None
     ):
         super().__init__()
 
         self.invite_link = invite_link
-        self.creator = creator
         self.date = date
         self.is_primary = is_primary
         self.is_revoked = is_revoked
+        self.creator = creator
+        self.name = name
+        self.creates_join_request = creates_join_request
         self.start_date = start_date
         self.expire_date = expire_date
         self.member_limit = member_limit
         self.member_count = member_count
+        self.pending_join_request_count = pending_join_request_count
 
     @staticmethod
     def _parse(
         client: "pyrogram.Client",
-        invite: "raw.types.ChatInviteExported",
+        invite: "raw.base.ExportedChatInvite",
         users: Dict[int, "raw.types.User"] = None
-    ) -> "ChatInviteLink":
+    ) -> Optional["ChatInviteLink"]:
+        if not isinstance(invite, raw.types.ChatInviteExported):
+            return None
+
         creator = (
             types.User._parse(client, users[invite.admin_id])
             if users is not None
@@ -93,11 +116,15 @@ def _parse(
 
         return ChatInviteLink(
             invite_link=invite.link,
-            creator=creator,
-            date=invite.date,
+            date=utils.timestamp_to_datetime(invite.date),
             is_primary=invite.permanent,
             is_revoked=invite.revoked,
-            expire_date=invite.expire_date,
+            creator=creator,
+            name=invite.title,
+            creates_join_request=invite.request_needed,
+            start_date=utils.timestamp_to_datetime(invite.start_date),
+            expire_date=utils.timestamp_to_datetime(invite.expire_date),
             member_limit=invite.usage_limit,
-            member_count=invite.usage
+            member_count=invite.usage,
+            pending_join_request_count=invite.requested
         )
diff --git a/pyrogram/types/user_and_chats/chat_join_request.py b/pyrogram/types/user_and_chats/chat_join_request.py
new file mode 100644
index 0000000000..b810640b76
--- /dev/null
+++ b/pyrogram/types/user_and_chats/chat_join_request.py
@@ -0,0 +1,139 @@
+#  Pyrogram - Telegram MTProto API Client Library for Python
+#  Copyright (C) 2017-present Dan 
+#
+#  This file is part of Pyrogram.
+#
+#  Pyrogram is free software: you can redistribute it and/or modify
+#  it under the terms of the GNU Lesser General Public License as published
+#  by the Free Software Foundation, either version 3 of the License, or
+#  (at your option) any later version.
+#
+#  Pyrogram is distributed in the hope that it will be useful,
+#  but WITHOUT ANY WARRANTY; without even the implied warranty of
+#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#  GNU Lesser General Public License for more details.
+#
+#  You should have received a copy of the GNU Lesser General Public License
+#  along with Pyrogram.  If not, see .
+
+from datetime import datetime
+from typing import Dict
+
+import pyrogram
+from pyrogram import raw, utils
+from pyrogram import types
+from ..object import Object
+from ..update import Update
+
+
+class ChatJoinRequest(Object, Update):
+    """Represents a join request sent to a chat.
+
+    Parameters:
+        chat (:obj:`~pyrogram.types.Chat`):
+            Chat to which the request was sent.
+
+        from_user (:obj:`~pyrogram.types.User`):
+            User that sent the join request.
+
+        date (:py:obj:`~datetime.datetime`):
+            Date the request was sent.
+
+        bio (``str``, *optional*):
+            Bio of the user.
+
+        invite_link (:obj:`~pyrogram.types.ChatInviteLink`, *optional*):
+            Chat invite link that was used by the user to send the join request.
+    """
+
+    def __init__(
+        self,
+        *,
+        client: "pyrogram.Client" = None,
+        chat: "types.Chat",
+        from_user: "types.User",
+        date: datetime,
+        bio: str = None,
+        invite_link: "types.ChatInviteLink" = None
+    ):
+        super().__init__(client)
+
+        self.chat = chat
+        self.from_user = from_user
+        self.date = date
+        self.bio = bio
+        self.invite_link = invite_link
+
+    @staticmethod
+    def _parse(
+        client: "pyrogram.Client",
+        update: "raw.types.UpdateBotChatInviteRequester",
+        users: Dict[int, "raw.types.User"],
+        chats: Dict[int, "raw.types.Chat"]
+    ) -> "ChatJoinRequest":
+        chat_id = utils.get_raw_peer_id(update.peer)
+
+        return ChatJoinRequest(
+            chat=types.Chat._parse_chat(client, chats[chat_id]),
+            from_user=types.User._parse(client, users[update.user_id]),
+            date=utils.timestamp_to_datetime(update.date),
+            bio=update.about,
+            invite_link=types.ChatInviteLink._parse(client, update.invite, users),
+            client=client
+        )
+
+    async def approve(self) -> bool:
+        """Bound method *approve* of :obj:`~pyrogram.types.ChatJoinRequest`.
+        
+        Use as a shortcut for:
+        
+        .. code-block:: python
+
+            await client.approve_chat_join_request(
+                chat_id=request.chat.id,
+                user_id=request.from_user.id
+            )
+            
+        Example:
+            .. code-block:: python
+
+                await request.approve()
+                
+        Returns:
+            ``bool``: True on success.
+        
+        Raises:
+            RPCError: In case of a Telegram RPC error.
+        """
+        return await self._client.approve_chat_join_request(
+            chat_id=self.chat.id,
+            user_id=self.from_user.id
+        )
+
+    async def decline(self) -> bool:
+        """Bound method *decline* of :obj:`~pyrogram.types.ChatJoinRequest`.
+        
+        Use as a shortcut for:
+        
+        .. code-block:: python
+
+            await client.decline_chat_join_request(
+                chat_id=request.chat.id,
+                user_id=request.from_user.id
+            )
+            
+        Example:
+            .. code-block:: python
+
+                await request.decline()
+                
+        Returns:
+            ``bool``: True on success.
+        
+        Raises:
+            RPCError: In case of a Telegram RPC error.
+        """
+        return await self._client.decline_chat_join_request(
+            chat_id=self.chat.id,
+            user_id=self.from_user.id
+        )
diff --git a/pyrogram/types/user_and_chats/chat_joiner.py b/pyrogram/types/user_and_chats/chat_joiner.py
new file mode 100644
index 0000000000..024f88ea26
--- /dev/null
+++ b/pyrogram/types/user_and_chats/chat_joiner.py
@@ -0,0 +1,82 @@
+#  Pyrogram - Telegram MTProto API Client Library for Python
+#  Copyright (C) 2017-present Dan 
+#
+#  This file is part of Pyrogram.
+#
+#  Pyrogram is free software: you can redistribute it and/or modify
+#  it under the terms of the GNU Lesser General Public License as published
+#  by the Free Software Foundation, either version 3 of the License, or
+#  (at your option) any later version.
+#
+#  Pyrogram is distributed in the hope that it will be useful,
+#  but WITHOUT ANY WARRANTY; without even the implied warranty of
+#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#  GNU Lesser General Public License for more details.
+#
+#  You should have received a copy of the GNU Lesser General Public License
+#  along with Pyrogram.  If not, see .
+
+from datetime import datetime
+from typing import Dict
+
+import pyrogram
+from pyrogram import raw, types, utils
+from ..object import Object
+
+
+class ChatJoiner(Object):
+    """Contains information about a joiner member of a chat.
+
+    Parameters:
+        user (:obj:`~pyrogram.types.User`):
+            Information about the user.
+
+        date (:py:obj:`~datetime.datetime`):
+            Date when the user joined.
+
+        bio (``str``, *optional*):
+            Bio of the user.
+
+        pending (``bool``, *optional*):
+            True in case the chat joiner has a pending request.
+
+        approved_by (:obj:`~pyrogram.types.User`, *optional*):
+            Administrator who approved this chat joiner.
+    """
+
+    def __init__(
+        self,
+        *,
+        client: "pyrogram.Client",
+        user: "types.User",
+        date: datetime = None,
+        bio: str = None,
+        pending: bool = None,
+        approved_by: "types.User" = None,
+    ):
+        super().__init__(client)
+
+        self.user = user
+        self.date = date
+        self.bio = bio
+        self.pending = pending
+        self.approved_by = approved_by
+
+    @staticmethod
+    def _parse(
+        client: "pyrogram.Client",
+        joiner: "raw.base.ChatInviteImporter",
+        users: Dict[int, "raw.base.User"],
+    ) -> "ChatJoiner":
+        return ChatJoiner(
+            user=types.User._parse(client, users[joiner.user_id]),
+            date=utils.timestamp_to_datetime(joiner.date),
+            pending=joiner.requested,
+            bio=joiner.about,
+            approved_by=(
+                types.User._parse(client, users[joiner.approved_by])
+                if joiner.approved_by
+                else None
+            ),
+            client=client
+        )
diff --git a/pyrogram/types/user_and_chats/chat_member.py b/pyrogram/types/user_and_chats/chat_member.py
index d33bcfc5d0..4459a8d1c0 100644
--- a/pyrogram/types/user_and_chats/chat_member.py
+++ b/pyrogram/types/user_and_chats/chat_member.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
@@ -16,9 +16,11 @@
 #  You should have received a copy of the GNU Lesser General Public License
 #  along with Pyrogram.  If not, see .
 
+from datetime import datetime
+from typing import Union, Dict
+
 import pyrogram
-from pyrogram import raw
-from pyrogram import types
+from pyrogram import raw, types, utils, enums
 from ..object import Object
 
 
@@ -26,24 +28,26 @@ class ChatMember(Object):
     """Contains information about one member of a chat.
 
     Parameters:
-        user (:obj:`~pyrogram.types.User`):
+        status (:obj:`~pyrogram.enums.ChatMemberStatus`):
+            The member's status in the chat.
+
+        user (:obj:`~pyrogram.types.User`, *optional*):
             Information about the user.
 
-        status (``str``):
-            The member's status in the chat.
-            Can be "creator", "administrator", "member", "restricted", "left" or "kicked".
+        chat (:obj:`~pyrogram.types.Chat`, *optional*):
+            Information about the chat (useful in case of banned channel senders).
 
-        title (``str``, *optional*):
+        joined_date (:py:obj:`~datetime.datetime`, *optional*):
+            Date when the user joined.
+            Not available for the owner.
+
+        custom_title (``str``, *optional*):
             A custom title that will be shown to all members instead of "Owner" or "Admin".
             Creator (owner) and administrators only. Can be None in case there's no custom title set.
 
-        until_date (``int``, *optional*):
-            Restricted and kicked only.
-            Date when restrictions will be lifted for this user; unix time.
-
-        joined_date (``int``, *optional*):
-            Date when the user joined, unix time.
-            Not available for creator.
+        until_date (:py:obj:`~datetime.datetime`, *optional*):
+            Restricted and banned only.
+            Date when restrictions will be lifted for this user.
 
         invited_by (:obj:`~pyrogram.types.User`, *optional*):
             Administrators and self member only. Information about the user who invited this member.
@@ -53,273 +57,171 @@ class ChatMember(Object):
             Administrators only. Information about the user who promoted this member as administrator.
 
         restricted_by (:obj:`~pyrogram.types.User`, *optional*):
-            Restricted and kicked only. Information about the user who restricted or kicked this member.
+            Restricted and banned only. Information about the user who restricted or banned this member.
 
         is_member (``bool``, *optional*):
             Restricted only. True, if the user is a member of the chat at the moment of the request.
 
-        is_anonymous (``bool``, *optional*):
-            True, if the user's presence in the chat is hidden.
-            Owner and administrators only.
-
         can_be_edited (``bool``, *optional*):
-            Administrators only.
-            True, if you are allowed to edit administrator privileges of the user.
-
-        can_manage_chat (``bool``, *optional*):
-            Administrators only.
-            True, if the administrator can access the chat event log, chat statistics, message statistics in channels,
-            see channel members, see anonymous administrators in supergroups and ignore slow mode.
-            Implied by any other administrator privilege.
-
-        can_post_messages (``bool``, *optional*):
-            Administrators only. Channels only.
-            True, if the administrator can post messages in the channel.
-
-        can_edit_messages (``bool``, *optional*):
-            Administrators only. Channels only.
-            True, if the administrator can edit messages of other users and can pin messages.
-
-        can_delete_messages (``bool``, *optional*):
-            Administrators only.
-            True, if the administrator can delete messages of other users.
-
-        can_restrict_members (``bool``, *optional*):
-            Administrators only.
-            True, if the administrator can restrict, ban or unban chat members.
-
-        can_promote_members (``bool``, *optional*):
-            Administrators only.
-            True, if the administrator can add new administrators with a subset of his own privileges or demote
-            administrators that he has promoted, directly or indirectly (promoted by administrators that were appointed
-            by the user).
-
-        can_change_info (``bool``, *optional*):
-            Administrators and restricted only.
-            True, if the user is allowed to change the chat title, photo and other settings.
-
-        can_invite_users (``bool``, *optional*):
-            Administrators and restricted only.
-            True, if the user is allowed to invite new users to the chat.
+            True, if the you are allowed to edit administrator privileges of the user.
 
-        can_pin_messages (``bool``, *optional*):
-            Administrators and restricted only. Groups and supergroups only.
-            True, if the user is allowed to pin messages.
+        permissions (:obj:`~pyrogram.types.ChatPermissions`, *optional*):
+            Restricted only. Restricted actions that a non-administrator user is allowed to take.
 
-        can_manage_voice_chats (``bool``, *optional*):
-            Administrators only. Groups and supergroups only.
-            True, if the administrator can manage voice chats (also called group calls).
-
-        can_send_messages (``bool``, *optional*):
-            Restricted only.
-            True, if the user is allowed to send text messages, contacts, locations and venues.
-
-        can_send_media_messages (``bool``, *optional*):
-            Restricted only.
-            True, if the user is allowed to send audios, documents, photos, videos, video notes and voice notes.
-
-        can_send_stickers (``bool``, *optional*):
-            True, if the user is allowed to send stickers, implies can_send_media_messages.
-
-        can_send_animations (``bool``, *optional*):
-            True, if the user is allowed to send animations (GIFs), implies can_send_media_messages.
-
-        can_send_games (``bool``, *optional*):
-            True, if the user is allowed to send games, implies can_send_media_messages.
-
-        can_use_inline_bots (``bool``, *optional*):
-            True, if the user is allowed to use inline bots, implies can_send_media_messages.
-
-        can_add_web_page_previews (``bool``, *optional*):
-            Restricted only.
-            True, if the user is allowed to add web page previews to their messages.
-
-        can_send_polls (``bool``, *optional*):
-            Restricted only.
-            True, if the user is allowed to send polls.
+        privileges (:obj:`~pyrogram.types.ChatPrivileges`, *optional*):
+            Administrators only. Privileged actions that an administrator is able to take.
     """
 
     def __init__(
         self,
         *,
         client: "pyrogram.Client" = None,
-        user: "types.User",
-        status: str,
-        title: str = None,
-        until_date: int = None,
-        joined_date: int = None,
+        status: "enums.ChatMemberStatus",
+        user: "types.User" = None,
+        chat: "types.Chat" = None,
+        custom_title: str = None,
+        until_date: datetime = None,
+        joined_date: datetime = None,
         invited_by: "types.User" = None,
         promoted_by: "types.User" = None,
         restricted_by: "types.User" = None,
         is_member: bool = None,
-        is_anonymous: bool = None,
-
-        # Admin permissions
         can_be_edited: bool = None,
-        can_manage_chat: bool = None,
-        can_post_messages: bool = None,  # Channels only
-        can_edit_messages: bool = None,  # Channels only
-        can_delete_messages: bool = None,
-        can_restrict_members: bool = None,
-        can_promote_members: bool = None,
-        can_change_info: bool = None,
-        can_invite_users: bool = None,
-        can_pin_messages: bool = None,  # Groups and supergroups only
-        can_manage_voice_chats: bool = None,
-
-        # Restricted user permissions
-        can_send_messages: bool = None,  # Text, contacts, locations and venues
-        can_send_media_messages: bool = None,  # Audios, documents, photos, videos, video notes and voice notes
-        can_send_stickers: bool = None,
-        can_send_animations: bool = None,
-        can_send_games: bool = None,
-        can_use_inline_bots: bool = None,
-        can_add_web_page_previews: bool = None,
-        can_send_polls: bool = None
+        permissions: "types.ChatPermissions" = None,
+        privileges: "types.ChatPrivileges" = None
     ):
         super().__init__(client)
 
-        self.user = user
         self.status = status
-        self.title = title
+        self.user = user
+        self.chat = chat
+        self.custom_title = custom_title
         self.until_date = until_date
         self.joined_date = joined_date
         self.invited_by = invited_by
         self.promoted_by = promoted_by
         self.restricted_by = restricted_by
         self.is_member = is_member
-        self.is_anonymous = is_anonymous
-
         self.can_be_edited = can_be_edited
-        self.can_manage_chat = can_manage_chat
-        self.can_post_messages = can_post_messages
-        self.can_edit_messages = can_edit_messages
-        self.can_delete_messages = can_delete_messages
-        self.can_restrict_members = can_restrict_members
-        self.can_promote_members = can_promote_members
-        self.can_change_info = can_change_info
-        self.can_invite_users = can_invite_users
-        self.can_pin_messages = can_pin_messages
-        self.can_manage_voice_chats = can_manage_voice_chats
-
-        self.can_send_messages = can_send_messages
-        self.can_send_media_messages = can_send_media_messages
-        self.can_send_stickers = can_send_stickers
-        self.can_send_animations = can_send_animations
-        self.can_send_games = can_send_games
-        self.can_use_inline_bots = can_use_inline_bots
-        self.can_add_web_page_previews = can_add_web_page_previews
-        self.can_send_polls = can_send_polls
+        self.permissions = permissions
+        self.privileges = privileges
 
     @staticmethod
-    def _parse(client, member, users, chats) -> "ChatMember":
-        if not isinstance(member, (raw.types.ChannelParticipantBanned, raw.types.ChannelParticipantLeft)):
-            user = types.User._parse(client, users[member.user_id])
-        else:
-            if isinstance(member.peer, raw.types.PeerUser):
-                user = types.User._parse(client, users[member.peer.user_id])
-            else:
-                user = None
-
-        invited_by = (
-            types.User._parse(client, users[member.inviter_id])
-            if getattr(member, "inviter_id", None) else None
-        )
-
-        if isinstance(member, (raw.types.ChannelParticipant,
-                               raw.types.ChannelParticipantSelf,
-                               raw.types.ChatParticipant)):
+    def _parse(
+        client: "pyrogram.Client",
+        member: Union["raw.base.ChatParticipant", "raw.base.ChannelParticipant"],
+        users: Dict[int, "raw.base.User"],
+        chats: Dict[int, "raw.base.Chat"]
+    ) -> "ChatMember":
+        # Chat participants
+        if isinstance(member, raw.types.ChatParticipant):
             return ChatMember(
-                user=user,
-                status="member",
-                joined_date=member.date,
-                invited_by=invited_by,
+                status=enums.ChatMemberStatus.MEMBER,
+                user=types.User._parse(client, users[member.user_id]),
+                joined_date=utils.timestamp_to_datetime(member.date),
+                invited_by=types.User._parse(client, users[member.inviter_id]),
                 client=client
             )
-
-        if isinstance(member, raw.types.ChatParticipantCreator):
+        elif isinstance(member, raw.types.ChatParticipantAdmin):
             return ChatMember(
-                user=user,
-                status="creator",
+                status=enums.ChatMemberStatus.ADMINISTRATOR,
+                user=types.User._parse(client, users[member.user_id]),
+                joined_date=utils.timestamp_to_datetime(member.date),
+                invited_by=types.User._parse(client, users[member.inviter_id]),
                 client=client
             )
-
-        if isinstance(member, raw.types.ChatParticipantAdmin):
+        elif isinstance(member, raw.types.ChatParticipantCreator):
             return ChatMember(
-                user=user,
-                status="administrator",
-                joined_date=member.date,
-                invited_by=invited_by,
+                status=enums.ChatMemberStatus.OWNER,
+                user=types.User._parse(client, users[member.user_id]),
                 client=client
             )
 
-        if isinstance(member, raw.types.ChannelParticipantCreator):
-            permissions = member.admin_rights
-
+        # Channel participants
+        if isinstance(member, raw.types.ChannelParticipant):
             return ChatMember(
-                user=user,
-                status="creator",
-                title=member.rank,
-                invited_by=invited_by,
-                can_change_info=permissions.change_info,
-                can_manage_chat=permissions.other,
-                can_post_messages=permissions.post_messages,
-                can_edit_messages=permissions.edit_messages,
-                can_delete_messages=permissions.delete_messages,
-                can_restrict_members=permissions.ban_users,
-                can_invite_users=permissions.invite_users,
-                can_pin_messages=permissions.pin_messages,
-                can_promote_members=permissions.add_admins,
-                can_manage_voice_chats=permissions.manage_call,
-                is_anonymous=permissions.anonymous,
+                status=enums.ChatMemberStatus.MEMBER,
+                user=types.User._parse(client, users[member.user_id]),
+                joined_date=utils.timestamp_to_datetime(member.date),
                 client=client
             )
-
-        if isinstance(member, raw.types.ChannelParticipantAdmin):
-            permissions = member.admin_rights
-
+        elif isinstance(member, raw.types.ChannelParticipantAdmin):
             return ChatMember(
-                user=user,
-                status="administrator",
-                title=member.rank,
-                joined_date=member.date,
-                invited_by=invited_by,
+                status=enums.ChatMemberStatus.ADMINISTRATOR,
+                user=types.User._parse(client, users[member.user_id]),
+                joined_date=utils.timestamp_to_datetime(member.date),
                 promoted_by=types.User._parse(client, users[member.promoted_by]),
+                invited_by=(
+                    types.User._parse(client, users[member.inviter_id])
+                    if member.inviter_id else None
+                ),
+                custom_title=member.rank,
                 can_be_edited=member.can_edit,
-                can_manage_chat=permissions.other,
-                can_change_info=permissions.change_info,
-                can_post_messages=permissions.post_messages,
-                can_edit_messages=permissions.edit_messages,
-                can_delete_messages=permissions.delete_messages,
-                can_restrict_members=permissions.ban_users,
-                can_invite_users=permissions.invite_users,
-                can_pin_messages=permissions.pin_messages,
-                can_promote_members=permissions.add_admins,
-                can_manage_voice_chats=permissions.manage_call,
-                is_anonymous=permissions.anonymous,
+                privileges=types.ChatPrivileges._parse(member.admin_rights),
                 client=client
             )
+        elif isinstance(member, raw.types.ChannelParticipantBanned):
+            peer = member.peer
+            peer_id = utils.get_raw_peer_id(peer)
 
-        if isinstance(member, raw.types.ChannelParticipantBanned):
-            denied_permissions = member.banned_rights
+            user = (
+                types.User._parse(client, users[peer_id])
+                if isinstance(peer, raw.types.PeerUser) else None
+            )
+
+            chat = (
+                types.Chat._parse_chat(client, chats[peer_id])
+                if not isinstance(peer, raw.types.PeerUser) else None
+            )
 
             return ChatMember(
+                status=(
+                    enums.ChatMemberStatus.BANNED
+                    if member.banned_rights.view_messages
+                    else enums.ChatMemberStatus.RESTRICTED
+                ),
                 user=user,
-                status="kicked" if member.banned_rights.view_messages else "restricted",
-                until_date=denied_permissions.until_date,
-                joined_date=member.date,
+                chat=chat,
+                until_date=utils.timestamp_to_datetime(member.banned_rights.until_date),
+                joined_date=utils.timestamp_to_datetime(member.date),
                 is_member=not member.left,
                 restricted_by=types.User._parse(client, users[member.kicked_by]),
-                can_send_messages=not denied_permissions.send_messages,
-                can_send_media_messages=not denied_permissions.send_media,
-                can_send_stickers=not denied_permissions.send_stickers,
-                can_send_animations=not denied_permissions.send_gifs,
-                can_send_games=not denied_permissions.send_games,
-                can_use_inline_bots=not denied_permissions.send_inline,
-                can_add_web_page_previews=not denied_permissions.embed_links,
-                can_send_polls=not denied_permissions.send_polls,
-                can_change_info=not denied_permissions.change_info,
-                can_invite_users=not denied_permissions.invite_users,
-                can_pin_messages=not denied_permissions.pin_messages,
+                permissions=types.ChatPermissions._parse(member.banned_rights),
+                client=client
+            )
+        elif isinstance(member, raw.types.ChannelParticipantCreator):
+            return ChatMember(
+                status=enums.ChatMemberStatus.OWNER,
+                user=types.User._parse(client, users[member.user_id]),
+                custom_title=member.rank,
+                privileges=types.ChatPrivileges._parse(member.admin_rights),
+                client=client
+            )
+        elif isinstance(member, raw.types.ChannelParticipantLeft):
+            peer = member.peer
+            peer_id = utils.get_raw_peer_id(peer)
+
+            user = (
+                types.User._parse(client, users[peer_id])
+                if isinstance(peer, raw.types.PeerUser) else None
+            )
+
+            chat = (
+                types.Chat._parse_chat(client, chats[peer_id])
+                if not isinstance(peer, raw.types.PeerUser) else None
+            )
+
+            return ChatMember(
+                status=enums.ChatMemberStatus.LEFT,
+                user=user,
+                chat=chat,
+                client=client
+            )
+        elif isinstance(member, raw.types.ChannelParticipantSelf):
+            return ChatMember(
+                status=enums.ChatMemberStatus.MEMBER,
+                user=types.User._parse(client, users[member.user_id]),
+                joined_date=utils.timestamp_to_datetime(member.date),
+                invited_by=types.User._parse(client, users[member.inviter_id]),
                 client=client
             )
diff --git a/pyrogram/types/user_and_chats/chat_member_updated.py b/pyrogram/types/user_and_chats/chat_member_updated.py
index a8d7abcafb..f8b6638db9 100644
--- a/pyrogram/types/user_and_chats/chat_member_updated.py
+++ b/pyrogram/types/user_and_chats/chat_member_updated.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
@@ -16,10 +16,11 @@
 #  You should have received a copy of the GNU Lesser General Public License
 #  along with Pyrogram.  If not, see .
 
+from datetime import datetime
 from typing import Dict, Union
 
 import pyrogram
-from pyrogram import raw
+from pyrogram import raw, utils
 from pyrogram import types
 from ..object import Object
 from ..update import Update
@@ -35,8 +36,8 @@ class ChatMemberUpdated(Object, Update):
         from_user (:obj:`~pyrogram.types.User`):
             Performer of the action, which resulted in the change.
 
-        date (``int``):
-            Date the change was done in Unix time.
+        date (:py:obj:`~datetime.datetime`):
+            Date the change was done.
 
         old_chat_member (:obj:`~pyrogram.types.ChatMember`, *optional*):
             Previous information about the chat member.
@@ -54,7 +55,7 @@ def __init__(
         client: "pyrogram.Client" = None,
         chat: "types.Chat",
         from_user: "types.User",
-        date: int,
+        date: datetime,
         old_chat_member: "types.ChatMember",
         new_chat_member: "types.ChatMember",
         invite_link: "types.ChatInviteLink" = None,
@@ -93,7 +94,7 @@ def _parse(
         return ChatMemberUpdated(
             chat=types.Chat._parse_chat(client, chats[chat_id]),
             from_user=types.User._parse(client, users[update.actor_id]),
-            date=update.date,
+            date=utils.timestamp_to_datetime(update.date),
             old_chat_member=old_chat_member,
             new_chat_member=new_chat_member,
             invite_link=invite_link,
diff --git a/pyrogram/types/user_and_chats/chat_permissions.py b/pyrogram/types/user_and_chats/chat_permissions.py
index a5bc3011b1..d920394c67 100644
--- a/pyrogram/types/user_and_chats/chat_permissions.py
+++ b/pyrogram/types/user_and_chats/chat_permissions.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
@@ -79,7 +79,7 @@ def __init__(
         self.can_pin_messages = can_pin_messages
 
     @staticmethod
-    def _parse(denied_permissions: "raw.types.ChatBannedRights") -> "ChatPermissions":
+    def _parse(denied_permissions: "raw.base.ChatBannedRights") -> "ChatPermissions":
         if isinstance(denied_permissions, raw.types.ChatBannedRights):
             return ChatPermissions(
                 can_send_messages=not denied_permissions.send_messages,
diff --git a/pyrogram/types/user_and_chats/chat_photo.py b/pyrogram/types/user_and_chats/chat_photo.py
index 959d1732e0..b3aba61dd0 100644
--- a/pyrogram/types/user_and_chats/chat_photo.py
+++ b/pyrogram/types/user_and_chats/chat_photo.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
diff --git a/pyrogram/types/user_and_chats/chat_preview.py b/pyrogram/types/user_and_chats/chat_preview.py
index f4a8540fda..e251c86581 100644
--- a/pyrogram/types/user_and_chats/chat_preview.py
+++ b/pyrogram/types/user_and_chats/chat_preview.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
diff --git a/pyrogram/types/user_and_chats/chat_privileges.py b/pyrogram/types/user_and_chats/chat_privileges.py
new file mode 100644
index 0000000000..09bb341dfc
--- /dev/null
+++ b/pyrogram/types/user_and_chats/chat_privileges.py
@@ -0,0 +1,112 @@
+#  Pyrogram - Telegram MTProto API Client Library for Python
+#  Copyright (C) 2017-present Dan 
+#
+#  This file is part of Pyrogram.
+#
+#  Pyrogram is free software: you can redistribute it and/or modify
+#  it under the terms of the GNU Lesser General Public License as published
+#  by the Free Software Foundation, either version 3 of the License, or
+#  (at your option) any later version.
+#
+#  Pyrogram is distributed in the hope that it will be useful,
+#  but WITHOUT ANY WARRANTY; without even the implied warranty of
+#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#  GNU Lesser General Public License for more details.
+#
+#  You should have received a copy of the GNU Lesser General Public License
+#  along with Pyrogram.  If not, see .
+
+from pyrogram import raw
+from ..object import Object
+
+
+class ChatPrivileges(Object):
+    """Describes privileged actions an administrator is able to take in a chat.
+
+    Parameters:
+        can_manage_chat (``bool``, *optional*):
+            True, if the administrator can access the chat event log, chat statistics, message statistics in channels,
+            see channel members, see anonymous administrators in supergroups and ignore slow mode.
+            Implied by any other administrator privilege.
+
+        can_delete_messages (``bool``, *optional*):
+            True, if the administrator can delete messages of other users.
+
+        can_manage_video_chats (``bool``, *optional*):
+            Groups and supergroups only.
+            True, if the administrator can manage video chats (also called group calls).
+
+        can_restrict_members (``bool``, *optional*):
+            True, if the administrator can restrict, ban or unban chat members.
+
+        can_promote_members (``bool``, *optional*):
+            True, if the administrator can add new administrators with a subset of his own privileges or demote
+            administrators that he has promoted, directly or indirectly (promoted by administrators that were appointed
+            by the user).
+
+        can_change_info (``bool``, *optional*):
+            True, if the user is allowed to change the chat title, photo and other settings.
+
+        can_post_messages (``bool``, *optional*):
+            Channels only.
+            True, if the administrator can post messages in the channel.
+
+        can_edit_messages (``bool``, *optional*):
+            Channels only.
+            True, if the administrator can edit messages of other users and can pin messages.
+
+        can_invite_users (``bool``, *optional*):
+            True, if the user is allowed to invite new users to the chat.
+
+        can_pin_messages (``bool``, *optional*):
+            Groups and supergroups only.
+            True, if the user is allowed to pin messages.
+
+        is_anonymous (``bool``, *optional*):
+            True, if the user's presence in the chat is hidden.
+    """
+
+    def __init__(
+        self,
+        *,
+        can_manage_chat: bool = True,
+        can_delete_messages: bool = False,
+        can_manage_video_chats: bool = False,  # Groups and supergroups only
+        can_restrict_members: bool = False,
+        can_promote_members: bool = False,
+        can_change_info: bool = False,
+        can_post_messages: bool = False,  # Channels only
+        can_edit_messages: bool = False,  # Channels only
+        can_invite_users: bool = False,
+        can_pin_messages: bool = False,  # Groups and supergroups only
+        is_anonymous: bool = False
+    ):
+        super().__init__(None)
+
+        self.can_manage_chat: bool = can_manage_chat
+        self.can_delete_messages: bool = can_delete_messages
+        self.can_manage_video_chats: bool = can_manage_video_chats
+        self.can_restrict_members: bool = can_restrict_members
+        self.can_promote_members: bool = can_promote_members
+        self.can_change_info: bool = can_change_info
+        self.can_post_messages: bool = can_post_messages
+        self.can_edit_messages: bool = can_edit_messages
+        self.can_invite_users: bool = can_invite_users
+        self.can_pin_messages: bool = can_pin_messages
+        self.is_anonymous: bool = is_anonymous
+
+    @staticmethod
+    def _parse(admin_rights: "raw.base.ChatAdminRights") -> "ChatPrivileges":
+        return ChatPrivileges(
+            can_manage_chat=admin_rights.other,
+            can_delete_messages=admin_rights.delete_messages,
+            can_manage_video_chats=admin_rights.manage_call,
+            can_restrict_members=admin_rights.ban_users,
+            can_promote_members=admin_rights.add_admins,
+            can_change_info=admin_rights.change_info,
+            can_post_messages=admin_rights.post_messages,
+            can_edit_messages=admin_rights.edit_messages,
+            can_invite_users=admin_rights.invite_users,
+            can_pin_messages=admin_rights.pin_messages,
+            is_anonymous=admin_rights.anonymous
+        )
diff --git a/pyrogram/types/user_and_chats/chat_reactions.py b/pyrogram/types/user_and_chats/chat_reactions.py
new file mode 100644
index 0000000000..057fb96654
--- /dev/null
+++ b/pyrogram/types/user_and_chats/chat_reactions.py
@@ -0,0 +1,69 @@
+#  Pyrogram - Telegram MTProto API Client Library for Python
+#  Copyright (C) 2017-present Dan 
+#
+#  This file is part of Pyrogram.
+#
+#  Pyrogram is free software: you can redistribute it and/or modify
+#  it under the terms of the GNU Lesser General Public License as published
+#  by the Free Software Foundation, either version 3 of the License, or
+#  (at your option) any later version.
+#
+#  Pyrogram is distributed in the hope that it will be useful,
+#  but WITHOUT ANY WARRANTY; without even the implied warranty of
+#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#  GNU Lesser General Public License for more details.
+#
+#  You should have received a copy of the GNU Lesser General Public License
+#  along with Pyrogram.  If not, see .
+
+from typing import Optional, List
+
+import pyrogram
+from pyrogram import raw, types
+from ..object import Object
+
+
+class ChatReactions(Object):
+    """A chat reactions
+
+    Parameters:
+        all_are_enabled (``bool``, *optional*)
+
+        allow_custom_emoji (``bool``, *optional*):
+            Whether custom emoji are allowed or not.
+
+        reactions (List of :obj:`~pyrogram.types.Reaction`, *optional*):
+            Reactions available.
+    """
+
+    def __init__(
+        self,
+        *,
+        client: "pyrogram.Client" = None,
+        all_are_enabled: Optional[bool] = None,
+        allow_custom_emoji: Optional[bool] = None,
+        reactions: Optional[List["types.Reaction"]] = None,
+    ):
+        super().__init__(client)
+
+        self.all_are_enabled = all_are_enabled
+        self.allow_custom_emoji = allow_custom_emoji
+        self.reactions = reactions
+
+    @staticmethod
+    def _parse(client, chat_reactions: "raw.base.ChatReactions") -> Optional["ChatReactions"]:
+        if isinstance(chat_reactions, raw.types.ChatReactionsAll):
+            return ChatReactions(
+                client=client,
+                all_are_enabled=True,
+                allow_custom_emoji=chat_reactions.allow_custom
+            )
+
+        if isinstance(chat_reactions, raw.types.ChatReactionsSome):
+            return ChatReactions(
+                client=client,
+                reactions=[types.Reaction._parse(client, reaction)
+                           for reaction in chat_reactions.reactions]
+            )
+
+        return None
diff --git a/pyrogram/types/user_and_chats/dialog.py b/pyrogram/types/user_and_chats/dialog.py
index 6c4a408a14..7259a89a57 100644
--- a/pyrogram/types/user_and_chats/dialog.py
+++ b/pyrogram/types/user_and_chats/dialog.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
diff --git a/pyrogram/types/user_and_chats/emoji_status.py b/pyrogram/types/user_and_chats/emoji_status.py
new file mode 100644
index 0000000000..50b46bfa46
--- /dev/null
+++ b/pyrogram/types/user_and_chats/emoji_status.py
@@ -0,0 +1,77 @@
+#  Pyrogram - Telegram MTProto API Client Library for Python
+#  Copyright (C) 2017-present Dan 
+#
+#  This file is part of Pyrogram.
+#
+#  Pyrogram is free software: you can redistribute it and/or modify
+#  it under the terms of the GNU Lesser General Public License as published
+#  by the Free Software Foundation, either version 3 of the License, or
+#  (at your option) any later version.
+#
+#  Pyrogram is distributed in the hope that it will be useful,
+#  but WITHOUT ANY WARRANTY; without even the implied warranty of
+#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#  GNU Lesser General Public License for more details.
+#
+#  You should have received a copy of the GNU Lesser General Public License
+#  along with Pyrogram.  If not, see .
+
+from datetime import datetime
+from typing import Optional
+
+import pyrogram
+from pyrogram import raw
+from pyrogram import utils
+from ..object import Object
+
+
+class EmojiStatus(Object):
+    """A user emoji status.
+
+    Parameters:
+        custom_emoji_id (``int``):
+            Custom emoji id.
+
+        until_date (:py:obj:`~datetime.datetime`, *optional*):
+            Valid until date.
+    """
+
+    def __init__(
+        self,
+        *,
+        client: "pyrogram.Client" = None,
+        custom_emoji_id: int,
+        until_date: Optional[datetime] = None
+    ):
+        super().__init__(client)
+
+        self.custom_emoji_id = custom_emoji_id
+        self.until_date = until_date
+
+    @staticmethod
+    def _parse(client, emoji_status: "raw.base.EmojiStatus") -> Optional["EmojiStatus"]:
+        if isinstance(emoji_status, raw.types.EmojiStatus):
+            return EmojiStatus(
+                client=client,
+                custom_emoji_id=emoji_status.document_id
+            )
+
+        if isinstance(emoji_status, raw.types.EmojiStatusUntil):
+            return EmojiStatus(
+                client=client,
+                custom_emoji_id=emoji_status.document_id,
+                until_date=utils.timestamp_to_datetime(emoji_status.until)
+            )
+
+        return None
+
+    def write(self):
+        if self.until_date:
+            return raw.types.EmojiStatusUntil(
+                document_id=self.custom_emoji_id,
+                until=utils.datetime_to_timestamp(self.until_date)
+            )
+
+        return raw.types.EmojiStatus(
+            document_id=self.custom_emoji_id
+        )
diff --git a/pyrogram/types/user_and_chats/invite_link_importer.py b/pyrogram/types/user_and_chats/invite_link_importer.py
index 4517ae3c8d..34e5f397f4 100644
--- a/pyrogram/types/user_and_chats/invite_link_importer.py
+++ b/pyrogram/types/user_and_chats/invite_link_importer.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
@@ -16,7 +16,9 @@
 #  You should have received a copy of the GNU Lesser General Public License
 #  along with Pyrogram.  If not, see .
 
-from pyrogram import raw
+from datetime import datetime
+
+from pyrogram import raw, utils
 from pyrogram import types
 from ..object import Object
 
@@ -25,21 +27,25 @@ class InviteLinkImporter(Object):
     """The date and user of when someone has joined with an invite link.
 
     Parameters:
-        date (``int``):
-            The unix time of when this user used the given link
+        date (:py:obj:`~datetime.datetime`):
+            The time of when this user used the given link
 
         user (:obj:`~pyrogram.types.User`):
             The user that has used the given invite link
     """
 
-    def __init__(self, *, date, user):
+    def __init__(
+        self, *,
+        date: datetime,
+        user: "types.User"
+    ):
         super().__init__(None)
 
         self.date = date
         self.user = user
 
     @staticmethod
-    def _parse(client, invite_importers: "raw.types.ChatInviteImporters"):
+    def _parse(client, invite_importers: "raw.types.messages.ChatInviteImporters"):
         importers = types.List()
 
         d = {i.id: i for i in invite_importers.users}
@@ -47,7 +53,7 @@ def _parse(client, invite_importers: "raw.types.ChatInviteImporters"):
         for j in invite_importers.importers:
             importers.append(
                 InviteLinkImporter(
-                    date=j.date,
+                    date=utils.timestamp_to_datetime(j.date),
                     user=types.User._parse(client=None, user=d[j.user_id])
                 )
             )
diff --git a/pyrogram/types/user_and_chats/restriction.py b/pyrogram/types/user_and_chats/restriction.py
index e3acd250be..8f7a1b7269 100644
--- a/pyrogram/types/user_and_chats/restriction.py
+++ b/pyrogram/types/user_and_chats/restriction.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
diff --git a/pyrogram/types/user_and_chats/user.py b/pyrogram/types/user_and_chats/user.py
index cc0c2036ce..e981357826 100644
--- a/pyrogram/types/user_and_chats/user.py
+++ b/pyrogram/types/user_and_chats/user.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
@@ -17,9 +17,11 @@
 #  along with Pyrogram.  If not, see .
 
 import html
+from datetime import datetime
 from typing import List, Optional
 
 import pyrogram
+from pyrogram import enums, utils
 from pyrogram import raw
 from pyrogram import types
 from ..object import Object
@@ -28,9 +30,9 @@
 
 class Link(str):
     HTML = "{text}"
-    MD = "[{text}]({url})"
+    MARKDOWN = "[{text}]({url})"
 
-    def __init__(self, url: str, text: str, style: str):
+    def __init__(self, url: str, text: str, style: enums.ParseMode):
         super().__init__()
 
         self.url = url
@@ -38,13 +40,11 @@ def __init__(self, url: str, text: str, style: str):
         self.style = style
 
     @staticmethod
-    def format(url: str, text: str, style: str):
-        if style in ["md", "markdown"]:
-            fmt = Link.MD
-        elif style in ["combined", "html", None]:
-            fmt = Link.HTML
+    def format(url: str, text: str, style: enums.ParseMode):
+        if style == enums.ParseMode.MARKDOWN:
+            fmt = Link.MARKDOWN
         else:
-            raise ValueError(f"{style} is not a valid style/parse mode")
+            fmt = Link.HTML
 
         return fmt.format(url=url, text=html.escape(text))
 
@@ -97,28 +97,23 @@ class User(Object, Update):
         is_support (``bool``, *optional*):
             True, if this user is part of the Telegram support team.
 
+        is_premium (``bool``, *optional*):
+            True, if this user is a premium user.
+
         first_name (``str``, *optional*):
             User's or bot's first name.
 
         last_name (``str``, *optional*):
             User's or bot's last name.
 
-        status (``str``, *optional*):
-            User's Last Seen & Online status.
-            Can be one of the following:
-            "*online*", user is online right now.
-            "*offline*", user is currently offline.
-            "*recently*", user with hidden last seen time who was online between 1 second and 2-3 days ago.
-            "*within_week*", user with hidden last seen time who was online between 2-3 and seven days ago.
-            "*within_month*", user with hidden last seen time who was online between 6-7 days and a month ago.
-            "*long_time_ago*", blocked user or user with hidden last seen time who was online more than a month ago.
-            *None*, for bots.
+        status (:obj:`~pyrogram.enums.UserStatus`, *optional*):
+            User's last seen & online status. ``None``, for bots.
 
-        last_online_date (``int``, *optional*):
-            Last online date of a user, unix time. Only available in case status is "*offline*".
+        last_online_date (:py:obj:`~datetime.datetime`, *optional*):
+            Last online date of a user. Only available in case status is :obj:`~pyrogram.enums.UserStatus.OFFLINE`.
 
-        next_offline_date (``int``, *optional*):
-            Date when a user will automatically go offline, unix time. Only available in case status is "*online*".
+        next_offline_date (:py:obj:`~datetime.datetime`, *optional*):
+            Date when a user will automatically go offline. Only available in case status is :obj:`~pyrogram.enums.UserStatus.ONLINE`.
 
         username (``str``, *optional*):
             User's or bot's username.
@@ -126,6 +121,9 @@ class User(Object, Update):
         language_code (``str``, *optional*):
             IETF language tag of the user's language.
 
+        emoji_status (:obj:`~pyrogram.types.EmojiStatus`, *optional*):
+            Emoji status.
+
         dc_id (``int``, *optional*):
             User's or bot's assigned DC (data center). Available only in case the user has set a public profile photo.
             Note that this information is approximate; it is based on where Telegram stores a user profile pictures and
@@ -164,13 +162,15 @@ def __init__(
         is_scam: bool = None,
         is_fake: bool = None,
         is_support: bool = None,
+        is_premium: bool = None,
         first_name: str = None,
         last_name: str = None,
-        status: str = None,
-        last_online_date: int = None,
-        next_offline_date: int = None,
+        status: "enums.UserStatus" = None,
+        last_online_date: datetime = None,
+        next_offline_date: datetime = None,
         username: str = None,
         language_code: str = None,
+        emoji_status: Optional["types.EmojiStatus"] = None,
         dc_id: int = None,
         phone_number: str = None,
         photo: "types.ChatPhoto" = None,
@@ -189,6 +189,7 @@ def __init__(
         self.is_scam = is_scam
         self.is_fake = is_fake
         self.is_support = is_support
+        self.is_premium = is_premium
         self.first_name = first_name
         self.last_name = last_name
         self.status = status
@@ -196,6 +197,7 @@ def __init__(
         self.next_offline_date = next_offline_date
         self.username = username
         self.language_code = language_code
+        self.emoji_status = emoji_status
         self.dc_id = dc_id
         self.phone_number = phone_number
         self.photo = photo
@@ -203,11 +205,15 @@ def __init__(
 
     @property
     def mention(self):
-        return Link(f"tg://user?id={self.id}", self.first_name, self._client.parse_mode)
+        return Link(
+            f"tg://user?id={self.id}",
+            self.first_name or "Deleted Account",
+            self._client.parse_mode
+        )
 
     @staticmethod
-    def _parse(client, user: "raw.types.User") -> Optional["User"]:
-        if user is None:
+    def _parse(client, user: "raw.base.User") -> Optional["User"]:
+        if user is None or isinstance(user, raw.types.UserEmpty):
             return None
 
         return User(
@@ -222,11 +228,13 @@ def _parse(client, user: "raw.types.User") -> Optional["User"]:
             is_scam=user.scam,
             is_fake=user.fake,
             is_support=user.support,
+            is_premium=user.premium,
             first_name=user.first_name,
             last_name=user.last_name,
             **User._parse_status(user.status, user.bot),
             username=user.username,
             language_code=user.lang_code,
+            emoji_status=types.EmojiStatus._parse(client, user.emoji_status),
             dc_id=getattr(user.photo, "dc_id", None),
             phone_number=user.phone,
             photo=types.ChatPhoto._parse(client, user.photo, user.id, user.access_hash),
@@ -237,17 +245,17 @@ def _parse(client, user: "raw.types.User") -> Optional["User"]:
     @staticmethod
     def _parse_status(user_status: "raw.base.UserStatus", is_bot: bool = False):
         if isinstance(user_status, raw.types.UserStatusOnline):
-            status, date = "online", user_status.expires
+            status, date = enums.UserStatus.ONLINE, user_status.expires
         elif isinstance(user_status, raw.types.UserStatusOffline):
-            status, date = "offline", user_status.was_online
+            status, date = enums.UserStatus.OFFLINE, user_status.was_online
         elif isinstance(user_status, raw.types.UserStatusRecently):
-            status, date = "recently", None
+            status, date = enums.UserStatus.RECENTLY, None
         elif isinstance(user_status, raw.types.UserStatusLastWeek):
-            status, date = "within_week", None
+            status, date = enums.UserStatus.LAST_WEEK, None
         elif isinstance(user_status, raw.types.UserStatusLastMonth):
-            status, date = "within_month", None
+            status, date = enums.UserStatus.LAST_MONTH, None
         else:
-            status, date = "long_time_ago", None
+            status, date = enums.UserStatus.LONG_AGO, None
 
         last_online_date = None
         next_offline_date = None
@@ -255,11 +263,11 @@ def _parse_status(user_status: "raw.base.UserStatus", is_bot: bool = False):
         if is_bot:
             status = None
 
-        if status == "online":
-            next_offline_date = date
+        if status == enums.UserStatus.ONLINE:
+            next_offline_date = utils.timestamp_to_datetime(date)
 
-        if status == "offline":
-            last_online_date = date
+        if status == enums.UserStatus.OFFLINE:
+            last_online_date = utils.timestamp_to_datetime(date)
 
         return {
             "status": status,
@@ -282,12 +290,12 @@ async def archive(self):
 
         .. code-block:: python
 
-            client.archive_chats(123456789)
+            await client.archive_chats(123456789)
 
         Example:
             .. code-block:: python
 
-                user.archive()
+               await user.archive()
 
         Returns:
             True on success.
@@ -305,12 +313,12 @@ async def unarchive(self):
 
         .. code-block:: python
 
-            client.unarchive_chats(123456789)
+            await client.unarchive_chats(123456789)
 
         Example:
             .. code-block:: python
 
-                user.unarchive()
+                await user.unarchive()
 
         Returns:
             True on success.
@@ -328,12 +336,12 @@ def block(self):
 
         .. code-block:: python
 
-            client.block_user(123456789)
+            await client.block_user(123456789)
 
         Example:
             .. code-block:: python
 
-                user.block()
+                await user.block()
 
         Returns:
             True on success.
diff --git a/pyrogram/types/user_and_chats/voice_chat_ended.py b/pyrogram/types/user_and_chats/video_chat_ended.py
similarity index 87%
rename from pyrogram/types/user_and_chats/voice_chat_ended.py
rename to pyrogram/types/user_and_chats/video_chat_ended.py
index febb813729..8c9fac6a0a 100644
--- a/pyrogram/types/user_and_chats/voice_chat_ended.py
+++ b/pyrogram/types/user_and_chats/video_chat_ended.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
@@ -20,7 +20,7 @@
 from ..object import Object
 
 
-class VoiceChatEnded(Object):
+class VideoChatEnded(Object):
     """A service message about a voice chat ended in the chat.
 
     Parameters:
@@ -37,5 +37,5 @@ def __init__(
         self.duration = duration
 
     @staticmethod
-    def _parse(action: "raw.types.MessageActionGroupCall") -> "VoiceChatEnded":
-        return VoiceChatEnded(duration=action.duration)
+    def _parse(action: "raw.types.MessageActionGroupCall") -> "VideoChatEnded":
+        return VideoChatEnded(duration=action.duration)
diff --git a/pyrogram/types/user_and_chats/voice_chat_members_invited.py b/pyrogram/types/user_and_chats/video_chat_members_invited.py
similarity index 88%
rename from pyrogram/types/user_and_chats/voice_chat_members_invited.py
rename to pyrogram/types/user_and_chats/video_chat_members_invited.py
index 0a093cbf92..9f2e3d1ef4 100644
--- a/pyrogram/types/user_and_chats/voice_chat_members_invited.py
+++ b/pyrogram/types/user_and_chats/video_chat_members_invited.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
@@ -22,7 +22,7 @@
 from ..object import Object
 
 
-class VoiceChatMembersInvited(Object):
+class VideoChatMembersInvited(Object):
     """A service message about new members invited to a voice chat.
 
 
@@ -44,7 +44,7 @@ def _parse(
         client,
         action: "raw.types.MessageActionInviteToGroupCall",
         users: Dict[int, "raw.types.User"]
-    ) -> "VoiceChatMembersInvited":
+    ) -> "VideoChatMembersInvited":
         users = [types.User._parse(client, users[i]) for i in action.users]
 
-        return VoiceChatMembersInvited(users=users)
+        return VideoChatMembersInvited(users=users)
diff --git a/pyrogram/types/user_and_chats/voice_chat_scheduled.py b/pyrogram/types/user_and_chats/video_chat_scheduled.py
similarity index 69%
rename from pyrogram/types/user_and_chats/voice_chat_scheduled.py
rename to pyrogram/types/user_and_chats/video_chat_scheduled.py
index fc7a5f1e5f..5bdd592515 100644
--- a/pyrogram/types/user_and_chats/voice_chat_scheduled.py
+++ b/pyrogram/types/user_and_chats/video_chat_scheduled.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
@@ -16,26 +16,28 @@
 #  You should have received a copy of the GNU Lesser General Public License
 #  along with Pyrogram.  If not, see .
 
-from pyrogram import raw
+from datetime import datetime
+
+from pyrogram import raw, utils
 from ..object import Object
 
 
-class VoiceChatScheduled(Object):
+class VideoChatScheduled(Object):
     """A service message about a voice chat scheduled in the chat.
 
     Parameters:
-        start_date (``int``):
-            Point in time (Unix timestamp) when the voice chat is supposed to be started by a chat administrator.
+        start_date (:py:obj:`~datetime.datetime`):
+            Point in time when the voice chat is supposed to be started by a chat administrator.
     """
 
     def __init__(
         self, *,
-        start_date: int
+        start_date: datetime
     ):
         super().__init__()
 
         self.start_date = start_date
 
     @staticmethod
-    def _parse(action: "raw.types.MessageActionGroupCallScheduled") -> "VoiceChatScheduled":
-        return VoiceChatScheduled(start_date=action.schedule_date)
+    def _parse(action: "raw.types.MessageActionGroupCallScheduled") -> "VideoChatScheduled":
+        return VideoChatScheduled(start_date=utils.timestamp_to_datetime(action.schedule_date))
diff --git a/pyrogram/types/user_and_chats/voice_chat_started.py b/pyrogram/types/user_and_chats/video_chat_started.py
similarity index 90%
rename from pyrogram/types/user_and_chats/voice_chat_started.py
rename to pyrogram/types/user_and_chats/video_chat_started.py
index 6ac546dce9..ff48b39cd6 100644
--- a/pyrogram/types/user_and_chats/voice_chat_started.py
+++ b/pyrogram/types/user_and_chats/video_chat_started.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
@@ -19,7 +19,7 @@
 from ..object import Object
 
 
-class VoiceChatStarted(Object):
+class VideoChatStarted(Object):
     """A service message about a voice chat started in the chat.
 
     Currently holds no information.
diff --git a/pyrogram/utils.py b/pyrogram/utils.py
index e5b15aad63..f7fe59706d 100644
--- a/pyrogram/utils.py
+++ b/pyrogram/utils.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
@@ -23,11 +23,12 @@
 import os
 import struct
 from concurrent.futures.thread import ThreadPoolExecutor
+from datetime import datetime, timezone
 from getpass import getpass
 from typing import Union, List, Dict, Optional
 
 import pyrogram
-from pyrogram import raw
+from pyrogram import raw, enums
 from pyrogram import types
 from pyrogram.file_id import FileId, FileType, PHOTO_TYPES, DOCUMENT_TYPES
 
@@ -41,13 +42,16 @@ async def ainput(prompt: str = "", *, hide: bool = False):
 
 def get_input_media_from_file_id(
     file_id: str,
-    expected_file_type: FileType = None
+    expected_file_type: FileType = None,
+    ttl_seconds: int = None
 ) -> Union["raw.types.InputMediaPhoto", "raw.types.InputMediaDocument"]:
     try:
         decoded = FileId.decode(file_id)
     except Exception:
-        raise ValueError(f'Failed to decode "{file_id}". The value does not represent an existing local file, '
-                         f'HTTP URL, or valid file id.')
+        raise ValueError(
+            f'Failed to decode "{file_id}". The value does not represent an existing local file, '
+            f"HTTP URL, or valid file id."
+        )
 
     file_type = decoded.file_type
 
@@ -63,7 +67,8 @@ def get_input_media_from_file_id(
                 id=decoded.media_id,
                 access_hash=decoded.access_hash,
                 file_reference=decoded.file_reference
-            )
+            ),
+            ttl_seconds=ttl_seconds
         )
 
     if file_type in DOCUMENT_TYPES:
@@ -72,13 +77,18 @@ def get_input_media_from_file_id(
                 id=decoded.media_id,
                 access_hash=decoded.access_hash,
                 file_reference=decoded.file_reference
-            )
+            ),
+            ttl_seconds=ttl_seconds
         )
 
     raise ValueError(f"Unknown file id: {file_id}")
 
 
-async def parse_messages(client, messages: "raw.types.messages.Messages", replies: int = 1) -> List["types.Message"]:
+async def parse_messages(
+    client,
+    messages: "raw.types.messages.Messages",
+    replies: int = 1
+) -> List["types.Message"]:
     users = {i.id: i for i in messages.users}
     chats = {i.id: i for i in messages.chats}
 
@@ -114,10 +124,10 @@ async def parse_messages(client, messages: "raw.types.messages.Messages", replie
             )
 
             for message in parsed_messages:
-                reply_id = messages_with_replies.get(message.message_id, None)
+                reply_id = messages_with_replies.get(message.id, None)
 
                 for reply in reply_messages:
-                    if reply.message_id == reply_id:
+                    if reply.id == reply_id:
                         message.reply_to_message = reply
 
     return types.List(parsed_messages)
@@ -132,10 +142,10 @@ def parse_deleted_messages(client, update) -> List["types.Message"]:
     for message in messages:
         parsed_messages.append(
             types.Message(
-                message_id=message,
+                id=message,
                 chat=types.Chat(
                     id=get_channel_id(channel_id),
-                    type="channel",
+                    type=enums.ChatType.CHANNEL,
                     client=client
                 ) if channel_id is not None else None,
                 client=client
@@ -145,20 +155,53 @@ def parse_deleted_messages(client, update) -> List["types.Message"]:
     return types.List(parsed_messages)
 
 
-def unpack_inline_message_id(inline_message_id: str) -> "raw.types.InputBotInlineMessageID":
-    r = inline_message_id + "=" * (-len(inline_message_id) % 4)
-    r = struct.unpack(" bytes:
     return bytes(i ^ j for i, j in zip(a, b))
 
 
-def compute_password_hash(algo: raw.types.PasswordKdfAlgoSHA256SHA256PBKDF2HMACSHA512iter100000SHA256ModPow,
-                          password: str) -> bytes:
+def compute_password_hash(
+    algo: raw.types.PasswordKdfAlgoSHA256SHA256PBKDF2HMACSHA512iter100000SHA256ModPow,
+    password: str
+) -> bytes:
     hash1 = sha256(algo.salt1 + password.encode() + algo.salt1)
     hash2 = sha256(algo.salt2 + hash1 + algo.salt2)
     hash3 = hashlib.pbkdf2_hmac("sha512", hash2, algo.salt1, 100000)
@@ -233,7 +278,10 @@ def compute_password_hash(algo: raw.types.PasswordKdfAlgoSHA256SHA256PBKDF2HMACS
 
 
 # noinspection PyPep8Naming
-def compute_password_check(r: raw.types.account.Password, password: str) -> raw.types.InputCheckPasswordSRP:
+def compute_password_check(
+    r: raw.types.account.Password,
+    password: str
+) -> raw.types.InputCheckPasswordSRP:
     algo = r.current_algo
 
     p_bytes = algo.p
@@ -293,15 +341,15 @@ def compute_password_check(r: raw.types.account.Password, password: str) -> raw.
 async def parse_text_entities(
     client: "pyrogram.Client",
     text: str,
-    parse_mode: str,
+    parse_mode: enums.ParseMode,
     entities: List["types.MessageEntity"]
-) -> Dict[str, raw.base.MessageEntity]:
+) -> Dict[str, Union[str, List[raw.base.MessageEntity]]]:
     if entities:
         # Inject the client instance because parsing user mentions requires it
         for entity in entities:
             entity._client = client
 
-        text, entities = text, [await entity.write() for entity in entities]
+        text, entities = text, [await entity.write() for entity in entities] or None
     else:
         text, entities = (await client.parser.parse(text, parse_mode)).values()
 
@@ -309,3 +357,15 @@ async def parse_text_entities(
         "message": text,
         "entities": entities
     }
+
+
+def zero_datetime() -> datetime:
+    return datetime.fromtimestamp(0, timezone.utc)
+
+
+def timestamp_to_datetime(ts: Optional[int]) -> Optional[datetime]:
+    return datetime.fromtimestamp(ts) if ts else None
+
+
+def datetime_to_timestamp(dt: Optional[datetime]) -> Optional[int]:
+    return int(dt.timestamp()) if dt else None
diff --git a/setup.py b/setup.py
index c2e2ce96b9..2cb7d7f677 100644
--- a/setup.py
+++ b/setup.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
@@ -16,15 +16,12 @@
 #  You should have received a copy of the GNU Lesser General Public License
 #  along with Pyrogram.  If not, see .
 
-import os
 import re
-import shutil
 from sys import argv
 
-from setuptools import setup, find_packages, Command
+from setuptools import setup, find_packages
 
 from compiler.api import compiler as api_compiler
-from compiler.docs import compiler as docs_compiler
 from compiler.errors import compiler as errors_compiler
 
 with open("requirements.txt", encoding="utf-8") as r:
@@ -36,96 +33,6 @@
 with open("README.md", encoding="utf-8") as f:
     readme = f.read()
 
-
-class Clean(Command):
-    DIST = ["./build", "./dist", "./Pyrogram.egg-info"]
-    API = [
-        "pyrogram/errors/exceptions", "pyrogram/raw/functions", "pyrogram/raw/types", "pyrogram/raw/base",
-        "pyrogram/raw/all.py"
-    ]
-    DOCS = [
-        "docs/source/telegram", "docs/build", "docs/source/api/methods", "docs/source/api/types",
-        "docs/source/api/bound-methods"
-    ]
-
-    ALL = DIST + API + DOCS
-
-    description = "Clean generated files"
-
-    user_options = [
-        ("dist", None, "Clean distribution files"),
-        ("api", None, "Clean generated API files"),
-        ("docs", None, "Clean generated docs files"),
-        ("all", None, "Clean all generated files"),
-    ]
-
-    def __init__(self, dist, **kw):
-        super().__init__(dist, **kw)
-
-        self.dist = None
-        self.api = None
-        self.docs = None
-        self.all = None
-
-    def initialize_options(self):
-        pass
-
-    def finalize_options(self):
-        pass
-
-    def run(self):
-        paths = set()
-
-        if self.dist:
-            paths.update(Clean.DIST)
-
-        if self.api:
-            paths.update(Clean.API)
-
-        if self.docs:
-            paths.update(Clean.DOCS)
-
-        if self.all or not paths:
-            paths.update(Clean.ALL)
-
-        for path in sorted(list(paths)):
-            try:
-                shutil.rmtree(path) if os.path.isdir(path) else os.remove(path)
-            except OSError:
-                print("skipping {}".format(path))
-            else:
-                print("removing {}".format(path))
-
-
-class Generate(Command):
-    description = "Generate Pyrogram files"
-
-    user_options = [
-        ("api", None, "Generate API files"),
-        ("docs", None, "Generate docs files")
-    ]
-
-    def __init__(self, dist, **kw):
-        super().__init__(dist, **kw)
-
-        self.api = None
-        self.docs = None
-
-    def initialize_options(self):
-        pass
-
-    def finalize_options(self):
-        pass
-
-    def run(self):
-        if self.api:
-            errors_compiler.start()
-            api_compiler.start()
-
-        if self.docs:
-            docs_compiler.start()
-
-
 if len(argv) > 1 and argv[1] in ["bdist_wheel", "install", "develop"]:
     api_compiler.start()
     errors_compiler.start()
@@ -133,26 +40,27 @@ def run(self):
 setup(
     name="Pyrogram",
     version=version,
-    description="Telegram MTProto API Client Library and Framework for Python",
+    description="Elegant, modern and asynchronous Telegram MTProto API framework in Python for users and bots",
     long_description=readme,
     long_description_content_type="text/markdown",
     url="https://github.com/pyrogram",
     download_url="https://github.com/pyrogram/pyrogram/releases/latest",
     author="Dan",
     author_email="dan@pyrogram.org",
-    license="LGPLv3+",
+    license="LGPLv3",
     classifiers=[
         "Development Status :: 5 - Production/Stable",
         "Intended Audience :: Developers",
         "Natural Language :: English",
-        "License :: OSI Approved :: GNU Lesser General Public License v3 or later (LGPLv3+)",
+        "License :: OSI Approved :: GNU Lesser General Public License v3 (LGPLv3)",
         "Operating System :: OS Independent",
         "Programming Language :: Python",
         "Programming Language :: Python :: 3",
-        "Programming Language :: Python :: 3.6",
         "Programming Language :: Python :: 3.7",
         "Programming Language :: Python :: 3.8",
         "Programming Language :: Python :: 3.9",
+        "Programming Language :: Python :: 3.10",
+        "Programming Language :: Python :: 3.11",
         "Programming Language :: Python :: Implementation",
         "Programming Language :: Python :: Implementation :: CPython",
         "Programming Language :: Python :: Implementation :: PyPy",
@@ -166,16 +74,15 @@ def run(self):
     keywords="telegram chat messenger mtproto api client library python",
     project_urls={
         "Tracker": "https://github.com/pyrogram/pyrogram/issues",
-        "Community": "https://t.me/Pyrogram",
+        "Community": "https://t.me/pyrogram",
         "Source": "https://github.com/pyrogram/pyrogram",
         "Documentation": "https://docs.pyrogram.org",
     },
-    python_requires="~=3.6",
+    python_requires="~=3.7",
+    package_data={
+        "pyrogram": ["py.typed"],
+    },
     packages=find_packages(exclude=["compiler*", "tests*"]),
     zip_safe=False,
-    install_requires=requires,
-    cmdclass={
-        "clean": Clean,
-        "generate": Generate
-    }
+    install_requires=requires
 )
diff --git a/tests/__init__.py b/tests/__init__.py
index 4ad4f32b47..46887cb7a5 100644
--- a/tests/__init__.py
+++ b/tests/__init__.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
diff --git a/tests/filters/__init__.py b/tests/filters/__init__.py
index 56187dd8b2..6711b51b4c 100644
--- a/tests/filters/__init__.py
+++ b/tests/filters/__init__.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
@@ -17,9 +17,11 @@
 #  along with Pyrogram.  If not, see .
 
 class Client:
-    @staticmethod
-    async def get_me():
-        return User("username")
+    def __init__(self):
+        self.me = User("username")
+
+    async def get_me(self):
+        return self.me
 
 
 class User:
diff --git a/tests/filters/test_command.py b/tests/filters/test_command.py
index 19e21d9dd5..234ed69fc3 100644
--- a/tests/filters/test_command.py
+++ b/tests/filters/test_command.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
diff --git a/tests/parser/__init__.py b/tests/parser/__init__.py
new file mode 100644
index 0000000000..46887cb7a5
--- /dev/null
+++ b/tests/parser/__init__.py
@@ -0,0 +1,17 @@
+#  Pyrogram - Telegram MTProto API Client Library for Python
+#  Copyright (C) 2017-present Dan 
+#
+#  This file is part of Pyrogram.
+#
+#  Pyrogram is free software: you can redistribute it and/or modify
+#  it under the terms of the GNU Lesser General Public License as published
+#  by the Free Software Foundation, either version 3 of the License, or
+#  (at your option) any later version.
+#
+#  Pyrogram is distributed in the hope that it will be useful,
+#  but WITHOUT ANY WARRANTY; without even the implied warranty of
+#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#  GNU Lesser General Public License for more details.
+#
+#  You should have received a copy of the GNU Lesser General Public License
+#  along with Pyrogram.  If not, see .
diff --git a/tests/parser/test_html.py b/tests/parser/test_html.py
new file mode 100644
index 0000000000..b9138f3cf1
--- /dev/null
+++ b/tests/parser/test_html.py
@@ -0,0 +1,147 @@
+#  Pyrogram - Telegram MTProto API Client Library for Python
+#  Copyright (C) 2017-present Dan 
+#
+#  This file is part of Pyrogram.
+#
+#  Pyrogram is free software: you can redistribute it and/or modify
+#  it under the terms of the GNU Lesser General Public License as published
+#  by the Free Software Foundation, either version 3 of the License, or
+#  (at your option) any later version.
+#
+#  Pyrogram is distributed in the hope that it will be useful,
+#  but WITHOUT ANY WARRANTY; without even the implied warranty of
+#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#  GNU Lesser General Public License for more details.
+#
+#  You should have received a copy of the GNU Lesser General Public License
+#  along with Pyrogram.  If not, see .
+
+import pyrogram
+from pyrogram.parser.html import HTML
+
+
+# expected: the expected unparsed HTML
+# text: original text without entities
+# entities: message entities coming from the server
+
+def test_html_unparse_bold():
+    expected = "bold"
+    text = "bold"
+    entities = pyrogram.types.List(
+        [pyrogram.types.MessageEntity(type=pyrogram.enums.MessageEntityType.BOLD, offset=0, length=4)])
+
+    assert HTML.unparse(text=text, entities=entities) == expected
+
+
+def test_html_unparse_italic():
+    expected = "italic"
+    text = "italic"
+    entities = pyrogram.types.List(
+        [pyrogram.types.MessageEntity(type=pyrogram.enums.MessageEntityType.ITALIC, offset=0, length=6)])
+
+    assert HTML.unparse(text=text, entities=entities) == expected
+
+
+def test_html_unparse_underline():
+    expected = "underline"
+    text = "underline"
+    entities = pyrogram.types.List(
+        [pyrogram.types.MessageEntity(type=pyrogram.enums.MessageEntityType.UNDERLINE, offset=0, length=9)])
+
+    assert HTML.unparse(text=text, entities=entities) == expected
+
+
+def test_html_unparse_strike():
+    expected = "strike"
+    text = "strike"
+    entities = pyrogram.types.List(
+        [pyrogram.types.MessageEntity(type=pyrogram.enums.MessageEntityType.STRIKETHROUGH, offset=0, length=6)])
+
+    assert HTML.unparse(text=text, entities=entities) == expected
+
+
+def test_html_unparse_spoiler():
+    expected = "spoiler"
+    text = "spoiler"
+    entities = pyrogram.types.List(
+        [pyrogram.types.MessageEntity(type=pyrogram.enums.MessageEntityType.SPOILER, offset=0, length=7)])
+
+    assert HTML.unparse(text=text, entities=entities) == expected
+
+
+def test_html_unparse_url():
+    expected = 'URL'
+    text = "URL"
+    entities = pyrogram.types.List([pyrogram.types.MessageEntity(type=pyrogram.enums.MessageEntityType.TEXT_LINK,
+                                                                 offset=0, length=3, url='https://pyrogram.org/')])
+
+    assert HTML.unparse(text=text, entities=entities) == expected
+
+
+def test_html_unparse_code():
+    expected = 'code'
+    text = "code"
+    entities = pyrogram.types.List(
+        [pyrogram.types.MessageEntity(type=pyrogram.enums.MessageEntityType.CODE, offset=0, length=4)])
+
+    assert HTML.unparse(text=text, entities=entities) == expected
+
+
+def test_html_unparse_pre():
+    expected = """
for i in range(10):
+    print(i)
""" + + text = """for i in range(10): + print(i)""" + + entities = pyrogram.types.List([pyrogram.types.MessageEntity(type=pyrogram.enums.MessageEntityType.PRE, offset=0, + length=32, language='python')]) + + assert HTML.unparse(text=text, entities=entities) == expected + + +def test_html_unparse_mixed(): + expected = "aaaaaaaaaabbbbbbbbbbccccccccccdddddddddd" \ + "eeeeeeeeeeffffffffffgggggggggghhhhhhhhhh" + text = "aaaaaaaaaabbbbbbbbbbccccccccccddddddddddeeeeeeeeeeffffffffffgggggggggghhhhhhhhhh" + entities = pyrogram.types.List( + [pyrogram.types.MessageEntity(type=pyrogram.enums.MessageEntityType.BOLD, offset=0, length=14), + pyrogram.types.MessageEntity(type=pyrogram.enums.MessageEntityType.ITALIC, offset=7, length=7), + pyrogram.types.MessageEntity(type=pyrogram.enums.MessageEntityType.UNDERLINE, offset=10, length=4), + pyrogram.types.MessageEntity(type=pyrogram.enums.MessageEntityType.UNDERLINE, offset=14, length=9), + pyrogram.types.MessageEntity(type=pyrogram.enums.MessageEntityType.ITALIC, offset=14, length=9), + pyrogram.types.MessageEntity(type=pyrogram.enums.MessageEntityType.UNDERLINE, offset=23, length=10), + pyrogram.types.MessageEntity(type=pyrogram.enums.MessageEntityType.STRIKETHROUGH, offset=30, length=3), + pyrogram.types.MessageEntity(type=pyrogram.enums.MessageEntityType.STRIKETHROUGH, offset=33, length=10), + pyrogram.types.MessageEntity(type=pyrogram.enums.MessageEntityType.SPOILER, offset=38, length=5), + pyrogram.types.MessageEntity(type=pyrogram.enums.MessageEntityType.SPOILER, offset=43, length=10), + pyrogram.types.MessageEntity(type=pyrogram.enums.MessageEntityType.CODE, offset=57, length=10)]) + + assert HTML.unparse(text=text, entities=entities) == expected + + +def test_html_unparse_escaped(): + expected = "<b>bold</b>" + text = "bold" + entities = pyrogram.types.List( + [pyrogram.types.MessageEntity(type=pyrogram.enums.MessageEntityType.BOLD, offset=0, length=11)]) + + assert HTML.unparse(text=text, entities=entities) == expected + + +def test_html_unparse_escaped_nested(): + expected = "<b>bold <u>underline</u> bold</b>" + text = "bold underline bold" + entities = pyrogram.types.List( + [pyrogram.types.MessageEntity(type=pyrogram.enums.MessageEntityType.BOLD, offset=0, length=33), + pyrogram.types.MessageEntity(type=pyrogram.enums.MessageEntityType.UNDERLINE, offset=8, length=16)]) + + assert HTML.unparse(text=text, entities=entities) == expected + + +def test_html_unparse_no_entities(): + expected = "text" + text = "text" + entities = [] + + assert HTML.unparse(text=text, entities=entities) == expected diff --git a/tests/test_file_id.py b/tests/test_file_id.py index 493590ab11..96152e48ca 100644 --- a/tests/test_file_id.py +++ b/tests/test_file_id.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2021 Dan +# Copyright (C) 2017-present Dan # # This file is part of Pyrogram. #