2017-12-05 11:42:09 +00:00
|
|
|
|
# Pyrogram - Telegram MTProto API Client Library for Python
|
2018-01-01 12:21:23 +00:00
|
|
|
|
# Copyright (C) 2017-2018 Dan Tès <https://github.com/delivrance>
|
2017-12-05 11:42:09 +00:00
|
|
|
|
#
|
|
|
|
|
# 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 <http://www.gnu.org/licenses/>.
|
|
|
|
|
|
|
|
|
|
import base64
|
2018-03-18 11:12:27 +00:00
|
|
|
|
import binascii
|
2017-12-05 11:42:09 +00:00
|
|
|
|
import json
|
|
|
|
|
import logging
|
2017-12-14 08:34:58 +00:00
|
|
|
|
import math
|
2017-12-14 09:57:30 +00:00
|
|
|
|
import mimetypes
|
2017-12-14 08:34:58 +00:00
|
|
|
|
import os
|
2017-12-25 11:30:48 +00:00
|
|
|
|
import re
|
2018-03-21 16:39:53 +00:00
|
|
|
|
import shutil
|
2018-03-17 18:24:27 +00:00
|
|
|
|
import struct
|
2018-04-23 11:37:50 +00:00
|
|
|
|
import sys
|
2018-03-21 12:39:23 +00:00
|
|
|
|
import tempfile
|
2018-02-08 19:46:47 +00:00
|
|
|
|
import threading
|
2017-12-05 11:42:09 +00:00
|
|
|
|
import time
|
|
|
|
|
from configparser import ConfigParser
|
2018-02-18 17:11:33 +00:00
|
|
|
|
from datetime import datetime
|
2017-12-14 08:34:58 +00:00
|
|
|
|
from hashlib import sha256, md5
|
2018-02-08 19:46:47 +00:00
|
|
|
|
from queue import Queue
|
2017-12-17 12:52:33 +00:00
|
|
|
|
from signal import signal, SIGINT, SIGTERM, SIGABRT
|
2018-02-08 19:46:47 +00:00
|
|
|
|
from threading import Event, Thread
|
2017-12-05 11:42:09 +00:00
|
|
|
|
|
2018-04-06 18:38:34 +00:00
|
|
|
|
import pyrogram
|
2017-12-05 11:42:09 +00:00
|
|
|
|
from pyrogram.api import functions, types
|
|
|
|
|
from pyrogram.api.core import Object
|
|
|
|
|
from pyrogram.api.errors import (
|
|
|
|
|
PhoneMigrate, NetworkMigrate, PhoneNumberInvalid,
|
|
|
|
|
PhoneNumberUnoccupied, PhoneCodeInvalid, PhoneCodeHashEmpty,
|
|
|
|
|
PhoneCodeExpired, PhoneCodeEmpty, SessionPasswordNeeded,
|
2018-01-08 05:31:48 +00:00
|
|
|
|
PasswordHashInvalid, FloodWait, PeerIdInvalid, FilePartMissing,
|
2018-02-18 17:11:33 +00:00
|
|
|
|
ChatAdminRequired, FirstnameInvalid, PhoneNumberBanned,
|
2018-04-08 14:50:18 +00:00
|
|
|
|
VolumeLocNotFound, UserMigrate, FileIdInvalid)
|
2018-01-28 00:44:38 +00:00
|
|
|
|
from pyrogram.crypto import AES
|
2017-12-05 11:42:09 +00:00
|
|
|
|
from pyrogram.session import Auth, Session
|
2018-02-18 17:11:33 +00:00
|
|
|
|
from pyrogram.session.internals import MsgId
|
2018-04-08 14:50:18 +00:00
|
|
|
|
from . import message_parser
|
2018-04-13 13:20:37 +00:00
|
|
|
|
from . import utils
|
2018-04-13 14:30:19 +00:00
|
|
|
|
from .dispatcher import Dispatcher
|
2018-04-14 19:48:15 +00:00
|
|
|
|
from .input_media_photo import InputMediaPhoto
|
|
|
|
|
from .input_media_video import InputMediaVideo
|
2018-01-23 14:18:52 +00:00
|
|
|
|
from .style import Markdown, HTML
|
2018-04-13 13:21:34 +00:00
|
|
|
|
from .syncer import Syncer
|
2017-12-05 11:42:09 +00:00
|
|
|
|
|
2018-04-23 11:37:50 +00:00
|
|
|
|
# Custom format for nice looking log lines
|
|
|
|
|
LOG_FORMAT = "[%(asctime)s.%(msecs)03d] %(filename)s:%(lineno)s %(levelname)s: %(message)s"
|
|
|
|
|
|
2017-12-05 11:42:09 +00:00
|
|
|
|
log = logging.getLogger(__name__)
|
|
|
|
|
|
2018-03-22 13:36:46 +00:00
|
|
|
|
|
2017-12-05 11:42:09 +00:00
|
|
|
|
class Client:
|
2018-01-03 16:39:12 +00:00
|
|
|
|
"""This class represents a Client, the main mean for interacting with Telegram.
|
|
|
|
|
It exposes bot-like methods for an easy access to the API as well as a simple way to
|
|
|
|
|
invoke every single Telegram API method available.
|
2017-12-29 20:44:45 +00:00
|
|
|
|
|
|
|
|
|
Args:
|
2018-03-25 19:41:19 +00:00
|
|
|
|
session_name (``str``):
|
2018-03-24 14:02:03 +00:00
|
|
|
|
Name to uniquely identify a session of either a User or a Bot.
|
|
|
|
|
For Users: pass a string of your choice, e.g.: "my_main_account".
|
|
|
|
|
For Bots: pass your Bot API token, e.g.: "123456:ABC-DEF1234ghIkl-zyx57W2v1u123ew11"
|
|
|
|
|
Note: as long as a valid User session file exists, Pyrogram won't ask you again to input your phone number.
|
2018-01-03 16:39:12 +00:00
|
|
|
|
|
2018-04-05 10:55:34 +00:00
|
|
|
|
api_id (``int``, 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.
|
|
|
|
|
|
|
|
|
|
api_hash (``str``, optional):
|
|
|
|
|
The *api_hash* part of your Telegram API Key, as string. E.g.: "0123456789abcdef0123456789abcdef"
|
|
|
|
|
This is an alternative way to pass it if you don't want to use the *config.ini* file.
|
2018-02-21 12:31:27 +00:00
|
|
|
|
|
2018-03-25 19:41:19 +00:00
|
|
|
|
proxy (``dict``, optional):
|
2018-03-24 14:02:03 +00:00
|
|
|
|
Your SOCKS5 Proxy settings as dict,
|
|
|
|
|
e.g.: *dict(hostname="11.22.33.44", port=1080, username="user", password="pass")*.
|
2018-02-21 12:31:27 +00:00
|
|
|
|
*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.
|
|
|
|
|
|
2018-03-25 19:41:19 +00:00
|
|
|
|
test_mode (``bool``, optional):
|
2018-01-15 12:42:05 +00:00
|
|
|
|
Enable or disable log-in to testing servers. Defaults to False.
|
2018-01-03 16:39:12 +00:00
|
|
|
|
Only applicable for new sessions and will be ignored in case previously
|
|
|
|
|
created sessions are loaded.
|
2018-01-24 20:46:28 +00:00
|
|
|
|
|
2018-03-25 19:41:19 +00:00
|
|
|
|
phone_number (``str``, optional):
|
2018-01-24 20:46:28 +00:00
|
|
|
|
Pass your phone number (with your Country Code prefix included) to avoid
|
|
|
|
|
entering it manually. Only applicable for new sessions.
|
|
|
|
|
|
2018-03-25 19:41:19 +00:00
|
|
|
|
phone_code (``str`` | ``callable``, optional):
|
2018-01-24 20:46:28 +00:00
|
|
|
|
Pass the phone code as string (for test numbers only), or pass a callback function
|
|
|
|
|
which must return the correct phone code as string (e.g., "12345").
|
|
|
|
|
Only applicable for new sessions.
|
|
|
|
|
|
2018-03-25 19:41:19 +00:00
|
|
|
|
password (``str``, optional):
|
2018-01-24 20:46:28 +00:00
|
|
|
|
Pass your Two-Step Verification password (if you have one) to avoid entering it
|
|
|
|
|
manually. Only applicable for new sessions.
|
|
|
|
|
|
2018-04-01 16:17:20 +00:00
|
|
|
|
force_sms (``str``, optional):
|
|
|
|
|
Pass True to force Telegram sending the authorization code via SMS.
|
|
|
|
|
Only applicable for new sessions.
|
|
|
|
|
|
2018-03-25 19:41:19 +00:00
|
|
|
|
first_name (``str``, optional):
|
2018-01-24 20:46:28 +00:00
|
|
|
|
Pass a First Name to avoid entering it manually. It will be used to automatically
|
|
|
|
|
create a new Telegram account in case the phone number you passed is not registered yet.
|
2018-04-01 16:18:06 +00:00
|
|
|
|
Only applicable for new sessions.
|
2018-01-24 20:46:28 +00:00
|
|
|
|
|
2018-03-25 19:41:19 +00:00
|
|
|
|
last_name (``str``, optional):
|
2018-01-24 20:46:28 +00:00
|
|
|
|
Same purpose as *first_name*; pass a Last Name to avoid entering it manually. It can
|
2018-04-01 16:18:06 +00:00
|
|
|
|
be an empty string: "". Only applicable for new sessions.
|
2018-01-26 10:49:07 +00:00
|
|
|
|
|
2018-03-25 19:41:19 +00:00
|
|
|
|
workers (``int``, optional):
|
2018-02-13 11:08:10 +00:00
|
|
|
|
Thread pool size for handling incoming updates. Defaults to 4.
|
2018-04-22 10:41:50 +00:00
|
|
|
|
|
|
|
|
|
workdir (``str``, optional):
|
|
|
|
|
Define a custom working directory. The working directory is the location in your filesystem
|
|
|
|
|
where Pyrogram will store your session files. Defaults to "." (current directory).
|
2017-12-29 20:44:45 +00:00
|
|
|
|
"""
|
|
|
|
|
|
2018-03-23 11:59:03 +00:00
|
|
|
|
INVITE_LINK_RE = re.compile(r"^(?:https?://)?(?:t\.me/joinchat/)([\w-]+)$")
|
2018-03-24 14:02:03 +00:00
|
|
|
|
BOT_TOKEN_RE = re.compile(r"^\d+:[\w-]+$")
|
2017-12-05 11:42:09 +00:00
|
|
|
|
DIALOGS_AT_ONCE = 100
|
2018-03-19 00:08:59 +00:00
|
|
|
|
UPDATES_WORKERS = 1
|
2018-02-18 17:11:33 +00:00
|
|
|
|
DOWNLOAD_WORKERS = 1
|
2018-04-13 14:20:51 +00:00
|
|
|
|
OFFLINE_SLEEP = 300
|
2017-12-05 11:42:09 +00:00
|
|
|
|
|
2018-04-08 14:50:18 +00:00
|
|
|
|
MEDIA_TYPE_ID = {
|
2018-04-15 23:07:02 +00:00
|
|
|
|
0: "thumbnail",
|
|
|
|
|
2: "photo",
|
|
|
|
|
3: "voice",
|
|
|
|
|
4: "video",
|
|
|
|
|
5: "document",
|
|
|
|
|
8: "sticker",
|
|
|
|
|
9: "audio",
|
|
|
|
|
10: "gif",
|
|
|
|
|
13: "video_note"
|
2018-04-08 14:50:18 +00:00
|
|
|
|
}
|
|
|
|
|
|
2018-01-24 16:53:30 +00:00
|
|
|
|
def __init__(self,
|
|
|
|
|
session_name: str,
|
2018-04-08 10:44:27 +00:00
|
|
|
|
api_id: int or str = None,
|
2018-04-05 10:55:34 +00:00
|
|
|
|
api_hash: str = None,
|
2018-04-13 10:30:13 +00:00
|
|
|
|
proxy: dict = None,
|
2018-01-24 16:53:30 +00:00
|
|
|
|
test_mode: bool = False,
|
|
|
|
|
phone_number: str = None,
|
|
|
|
|
phone_code: str or callable = None,
|
|
|
|
|
password: str = None,
|
2018-04-01 15:38:22 +00:00
|
|
|
|
force_sms: bool = False,
|
2018-01-24 16:53:30 +00:00
|
|
|
|
first_name: str = None,
|
2018-01-26 10:41:09 +00:00
|
|
|
|
last_name: str = None,
|
2018-04-19 08:06:41 +00:00
|
|
|
|
workers: int = 4,
|
|
|
|
|
workdir: str = "."):
|
2017-12-05 11:42:09 +00:00
|
|
|
|
self.session_name = session_name
|
2018-04-08 11:21:16 +00:00
|
|
|
|
self.api_id = int(api_id) if api_id else None
|
2018-04-05 10:55:34 +00:00
|
|
|
|
self.api_hash = api_hash
|
2018-02-21 12:31:27 +00:00
|
|
|
|
self.proxy = proxy
|
2017-12-05 11:42:09 +00:00
|
|
|
|
self.test_mode = test_mode
|
|
|
|
|
|
2018-01-24 16:53:30 +00:00
|
|
|
|
self.phone_number = phone_number
|
|
|
|
|
self.password = password
|
|
|
|
|
self.phone_code = phone_code
|
|
|
|
|
self.first_name = first_name
|
|
|
|
|
self.last_name = last_name
|
2018-04-01 15:38:22 +00:00
|
|
|
|
self.force_sms = force_sms
|
2018-01-24 16:53:30 +00:00
|
|
|
|
|
2018-01-26 10:41:09 +00:00
|
|
|
|
self.workers = workers
|
2018-04-19 08:06:41 +00:00
|
|
|
|
self.workdir = workdir
|
2018-01-26 10:41:09 +00:00
|
|
|
|
|
2018-03-24 14:02:03 +00:00
|
|
|
|
self.token = None
|
|
|
|
|
|
2017-12-05 11:42:09 +00:00
|
|
|
|
self.dc_id = None
|
|
|
|
|
self.auth_key = None
|
|
|
|
|
self.user_id = None
|
2018-04-13 13:05:46 +00:00
|
|
|
|
self.date = None
|
2017-12-05 11:42:09 +00:00
|
|
|
|
|
2018-03-19 02:37:43 +00:00
|
|
|
|
self.rnd_id = MsgId
|
2017-12-05 11:42:09 +00:00
|
|
|
|
|
|
|
|
|
self.peers_by_id = {}
|
|
|
|
|
self.peers_by_username = {}
|
2018-02-20 14:28:01 +00:00
|
|
|
|
self.peers_by_phone = {}
|
2017-12-05 11:42:09 +00:00
|
|
|
|
|
2018-02-11 12:19:52 +00:00
|
|
|
|
self.channels_pts = {}
|
|
|
|
|
|
2017-12-13 09:44:24 +00:00
|
|
|
|
self.markdown = Markdown(self.peers_by_id)
|
2018-01-21 23:26:43 +00:00
|
|
|
|
self.html = HTML(self.peers_by_id)
|
2017-12-13 09:44:24 +00:00
|
|
|
|
|
2017-12-05 11:42:09 +00:00
|
|
|
|
self.session = None
|
|
|
|
|
|
2018-03-19 02:37:43 +00:00
|
|
|
|
self.is_started = None
|
2018-02-25 17:43:30 +00:00
|
|
|
|
self.is_idle = None
|
2017-12-08 22:40:29 +00:00
|
|
|
|
|
2018-02-13 11:08:10 +00:00
|
|
|
|
self.updates_queue = Queue()
|
2018-04-13 17:09:00 +00:00
|
|
|
|
self.updates_workers_list = []
|
2018-04-06 16:48:41 +00:00
|
|
|
|
self.download_queue = Queue()
|
2018-04-13 17:09:00 +00:00
|
|
|
|
self.download_workers_list = []
|
2018-04-06 16:48:41 +00:00
|
|
|
|
|
|
|
|
|
self.dispatcher = Dispatcher(self, workers)
|
2018-02-13 11:08:10 +00:00
|
|
|
|
self.update_handler = None
|
2018-02-08 19:46:47 +00:00
|
|
|
|
|
2018-04-10 12:54:39 +00:00
|
|
|
|
def on_message(self, filters=None, group: int = 0):
|
2018-04-11 01:16:48 +00:00
|
|
|
|
"""Use this decorator to automatically register a function for handling
|
|
|
|
|
messages. This does the same thing as :meth:`add_handler` using the
|
|
|
|
|
MessageHandler.
|
|
|
|
|
|
|
|
|
|
Args:
|
|
|
|
|
filters (:obj:`Filters <pyrogram.Filters>`):
|
|
|
|
|
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.
|
|
|
|
|
"""
|
|
|
|
|
|
2018-04-09 22:24:03 +00:00
|
|
|
|
def decorator(func):
|
2018-04-10 12:54:39 +00:00
|
|
|
|
self.add_handler(pyrogram.MessageHandler(func, filters), group)
|
2018-04-09 22:24:03 +00:00
|
|
|
|
return func
|
2018-04-06 18:38:34 +00:00
|
|
|
|
|
|
|
|
|
return decorator
|
|
|
|
|
|
2018-04-08 11:20:31 +00:00
|
|
|
|
def on_raw_update(self, group: int = 0):
|
2018-04-11 01:16:48 +00:00
|
|
|
|
"""Use this decorator to automatically register a function for handling
|
|
|
|
|
raw updates. This does the same thing as :meth:`add_handler` using the
|
|
|
|
|
RawUpdateHandler.
|
|
|
|
|
|
|
|
|
|
Args:
|
|
|
|
|
group (``int``, optional):
|
|
|
|
|
The group identifier, defaults to 0.
|
|
|
|
|
"""
|
|
|
|
|
|
2018-04-10 12:54:39 +00:00
|
|
|
|
def decorator(func):
|
|
|
|
|
self.add_handler(pyrogram.RawUpdateHandler(func), group)
|
|
|
|
|
return func
|
|
|
|
|
|
|
|
|
|
return decorator
|
2018-04-08 11:20:31 +00:00
|
|
|
|
|
2018-04-11 01:16:48 +00:00
|
|
|
|
def add_handler(self, handler, group: int = 0):
|
|
|
|
|
"""Use this method to register an event handler.
|
|
|
|
|
|
|
|
|
|
You can register multiple handlers, but at most one handler within a group
|
|
|
|
|
will be used for a single event. To handle the same event more than once, register
|
|
|
|
|
your handler using a different group id (lower group id == higher priority).
|
|
|
|
|
|
|
|
|
|
Args:
|
2018-04-11 01:53:10 +00:00
|
|
|
|
handler (``Handler``):
|
2018-04-11 01:16:48 +00:00
|
|
|
|
The handler to be registered.
|
|
|
|
|
|
|
|
|
|
group (``int``, optional):
|
|
|
|
|
The group identifier, defaults to 0.
|
|
|
|
|
"""
|
2018-04-06 16:48:41 +00:00
|
|
|
|
self.dispatcher.add_handler(handler, group)
|
2018-02-18 17:11:33 +00:00
|
|
|
|
|
2018-04-23 11:37:50 +00:00
|
|
|
|
def start(self, debug: bool = False):
|
2018-01-03 16:39:12 +00:00
|
|
|
|
"""Use this method to start the Client after creating it.
|
2018-01-24 20:46:28 +00:00
|
|
|
|
Requires no parameters.
|
|
|
|
|
|
2018-04-23 11:37:50 +00:00
|
|
|
|
Args:
|
|
|
|
|
debug (``bool``, optional):
|
|
|
|
|
Enable or disable debug mode. When enabled, extra logging
|
|
|
|
|
lines will be printed out on your console.
|
|
|
|
|
|
2018-01-24 20:46:28 +00:00
|
|
|
|
Raises:
|
2018-03-25 19:59:04 +00:00
|
|
|
|
:class:`Error <pyrogram.Error>`
|
2018-01-24 20:46:28 +00:00
|
|
|
|
"""
|
2018-04-03 09:45:19 +00:00
|
|
|
|
if self.is_started:
|
|
|
|
|
raise ConnectionError("Client has already been started")
|
|
|
|
|
|
2018-04-23 11:37:50 +00:00
|
|
|
|
logging.basicConfig(
|
|
|
|
|
format=LOG_FORMAT,
|
|
|
|
|
datefmt="%Y-%m-%d %H:%M:%S",
|
|
|
|
|
stream=sys.stdout
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
if debug:
|
|
|
|
|
logging.getLogger().setLevel(logging.INFO)
|
|
|
|
|
else:
|
|
|
|
|
logging.getLogger().setLevel(logging.WARNING)
|
|
|
|
|
|
2018-03-24 14:02:03 +00:00
|
|
|
|
if self.BOT_TOKEN_RE.match(self.session_name):
|
|
|
|
|
self.token = self.session_name
|
|
|
|
|
self.session_name = self.session_name.split(":")[0]
|
|
|
|
|
|
2017-12-29 20:44:45 +00:00
|
|
|
|
self.load_config()
|
2018-04-13 13:03:46 +00:00
|
|
|
|
self.load_session()
|
2017-12-29 20:44:45 +00:00
|
|
|
|
|
2018-01-26 10:41:09 +00:00
|
|
|
|
self.session = Session(
|
|
|
|
|
self.dc_id,
|
|
|
|
|
self.test_mode,
|
|
|
|
|
self.proxy,
|
|
|
|
|
self.auth_key,
|
2018-04-05 10:55:34 +00:00
|
|
|
|
self.api_id,
|
2018-02-08 19:46:47 +00:00
|
|
|
|
client=self
|
2018-01-26 10:41:09 +00:00
|
|
|
|
)
|
2017-12-29 20:44:45 +00:00
|
|
|
|
|
2018-03-15 11:18:48 +00:00
|
|
|
|
self.session.start()
|
2018-03-19 02:37:43 +00:00
|
|
|
|
self.is_started = True
|
2017-12-29 20:44:45 +00:00
|
|
|
|
|
|
|
|
|
if self.user_id is None:
|
2018-03-15 11:18:48 +00:00
|
|
|
|
if self.token is None:
|
|
|
|
|
self.authorize_user()
|
|
|
|
|
else:
|
|
|
|
|
self.authorize_bot()
|
2017-12-29 20:44:45 +00:00
|
|
|
|
|
|
|
|
|
self.save_session()
|
|
|
|
|
|
2018-03-15 11:18:48 +00:00
|
|
|
|
if self.token is None:
|
2018-04-13 13:47:07 +00:00
|
|
|
|
now = time.time()
|
|
|
|
|
|
|
|
|
|
if abs(now - self.date) > Client.OFFLINE_SLEEP:
|
2018-04-13 14:41:20 +00:00
|
|
|
|
self.peers_by_username = {}
|
|
|
|
|
self.peers_by_phone = {}
|
|
|
|
|
|
2018-04-13 13:47:07 +00:00
|
|
|
|
self.get_dialogs()
|
|
|
|
|
self.get_contacts()
|
|
|
|
|
else:
|
|
|
|
|
self.send(functions.messages.GetPinnedDialogs())
|
|
|
|
|
self.get_dialogs_chunk(0)
|
2018-03-15 19:41:13 +00:00
|
|
|
|
else:
|
|
|
|
|
self.send(functions.updates.GetState())
|
2018-03-15 11:18:48 +00:00
|
|
|
|
|
2018-02-13 11:08:10 +00:00
|
|
|
|
for i in range(self.UPDATES_WORKERS):
|
2018-04-13 17:09:00 +00:00
|
|
|
|
self.updates_workers_list.append(
|
|
|
|
|
Thread(
|
|
|
|
|
target=self.updates_worker,
|
|
|
|
|
name="UpdatesWorker#{}".format(i + 1)
|
|
|
|
|
)
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
self.updates_workers_list[-1].start()
|
2017-12-29 20:44:45 +00:00
|
|
|
|
|
2018-02-18 17:11:33 +00:00
|
|
|
|
for i in range(self.DOWNLOAD_WORKERS):
|
2018-04-13 17:09:00 +00:00
|
|
|
|
self.download_workers_list.append(
|
|
|
|
|
Thread(
|
|
|
|
|
target=self.download_worker,
|
|
|
|
|
name="DownloadWorker#{}".format(i + 1)
|
|
|
|
|
)
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
self.download_workers_list[-1].start()
|
2018-02-18 17:11:33 +00:00
|
|
|
|
|
2018-04-06 16:48:41 +00:00
|
|
|
|
self.dispatcher.start()
|
|
|
|
|
|
2017-12-29 20:44:45 +00:00
|
|
|
|
mimetypes.init()
|
2018-04-13 13:21:34 +00:00
|
|
|
|
Syncer.add(self)
|
2017-12-29 20:44:45 +00:00
|
|
|
|
|
|
|
|
|
def stop(self):
|
2018-01-03 16:39:12 +00:00
|
|
|
|
"""Use this method to manually stop the Client.
|
|
|
|
|
Requires no parameters.
|
|
|
|
|
"""
|
2018-04-03 12:54:34 +00:00
|
|
|
|
if not self.is_started:
|
|
|
|
|
raise ConnectionError("Client is already stopped")
|
|
|
|
|
|
2018-02-13 11:08:10 +00:00
|
|
|
|
for _ in range(self.UPDATES_WORKERS):
|
|
|
|
|
self.updates_queue.put(None)
|
2018-02-08 19:46:47 +00:00
|
|
|
|
|
2018-04-13 17:09:00 +00:00
|
|
|
|
for i in self.updates_workers_list:
|
|
|
|
|
i.join()
|
|
|
|
|
|
2018-02-18 17:11:33 +00:00
|
|
|
|
for _ in range(self.DOWNLOAD_WORKERS):
|
|
|
|
|
self.download_queue.put(None)
|
|
|
|
|
|
2018-04-13 17:09:00 +00:00
|
|
|
|
for i in self.download_workers_list:
|
|
|
|
|
i.join()
|
|
|
|
|
|
2018-04-06 16:48:41 +00:00
|
|
|
|
self.dispatcher.stop()
|
|
|
|
|
|
2018-04-13 17:09:00 +00:00
|
|
|
|
self.is_started = False
|
|
|
|
|
self.session.stop()
|
|
|
|
|
|
2018-04-13 13:21:34 +00:00
|
|
|
|
Syncer.remove(self)
|
|
|
|
|
|
2018-03-15 11:18:48 +00:00
|
|
|
|
def authorize_bot(self):
|
|
|
|
|
try:
|
|
|
|
|
r = self.send(
|
|
|
|
|
functions.auth.ImportBotAuthorization(
|
|
|
|
|
flags=0,
|
2018-04-05 10:55:34 +00:00
|
|
|
|
api_id=self.api_id,
|
|
|
|
|
api_hash=self.api_hash,
|
2018-03-15 11:18:48 +00:00
|
|
|
|
bot_auth_token=self.token
|
|
|
|
|
)
|
|
|
|
|
)
|
|
|
|
|
except UserMigrate as e:
|
|
|
|
|
self.session.stop()
|
|
|
|
|
|
|
|
|
|
self.dc_id = e.x
|
|
|
|
|
self.auth_key = Auth(self.dc_id, self.test_mode, self.proxy).create()
|
|
|
|
|
|
|
|
|
|
self.session = Session(
|
|
|
|
|
self.dc_id,
|
|
|
|
|
self.test_mode,
|
|
|
|
|
self.proxy,
|
|
|
|
|
self.auth_key,
|
2018-04-05 10:55:34 +00:00
|
|
|
|
self.api_id,
|
2018-03-15 11:18:48 +00:00
|
|
|
|
client=self
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
self.session.start()
|
|
|
|
|
self.authorize_bot()
|
|
|
|
|
else:
|
|
|
|
|
self.user_id = r.user.id
|
|
|
|
|
|
|
|
|
|
def authorize_user(self):
|
|
|
|
|
phone_number_invalid_raises = self.phone_number is not None
|
|
|
|
|
phone_code_invalid_raises = self.phone_code is not None
|
|
|
|
|
password_hash_invalid_raises = self.password is not None
|
|
|
|
|
first_name_invalid_raises = self.first_name is not None
|
|
|
|
|
|
|
|
|
|
while True:
|
|
|
|
|
if self.phone_number is None:
|
|
|
|
|
self.phone_number = input("Enter phone number: ")
|
|
|
|
|
|
|
|
|
|
while True:
|
|
|
|
|
confirm = input("Is \"{}\" correct? (y/n): ".format(self.phone_number))
|
|
|
|
|
|
|
|
|
|
if confirm in ("y", "1"):
|
|
|
|
|
break
|
|
|
|
|
elif confirm in ("n", "2"):
|
|
|
|
|
self.phone_number = input("Enter phone number: ")
|
|
|
|
|
|
|
|
|
|
self.phone_number = self.phone_number.strip("+")
|
|
|
|
|
|
|
|
|
|
try:
|
|
|
|
|
r = self.send(
|
|
|
|
|
functions.auth.SendCode(
|
|
|
|
|
self.phone_number,
|
2018-04-05 10:55:34 +00:00
|
|
|
|
self.api_id,
|
|
|
|
|
self.api_hash
|
2018-03-15 11:18:48 +00:00
|
|
|
|
)
|
|
|
|
|
)
|
|
|
|
|
except (PhoneMigrate, NetworkMigrate) as e:
|
|
|
|
|
self.session.stop()
|
|
|
|
|
|
|
|
|
|
self.dc_id = e.x
|
|
|
|
|
self.auth_key = Auth(self.dc_id, self.test_mode, self.proxy).create()
|
|
|
|
|
|
|
|
|
|
self.session = Session(
|
|
|
|
|
self.dc_id,
|
|
|
|
|
self.test_mode,
|
|
|
|
|
self.proxy,
|
|
|
|
|
self.auth_key,
|
2018-04-05 10:55:34 +00:00
|
|
|
|
self.api_id,
|
2018-03-15 11:18:48 +00:00
|
|
|
|
client=self
|
|
|
|
|
)
|
|
|
|
|
self.session.start()
|
|
|
|
|
|
|
|
|
|
r = self.send(
|
|
|
|
|
functions.auth.SendCode(
|
|
|
|
|
self.phone_number,
|
2018-04-05 10:55:34 +00:00
|
|
|
|
self.api_id,
|
|
|
|
|
self.api_hash
|
2018-03-15 11:18:48 +00:00
|
|
|
|
)
|
|
|
|
|
)
|
|
|
|
|
break
|
|
|
|
|
except (PhoneNumberInvalid, PhoneNumberBanned) as e:
|
|
|
|
|
if phone_number_invalid_raises:
|
|
|
|
|
raise
|
|
|
|
|
else:
|
|
|
|
|
print(e.MESSAGE)
|
|
|
|
|
self.phone_number = None
|
|
|
|
|
except FloodWait as e:
|
|
|
|
|
print(e.MESSAGE.format(x=e.x))
|
|
|
|
|
time.sleep(e.x)
|
|
|
|
|
except Exception as e:
|
|
|
|
|
log.error(e, exc_info=True)
|
|
|
|
|
else:
|
|
|
|
|
break
|
|
|
|
|
|
|
|
|
|
phone_registered = r.phone_registered
|
|
|
|
|
phone_code_hash = r.phone_code_hash
|
|
|
|
|
|
2018-04-01 15:38:22 +00:00
|
|
|
|
if self.force_sms:
|
|
|
|
|
self.send(
|
|
|
|
|
functions.auth.ResendCode(
|
|
|
|
|
phone_number=self.phone_number,
|
|
|
|
|
phone_code_hash=phone_code_hash
|
|
|
|
|
)
|
|
|
|
|
)
|
|
|
|
|
|
2018-03-15 11:18:48 +00:00
|
|
|
|
while True:
|
|
|
|
|
self.phone_code = (
|
|
|
|
|
input("Enter phone code: ") if self.phone_code is None
|
|
|
|
|
else self.phone_code if type(self.phone_code) is str
|
2018-04-08 10:46:02 +00:00
|
|
|
|
else str(self.phone_code())
|
2018-03-15 11:18:48 +00:00
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
try:
|
|
|
|
|
if phone_registered:
|
|
|
|
|
r = self.send(
|
|
|
|
|
functions.auth.SignIn(
|
|
|
|
|
self.phone_number,
|
|
|
|
|
phone_code_hash,
|
|
|
|
|
self.phone_code
|
|
|
|
|
)
|
|
|
|
|
)
|
|
|
|
|
else:
|
|
|
|
|
try:
|
|
|
|
|
self.send(
|
|
|
|
|
functions.auth.SignIn(
|
|
|
|
|
self.phone_number,
|
|
|
|
|
phone_code_hash,
|
|
|
|
|
self.phone_code
|
|
|
|
|
)
|
|
|
|
|
)
|
|
|
|
|
except PhoneNumberUnoccupied:
|
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
self.first_name = self.first_name if self.first_name is not None else input("First name: ")
|
|
|
|
|
self.last_name = self.last_name if self.last_name is not None else input("Last name: ")
|
|
|
|
|
|
|
|
|
|
r = self.send(
|
|
|
|
|
functions.auth.SignUp(
|
|
|
|
|
self.phone_number,
|
|
|
|
|
phone_code_hash,
|
|
|
|
|
self.phone_code,
|
|
|
|
|
self.first_name,
|
|
|
|
|
self.last_name
|
|
|
|
|
)
|
|
|
|
|
)
|
|
|
|
|
except (PhoneCodeInvalid, PhoneCodeEmpty, PhoneCodeExpired, PhoneCodeHashEmpty) as e:
|
|
|
|
|
if phone_code_invalid_raises:
|
|
|
|
|
raise
|
|
|
|
|
else:
|
|
|
|
|
print(e.MESSAGE)
|
|
|
|
|
self.phone_code = None
|
|
|
|
|
except FirstnameInvalid as e:
|
|
|
|
|
if first_name_invalid_raises:
|
|
|
|
|
raise
|
|
|
|
|
else:
|
|
|
|
|
print(e.MESSAGE)
|
|
|
|
|
self.first_name = None
|
|
|
|
|
except SessionPasswordNeeded as e:
|
|
|
|
|
print(e.MESSAGE)
|
|
|
|
|
r = self.send(functions.account.GetPassword())
|
|
|
|
|
|
|
|
|
|
while True:
|
|
|
|
|
try:
|
|
|
|
|
|
|
|
|
|
if self.password is None:
|
|
|
|
|
print("Hint: {}".format(r.hint))
|
|
|
|
|
self.password = input("Enter password: ") # TODO: Use getpass
|
|
|
|
|
|
|
|
|
|
if type(self.password) is str:
|
|
|
|
|
self.password = r.current_salt + self.password.encode() + r.current_salt
|
|
|
|
|
|
|
|
|
|
password_hash = sha256(self.password).digest()
|
|
|
|
|
|
|
|
|
|
r = self.send(functions.auth.CheckPassword(password_hash))
|
|
|
|
|
except PasswordHashInvalid as e:
|
|
|
|
|
if password_hash_invalid_raises:
|
|
|
|
|
raise
|
|
|
|
|
else:
|
|
|
|
|
print(e.MESSAGE)
|
|
|
|
|
self.password = None
|
|
|
|
|
except FloodWait as e:
|
|
|
|
|
print(e.MESSAGE.format(x=e.x))
|
|
|
|
|
time.sleep(e.x)
|
|
|
|
|
except Exception as e:
|
|
|
|
|
log.error(e, exc_info=True)
|
|
|
|
|
else:
|
|
|
|
|
break
|
|
|
|
|
break
|
|
|
|
|
except FloodWait as e:
|
|
|
|
|
print(e.MESSAGE.format(x=e.x))
|
|
|
|
|
time.sleep(e.x)
|
|
|
|
|
except Exception as e:
|
|
|
|
|
log.error(e, exc_info=True)
|
|
|
|
|
else:
|
|
|
|
|
break
|
|
|
|
|
|
|
|
|
|
self.password = None
|
|
|
|
|
self.user_id = r.user.id
|
|
|
|
|
|
2018-02-09 00:52:40 +00:00
|
|
|
|
def fetch_peers(self, entities: list):
|
|
|
|
|
for entity in entities:
|
2018-03-26 11:41:00 +00:00
|
|
|
|
if isinstance(entity, types.User):
|
2018-02-09 00:52:40 +00:00
|
|
|
|
user_id = entity.id
|
|
|
|
|
|
|
|
|
|
access_hash = entity.access_hash
|
|
|
|
|
|
|
|
|
|
if access_hash is None:
|
|
|
|
|
continue
|
|
|
|
|
|
|
|
|
|
username = entity.username
|
2018-02-20 14:28:01 +00:00
|
|
|
|
phone = entity.phone
|
2018-02-09 00:52:40 +00:00
|
|
|
|
|
2018-03-26 11:41:00 +00:00
|
|
|
|
input_peer = types.InputPeerUser(
|
2018-02-09 00:52:40 +00:00
|
|
|
|
user_id=user_id,
|
|
|
|
|
access_hash=access_hash
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
self.peers_by_id[user_id] = input_peer
|
|
|
|
|
|
|
|
|
|
if username is not None:
|
2018-03-19 00:40:36 +00:00
|
|
|
|
self.peers_by_username[username.lower()] = input_peer
|
2018-02-09 00:52:40 +00:00
|
|
|
|
|
2018-02-20 14:28:01 +00:00
|
|
|
|
if phone is not None:
|
|
|
|
|
self.peers_by_phone[phone] = input_peer
|
|
|
|
|
|
2018-03-26 11:41:00 +00:00
|
|
|
|
if isinstance(entity, types.Chat):
|
2018-02-09 00:52:40 +00:00
|
|
|
|
chat_id = entity.id
|
2018-03-08 11:28:38 +00:00
|
|
|
|
peer_id = -chat_id
|
2018-02-09 00:52:40 +00:00
|
|
|
|
|
2018-03-26 11:41:00 +00:00
|
|
|
|
input_peer = types.InputPeerChat(
|
2018-02-09 00:52:40 +00:00
|
|
|
|
chat_id=chat_id
|
|
|
|
|
)
|
|
|
|
|
|
2018-03-08 11:28:38 +00:00
|
|
|
|
self.peers_by_id[peer_id] = input_peer
|
2018-02-09 00:52:40 +00:00
|
|
|
|
|
2018-03-26 11:41:00 +00:00
|
|
|
|
if isinstance(entity, types.Channel):
|
2018-02-09 00:52:40 +00:00
|
|
|
|
channel_id = entity.id
|
|
|
|
|
peer_id = int("-100" + str(channel_id))
|
|
|
|
|
|
|
|
|
|
access_hash = entity.access_hash
|
|
|
|
|
|
|
|
|
|
if access_hash is None:
|
|
|
|
|
continue
|
|
|
|
|
|
|
|
|
|
username = entity.username
|
|
|
|
|
|
2018-03-26 11:41:00 +00:00
|
|
|
|
input_peer = types.InputPeerChannel(
|
2018-02-09 00:52:40 +00:00
|
|
|
|
channel_id=channel_id,
|
|
|
|
|
access_hash=access_hash
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
self.peers_by_id[peer_id] = input_peer
|
|
|
|
|
|
|
|
|
|
if username is not None:
|
2018-03-19 00:40:36 +00:00
|
|
|
|
self.peers_by_username[username.lower()] = input_peer
|
2018-02-09 00:52:40 +00:00
|
|
|
|
|
2018-02-18 17:11:33 +00:00
|
|
|
|
def download_worker(self):
|
|
|
|
|
name = threading.current_thread().name
|
|
|
|
|
log.debug("{} started".format(name))
|
|
|
|
|
|
|
|
|
|
while True:
|
2018-02-19 12:11:35 +00:00
|
|
|
|
media = self.download_queue.get()
|
2018-02-18 17:11:33 +00:00
|
|
|
|
|
2018-02-19 12:11:35 +00:00
|
|
|
|
if media is None:
|
2018-02-18 17:11:33 +00:00
|
|
|
|
break
|
|
|
|
|
|
2018-04-15 23:07:02 +00:00
|
|
|
|
temp_file_path = ""
|
|
|
|
|
final_file_path = ""
|
|
|
|
|
|
2018-02-18 17:11:33 +00:00
|
|
|
|
try:
|
2018-03-20 20:20:04 +00:00
|
|
|
|
media, file_name, done, progress, path = media
|
2018-03-20 13:05:41 +00:00
|
|
|
|
|
2018-04-15 23:07:02 +00:00
|
|
|
|
file_id = media.file_id
|
|
|
|
|
size = media.file_size
|
|
|
|
|
|
2018-03-21 12:39:23 +00:00
|
|
|
|
directory, file_name = os.path.split(file_name)
|
|
|
|
|
directory = directory or "downloads"
|
2018-03-20 13:27:44 +00:00
|
|
|
|
|
2018-04-15 23:07:02 +00:00
|
|
|
|
try:
|
|
|
|
|
decoded = utils.decode(file_id)
|
|
|
|
|
fmt = "<iiqqqqi" if len(decoded) > 24 else "<iiqq"
|
|
|
|
|
unpacked = struct.unpack(fmt, decoded)
|
|
|
|
|
except (AssertionError, binascii.Error, struct.error):
|
|
|
|
|
raise FileIdInvalid from None
|
|
|
|
|
else:
|
|
|
|
|
media_type = unpacked[0]
|
|
|
|
|
dc_id = unpacked[1]
|
|
|
|
|
id = unpacked[2]
|
|
|
|
|
access_hash = unpacked[3]
|
|
|
|
|
volume_id = None
|
|
|
|
|
secret = None
|
|
|
|
|
local_id = None
|
|
|
|
|
|
|
|
|
|
if len(decoded) > 24:
|
|
|
|
|
volume_id = unpacked[4]
|
|
|
|
|
secret = unpacked[5]
|
|
|
|
|
local_id = unpacked[6]
|
|
|
|
|
|
|
|
|
|
media_type_str = Client.MEDIA_TYPE_ID.get(media_type, None)
|
|
|
|
|
|
|
|
|
|
if media_type_str:
|
|
|
|
|
log.info("The file_id belongs to a {}".format(media_type_str))
|
2018-03-13 15:26:53 +00:00
|
|
|
|
else:
|
2018-04-15 23:07:02 +00:00
|
|
|
|
raise FileIdInvalid("Unknown media type: {}".format(unpacked[0]))
|
2018-02-23 13:42:50 +00:00
|
|
|
|
|
2018-04-15 23:07:02 +00:00
|
|
|
|
file_name = file_name or getattr(media, "file_name", None)
|
|
|
|
|
|
|
|
|
|
if not file_name:
|
|
|
|
|
if media_type == 3:
|
|
|
|
|
extension = ".ogg"
|
|
|
|
|
elif media_type in (4, 10, 13):
|
|
|
|
|
extension = mimetypes.guess_extension(media.mime_type) or ".mp4"
|
|
|
|
|
elif media_type == 5:
|
|
|
|
|
extension = mimetypes.guess_extension(media.mime_type) or ".unknown"
|
|
|
|
|
elif media_type == 8:
|
|
|
|
|
extension = ".webp"
|
|
|
|
|
elif media_type == 9:
|
|
|
|
|
extension = mimetypes.guess_extension(media.mime_type) or ".mp3"
|
|
|
|
|
elif media_type == 0:
|
|
|
|
|
extension = ".jpg"
|
|
|
|
|
elif media_type == 2:
|
|
|
|
|
extension = ".jpg"
|
|
|
|
|
else:
|
|
|
|
|
continue
|
2018-02-23 13:42:50 +00:00
|
|
|
|
|
2018-04-15 23:07:02 +00:00
|
|
|
|
file_name = "{}_{}_{}{}".format(
|
|
|
|
|
media_type_str,
|
|
|
|
|
datetime.fromtimestamp(media.date or time.time()).strftime("%Y-%m-%d_%H-%M-%S"),
|
|
|
|
|
self.rnd_id(),
|
|
|
|
|
extension
|
|
|
|
|
)
|
2018-02-23 13:42:50 +00:00
|
|
|
|
|
2018-04-15 23:07:02 +00:00
|
|
|
|
temp_file_path = self.get_file(
|
|
|
|
|
dc_id=dc_id,
|
|
|
|
|
id=id,
|
|
|
|
|
access_hash=access_hash,
|
|
|
|
|
volume_id=volume_id,
|
|
|
|
|
local_id=local_id,
|
|
|
|
|
secret=secret,
|
|
|
|
|
size=size,
|
|
|
|
|
progress=progress
|
|
|
|
|
)
|
2018-02-23 13:42:50 +00:00
|
|
|
|
|
2018-03-21 12:39:23 +00:00
|
|
|
|
if temp_file_path:
|
2018-03-21 15:17:13 +00:00
|
|
|
|
final_file_path = os.path.abspath(re.sub("\\\\", "/", os.path.join(directory, file_name)))
|
2018-03-21 14:42:32 +00:00
|
|
|
|
os.makedirs(directory, exist_ok=True)
|
|
|
|
|
shutil.move(temp_file_path, final_file_path)
|
2018-02-18 17:11:33 +00:00
|
|
|
|
except Exception as e:
|
|
|
|
|
log.error(e, exc_info=True)
|
|
|
|
|
|
2018-03-20 20:20:04 +00:00
|
|
|
|
try:
|
2018-03-21 12:39:23 +00:00
|
|
|
|
os.remove(temp_file_path)
|
|
|
|
|
except OSError:
|
|
|
|
|
pass
|
|
|
|
|
else:
|
|
|
|
|
# TODO: "" or None for faulty download, which is better?
|
|
|
|
|
# os.path methods return "" in case something does not exist, I prefer this.
|
|
|
|
|
# For now let's keep None
|
|
|
|
|
path[0] = final_file_path or None
|
|
|
|
|
finally:
|
|
|
|
|
done.set()
|
2018-03-20 20:20:04 +00:00
|
|
|
|
|
2018-02-18 17:11:33 +00:00
|
|
|
|
log.debug("{} stopped".format(name))
|
|
|
|
|
|
2018-02-13 11:08:10 +00:00
|
|
|
|
def updates_worker(self):
|
2018-02-08 19:46:47 +00:00
|
|
|
|
name = threading.current_thread().name
|
|
|
|
|
log.debug("{} started".format(name))
|
|
|
|
|
|
|
|
|
|
while True:
|
2018-02-13 11:08:10 +00:00
|
|
|
|
updates = self.updates_queue.get()
|
2018-02-08 19:46:47 +00:00
|
|
|
|
|
2018-02-13 11:08:10 +00:00
|
|
|
|
if updates is None:
|
2018-02-08 19:46:47 +00:00
|
|
|
|
break
|
|
|
|
|
|
|
|
|
|
try:
|
2018-02-13 11:08:10 +00:00
|
|
|
|
if isinstance(updates, (types.Update, types.UpdatesCombined)):
|
|
|
|
|
self.fetch_peers(updates.users)
|
|
|
|
|
self.fetch_peers(updates.chats)
|
2018-02-09 00:52:40 +00:00
|
|
|
|
|
2018-02-13 11:08:10 +00:00
|
|
|
|
for update in updates.updates:
|
2018-02-11 12:19:52 +00:00
|
|
|
|
channel_id = getattr(
|
|
|
|
|
getattr(
|
|
|
|
|
getattr(
|
2018-02-13 11:08:10 +00:00
|
|
|
|
update, "message", None
|
2018-02-11 12:19:52 +00:00
|
|
|
|
), "to_id", None
|
|
|
|
|
), "channel_id", None
|
2018-02-13 11:08:10 +00:00
|
|
|
|
) or getattr(update, "channel_id", None)
|
2018-02-11 12:19:52 +00:00
|
|
|
|
|
2018-02-13 11:08:10 +00:00
|
|
|
|
pts = getattr(update, "pts", None)
|
2018-03-19 00:08:34 +00:00
|
|
|
|
pts_count = getattr(update, "pts_count", None)
|
|
|
|
|
|
|
|
|
|
if isinstance(update, types.UpdateNewChannelMessage):
|
2018-03-25 15:49:43 +00:00
|
|
|
|
message = update.message
|
|
|
|
|
|
|
|
|
|
if not isinstance(message, types.MessageEmpty):
|
|
|
|
|
diff = self.send(
|
|
|
|
|
functions.updates.GetChannelDifference(
|
2018-04-02 09:11:38 +00:00
|
|
|
|
channel=self.resolve_peer(int("-100" + str(update.message.to_id.channel_id))),
|
2018-03-25 15:49:43 +00:00
|
|
|
|
filter=types.ChannelMessagesFilter(
|
|
|
|
|
ranges=[types.MessageRange(
|
|
|
|
|
min_id=update.message.id,
|
|
|
|
|
max_id=update.message.id
|
|
|
|
|
)]
|
|
|
|
|
),
|
|
|
|
|
pts=pts - pts_count,
|
|
|
|
|
limit=pts
|
|
|
|
|
)
|
2018-03-19 00:08:34 +00:00
|
|
|
|
)
|
|
|
|
|
|
2018-03-26 11:34:54 +00:00
|
|
|
|
if not isinstance(diff, types.updates.ChannelDifferenceEmpty):
|
|
|
|
|
updates.users += diff.users
|
|
|
|
|
updates.chats += diff.chats
|
2018-02-11 12:19:52 +00:00
|
|
|
|
|
|
|
|
|
if channel_id and pts:
|
|
|
|
|
if channel_id not in self.channels_pts:
|
|
|
|
|
self.channels_pts[channel_id] = []
|
|
|
|
|
|
|
|
|
|
if pts in self.channels_pts[channel_id]:
|
|
|
|
|
continue
|
|
|
|
|
|
|
|
|
|
self.channels_pts[channel_id].append(pts)
|
|
|
|
|
|
|
|
|
|
if len(self.channels_pts[channel_id]) > 50:
|
|
|
|
|
self.channels_pts[channel_id] = self.channels_pts[channel_id][25:]
|
|
|
|
|
|
2018-04-06 16:48:41 +00:00
|
|
|
|
self.dispatcher.updates.put((update, updates.users, updates.chats))
|
2018-02-13 11:08:10 +00:00
|
|
|
|
elif isinstance(updates, (types.UpdateShortMessage, types.UpdateShortChatMessage)):
|
2018-02-10 15:30:13 +00:00
|
|
|
|
diff = self.send(
|
|
|
|
|
functions.updates.GetDifference(
|
2018-02-13 11:08:10 +00:00
|
|
|
|
pts=updates.pts - updates.pts_count,
|
|
|
|
|
date=updates.date,
|
2018-02-10 15:30:13 +00:00
|
|
|
|
qts=-1
|
2018-02-09 00:52:40 +00:00
|
|
|
|
)
|
2018-02-10 15:30:13 +00:00
|
|
|
|
)
|
2018-02-09 00:52:40 +00:00
|
|
|
|
|
2018-04-06 16:48:41 +00:00
|
|
|
|
self.dispatcher.updates.put((
|
2018-02-10 15:30:13 +00:00
|
|
|
|
types.UpdateNewMessage(
|
|
|
|
|
message=diff.new_messages[0],
|
2018-02-13 11:08:10 +00:00
|
|
|
|
pts=updates.pts,
|
|
|
|
|
pts_count=updates.pts_count
|
2018-02-10 15:30:13 +00:00
|
|
|
|
),
|
|
|
|
|
diff.users,
|
|
|
|
|
diff.chats
|
|
|
|
|
))
|
2018-02-13 11:08:10 +00:00
|
|
|
|
elif isinstance(updates, types.UpdateShort):
|
2018-04-06 16:48:41 +00:00
|
|
|
|
self.dispatcher.updates.put((updates.update, [], []))
|
2018-02-08 19:46:47 +00:00
|
|
|
|
except Exception as e:
|
|
|
|
|
log.error(e, exc_info=True)
|
|
|
|
|
|
|
|
|
|
log.debug("{} stopped".format(name))
|
|
|
|
|
|
2017-12-30 18:23:18 +00:00
|
|
|
|
def signal_handler(self, *args):
|
2018-02-25 17:43:30 +00:00
|
|
|
|
self.is_idle = False
|
2017-12-30 18:23:18 +00:00
|
|
|
|
|
|
|
|
|
def idle(self, stop_signals: tuple = (SIGINT, SIGTERM, SIGABRT)):
|
|
|
|
|
"""Blocks the program execution until one of the signals are received,
|
|
|
|
|
then gently stop the Client by closing the underlying connection.
|
|
|
|
|
|
|
|
|
|
Args:
|
2018-03-25 19:41:19 +00:00
|
|
|
|
stop_signals (``tuple``, optional):
|
2017-12-30 18:23:18 +00:00
|
|
|
|
Iterable containing signals the signal handler will listen to.
|
2018-01-15 12:42:05 +00:00
|
|
|
|
Defaults to (SIGINT, SIGTERM, SIGABRT).
|
2017-12-30 18:23:18 +00:00
|
|
|
|
"""
|
|
|
|
|
for s in stop_signals:
|
|
|
|
|
signal(s, self.signal_handler)
|
|
|
|
|
|
2018-02-25 17:43:30 +00:00
|
|
|
|
self.is_idle = True
|
|
|
|
|
|
|
|
|
|
while self.is_idle:
|
|
|
|
|
time.sleep(1)
|
2017-12-30 18:23:18 +00:00
|
|
|
|
|
2018-04-21 18:50:30 +00:00
|
|
|
|
self.stop()
|
|
|
|
|
|
2017-12-05 11:42:09 +00:00
|
|
|
|
def send(self, data: Object):
|
2018-02-12 15:39:57 +00:00
|
|
|
|
"""Use this method to send Raw Function queries.
|
2017-12-29 20:44:45 +00:00
|
|
|
|
|
|
|
|
|
This method makes possible to manually call every single Telegram API method in a low-level manner.
|
2018-03-25 20:12:52 +00:00
|
|
|
|
Available functions are listed in the :obj:`functions <pyrogram.api.functions>` package and may accept compound
|
|
|
|
|
data types from :obj:`types <pyrogram.api.types>` as well as bare types such as ``int``, ``str``, etc...
|
2017-12-29 20:44:45 +00:00
|
|
|
|
|
|
|
|
|
Args:
|
2018-03-25 19:41:19 +00:00
|
|
|
|
data (``Object``):
|
2017-12-29 20:44:45 +00:00
|
|
|
|
The API Scheme function filled with proper arguments.
|
|
|
|
|
|
2017-12-30 18:23:18 +00:00
|
|
|
|
Raises:
|
2018-03-25 19:59:04 +00:00
|
|
|
|
:class:`Error <pyrogram.Error>`
|
2017-12-29 20:44:45 +00:00
|
|
|
|
"""
|
2018-04-03 09:40:08 +00:00
|
|
|
|
if not self.is_started:
|
|
|
|
|
raise ConnectionError("Client has not been started")
|
2018-02-26 14:44:08 +00:00
|
|
|
|
|
2018-04-03 09:40:08 +00:00
|
|
|
|
r = self.session.send(data)
|
2018-02-26 14:44:08 +00:00
|
|
|
|
|
2018-04-03 09:40:08 +00:00
|
|
|
|
self.fetch_peers(getattr(r, "users", []))
|
|
|
|
|
self.fetch_peers(getattr(r, "chats", []))
|
|
|
|
|
|
|
|
|
|
return r
|
2017-12-05 11:42:09 +00:00
|
|
|
|
|
|
|
|
|
def load_config(self):
|
2018-01-16 21:05:19 +00:00
|
|
|
|
parser = ConfigParser()
|
|
|
|
|
parser.read("config.ini")
|
2017-12-05 11:42:09 +00:00
|
|
|
|
|
2018-04-05 10:55:34 +00:00
|
|
|
|
if self.api_id and self.api_hash:
|
|
|
|
|
pass
|
2018-02-21 12:31:27 +00:00
|
|
|
|
else:
|
2018-04-05 10:55:34 +00:00
|
|
|
|
if parser.has_section("pyrogram"):
|
|
|
|
|
self.api_id = parser.getint("pyrogram", "api_id")
|
|
|
|
|
self.api_hash = parser.get("pyrogram", "api_hash")
|
|
|
|
|
else:
|
2018-04-12 12:11:01 +00:00
|
|
|
|
raise AttributeError(
|
|
|
|
|
"No API Key found. "
|
|
|
|
|
"More info: https://docs.pyrogram.ml/start/ProjectSetup#configuration"
|
|
|
|
|
)
|
2017-12-05 11:42:09 +00:00
|
|
|
|
|
2018-04-13 10:30:13 +00:00
|
|
|
|
if self.proxy:
|
|
|
|
|
pass
|
|
|
|
|
else:
|
|
|
|
|
self.proxy = {}
|
|
|
|
|
|
|
|
|
|
if parser.has_section("proxy"):
|
|
|
|
|
self.proxy["enabled"] = parser.getboolean("proxy", "enabled")
|
|
|
|
|
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
|
2018-01-16 21:05:19 +00:00
|
|
|
|
|
2018-04-13 13:03:46 +00:00
|
|
|
|
def load_session(self):
|
2017-12-05 11:42:09 +00:00
|
|
|
|
try:
|
2018-04-19 08:06:41 +00:00
|
|
|
|
with open(os.path.join(self.workdir, "{}.session".format(self.session_name)), encoding="utf-8") as f:
|
2017-12-05 11:42:09 +00:00
|
|
|
|
s = json.load(f)
|
|
|
|
|
except FileNotFoundError:
|
|
|
|
|
self.dc_id = 1
|
2018-04-13 13:47:07 +00:00
|
|
|
|
self.date = 0
|
2018-01-16 21:05:19 +00:00
|
|
|
|
self.auth_key = Auth(self.dc_id, self.test_mode, self.proxy).create()
|
2017-12-05 11:42:09 +00:00
|
|
|
|
else:
|
|
|
|
|
self.dc_id = s["dc_id"]
|
|
|
|
|
self.test_mode = s["test_mode"]
|
|
|
|
|
self.auth_key = base64.b64decode("".join(s["auth_key"]))
|
|
|
|
|
self.user_id = s["user_id"]
|
2018-04-13 13:47:07 +00:00
|
|
|
|
self.date = s.get("date", 0)
|
2017-12-05 11:42:09 +00:00
|
|
|
|
|
2018-04-13 13:20:37 +00:00
|
|
|
|
for k, v in s.get("peers_by_id", {}).items():
|
|
|
|
|
self.peers_by_id[int(k)] = utils.get_input_peer(int(k), v)
|
|
|
|
|
|
|
|
|
|
for k, v in s.get("peers_by_username", {}).items():
|
2018-04-13 13:47:07 +00:00
|
|
|
|
peer = self.peers_by_id.get(v, None)
|
|
|
|
|
|
|
|
|
|
if peer:
|
|
|
|
|
self.peers_by_username[k] = peer
|
2018-04-13 13:20:37 +00:00
|
|
|
|
|
|
|
|
|
for k, v in s.get("peers_by_phone", {}).items():
|
2018-04-13 13:47:07 +00:00
|
|
|
|
peer = self.peers_by_id.get(v, None)
|
|
|
|
|
|
|
|
|
|
if peer:
|
|
|
|
|
self.peers_by_phone[k] = peer
|
2017-12-05 11:42:09 +00:00
|
|
|
|
|
|
|
|
|
def save_session(self):
|
|
|
|
|
auth_key = base64.b64encode(self.auth_key).decode()
|
|
|
|
|
auth_key = [auth_key[i: i + 43] for i in range(0, len(auth_key), 43)]
|
|
|
|
|
|
2018-04-19 08:06:41 +00:00
|
|
|
|
os.makedirs(self.workdir, exist_ok=True)
|
|
|
|
|
|
|
|
|
|
with open(os.path.join(self.workdir, "{}.session".format(self.session_name)), "w", encoding="utf-8") as f:
|
2017-12-05 11:42:09 +00:00
|
|
|
|
json.dump(
|
|
|
|
|
dict(
|
|
|
|
|
dc_id=self.dc_id,
|
|
|
|
|
test_mode=self.test_mode,
|
|
|
|
|
auth_key=auth_key,
|
|
|
|
|
user_id=self.user_id,
|
2018-04-13 13:05:46 +00:00
|
|
|
|
date=self.date
|
2017-12-05 11:42:09 +00:00
|
|
|
|
),
|
|
|
|
|
f,
|
|
|
|
|
indent=4
|
|
|
|
|
)
|
|
|
|
|
|
2018-04-13 13:47:07 +00:00
|
|
|
|
def get_dialogs_chunk(self, offset_date):
|
2018-04-13 16:53:55 +00:00
|
|
|
|
while True:
|
|
|
|
|
try:
|
|
|
|
|
r = self.send(
|
|
|
|
|
functions.messages.GetDialogs(
|
|
|
|
|
offset_date, 0, types.InputPeerEmpty(),
|
|
|
|
|
self.DIALOGS_AT_ONCE, True
|
|
|
|
|
)
|
|
|
|
|
)
|
|
|
|
|
except FloodWait as e:
|
|
|
|
|
log.warning("get_dialogs flood: waiting {} seconds".format(e.x))
|
|
|
|
|
time.sleep(e.x)
|
|
|
|
|
else:
|
|
|
|
|
log.info("Total peers: {}".format(len(self.peers_by_id)))
|
|
|
|
|
return r
|
2018-04-13 13:47:07 +00:00
|
|
|
|
|
|
|
|
|
def get_dialogs(self):
|
|
|
|
|
self.send(functions.messages.GetPinnedDialogs())
|
2017-12-05 11:42:09 +00:00
|
|
|
|
|
2018-04-13 13:47:07 +00:00
|
|
|
|
dialogs = self.get_dialogs_chunk(0)
|
|
|
|
|
offset_date = utils.get_offset_date(dialogs)
|
2017-12-05 11:42:09 +00:00
|
|
|
|
|
|
|
|
|
while len(dialogs.dialogs) == self.DIALOGS_AT_ONCE:
|
2018-04-13 16:53:55 +00:00
|
|
|
|
dialogs = self.get_dialogs_chunk(offset_date)
|
2018-04-13 13:47:07 +00:00
|
|
|
|
offset_date = utils.get_offset_date(dialogs)
|
2018-04-02 10:14:22 +00:00
|
|
|
|
|
2018-04-13 13:47:07 +00:00
|
|
|
|
self.get_dialogs_chunk(0)
|
2018-04-02 10:14:22 +00:00
|
|
|
|
|
2018-01-26 14:22:07 +00:00
|
|
|
|
def resolve_peer(self, peer_id: int or str):
|
2018-02-13 15:24:04 +00:00
|
|
|
|
"""Use this method to get the *InputPeer* of a known *peer_id*.
|
2018-01-25 15:41:59 +00:00
|
|
|
|
|
2018-02-15 20:12:42 +00:00
|
|
|
|
It is intended to be used when working with Raw Functions (i.e: a Telegram API method you wish to use which is
|
2018-02-13 15:24:04 +00:00
|
|
|
|
not available yet in the Client class as an easy-to-use method).
|
|
|
|
|
|
|
|
|
|
Args:
|
2018-03-25 19:41:19 +00:00
|
|
|
|
peer_id (``int`` | ``str`` | ``Peer``):
|
|
|
|
|
The Peer ID you want to extract the InputPeer from. Can be one of these types: ``int`` (direct ID),
|
|
|
|
|
``str`` (@username), :obj:`PeerUser <pyrogram.api.types.PeerUser>`,
|
2018-02-13 15:24:04 +00:00
|
|
|
|
:obj:`PeerChat <pyrogram.api.types.PeerChat>`, :obj:`PeerChannel <pyrogram.api.types.PeerChannel>`
|
|
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
|
:obj:`InputPeerUser <pyrogram.api.types.InputPeerUser>` or
|
|
|
|
|
:obj:`InputPeerChat <pyrogram.api.types.InputPeerChat>` or
|
|
|
|
|
:obj:`InputPeerChannel <pyrogram.api.types.InputPeerChannel>` depending on the *peer_id*.
|
|
|
|
|
|
|
|
|
|
Raises:
|
2018-03-25 19:59:04 +00:00
|
|
|
|
:class:`Error <pyrogram.Error>`
|
2018-02-13 15:24:04 +00:00
|
|
|
|
"""
|
2018-02-13 13:28:45 +00:00
|
|
|
|
if type(peer_id) is str:
|
|
|
|
|
if peer_id in ("self", "me"):
|
2018-03-26 11:41:00 +00:00
|
|
|
|
return types.InputPeerSelf()
|
2018-01-25 15:41:59 +00:00
|
|
|
|
|
2018-03-17 18:24:27 +00:00
|
|
|
|
match = self.INVITE_LINK_RE.match(peer_id)
|
|
|
|
|
|
2018-03-18 11:12:27 +00:00
|
|
|
|
try:
|
|
|
|
|
decoded = base64.b64decode(match.group(1) + "=" * (-len(match.group(1)) % 4), "-_")
|
2018-03-17 18:24:27 +00:00
|
|
|
|
return self.resolve_peer(struct.unpack(">2iq", decoded)[1])
|
2018-03-18 11:12:27 +00:00
|
|
|
|
except (AttributeError, binascii.Error, struct.error):
|
|
|
|
|
pass
|
2018-03-17 18:24:27 +00:00
|
|
|
|
|
2018-04-05 09:31:01 +00:00
|
|
|
|
peer_id = re.sub(r"[@+\s]", "", peer_id.lower())
|
2018-02-20 14:48:10 +00:00
|
|
|
|
|
|
|
|
|
try:
|
|
|
|
|
int(peer_id)
|
|
|
|
|
except ValueError:
|
|
|
|
|
try:
|
|
|
|
|
return self.peers_by_username[peer_id]
|
|
|
|
|
except KeyError:
|
2018-03-19 00:51:47 +00:00
|
|
|
|
self.send(functions.contacts.ResolveUsername(peer_id))
|
|
|
|
|
return self.peers_by_username[peer_id]
|
2018-02-20 14:48:10 +00:00
|
|
|
|
else:
|
2018-02-20 14:28:01 +00:00
|
|
|
|
try:
|
|
|
|
|
return self.peers_by_phone[peer_id]
|
|
|
|
|
except KeyError:
|
|
|
|
|
raise PeerIdInvalid
|
|
|
|
|
|
2018-02-13 13:28:45 +00:00
|
|
|
|
if type(peer_id) is not int:
|
|
|
|
|
if isinstance(peer_id, types.PeerUser):
|
|
|
|
|
peer_id = peer_id.user_id
|
|
|
|
|
elif isinstance(peer_id, types.PeerChat):
|
2018-03-08 11:28:38 +00:00
|
|
|
|
peer_id = -peer_id.chat_id
|
2018-02-13 13:28:45 +00:00
|
|
|
|
elif isinstance(peer_id, types.PeerChannel):
|
|
|
|
|
peer_id = int("-100" + str(peer_id.channel_id))
|
|
|
|
|
|
2018-03-08 11:28:38 +00:00
|
|
|
|
try: # User
|
2018-02-13 13:28:45 +00:00
|
|
|
|
return self.peers_by_id[peer_id]
|
|
|
|
|
except KeyError:
|
2018-03-08 11:28:38 +00:00
|
|
|
|
try: # Chat
|
|
|
|
|
return self.peers_by_id[-peer_id]
|
2018-02-13 13:28:45 +00:00
|
|
|
|
except KeyError:
|
2018-03-08 11:28:38 +00:00
|
|
|
|
try: # Channel
|
|
|
|
|
return self.peers_by_id[int("-100" + str(peer_id))]
|
|
|
|
|
except (KeyError, ValueError):
|
|
|
|
|
raise PeerIdInvalid
|
2017-12-06 19:45:56 +00:00
|
|
|
|
|
|
|
|
|
def get_me(self):
|
2017-12-29 20:44:45 +00:00
|
|
|
|
"""A simple method for testing the user authorization. Requires no parameters.
|
|
|
|
|
|
|
|
|
|
Returns:
|
2018-01-06 11:27:28 +00:00
|
|
|
|
Full information about the user in form of a :obj:`UserFull <pyrogram.api.types.UserFull>` object.
|
2017-12-30 18:23:18 +00:00
|
|
|
|
|
|
|
|
|
Raises:
|
2018-03-25 19:59:04 +00:00
|
|
|
|
:class:`Error <pyrogram.Error>`
|
2017-12-29 20:44:45 +00:00
|
|
|
|
"""
|
2017-12-06 19:45:56 +00:00
|
|
|
|
return self.send(
|
|
|
|
|
functions.users.GetFullUser(
|
2018-03-26 11:41:00 +00:00
|
|
|
|
types.InputPeerSelf()
|
2017-12-06 19:45:56 +00:00
|
|
|
|
)
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
def send_message(self,
|
|
|
|
|
chat_id: int or str,
|
|
|
|
|
text: str,
|
2018-01-23 14:18:52 +00:00
|
|
|
|
parse_mode: str = "",
|
2017-12-06 19:45:56 +00:00
|
|
|
|
disable_web_page_preview: bool = None,
|
|
|
|
|
disable_notification: bool = None,
|
2018-02-07 02:24:50 +00:00
|
|
|
|
reply_to_message_id: int = None):
|
2017-12-29 20:44:45 +00:00
|
|
|
|
"""Use this method to send text messages.
|
|
|
|
|
|
|
|
|
|
Args:
|
2018-03-25 19:41:19 +00:00
|
|
|
|
chat_id (``int`` | ``str``):
|
2018-03-18 12:00:28 +00:00
|
|
|
|
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).
|
|
|
|
|
For a private channel/supergroup you can use its *t.me/joinchat/* link.
|
2017-12-29 20:44:45 +00:00
|
|
|
|
|
2018-03-25 19:41:19 +00:00
|
|
|
|
text (``str``):
|
2017-12-29 20:44:45 +00:00
|
|
|
|
Text of the message to be sent.
|
|
|
|
|
|
2018-03-25 19:41:19 +00:00
|
|
|
|
parse_mode (``str``):
|
2018-03-25 20:12:52 +00:00
|
|
|
|
Use :obj:`MARKDOWN <pyrogram.ParseMode.MARKDOWN>` or :obj:`HTML <pyrogram.ParseMode.HTML>`
|
|
|
|
|
if you want Telegram apps to show bold, italic, fixed-width text or inline URLs in your message.
|
2018-01-23 14:38:01 +00:00
|
|
|
|
Defaults to Markdown.
|
2018-01-23 14:18:52 +00:00
|
|
|
|
|
2018-03-25 19:41:19 +00:00
|
|
|
|
disable_web_page_preview (``bool``, optional):
|
2017-12-29 20:44:45 +00:00
|
|
|
|
Disables link previews for links in this message.
|
|
|
|
|
|
2018-03-25 19:41:19 +00:00
|
|
|
|
disable_notification (``bool``, optional):
|
2017-12-29 20:44:45 +00:00
|
|
|
|
Sends the message silently.
|
|
|
|
|
Users will receive a notification with no sound.
|
|
|
|
|
|
2018-03-25 19:41:19 +00:00
|
|
|
|
reply_to_message_id (``bool``, optional):
|
2017-12-29 20:44:45 +00:00
|
|
|
|
If the message is a reply, ID of the original message.
|
|
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
|
On success, the sent Message is returned.
|
2017-12-30 18:23:18 +00:00
|
|
|
|
|
|
|
|
|
Raises:
|
2018-03-25 19:59:04 +00:00
|
|
|
|
:class:`Error <pyrogram.Error>`
|
2017-12-29 20:44:45 +00:00
|
|
|
|
"""
|
2018-01-23 14:18:52 +00:00
|
|
|
|
style = self.html if parse_mode.lower() == "html" else self.markdown
|
|
|
|
|
|
2017-12-05 11:42:09 +00:00
|
|
|
|
return self.send(
|
|
|
|
|
functions.messages.SendMessage(
|
2017-12-06 19:45:56 +00:00
|
|
|
|
peer=self.resolve_peer(chat_id),
|
2017-12-06 19:26:01 +00:00
|
|
|
|
no_webpage=disable_web_page_preview or None,
|
|
|
|
|
silent=disable_notification or None,
|
2018-02-07 02:24:50 +00:00
|
|
|
|
reply_to_msg_id=reply_to_message_id,
|
2017-12-06 19:45:56 +00:00
|
|
|
|
random_id=self.rnd_id(),
|
2018-01-23 14:18:52 +00:00
|
|
|
|
**style.parse(text)
|
2017-12-06 19:45:56 +00:00
|
|
|
|
)
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
def forward_messages(self,
|
|
|
|
|
chat_id: int or str,
|
|
|
|
|
from_chat_id: int or str,
|
2018-04-23 11:49:31 +00:00
|
|
|
|
message_ids: list or int,
|
2017-12-06 19:45:56 +00:00
|
|
|
|
disable_notification: bool = None):
|
2017-12-29 20:44:45 +00:00
|
|
|
|
"""Use this method to forward messages of any kind.
|
|
|
|
|
|
|
|
|
|
Args:
|
2018-03-25 19:41:19 +00:00
|
|
|
|
chat_id (``int`` | ``str``):
|
2018-03-18 12:00:28 +00:00
|
|
|
|
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).
|
|
|
|
|
For a private channel/supergroup you can use its *t.me/joinchat/* link.
|
2017-12-29 20:44:45 +00:00
|
|
|
|
|
2018-03-25 19:41:19 +00:00
|
|
|
|
from_chat_id (``int`` | ``str``):
|
2018-03-18 12:00:28 +00:00
|
|
|
|
Unique identifier (int) or username (str) of the source chat where the original message was sent.
|
|
|
|
|
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).
|
|
|
|
|
For a private channel/supergroup you can use its *t.me/joinchat/* link.
|
2018-01-03 16:39:12 +00:00
|
|
|
|
|
2018-04-23 11:49:31 +00:00
|
|
|
|
message_ids (``list`` | ``int``):
|
|
|
|
|
A list of Message identifiers in the chat specified in *from_chat_id* or a single message id.
|
2017-12-29 20:44:45 +00:00
|
|
|
|
|
2018-03-25 19:41:19 +00:00
|
|
|
|
disable_notification (``bool``, optional):
|
2017-12-29 20:44:45 +00:00
|
|
|
|
Sends the message silently.
|
|
|
|
|
Users will receive a notification with no sound.
|
|
|
|
|
|
|
|
|
|
Returns:
|
2018-04-23 16:33:20 +00:00
|
|
|
|
On success and in case *message_ids* was a list, the returned value will be a list of the forwarded
|
|
|
|
|
:obj:`Messages <pyrogram.api.types.pyrogram.Message>` even if a list contains just one element, otherwise if
|
|
|
|
|
*message_ids* was an integer, the single forwarded :obj:`Message <pyrogram.api.types.pyrogram.Message>`
|
|
|
|
|
is returned.
|
2017-12-30 18:23:18 +00:00
|
|
|
|
|
|
|
|
|
Raises:
|
2018-03-25 19:59:04 +00:00
|
|
|
|
:class:`Error <pyrogram.Error>`
|
2017-12-29 20:44:45 +00:00
|
|
|
|
"""
|
2018-04-23 16:33:20 +00:00
|
|
|
|
is_list = isinstance(message_ids, list)
|
|
|
|
|
message_ids = message_ids if is_list else [message_ids]
|
2018-04-23 11:49:31 +00:00
|
|
|
|
|
2018-04-23 16:33:20 +00:00
|
|
|
|
r = self.send(
|
2017-12-06 19:45:56 +00:00
|
|
|
|
functions.messages.ForwardMessages(
|
|
|
|
|
to_peer=self.resolve_peer(chat_id),
|
|
|
|
|
from_peer=self.resolve_peer(from_chat_id),
|
|
|
|
|
id=message_ids,
|
|
|
|
|
silent=disable_notification or None,
|
|
|
|
|
random_id=[self.rnd_id() for _ in message_ids]
|
2017-12-05 11:42:09 +00:00
|
|
|
|
)
|
|
|
|
|
)
|
2017-12-06 20:01:23 +00:00
|
|
|
|
|
2018-04-23 16:33:20 +00:00
|
|
|
|
messages = []
|
|
|
|
|
|
|
|
|
|
users = {i.id: i for i in r.users}
|
|
|
|
|
chats = {i.id: i for i in r.chats}
|
|
|
|
|
|
|
|
|
|
for i in r.updates:
|
|
|
|
|
if isinstance(i, (types.UpdateNewMessage, types.UpdateNewChannelMessage)):
|
|
|
|
|
messages.append(
|
|
|
|
|
message_parser.parse_message(self, i.message, users, chats)
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
return messages if is_list else messages[0]
|
|
|
|
|
|
2017-12-29 20:44:45 +00:00
|
|
|
|
def send_photo(self,
|
2017-12-18 13:41:32 +00:00
|
|
|
|
chat_id: int or str,
|
2017-12-29 20:44:45 +00:00
|
|
|
|
photo: str,
|
|
|
|
|
caption: str = "",
|
2018-01-23 14:18:52 +00:00
|
|
|
|
parse_mode: str = "",
|
2017-12-29 20:44:45 +00:00
|
|
|
|
ttl_seconds: int = None,
|
2017-12-18 13:41:32 +00:00
|
|
|
|
disable_notification: bool = None,
|
2018-03-08 09:23:48 +00:00
|
|
|
|
reply_to_message_id: int = None,
|
|
|
|
|
progress: callable = None):
|
2017-12-29 20:44:45 +00:00
|
|
|
|
"""Use this method to send photos.
|
2017-12-14 08:34:58 +00:00
|
|
|
|
|
2017-12-29 20:44:45 +00:00
|
|
|
|
Args:
|
2018-03-25 19:41:19 +00:00
|
|
|
|
chat_id (``int`` | ``str``):
|
2018-03-18 12:00:28 +00:00
|
|
|
|
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).
|
|
|
|
|
For a private channel/supergroup you can use its *t.me/joinchat/* link.
|
2017-12-14 08:34:58 +00:00
|
|
|
|
|
2018-03-25 19:41:19 +00:00
|
|
|
|
photo (``str``):
|
2017-12-29 20:44:45 +00:00
|
|
|
|
Photo to send.
|
2018-04-08 14:50:18 +00:00
|
|
|
|
Pass a file_id as string to send a photo that exists on the Telegram servers,
|
|
|
|
|
pass an HTTP URL as a string for Telegram to get a photo from the Internet, or
|
|
|
|
|
pass a file path as string to upload a new photo that exists on your local machine.
|
2017-12-14 08:34:58 +00:00
|
|
|
|
|
2018-03-25 19:41:19 +00:00
|
|
|
|
caption (``bool``, optional):
|
2017-12-29 20:44:45 +00:00
|
|
|
|
Photo caption, 0-200 characters.
|
2017-12-14 08:34:58 +00:00
|
|
|
|
|
2018-03-25 19:41:19 +00:00
|
|
|
|
parse_mode (``str``):
|
2018-03-25 20:12:52 +00:00
|
|
|
|
Use :obj:`MARKDOWN <pyrogram.ParseMode.MARKDOWN>` or :obj:`HTML <pyrogram.ParseMode.HTML>`
|
|
|
|
|
if you want Telegram apps to show bold, italic, fixed-width text or inline URLs in your caption.
|
2018-01-23 14:38:01 +00:00
|
|
|
|
Defaults to Markdown.
|
2018-01-23 14:18:52 +00:00
|
|
|
|
|
2018-03-25 19:41:19 +00:00
|
|
|
|
ttl_seconds (``int``, optional):
|
2017-12-29 20:44:45 +00:00
|
|
|
|
Self-Destruct Timer.
|
2018-03-25 20:12:52 +00:00
|
|
|
|
If you set a timer, the photo will self-destruct in *ttl_seconds*
|
2018-01-03 16:39:12 +00:00
|
|
|
|
seconds after it was viewed.
|
2017-12-18 13:41:32 +00:00
|
|
|
|
|
2018-03-25 19:41:19 +00:00
|
|
|
|
disable_notification (``bool``, optional):
|
2017-12-29 20:44:45 +00:00
|
|
|
|
Sends the message silently.
|
|
|
|
|
Users will receive a notification with no sound.
|
2017-12-18 13:41:32 +00:00
|
|
|
|
|
2018-03-25 19:41:19 +00:00
|
|
|
|
reply_to_message_id (``int``, optional):
|
2017-12-29 20:44:45 +00:00
|
|
|
|
If the message is a reply, ID of the original message.
|
2017-12-14 08:44:51 +00:00
|
|
|
|
|
2018-03-25 19:41:19 +00:00
|
|
|
|
progress (``callable``):
|
2018-03-08 09:25:10 +00:00
|
|
|
|
Pass a callback function to view the upload progress.
|
2018-03-08 09:34:04 +00:00
|
|
|
|
The function must accept two arguments (current, total).
|
2018-03-08 09:25:10 +00:00
|
|
|
|
|
|
|
|
|
Other Parameters:
|
2018-03-25 19:41:19 +00:00
|
|
|
|
current (``int``):
|
2018-03-08 09:25:10 +00:00
|
|
|
|
The amount of bytes uploaded so far.
|
|
|
|
|
|
2018-03-25 19:41:19 +00:00
|
|
|
|
total (``int``):
|
2018-03-08 09:25:10 +00:00
|
|
|
|
The size of the file.
|
|
|
|
|
|
2017-12-29 20:44:45 +00:00
|
|
|
|
Returns:
|
2018-04-16 11:28:53 +00:00
|
|
|
|
On success, the sent :obj:`Message <pyrogram.api.types.pyrogram.Message>` is returned.
|
2017-12-30 18:23:18 +00:00
|
|
|
|
|
|
|
|
|
Raises:
|
2018-03-25 19:59:04 +00:00
|
|
|
|
:class:`Error <pyrogram.Error>`
|
2017-12-29 20:44:45 +00:00
|
|
|
|
"""
|
2018-04-08 14:50:18 +00:00
|
|
|
|
file = None
|
2018-01-23 14:18:52 +00:00
|
|
|
|
style = self.html if parse_mode.lower() == "html" else self.markdown
|
2018-04-08 14:50:18 +00:00
|
|
|
|
|
|
|
|
|
if os.path.exists(photo):
|
|
|
|
|
file = self.save_file(photo, progress=progress)
|
|
|
|
|
media = types.InputMediaUploadedPhoto(
|
|
|
|
|
file=file,
|
|
|
|
|
ttl_seconds=ttl_seconds
|
|
|
|
|
)
|
|
|
|
|
elif photo.startswith("http"):
|
|
|
|
|
media = types.InputMediaPhotoExternal(
|
|
|
|
|
url=photo,
|
|
|
|
|
ttl_seconds=ttl_seconds
|
|
|
|
|
)
|
|
|
|
|
else:
|
|
|
|
|
try:
|
2018-04-13 14:30:19 +00:00
|
|
|
|
decoded = utils.decode(photo)
|
2018-04-08 14:50:18 +00:00
|
|
|
|
fmt = "<iiqqqqi" if len(decoded) > 24 else "<iiqq"
|
|
|
|
|
unpacked = struct.unpack(fmt, decoded)
|
|
|
|
|
except (AssertionError, binascii.Error, struct.error):
|
|
|
|
|
raise FileIdInvalid from None
|
|
|
|
|
else:
|
|
|
|
|
if unpacked[0] != 2:
|
|
|
|
|
media_type = Client.MEDIA_TYPE_ID.get(unpacked[0], None)
|
|
|
|
|
|
|
|
|
|
if media_type:
|
|
|
|
|
raise FileIdInvalid("The file_id belongs to a {}".format(media_type))
|
|
|
|
|
else:
|
|
|
|
|
raise FileIdInvalid("Unknown media type: {}".format(unpacked[0]))
|
|
|
|
|
|
|
|
|
|
media = types.InputMediaPhoto(
|
|
|
|
|
id=types.InputPhoto(
|
|
|
|
|
id=unpacked[2],
|
|
|
|
|
access_hash=unpacked[3]
|
|
|
|
|
),
|
|
|
|
|
ttl_seconds=ttl_seconds
|
|
|
|
|
)
|
2017-12-18 13:41:32 +00:00
|
|
|
|
|
|
|
|
|
while True:
|
|
|
|
|
try:
|
|
|
|
|
r = self.send(
|
|
|
|
|
functions.messages.SendMedia(
|
|
|
|
|
peer=self.resolve_peer(chat_id),
|
2018-04-08 14:50:18 +00:00
|
|
|
|
media=media,
|
2017-12-18 13:41:32 +00:00
|
|
|
|
silent=disable_notification or None,
|
|
|
|
|
reply_to_msg_id=reply_to_message_id,
|
2018-01-20 18:40:09 +00:00
|
|
|
|
random_id=self.rnd_id(),
|
2018-01-23 14:18:52 +00:00
|
|
|
|
**style.parse(caption)
|
2017-12-18 13:41:32 +00:00
|
|
|
|
)
|
|
|
|
|
)
|
|
|
|
|
except FilePartMissing as e:
|
|
|
|
|
self.save_file(photo, file_id=file.id, file_part=e.x)
|
|
|
|
|
else:
|
2018-04-08 14:50:18 +00:00
|
|
|
|
for i in r.updates:
|
|
|
|
|
if isinstance(i, (types.UpdateNewMessage, types.UpdateNewChannelMessage)):
|
|
|
|
|
users = {i.id: i for i in r.users}
|
|
|
|
|
chats = {i.id: i for i in r.chats}
|
|
|
|
|
|
|
|
|
|
return message_parser.parse_message(self, i.message, users, chats)
|
2017-12-14 09:57:30 +00:00
|
|
|
|
|
|
|
|
|
def send_audio(self,
|
|
|
|
|
chat_id: int or str,
|
|
|
|
|
audio: str,
|
|
|
|
|
caption: str = "",
|
2018-01-23 14:18:52 +00:00
|
|
|
|
parse_mode: str = "",
|
2017-12-14 09:57:30 +00:00
|
|
|
|
duration: int = 0,
|
|
|
|
|
performer: str = None,
|
|
|
|
|
title: str = None,
|
|
|
|
|
disable_notification: bool = None,
|
2018-03-08 09:23:48 +00:00
|
|
|
|
reply_to_message_id: int = None,
|
|
|
|
|
progress: callable = None):
|
2017-12-29 20:44:45 +00:00
|
|
|
|
"""Use this method to send audio files.
|
|
|
|
|
|
2018-01-03 16:39:12 +00:00
|
|
|
|
For sending voice messages, use the :obj:`send_voice` method instead.
|
2017-12-29 20:44:45 +00:00
|
|
|
|
|
|
|
|
|
Args:
|
2018-03-25 19:41:19 +00:00
|
|
|
|
chat_id (``int`` | ``str``):
|
2018-03-18 12:00:28 +00:00
|
|
|
|
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).
|
|
|
|
|
For a private channel/supergroup you can use its *t.me/joinchat/* link.
|
2017-12-29 20:44:45 +00:00
|
|
|
|
|
2018-03-25 19:41:19 +00:00
|
|
|
|
audio (``str``):
|
2017-12-29 20:44:45 +00:00
|
|
|
|
Audio file to send.
|
2018-04-09 20:02:44 +00:00
|
|
|
|
Pass a file_id as string to send an audio file that exists on the Telegram servers,
|
|
|
|
|
pass an HTTP URL as a string for Telegram to get an audio file from the Internet, or
|
|
|
|
|
pass a file path as string to upload a new audio file that exists on your local machine.
|
2017-12-29 20:44:45 +00:00
|
|
|
|
|
2018-03-25 19:41:19 +00:00
|
|
|
|
caption (``str``, optional):
|
2017-12-29 20:44:45 +00:00
|
|
|
|
Audio caption, 0-200 characters.
|
|
|
|
|
|
2018-03-25 19:41:19 +00:00
|
|
|
|
parse_mode (``str``):
|
2018-03-25 20:12:52 +00:00
|
|
|
|
Use :obj:`MARKDOWN <pyrogram.ParseMode.MARKDOWN>` or :obj:`HTML <pyrogram.ParseMode.HTML>`
|
|
|
|
|
if you want Telegram apps to show bold, italic, fixed-width text or inline URLs in your caption.
|
2018-01-23 14:38:01 +00:00
|
|
|
|
Defaults to Markdown.
|
2018-01-23 14:18:52 +00:00
|
|
|
|
|
2018-03-25 19:41:19 +00:00
|
|
|
|
duration (``int``, optional):
|
2017-12-29 20:44:45 +00:00
|
|
|
|
Duration of the audio in seconds.
|
|
|
|
|
|
2018-03-25 19:41:19 +00:00
|
|
|
|
performer (``str``, optional):
|
2017-12-29 20:44:45 +00:00
|
|
|
|
Performer.
|
|
|
|
|
|
2018-03-25 19:41:19 +00:00
|
|
|
|
title (``str``, optional):
|
2017-12-29 20:44:45 +00:00
|
|
|
|
Track name.
|
|
|
|
|
|
2018-03-25 19:41:19 +00:00
|
|
|
|
disable_notification (``bool``, optional):
|
2017-12-29 20:44:45 +00:00
|
|
|
|
Sends the message silently.
|
|
|
|
|
Users will receive a notification with no sound.
|
|
|
|
|
|
2018-03-25 19:41:19 +00:00
|
|
|
|
reply_to_message_id (``int``, optional):
|
2017-12-29 20:44:45 +00:00
|
|
|
|
If the message is a reply, ID of the original message.
|
|
|
|
|
|
2018-03-25 19:41:19 +00:00
|
|
|
|
progress (``callable``):
|
2018-03-08 09:25:10 +00:00
|
|
|
|
Pass a callback function to view the upload progress.
|
2018-03-08 09:34:04 +00:00
|
|
|
|
The function must accept two arguments (current, total).
|
2018-03-08 09:25:10 +00:00
|
|
|
|
|
|
|
|
|
Other Parameters:
|
2018-03-25 19:41:19 +00:00
|
|
|
|
current (``int``):
|
2018-03-08 09:25:10 +00:00
|
|
|
|
The amount of bytes uploaded so far.
|
|
|
|
|
|
2018-03-25 19:41:19 +00:00
|
|
|
|
total (``int``):
|
2018-03-08 09:25:10 +00:00
|
|
|
|
The size of the file.
|
|
|
|
|
|
2017-12-29 20:44:45 +00:00
|
|
|
|
Returns:
|
2018-04-16 11:28:53 +00:00
|
|
|
|
On success, the sent :obj:`Message <pyrogram.api.types.pyrogram.Message>` is returned.
|
2017-12-30 18:23:18 +00:00
|
|
|
|
|
|
|
|
|
Raises:
|
2018-03-25 19:59:04 +00:00
|
|
|
|
:class:`Error <pyrogram.Error>`
|
2017-12-29 20:44:45 +00:00
|
|
|
|
"""
|
2018-04-09 20:02:44 +00:00
|
|
|
|
file = None
|
2018-01-23 14:18:52 +00:00
|
|
|
|
style = self.html if parse_mode.lower() == "html" else self.markdown
|
2018-04-09 20:02:44 +00:00
|
|
|
|
|
|
|
|
|
if os.path.exists(audio):
|
|
|
|
|
file = self.save_file(audio, progress=progress)
|
|
|
|
|
media = types.InputMediaUploadedDocument(
|
|
|
|
|
mime_type=mimetypes.types_map.get("." + audio.split(".")[-1], "audio/mpeg"),
|
|
|
|
|
file=file,
|
|
|
|
|
attributes=[
|
|
|
|
|
types.DocumentAttributeAudio(
|
|
|
|
|
duration=duration,
|
|
|
|
|
performer=performer,
|
|
|
|
|
title=title
|
|
|
|
|
),
|
|
|
|
|
types.DocumentAttributeFilename(os.path.basename(audio))
|
|
|
|
|
]
|
|
|
|
|
)
|
|
|
|
|
elif audio.startswith("http"):
|
|
|
|
|
media = types.InputMediaDocumentExternal(
|
|
|
|
|
url=audio
|
|
|
|
|
)
|
|
|
|
|
else:
|
|
|
|
|
try:
|
2018-04-13 14:30:19 +00:00
|
|
|
|
decoded = utils.decode(audio)
|
2018-04-09 20:02:44 +00:00
|
|
|
|
fmt = "<iiqqqqi" if len(decoded) > 24 else "<iiqq"
|
|
|
|
|
unpacked = struct.unpack(fmt, decoded)
|
|
|
|
|
except (AssertionError, binascii.Error, struct.error):
|
|
|
|
|
raise FileIdInvalid from None
|
|
|
|
|
else:
|
|
|
|
|
if unpacked[0] != 9:
|
|
|
|
|
media_type = Client.MEDIA_TYPE_ID.get(unpacked[0], None)
|
|
|
|
|
|
|
|
|
|
if media_type:
|
|
|
|
|
raise FileIdInvalid("The file_id belongs to a {}".format(media_type))
|
|
|
|
|
else:
|
|
|
|
|
raise FileIdInvalid("Unknown media type: {}".format(unpacked[0]))
|
|
|
|
|
|
|
|
|
|
media = types.InputMediaDocument(
|
|
|
|
|
id=types.InputDocument(
|
|
|
|
|
id=unpacked[2],
|
|
|
|
|
access_hash=unpacked[3]
|
|
|
|
|
)
|
|
|
|
|
)
|
2017-12-18 13:41:32 +00:00
|
|
|
|
|
|
|
|
|
while True:
|
|
|
|
|
try:
|
|
|
|
|
r = self.send(
|
|
|
|
|
functions.messages.SendMedia(
|
|
|
|
|
peer=self.resolve_peer(chat_id),
|
2018-04-09 20:02:44 +00:00
|
|
|
|
media=media,
|
2017-12-18 13:41:32 +00:00
|
|
|
|
silent=disable_notification or None,
|
|
|
|
|
reply_to_msg_id=reply_to_message_id,
|
2018-01-20 18:40:09 +00:00
|
|
|
|
random_id=self.rnd_id(),
|
2018-01-23 14:18:52 +00:00
|
|
|
|
**style.parse(caption)
|
2017-12-18 13:41:32 +00:00
|
|
|
|
)
|
|
|
|
|
)
|
|
|
|
|
except FilePartMissing as e:
|
|
|
|
|
self.save_file(audio, file_id=file.id, file_part=e.x)
|
|
|
|
|
else:
|
2018-04-09 20:02:44 +00:00
|
|
|
|
for i in r.updates:
|
|
|
|
|
if isinstance(i, (types.UpdateNewMessage, types.UpdateNewChannelMessage)):
|
|
|
|
|
users = {i.id: i for i in r.users}
|
|
|
|
|
chats = {i.id: i for i in r.chats}
|
|
|
|
|
|
|
|
|
|
return message_parser.parse_message(self, i.message, users, chats)
|
2017-12-15 09:09:29 +00:00
|
|
|
|
|
|
|
|
|
def send_document(self,
|
|
|
|
|
chat_id: int or str,
|
|
|
|
|
document: str,
|
|
|
|
|
caption: str = "",
|
2018-01-23 14:18:52 +00:00
|
|
|
|
parse_mode: str = "",
|
2017-12-15 09:09:29 +00:00
|
|
|
|
disable_notification: bool = None,
|
2018-03-08 09:23:48 +00:00
|
|
|
|
reply_to_message_id: int = None,
|
|
|
|
|
progress: callable = None):
|
2017-12-29 20:44:45 +00:00
|
|
|
|
"""Use this method to send general files.
|
2017-12-18 13:41:32 +00:00
|
|
|
|
|
2017-12-29 20:44:45 +00:00
|
|
|
|
Args:
|
2018-03-25 19:41:19 +00:00
|
|
|
|
chat_id (``int`` | ``str``):
|
2018-03-18 12:00:28 +00:00
|
|
|
|
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).
|
|
|
|
|
For a private channel/supergroup you can use its *t.me/joinchat/* link.
|
2017-12-29 20:44:45 +00:00
|
|
|
|
|
2018-03-25 19:41:19 +00:00
|
|
|
|
document (``str``):
|
2017-12-29 20:44:45 +00:00
|
|
|
|
File to send.
|
2018-04-14 17:48:15 +00:00
|
|
|
|
Pass a file_id as string to send a file that exists on the Telegram servers,
|
|
|
|
|
pass an HTTP URL as a string for Telegram to get a file from the Internet, or
|
|
|
|
|
pass a file path as string to upload a new file that exists on your local machine.
|
2017-12-29 20:44:45 +00:00
|
|
|
|
|
2018-03-25 19:41:19 +00:00
|
|
|
|
caption (``str``, optional):
|
2017-12-29 20:44:45 +00:00
|
|
|
|
Document caption, 0-200 characters.
|
|
|
|
|
|
2018-03-25 19:41:19 +00:00
|
|
|
|
parse_mode (``str``):
|
2018-03-25 20:12:52 +00:00
|
|
|
|
Use :obj:`MARKDOWN <pyrogram.ParseMode.MARKDOWN>` or :obj:`HTML <pyrogram.ParseMode.HTML>`
|
|
|
|
|
if you want Telegram apps to show bold, italic, fixed-width text or inline URLs in your caption.
|
2018-01-23 14:38:01 +00:00
|
|
|
|
Defaults to Markdown.
|
2018-01-23 14:18:52 +00:00
|
|
|
|
|
2018-03-25 19:41:19 +00:00
|
|
|
|
disable_notification (``bool``, optional):
|
2017-12-29 20:44:45 +00:00
|
|
|
|
Sends the message silently.
|
|
|
|
|
Users will receive a notification with no sound.
|
|
|
|
|
|
2018-03-25 19:41:19 +00:00
|
|
|
|
reply_to_message_id (``int``, optional):
|
2017-12-29 20:44:45 +00:00
|
|
|
|
If the message is a reply, ID of the original message.
|
|
|
|
|
|
2018-03-25 19:41:19 +00:00
|
|
|
|
progress (``callable``):
|
2018-03-08 09:25:10 +00:00
|
|
|
|
Pass a callback function to view the upload progress.
|
2018-03-08 09:34:04 +00:00
|
|
|
|
The function must accept two arguments (current, total).
|
2018-03-08 09:25:10 +00:00
|
|
|
|
|
|
|
|
|
Other Parameters:
|
2018-03-25 19:41:19 +00:00
|
|
|
|
current (``int``):
|
2018-03-08 09:25:10 +00:00
|
|
|
|
The amount of bytes uploaded so far.
|
|
|
|
|
|
2018-03-25 19:41:19 +00:00
|
|
|
|
total (``int``):
|
2018-03-08 09:25:10 +00:00
|
|
|
|
The size of the file.
|
|
|
|
|
|
2017-12-29 20:44:45 +00:00
|
|
|
|
Returns:
|
2018-04-16 11:28:53 +00:00
|
|
|
|
On success, the sent :obj:`Message <pyrogram.api.types.pyrogram.Message>` is returned.
|
2017-12-30 18:23:18 +00:00
|
|
|
|
|
|
|
|
|
Raises:
|
2018-03-25 19:59:04 +00:00
|
|
|
|
:class:`Error <pyrogram.Error>`
|
2017-12-29 20:44:45 +00:00
|
|
|
|
"""
|
2018-04-14 17:48:15 +00:00
|
|
|
|
file = None
|
2018-01-23 14:18:52 +00:00
|
|
|
|
style = self.html if parse_mode.lower() == "html" else self.markdown
|
2018-04-14 17:48:15 +00:00
|
|
|
|
|
|
|
|
|
if os.path.exists(document):
|
|
|
|
|
file = self.save_file(document, progress=progress)
|
|
|
|
|
media = types.InputMediaUploadedDocument(
|
|
|
|
|
mime_type=mimetypes.types_map.get("." + document.split(".")[-1], "text/plain"),
|
|
|
|
|
file=file,
|
|
|
|
|
attributes=[
|
|
|
|
|
types.DocumentAttributeFilename(os.path.basename(document))
|
|
|
|
|
]
|
|
|
|
|
)
|
|
|
|
|
elif document.startswith("http"):
|
|
|
|
|
media = types.InputMediaDocumentExternal(
|
|
|
|
|
url=document
|
|
|
|
|
)
|
|
|
|
|
else:
|
|
|
|
|
try:
|
|
|
|
|
decoded = utils.decode(document)
|
|
|
|
|
fmt = "<iiqqqqi" if len(decoded) > 24 else "<iiqq"
|
|
|
|
|
unpacked = struct.unpack(fmt, decoded)
|
|
|
|
|
except (AssertionError, binascii.Error, struct.error):
|
|
|
|
|
raise FileIdInvalid from None
|
|
|
|
|
else:
|
|
|
|
|
if unpacked[0] not in (5, 10):
|
|
|
|
|
media_type = Client.MEDIA_TYPE_ID.get(unpacked[0], None)
|
|
|
|
|
|
|
|
|
|
if media_type:
|
|
|
|
|
raise FileIdInvalid("The file_id belongs to a {}".format(media_type))
|
|
|
|
|
else:
|
|
|
|
|
raise FileIdInvalid("Unknown media type: {}".format(unpacked[0]))
|
|
|
|
|
|
|
|
|
|
media = types.InputMediaDocument(
|
|
|
|
|
id=types.InputDocument(
|
|
|
|
|
id=unpacked[2],
|
|
|
|
|
access_hash=unpacked[3]
|
|
|
|
|
)
|
|
|
|
|
)
|
2017-12-29 20:44:45 +00:00
|
|
|
|
|
|
|
|
|
while True:
|
|
|
|
|
try:
|
|
|
|
|
r = self.send(
|
2017-12-18 13:41:32 +00:00
|
|
|
|
functions.messages.SendMedia(
|
|
|
|
|
peer=self.resolve_peer(chat_id),
|
2018-04-14 17:48:15 +00:00
|
|
|
|
media=media,
|
2017-12-18 13:41:32 +00:00
|
|
|
|
silent=disable_notification or None,
|
|
|
|
|
reply_to_msg_id=reply_to_message_id,
|
2018-01-20 18:40:09 +00:00
|
|
|
|
random_id=self.rnd_id(),
|
2018-01-23 14:18:52 +00:00
|
|
|
|
**style.parse(caption)
|
2017-12-18 13:41:32 +00:00
|
|
|
|
)
|
|
|
|
|
)
|
|
|
|
|
except FilePartMissing as e:
|
|
|
|
|
self.save_file(document, file_id=file.id, file_part=e.x)
|
|
|
|
|
else:
|
2018-04-14 17:48:15 +00:00
|
|
|
|
for i in r.updates:
|
|
|
|
|
if isinstance(i, (types.UpdateNewMessage, types.UpdateNewChannelMessage)):
|
|
|
|
|
users = {i.id: i for i in r.users}
|
|
|
|
|
chats = {i.id: i for i in r.chats}
|
|
|
|
|
|
|
|
|
|
return message_parser.parse_message(self, i.message, users, chats)
|
2017-12-16 00:16:52 +00:00
|
|
|
|
|
2018-02-24 16:24:35 +00:00
|
|
|
|
def send_sticker(self,
|
|
|
|
|
chat_id: int or str,
|
|
|
|
|
sticker: str,
|
|
|
|
|
disable_notification: bool = None,
|
2018-03-08 09:23:48 +00:00
|
|
|
|
reply_to_message_id: int = None,
|
|
|
|
|
progress: callable = None):
|
2018-02-24 16:24:35 +00:00
|
|
|
|
"""Use this method to send .webp stickers.
|
|
|
|
|
|
|
|
|
|
Args:
|
2018-03-25 19:41:19 +00:00
|
|
|
|
chat_id (``int`` | ``str``):
|
2018-03-18 12:00:28 +00:00
|
|
|
|
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).
|
|
|
|
|
For a private channel/supergroup you can use its *t.me/joinchat/* link.
|
2018-02-24 16:24:35 +00:00
|
|
|
|
|
2018-03-25 19:41:19 +00:00
|
|
|
|
sticker (``str``):
|
2018-02-24 16:24:35 +00:00
|
|
|
|
Sticker to send.
|
2018-04-14 17:56:11 +00:00
|
|
|
|
Pass a file_id as string to send a sticker that exists on the Telegram servers,
|
|
|
|
|
pass an HTTP URL as a string for Telegram to get a .webp sticker file from the Internet, or
|
|
|
|
|
pass a file path as string to upload a new sticker that exists on your local machine.
|
2018-02-24 16:24:35 +00:00
|
|
|
|
|
2018-03-25 19:41:19 +00:00
|
|
|
|
disable_notification (``bool``, optional):
|
2018-02-24 16:24:35 +00:00
|
|
|
|
Sends the message silently.
|
|
|
|
|
Users will receive a notification with no sound.
|
|
|
|
|
|
2018-03-25 19:41:19 +00:00
|
|
|
|
reply_to_message_id (``int``, optional):
|
2018-02-24 16:24:35 +00:00
|
|
|
|
If the message is a reply, ID of the original message.
|
|
|
|
|
|
2018-03-25 19:41:19 +00:00
|
|
|
|
progress (``callable``):
|
2018-03-08 09:25:10 +00:00
|
|
|
|
Pass a callback function to view the upload progress.
|
2018-03-08 09:34:04 +00:00
|
|
|
|
The function must accept two arguments (current, total).
|
2018-03-08 09:25:10 +00:00
|
|
|
|
|
|
|
|
|
Other Parameters:
|
2018-03-25 19:41:19 +00:00
|
|
|
|
current (``int``):
|
2018-03-08 09:25:10 +00:00
|
|
|
|
The amount of bytes uploaded so far.
|
|
|
|
|
|
2018-03-25 19:41:19 +00:00
|
|
|
|
total (``int``):
|
2018-03-08 09:25:10 +00:00
|
|
|
|
The size of the file.
|
|
|
|
|
|
2018-02-24 16:24:35 +00:00
|
|
|
|
Returns:
|
2018-04-16 11:28:53 +00:00
|
|
|
|
On success, the sent :obj:`Message <pyrogram.api.types.pyrogram.Message>` is returned.
|
2018-02-24 16:24:35 +00:00
|
|
|
|
|
|
|
|
|
Raises:
|
2018-03-25 19:59:04 +00:00
|
|
|
|
:class:`Error <pyrogram.Error>`
|
2018-02-24 16:24:35 +00:00
|
|
|
|
"""
|
2018-04-14 17:56:11 +00:00
|
|
|
|
file = None
|
|
|
|
|
|
|
|
|
|
if os.path.exists(sticker):
|
|
|
|
|
file = self.save_file(sticker, progress=progress)
|
|
|
|
|
media = types.InputMediaUploadedDocument(
|
|
|
|
|
mime_type="image/webp",
|
|
|
|
|
file=file,
|
|
|
|
|
attributes=[
|
|
|
|
|
types.DocumentAttributeFilename(os.path.basename(sticker))
|
|
|
|
|
]
|
|
|
|
|
)
|
|
|
|
|
elif sticker.startswith("http"):
|
|
|
|
|
media = types.InputMediaDocumentExternal(
|
|
|
|
|
url=sticker
|
|
|
|
|
)
|
|
|
|
|
else:
|
|
|
|
|
try:
|
|
|
|
|
decoded = utils.decode(sticker)
|
|
|
|
|
fmt = "<iiqqqqi" if len(decoded) > 24 else "<iiqq"
|
|
|
|
|
unpacked = struct.unpack(fmt, decoded)
|
|
|
|
|
except (AssertionError, binascii.Error, struct.error):
|
|
|
|
|
raise FileIdInvalid from None
|
|
|
|
|
else:
|
|
|
|
|
if unpacked[0] != 8:
|
|
|
|
|
media_type = Client.MEDIA_TYPE_ID.get(unpacked[0], None)
|
|
|
|
|
|
|
|
|
|
if media_type:
|
|
|
|
|
raise FileIdInvalid("The file_id belongs to a {}".format(media_type))
|
|
|
|
|
else:
|
|
|
|
|
raise FileIdInvalid("Unknown media type: {}".format(unpacked[0]))
|
|
|
|
|
|
|
|
|
|
media = types.InputMediaDocument(
|
|
|
|
|
id=types.InputDocument(
|
|
|
|
|
id=unpacked[2],
|
|
|
|
|
access_hash=unpacked[3]
|
|
|
|
|
)
|
|
|
|
|
)
|
2018-02-24 16:24:35 +00:00
|
|
|
|
|
|
|
|
|
while True:
|
|
|
|
|
try:
|
|
|
|
|
r = self.send(
|
|
|
|
|
functions.messages.SendMedia(
|
|
|
|
|
peer=self.resolve_peer(chat_id),
|
2018-04-14 17:56:11 +00:00
|
|
|
|
media=media,
|
2018-02-24 16:24:35 +00:00
|
|
|
|
silent=disable_notification or None,
|
|
|
|
|
reply_to_msg_id=reply_to_message_id,
|
|
|
|
|
random_id=self.rnd_id(),
|
|
|
|
|
message=""
|
|
|
|
|
)
|
|
|
|
|
)
|
|
|
|
|
except FilePartMissing as e:
|
|
|
|
|
self.save_file(sticker, file_id=file.id, file_part=e.x)
|
|
|
|
|
else:
|
2018-04-14 17:56:11 +00:00
|
|
|
|
for i in r.updates:
|
|
|
|
|
if isinstance(i, (types.UpdateNewMessage, types.UpdateNewChannelMessage)):
|
|
|
|
|
users = {i.id: i for i in r.users}
|
|
|
|
|
chats = {i.id: i for i in r.chats}
|
|
|
|
|
|
|
|
|
|
return message_parser.parse_message(self, i.message, users, chats)
|
2018-02-24 16:24:35 +00:00
|
|
|
|
|
2017-12-16 00:16:52 +00:00
|
|
|
|
def send_video(self,
|
|
|
|
|
chat_id: int or str,
|
|
|
|
|
video: str,
|
2018-01-23 14:18:52 +00:00
|
|
|
|
caption: str = "",
|
|
|
|
|
parse_mode: str = "",
|
2017-12-16 00:16:52 +00:00
|
|
|
|
duration: int = 0,
|
|
|
|
|
width: int = 0,
|
|
|
|
|
height: int = 0,
|
2018-02-28 23:36:56 +00:00
|
|
|
|
thumb: str = None,
|
2018-03-24 14:10:27 +00:00
|
|
|
|
supports_streaming: bool = True,
|
2017-12-16 00:16:52 +00:00
|
|
|
|
disable_notification: bool = None,
|
2018-03-08 09:23:48 +00:00
|
|
|
|
reply_to_message_id: int = None,
|
|
|
|
|
progress: callable = None):
|
2017-12-29 20:44:45 +00:00
|
|
|
|
"""Use this method to send video files.
|
|
|
|
|
|
|
|
|
|
Args:
|
2018-03-25 19:41:19 +00:00
|
|
|
|
chat_id (``int`` | ``str``):
|
2018-03-18 12:00:28 +00:00
|
|
|
|
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).
|
|
|
|
|
For a private channel/supergroup you can use its *t.me/joinchat/* link.
|
2017-12-29 20:44:45 +00:00
|
|
|
|
|
2018-03-25 19:41:19 +00:00
|
|
|
|
video (``str``):
|
2017-12-29 20:44:45 +00:00
|
|
|
|
Video to send.
|
2018-04-14 18:41:23 +00:00
|
|
|
|
Pass a file_id as string to send a video that exists on the Telegram servers,
|
|
|
|
|
pass an HTTP URL as a string for Telegram to get a video from the Internet, or
|
|
|
|
|
pass a file path as string to upload a new video that exists on your local machine.
|
2017-12-29 20:44:45 +00:00
|
|
|
|
|
2018-03-25 19:41:19 +00:00
|
|
|
|
caption (``str``, optional):
|
2018-01-23 14:18:52 +00:00
|
|
|
|
Video caption, 0-200 characters.
|
|
|
|
|
|
2018-03-25 19:41:19 +00:00
|
|
|
|
parse_mode (``str``):
|
2018-03-25 20:12:52 +00:00
|
|
|
|
Use :obj:`MARKDOWN <pyrogram.ParseMode.MARKDOWN>` or :obj:`HTML <pyrogram.ParseMode.HTML>`
|
|
|
|
|
if you want Telegram apps to show bold, italic, fixed-width text or inline URLs in your caption.
|
2018-01-23 14:38:01 +00:00
|
|
|
|
Defaults to Markdown.
|
2018-01-23 14:18:52 +00:00
|
|
|
|
|
2018-03-25 19:41:19 +00:00
|
|
|
|
duration (``int``, optional):
|
2017-12-29 20:44:45 +00:00
|
|
|
|
Duration of sent video in seconds.
|
|
|
|
|
|
2018-03-25 19:41:19 +00:00
|
|
|
|
width (``int``, optional):
|
2017-12-29 20:44:45 +00:00
|
|
|
|
Video width.
|
|
|
|
|
|
2018-03-25 19:41:19 +00:00
|
|
|
|
height (``int``, optional):
|
2017-12-29 20:44:45 +00:00
|
|
|
|
Video height.
|
|
|
|
|
|
2018-03-25 19:41:19 +00:00
|
|
|
|
thumb (``str``, optional):
|
2018-03-08 09:11:47 +00:00
|
|
|
|
Video thumbnail.
|
|
|
|
|
Pass a file path as string to send an image that exists on your local machine.
|
|
|
|
|
Thumbnail should have 90 or less pixels of width and 90 or less pixels of height.
|
2018-02-28 23:36:56 +00:00
|
|
|
|
|
2018-03-25 19:41:19 +00:00
|
|
|
|
supports_streaming (``bool``, optional):
|
2018-02-14 14:50:24 +00:00
|
|
|
|
Pass True, if the uploaded video is suitable for streaming.
|
|
|
|
|
|
2018-03-25 19:41:19 +00:00
|
|
|
|
disable_notification (``bool``, optional):
|
2017-12-29 20:44:45 +00:00
|
|
|
|
Sends the message silently.
|
|
|
|
|
Users will receive a notification with no sound.
|
|
|
|
|
|
2018-03-25 19:41:19 +00:00
|
|
|
|
reply_to_message_id (``int``, optional):
|
2017-12-29 20:44:45 +00:00
|
|
|
|
If the message is a reply, ID of the original message.
|
|
|
|
|
|
2018-03-25 19:41:19 +00:00
|
|
|
|
progress (``callable``):
|
2018-03-08 09:25:10 +00:00
|
|
|
|
Pass a callback function to view the upload progress.
|
2018-03-08 09:34:04 +00:00
|
|
|
|
The function must accept two arguments (current, total).
|
2018-03-08 09:25:10 +00:00
|
|
|
|
|
|
|
|
|
Other Parameters:
|
2018-03-25 19:41:19 +00:00
|
|
|
|
current (``int``):
|
2018-03-08 09:25:10 +00:00
|
|
|
|
The amount of bytes uploaded so far.
|
|
|
|
|
|
2018-03-25 19:41:19 +00:00
|
|
|
|
total (``int``):
|
2018-03-08 09:25:10 +00:00
|
|
|
|
The size of the file.
|
|
|
|
|
|
2017-12-29 20:44:45 +00:00
|
|
|
|
Returns:
|
2018-04-16 11:28:53 +00:00
|
|
|
|
On success, the sent :obj:`Message <pyrogram.api.types.pyrogram.Message>` is returned.
|
2017-12-30 18:23:18 +00:00
|
|
|
|
|
|
|
|
|
Raises:
|
2018-03-25 19:59:04 +00:00
|
|
|
|
:class:`Error <pyrogram.Error>`
|
2017-12-29 20:44:45 +00:00
|
|
|
|
"""
|
2018-04-14 18:41:23 +00:00
|
|
|
|
file = None
|
2018-01-23 14:18:52 +00:00
|
|
|
|
style = self.html if parse_mode.lower() == "html" else self.markdown
|
2018-04-14 18:41:23 +00:00
|
|
|
|
|
|
|
|
|
if os.path.exists(video):
|
|
|
|
|
thumb = None if thumb is None else self.save_file(thumb)
|
|
|
|
|
file = self.save_file(video, progress=progress)
|
|
|
|
|
media = types.InputMediaUploadedDocument(
|
|
|
|
|
mime_type=mimetypes.types_map[".mp4"],
|
|
|
|
|
file=file,
|
|
|
|
|
thumb=thumb,
|
|
|
|
|
attributes=[
|
|
|
|
|
types.DocumentAttributeVideo(
|
|
|
|
|
supports_streaming=supports_streaming or None,
|
|
|
|
|
duration=duration,
|
|
|
|
|
w=width,
|
|
|
|
|
h=height
|
|
|
|
|
),
|
|
|
|
|
types.DocumentAttributeFilename(os.path.basename(video))
|
|
|
|
|
]
|
|
|
|
|
)
|
|
|
|
|
elif video.startswith("http"):
|
|
|
|
|
media = types.InputMediaDocumentExternal(
|
|
|
|
|
url=video
|
|
|
|
|
)
|
|
|
|
|
else:
|
|
|
|
|
try:
|
|
|
|
|
decoded = utils.decode(video)
|
|
|
|
|
fmt = "<iiqqqqi" if len(decoded) > 24 else "<iiqq"
|
|
|
|
|
unpacked = struct.unpack(fmt, decoded)
|
|
|
|
|
except (AssertionError, binascii.Error, struct.error):
|
|
|
|
|
raise FileIdInvalid from None
|
|
|
|
|
else:
|
|
|
|
|
if unpacked[0] != 4:
|
|
|
|
|
media_type = Client.MEDIA_TYPE_ID.get(unpacked[0], None)
|
|
|
|
|
|
|
|
|
|
if media_type:
|
|
|
|
|
raise FileIdInvalid("The file_id belongs to a {}".format(media_type))
|
|
|
|
|
else:
|
|
|
|
|
raise FileIdInvalid("Unknown media type: {}".format(unpacked[0]))
|
|
|
|
|
|
|
|
|
|
media = types.InputMediaDocument(
|
|
|
|
|
id=types.InputDocument(
|
|
|
|
|
id=unpacked[2],
|
|
|
|
|
access_hash=unpacked[3]
|
|
|
|
|
)
|
|
|
|
|
)
|
2017-12-18 13:41:32 +00:00
|
|
|
|
|
|
|
|
|
while True:
|
|
|
|
|
try:
|
|
|
|
|
r = self.send(
|
|
|
|
|
functions.messages.SendMedia(
|
|
|
|
|
peer=self.resolve_peer(chat_id),
|
2018-04-14 18:41:23 +00:00
|
|
|
|
media=media,
|
2017-12-18 13:41:32 +00:00
|
|
|
|
silent=disable_notification or None,
|
|
|
|
|
reply_to_msg_id=reply_to_message_id,
|
2018-01-20 18:40:09 +00:00
|
|
|
|
random_id=self.rnd_id(),
|
2018-01-23 14:18:52 +00:00
|
|
|
|
**style.parse(caption)
|
2017-12-18 13:41:32 +00:00
|
|
|
|
)
|
|
|
|
|
)
|
|
|
|
|
except FilePartMissing as e:
|
|
|
|
|
self.save_file(video, file_id=file.id, file_part=e.x)
|
|
|
|
|
else:
|
2018-04-14 18:41:23 +00:00
|
|
|
|
for i in r.updates:
|
|
|
|
|
if isinstance(i, (types.UpdateNewMessage, types.UpdateNewChannelMessage)):
|
|
|
|
|
users = {i.id: i for i in r.users}
|
|
|
|
|
chats = {i.id: i for i in r.chats}
|
|
|
|
|
|
|
|
|
|
return message_parser.parse_message(self, i.message, users, chats)
|
2017-12-16 00:27:13 +00:00
|
|
|
|
|
|
|
|
|
def send_voice(self,
|
|
|
|
|
chat_id: int or str,
|
|
|
|
|
voice: str,
|
|
|
|
|
caption: str = "",
|
2018-01-23 14:18:52 +00:00
|
|
|
|
parse_mode: str = "",
|
2017-12-16 00:27:13 +00:00
|
|
|
|
duration: int = 0,
|
|
|
|
|
disable_notification: bool = None,
|
2018-03-08 09:23:48 +00:00
|
|
|
|
reply_to_message_id: int = None,
|
|
|
|
|
progress: callable = None):
|
2017-12-29 20:44:45 +00:00
|
|
|
|
"""Use this method to send audio files.
|
|
|
|
|
|
|
|
|
|
Args:
|
2018-03-25 19:41:19 +00:00
|
|
|
|
chat_id (``int`` | ``str``):
|
2018-03-18 12:00:28 +00:00
|
|
|
|
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).
|
|
|
|
|
For a private channel/supergroup you can use its *t.me/joinchat/* link.
|
2017-12-29 20:44:45 +00:00
|
|
|
|
|
2018-03-25 19:41:19 +00:00
|
|
|
|
voice (``str``):
|
2017-12-29 20:44:45 +00:00
|
|
|
|
Audio file to send.
|
2018-04-14 18:42:32 +00:00
|
|
|
|
Pass a file_id as string to send an audio that exists on the Telegram servers,
|
|
|
|
|
pass an HTTP URL as a string for Telegram to get an audio from the Internet, or
|
|
|
|
|
pass a file path as string to upload a new audio that exists on your local machine.
|
2017-12-29 20:44:45 +00:00
|
|
|
|
|
2018-03-25 19:41:19 +00:00
|
|
|
|
caption (``str``, optional):
|
2017-12-29 20:44:45 +00:00
|
|
|
|
Voice message caption, 0-200 characters.
|
|
|
|
|
|
2018-03-25 19:41:19 +00:00
|
|
|
|
parse_mode (``str``):
|
2018-03-25 20:12:52 +00:00
|
|
|
|
Use :obj:`MARKDOWN <pyrogram.ParseMode.MARKDOWN>` or :obj:`HTML <pyrogram.ParseMode.HTML>`
|
|
|
|
|
if you want Telegram apps to show bold, italic, fixed-width text or inline URLs in your caption.
|
2018-01-23 14:38:01 +00:00
|
|
|
|
Defaults to Markdown.
|
2018-01-23 14:18:52 +00:00
|
|
|
|
|
2018-03-25 19:41:19 +00:00
|
|
|
|
duration (``int``, optional):
|
2017-12-29 20:44:45 +00:00
|
|
|
|
Duration of the voice message in seconds.
|
|
|
|
|
|
2018-03-25 19:41:19 +00:00
|
|
|
|
disable_notification (``bool``, optional):
|
2017-12-29 20:44:45 +00:00
|
|
|
|
Sends the message silently.
|
|
|
|
|
Users will receive a notification with no sound.
|
|
|
|
|
|
2018-03-25 19:41:19 +00:00
|
|
|
|
reply_to_message_id (``int``, optional):
|
2017-12-29 20:44:45 +00:00
|
|
|
|
If the message is a reply, ID of the original message
|
|
|
|
|
|
2018-03-25 19:41:19 +00:00
|
|
|
|
progress (``callable``):
|
2018-03-08 09:25:10 +00:00
|
|
|
|
Pass a callback function to view the upload progress.
|
2018-03-08 09:34:04 +00:00
|
|
|
|
The function must accept two arguments (current, total).
|
2018-03-08 09:25:10 +00:00
|
|
|
|
|
|
|
|
|
Other Parameters:
|
2018-03-25 19:41:19 +00:00
|
|
|
|
current (``int``):
|
2018-03-08 09:25:10 +00:00
|
|
|
|
The amount of bytes uploaded so far.
|
|
|
|
|
|
2018-03-25 19:41:19 +00:00
|
|
|
|
total (``int``):
|
2018-03-08 09:25:10 +00:00
|
|
|
|
The size of the file.
|
|
|
|
|
|
2017-12-29 20:44:45 +00:00
|
|
|
|
Returns:
|
2018-04-16 11:28:53 +00:00
|
|
|
|
On success, the sent :obj:`Message <pyrogram.api.types.pyrogram.Message>` is returned.
|
2017-12-30 18:23:18 +00:00
|
|
|
|
|
|
|
|
|
Raises:
|
2018-03-25 19:59:04 +00:00
|
|
|
|
:class:`Error <pyrogram.Error>`
|
2017-12-29 20:44:45 +00:00
|
|
|
|
"""
|
2018-04-14 18:42:32 +00:00
|
|
|
|
file = None
|
2018-01-23 14:18:52 +00:00
|
|
|
|
style = self.html if parse_mode.lower() == "html" else self.markdown
|
2018-04-14 18:42:32 +00:00
|
|
|
|
|
|
|
|
|
if os.path.exists(voice):
|
|
|
|
|
file = self.save_file(voice, progress=progress)
|
|
|
|
|
media = types.InputMediaUploadedDocument(
|
|
|
|
|
mime_type=mimetypes.types_map.get("." + voice.split(".")[-1], "audio/mpeg"),
|
|
|
|
|
file=file,
|
|
|
|
|
attributes=[
|
|
|
|
|
types.DocumentAttributeAudio(
|
|
|
|
|
voice=True,
|
|
|
|
|
duration=duration
|
|
|
|
|
)
|
|
|
|
|
]
|
|
|
|
|
)
|
|
|
|
|
elif voice.startswith("http"):
|
|
|
|
|
media = types.InputMediaDocumentExternal(
|
|
|
|
|
url=voice
|
|
|
|
|
)
|
|
|
|
|
else:
|
|
|
|
|
try:
|
|
|
|
|
decoded = utils.decode(voice)
|
|
|
|
|
fmt = "<iiqqqqi" if len(decoded) > 24 else "<iiqq"
|
|
|
|
|
unpacked = struct.unpack(fmt, decoded)
|
|
|
|
|
except (AssertionError, binascii.Error, struct.error):
|
|
|
|
|
raise FileIdInvalid from None
|
|
|
|
|
else:
|
|
|
|
|
if unpacked[0] != 3:
|
|
|
|
|
media_type = Client.MEDIA_TYPE_ID.get(unpacked[0], None)
|
|
|
|
|
|
|
|
|
|
if media_type:
|
|
|
|
|
raise FileIdInvalid("The file_id belongs to a {}".format(media_type))
|
|
|
|
|
else:
|
|
|
|
|
raise FileIdInvalid("Unknown media type: {}".format(unpacked[0]))
|
|
|
|
|
|
|
|
|
|
media = types.InputMediaDocument(
|
|
|
|
|
id=types.InputDocument(
|
|
|
|
|
id=unpacked[2],
|
|
|
|
|
access_hash=unpacked[3]
|
|
|
|
|
)
|
|
|
|
|
)
|
2017-12-18 13:41:32 +00:00
|
|
|
|
|
|
|
|
|
while True:
|
|
|
|
|
try:
|
|
|
|
|
r = self.send(
|
|
|
|
|
functions.messages.SendMedia(
|
|
|
|
|
peer=self.resolve_peer(chat_id),
|
2018-04-14 18:42:32 +00:00
|
|
|
|
media=media,
|
2017-12-18 13:41:32 +00:00
|
|
|
|
silent=disable_notification or None,
|
|
|
|
|
reply_to_msg_id=reply_to_message_id,
|
2018-01-20 18:40:09 +00:00
|
|
|
|
random_id=self.rnd_id(),
|
2018-01-23 14:18:52 +00:00
|
|
|
|
**style.parse(caption)
|
2017-12-18 13:41:32 +00:00
|
|
|
|
)
|
|
|
|
|
)
|
|
|
|
|
except FilePartMissing as e:
|
|
|
|
|
self.save_file(voice, file_id=file.id, file_part=e.x)
|
|
|
|
|
else:
|
2018-04-14 18:42:32 +00:00
|
|
|
|
for i in r.updates:
|
|
|
|
|
if isinstance(i, (types.UpdateNewMessage, types.UpdateNewChannelMessage)):
|
|
|
|
|
users = {i.id: i for i in r.users}
|
|
|
|
|
chats = {i.id: i for i in r.chats}
|
|
|
|
|
|
|
|
|
|
return message_parser.parse_message(self, i.message, users, chats)
|
2017-12-16 00:45:29 +00:00
|
|
|
|
|
|
|
|
|
def send_video_note(self,
|
|
|
|
|
chat_id: int or str,
|
|
|
|
|
video_note: str,
|
|
|
|
|
duration: int = 0,
|
|
|
|
|
length: int = 1,
|
|
|
|
|
disable_notification: bool = None,
|
2018-03-08 09:23:48 +00:00
|
|
|
|
reply_to_message_id: int = None,
|
|
|
|
|
progress: callable = None):
|
2017-12-29 20:44:45 +00:00
|
|
|
|
"""Use this method to send video messages.
|
|
|
|
|
|
|
|
|
|
Args:
|
2018-03-25 19:41:19 +00:00
|
|
|
|
chat_id (``int`` | ``str``):
|
2018-03-18 12:00:28 +00:00
|
|
|
|
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).
|
|
|
|
|
For a private channel/supergroup you can use its *t.me/joinchat/* link.
|
2017-12-29 20:44:45 +00:00
|
|
|
|
|
2018-03-25 19:41:19 +00:00
|
|
|
|
video_note (``str``):
|
2017-12-29 20:44:45 +00:00
|
|
|
|
Video note to send.
|
2018-04-14 18:49:16 +00:00
|
|
|
|
Pass a file_id as string to send a video note that exists on the Telegram servers, or
|
|
|
|
|
pass a file path as string to upload a new video note that exists on your local machine.
|
|
|
|
|
Sending video notes by a URL is currently unsupported.
|
2017-12-29 20:44:45 +00:00
|
|
|
|
|
2018-03-25 19:41:19 +00:00
|
|
|
|
duration (``int``, optional):
|
2017-12-29 20:44:45 +00:00
|
|
|
|
Duration of sent video in seconds.
|
|
|
|
|
|
2018-03-25 19:41:19 +00:00
|
|
|
|
length (``int``, optional):
|
2017-12-29 20:44:45 +00:00
|
|
|
|
Video width and height.
|
|
|
|
|
|
2018-03-25 19:41:19 +00:00
|
|
|
|
disable_notification (``bool``, optional):
|
2017-12-29 20:44:45 +00:00
|
|
|
|
Sends the message silently.
|
|
|
|
|
Users will receive a notification with no sound.
|
|
|
|
|
|
2018-03-25 19:41:19 +00:00
|
|
|
|
reply_to_message_id (``int``, optional):
|
2017-12-29 20:44:45 +00:00
|
|
|
|
If the message is a reply, ID of the original message
|
|
|
|
|
|
2018-03-25 19:41:19 +00:00
|
|
|
|
progress (``callable``):
|
2018-03-08 09:25:10 +00:00
|
|
|
|
Pass a callback function to view the upload progress.
|
2018-03-08 09:34:04 +00:00
|
|
|
|
The function must accept two arguments (current, total).
|
2018-03-08 09:25:10 +00:00
|
|
|
|
|
|
|
|
|
Other Parameters:
|
2018-03-25 19:41:19 +00:00
|
|
|
|
current (``int``):
|
2018-03-08 09:25:10 +00:00
|
|
|
|
The amount of bytes uploaded so far.
|
|
|
|
|
|
2018-03-25 19:41:19 +00:00
|
|
|
|
total (``int``):
|
2018-03-08 09:25:10 +00:00
|
|
|
|
The size of the file.
|
|
|
|
|
|
2017-12-29 20:44:45 +00:00
|
|
|
|
Returns:
|
2018-04-16 11:28:53 +00:00
|
|
|
|
On success, the sent :obj:`Message <pyrogram.api.types.pyrogram.Message>` is returned.
|
2017-12-30 18:23:18 +00:00
|
|
|
|
|
|
|
|
|
Raises:
|
2018-03-25 19:59:04 +00:00
|
|
|
|
:class:`Error <pyrogram.Error>`
|
2017-12-29 20:44:45 +00:00
|
|
|
|
"""
|
2018-04-14 18:49:16 +00:00
|
|
|
|
file = None
|
|
|
|
|
|
|
|
|
|
if os.path.exists(video_note):
|
|
|
|
|
file = self.save_file(video_note, progress=progress)
|
|
|
|
|
media = types.InputMediaUploadedDocument(
|
|
|
|
|
mime_type=mimetypes.types_map[".mp4"],
|
|
|
|
|
file=file,
|
|
|
|
|
attributes=[
|
|
|
|
|
types.DocumentAttributeVideo(
|
|
|
|
|
round_message=True,
|
|
|
|
|
duration=duration,
|
|
|
|
|
w=length,
|
|
|
|
|
h=length
|
|
|
|
|
)
|
|
|
|
|
]
|
|
|
|
|
)
|
|
|
|
|
else:
|
|
|
|
|
try:
|
|
|
|
|
decoded = utils.decode(video_note)
|
|
|
|
|
fmt = "<iiqqqqi" if len(decoded) > 24 else "<iiqq"
|
|
|
|
|
unpacked = struct.unpack(fmt, decoded)
|
|
|
|
|
except (AssertionError, binascii.Error, struct.error):
|
|
|
|
|
raise FileIdInvalid from None
|
|
|
|
|
else:
|
|
|
|
|
if unpacked[0] != 13:
|
|
|
|
|
media_type = Client.MEDIA_TYPE_ID.get(unpacked[0], None)
|
|
|
|
|
|
|
|
|
|
if media_type:
|
|
|
|
|
raise FileIdInvalid("The file_id belongs to a {}".format(media_type))
|
|
|
|
|
else:
|
|
|
|
|
raise FileIdInvalid("Unknown media type: {}".format(unpacked[0]))
|
|
|
|
|
|
|
|
|
|
media = types.InputMediaDocument(
|
|
|
|
|
id=types.InputDocument(
|
|
|
|
|
id=unpacked[2],
|
|
|
|
|
access_hash=unpacked[3]
|
|
|
|
|
)
|
|
|
|
|
)
|
2017-12-16 01:03:09 +00:00
|
|
|
|
|
2017-12-18 13:41:32 +00:00
|
|
|
|
while True:
|
|
|
|
|
try:
|
|
|
|
|
r = self.send(
|
|
|
|
|
functions.messages.SendMedia(
|
|
|
|
|
peer=self.resolve_peer(chat_id),
|
2018-04-14 18:49:16 +00:00
|
|
|
|
media=media,
|
2017-12-18 13:41:32 +00:00
|
|
|
|
silent=disable_notification or None,
|
|
|
|
|
reply_to_msg_id=reply_to_message_id,
|
2018-04-14 18:49:16 +00:00
|
|
|
|
random_id=self.rnd_id(),
|
|
|
|
|
message=""
|
2017-12-18 13:41:32 +00:00
|
|
|
|
)
|
|
|
|
|
)
|
|
|
|
|
except FilePartMissing as e:
|
|
|
|
|
self.save_file(video_note, file_id=file.id, file_part=e.x)
|
|
|
|
|
else:
|
2018-04-14 18:49:16 +00:00
|
|
|
|
for i in r.updates:
|
|
|
|
|
if isinstance(i, (types.UpdateNewMessage, types.UpdateNewChannelMessage)):
|
|
|
|
|
users = {i.id: i for i in r.users}
|
|
|
|
|
chats = {i.id: i for i in r.chats}
|
|
|
|
|
|
|
|
|
|
return message_parser.parse_message(self, i.message, users, chats)
|
2017-12-19 13:00:19 +00:00
|
|
|
|
|
2018-03-08 09:23:48 +00:00
|
|
|
|
# TODO: Add progress parameter
|
2018-04-14 19:48:15 +00:00
|
|
|
|
# TODO: Return new Message object
|
|
|
|
|
# TODO: Figure out how to send albums using URLs
|
2018-02-24 16:46:40 +00:00
|
|
|
|
def send_media_group(self,
|
|
|
|
|
chat_id: int or str,
|
|
|
|
|
media: list,
|
|
|
|
|
disable_notification: bool = None,
|
|
|
|
|
reply_to_message_id: int = None):
|
|
|
|
|
"""Use this method to send a group of photos or videos as an album.
|
|
|
|
|
On success, an Update containing the sent Messages is returned.
|
|
|
|
|
|
|
|
|
|
Args:
|
2018-03-25 19:41:19 +00:00
|
|
|
|
chat_id (``int`` | ``str``):
|
2018-03-18 12:00:28 +00:00
|
|
|
|
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).
|
|
|
|
|
For a private channel/supergroup you can use its *t.me/joinchat/* link.
|
2018-02-24 16:46:40 +00:00
|
|
|
|
|
2018-03-25 19:41:19 +00:00
|
|
|
|
media (``list``):
|
2018-04-18 09:15:09 +00:00
|
|
|
|
A list containing either :obj:`InputMediaPhoto <pyrogram.InputMediaPhoto>` or
|
|
|
|
|
:obj:`InputMediaVideo <pyrogram.InputMediaVideo>` objects
|
2018-02-24 16:46:40 +00:00
|
|
|
|
describing photos and videos to be sent, must include 2–10 items.
|
|
|
|
|
|
2018-03-25 19:41:19 +00:00
|
|
|
|
disable_notification (``bool``, optional):
|
2018-02-24 16:46:40 +00:00
|
|
|
|
Sends the message silently.
|
|
|
|
|
Users will receive a notification with no sound.
|
|
|
|
|
|
2018-03-25 19:41:19 +00:00
|
|
|
|
reply_to_message_id (``int``, optional):
|
2018-02-24 16:46:40 +00:00
|
|
|
|
If the message is a reply, ID of the original message.
|
|
|
|
|
"""
|
|
|
|
|
multi_media = []
|
|
|
|
|
|
|
|
|
|
for i in media:
|
2018-04-14 19:48:15 +00:00
|
|
|
|
style = self.html if i.parse_mode.lower() == "html" else self.markdown
|
|
|
|
|
|
|
|
|
|
if isinstance(i, InputMediaPhoto):
|
|
|
|
|
if os.path.exists(i.media):
|
|
|
|
|
media = self.send(
|
|
|
|
|
functions.messages.UploadMedia(
|
|
|
|
|
peer=self.resolve_peer(chat_id),
|
|
|
|
|
media=types.InputMediaUploadedPhoto(
|
|
|
|
|
file=self.save_file(i.media)
|
|
|
|
|
)
|
2018-02-24 16:46:40 +00:00
|
|
|
|
)
|
|
|
|
|
)
|
|
|
|
|
|
2018-04-14 19:48:15 +00:00
|
|
|
|
media = types.InputMediaPhoto(
|
2018-02-24 16:46:40 +00:00
|
|
|
|
id=types.InputPhoto(
|
|
|
|
|
id=media.photo.id,
|
|
|
|
|
access_hash=media.photo.access_hash
|
|
|
|
|
)
|
2018-04-14 19:48:15 +00:00
|
|
|
|
)
|
|
|
|
|
else:
|
|
|
|
|
try:
|
|
|
|
|
decoded = utils.decode(i.media)
|
|
|
|
|
fmt = "<iiqqqqi" if len(decoded) > 24 else "<iiqq"
|
|
|
|
|
unpacked = struct.unpack(fmt, decoded)
|
|
|
|
|
except (AssertionError, binascii.Error, struct.error):
|
|
|
|
|
raise FileIdInvalid from None
|
|
|
|
|
else:
|
|
|
|
|
if unpacked[0] != 2:
|
|
|
|
|
media_type = Client.MEDIA_TYPE_ID.get(unpacked[0], None)
|
|
|
|
|
|
|
|
|
|
if media_type:
|
|
|
|
|
raise FileIdInvalid("The file_id belongs to a {}".format(media_type))
|
|
|
|
|
else:
|
|
|
|
|
raise FileIdInvalid("Unknown media type: {}".format(unpacked[0]))
|
|
|
|
|
|
|
|
|
|
media = types.InputMediaPhoto(
|
|
|
|
|
id=types.InputPhoto(
|
|
|
|
|
id=unpacked[2],
|
|
|
|
|
access_hash=unpacked[3]
|
|
|
|
|
)
|
|
|
|
|
)
|
|
|
|
|
elif isinstance(i, InputMediaVideo):
|
|
|
|
|
if os.path.exists(i.media):
|
|
|
|
|
media = self.send(
|
|
|
|
|
functions.messages.UploadMedia(
|
|
|
|
|
peer=self.resolve_peer(chat_id),
|
|
|
|
|
media=types.InputMediaUploadedDocument(
|
|
|
|
|
file=self.save_file(i.media),
|
|
|
|
|
mime_type=mimetypes.types_map[".mp4"],
|
|
|
|
|
attributes=[
|
|
|
|
|
types.DocumentAttributeVideo(
|
|
|
|
|
supports_streaming=i.supports_streaming or None,
|
|
|
|
|
duration=i.duration,
|
|
|
|
|
w=i.width,
|
|
|
|
|
h=i.height
|
|
|
|
|
),
|
|
|
|
|
types.DocumentAttributeFilename(os.path.basename(i.media))
|
|
|
|
|
]
|
|
|
|
|
)
|
2018-02-24 16:46:40 +00:00
|
|
|
|
)
|
|
|
|
|
)
|
|
|
|
|
|
2018-04-14 19:48:15 +00:00
|
|
|
|
media = types.InputMediaDocument(
|
2018-02-24 16:46:40 +00:00
|
|
|
|
id=types.InputDocument(
|
|
|
|
|
id=media.document.id,
|
|
|
|
|
access_hash=media.document.access_hash
|
|
|
|
|
)
|
2018-04-14 19:48:15 +00:00
|
|
|
|
)
|
|
|
|
|
else:
|
|
|
|
|
try:
|
|
|
|
|
decoded = utils.decode(i.media)
|
|
|
|
|
fmt = "<iiqqqqi" if len(decoded) > 24 else "<iiqq"
|
|
|
|
|
unpacked = struct.unpack(fmt, decoded)
|
|
|
|
|
except (AssertionError, binascii.Error, struct.error):
|
|
|
|
|
raise FileIdInvalid from None
|
|
|
|
|
else:
|
|
|
|
|
if unpacked[0] != 4:
|
|
|
|
|
media_type = Client.MEDIA_TYPE_ID.get(unpacked[0], None)
|
|
|
|
|
|
|
|
|
|
if media_type:
|
|
|
|
|
raise FileIdInvalid("The file_id belongs to a {}".format(media_type))
|
|
|
|
|
else:
|
|
|
|
|
raise FileIdInvalid("Unknown media type: {}".format(unpacked[0]))
|
|
|
|
|
|
|
|
|
|
media = types.InputMediaDocument(
|
|
|
|
|
id=types.InputDocument(
|
|
|
|
|
id=unpacked[2],
|
|
|
|
|
access_hash=unpacked[3]
|
|
|
|
|
)
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
multi_media.append(
|
|
|
|
|
types.InputSingleMedia(
|
|
|
|
|
media=media,
|
2018-02-24 16:46:40 +00:00
|
|
|
|
random_id=self.rnd_id(),
|
|
|
|
|
**style.parse(i.caption)
|
|
|
|
|
)
|
2018-04-14 19:48:15 +00:00
|
|
|
|
)
|
2018-02-24 16:46:40 +00:00
|
|
|
|
|
|
|
|
|
return self.send(
|
|
|
|
|
functions.messages.SendMultiMedia(
|
|
|
|
|
peer=self.resolve_peer(chat_id),
|
|
|
|
|
multi_media=multi_media,
|
|
|
|
|
silent=disable_notification or None,
|
|
|
|
|
reply_to_msg_id=reply_to_message_id
|
|
|
|
|
)
|
|
|
|
|
)
|
|
|
|
|
|
2017-12-29 20:44:45 +00:00
|
|
|
|
def send_location(self,
|
|
|
|
|
chat_id: int or str,
|
|
|
|
|
latitude: float,
|
|
|
|
|
longitude: float,
|
|
|
|
|
disable_notification: bool = None,
|
|
|
|
|
reply_to_message_id: int = None):
|
|
|
|
|
"""Use this method to send points on the map.
|
|
|
|
|
|
|
|
|
|
Args:
|
2018-03-25 19:41:19 +00:00
|
|
|
|
chat_id (``int`` | ``str``):
|
2018-03-18 12:00:28 +00:00
|
|
|
|
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).
|
|
|
|
|
For a private channel/supergroup you can use its *t.me/joinchat/* link.
|
2017-12-29 20:44:45 +00:00
|
|
|
|
|
2018-03-25 19:41:19 +00:00
|
|
|
|
latitude (``float``):
|
2017-12-29 20:44:45 +00:00
|
|
|
|
Latitude of the location.
|
|
|
|
|
|
2018-03-25 19:41:19 +00:00
|
|
|
|
longitude (``float``):
|
2017-12-29 20:44:45 +00:00
|
|
|
|
Longitude of the location.
|
|
|
|
|
|
2018-03-25 19:41:19 +00:00
|
|
|
|
disable_notification (``bool``, optional):
|
2017-12-29 20:44:45 +00:00
|
|
|
|
Sends the message silently.
|
|
|
|
|
Users will receive a notification with no sound.
|
|
|
|
|
|
2018-03-25 19:41:19 +00:00
|
|
|
|
reply_to_message_id (``int``, optional):
|
2017-12-29 20:44:45 +00:00
|
|
|
|
If the message is a reply, ID of the original message
|
|
|
|
|
|
|
|
|
|
Returns:
|
2018-04-16 11:28:53 +00:00
|
|
|
|
On success, the sent :obj:`Message <pyrogram.api.types.pyrogram.Message>` is returned.
|
2017-12-30 18:23:18 +00:00
|
|
|
|
|
|
|
|
|
Raises:
|
2018-03-25 19:59:04 +00:00
|
|
|
|
:class:`Error <pyrogram.Error>`
|
2017-12-29 20:44:45 +00:00
|
|
|
|
"""
|
2018-04-16 09:51:32 +00:00
|
|
|
|
r = self.send(
|
2017-12-29 20:44:45 +00:00
|
|
|
|
functions.messages.SendMedia(
|
|
|
|
|
peer=self.resolve_peer(chat_id),
|
|
|
|
|
media=types.InputMediaGeoPoint(
|
|
|
|
|
types.InputGeoPoint(
|
|
|
|
|
latitude,
|
|
|
|
|
longitude
|
|
|
|
|
)
|
|
|
|
|
),
|
2018-01-19 14:49:17 +00:00
|
|
|
|
message="",
|
2017-12-29 20:44:45 +00:00
|
|
|
|
silent=disable_notification or None,
|
|
|
|
|
reply_to_msg_id=reply_to_message_id,
|
|
|
|
|
random_id=self.rnd_id()
|
|
|
|
|
)
|
|
|
|
|
)
|
|
|
|
|
|
2018-04-16 09:51:32 +00:00
|
|
|
|
for i in r.updates:
|
|
|
|
|
if isinstance(i, (types.UpdateNewMessage, types.UpdateNewChannelMessage)):
|
|
|
|
|
users = {i.id: i for i in r.users}
|
|
|
|
|
chats = {i.id: i for i in r.chats}
|
|
|
|
|
|
|
|
|
|
return message_parser.parse_message(self, i.message, users, chats)
|
|
|
|
|
|
2017-12-29 20:44:45 +00:00
|
|
|
|
def send_venue(self,
|
|
|
|
|
chat_id: int or str,
|
|
|
|
|
latitude: float,
|
|
|
|
|
longitude: float,
|
|
|
|
|
title: str,
|
|
|
|
|
address: str,
|
|
|
|
|
foursquare_id: str = "",
|
|
|
|
|
disable_notification: bool = None,
|
|
|
|
|
reply_to_message_id: int = None):
|
|
|
|
|
"""Use this method to send information about a venue.
|
|
|
|
|
|
|
|
|
|
Args:
|
2018-03-25 19:41:19 +00:00
|
|
|
|
chat_id (``int`` | ``str``):
|
2018-03-18 12:00:28 +00:00
|
|
|
|
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).
|
|
|
|
|
For a private channel/supergroup you can use its *t.me/joinchat/* link.
|
2017-12-29 20:44:45 +00:00
|
|
|
|
|
2018-03-25 19:41:19 +00:00
|
|
|
|
latitude (``float``):
|
2017-12-29 20:44:45 +00:00
|
|
|
|
Latitude of the venue.
|
|
|
|
|
|
2018-03-25 19:41:19 +00:00
|
|
|
|
longitude (``float``):
|
2017-12-29 20:44:45 +00:00
|
|
|
|
Longitude of the venue.
|
|
|
|
|
|
2018-03-25 19:41:19 +00:00
|
|
|
|
title (``str``):
|
2017-12-29 20:44:45 +00:00
|
|
|
|
Name of the venue.
|
|
|
|
|
|
2018-03-25 19:41:19 +00:00
|
|
|
|
address (``str``):
|
2017-12-29 20:44:45 +00:00
|
|
|
|
Address of the venue.
|
|
|
|
|
|
2018-03-25 19:41:19 +00:00
|
|
|
|
foursquare_id (``str``, optional):
|
2017-12-29 20:44:45 +00:00
|
|
|
|
Foursquare identifier of the venue.
|
|
|
|
|
|
2018-03-25 19:41:19 +00:00
|
|
|
|
disable_notification (``bool``, optional):
|
2017-12-29 20:44:45 +00:00
|
|
|
|
Sends the message silently.
|
|
|
|
|
Users will receive a notification with no sound.
|
|
|
|
|
|
2018-03-25 19:41:19 +00:00
|
|
|
|
reply_to_message_id (``int``, optional):
|
2017-12-29 20:44:45 +00:00
|
|
|
|
If the message is a reply, ID of the original message
|
|
|
|
|
|
|
|
|
|
Returns:
|
2018-04-16 11:28:53 +00:00
|
|
|
|
On success, the sent :obj:`Message <pyrogram.api.types.pyrogram.Message>` is returned.
|
2017-12-30 18:23:18 +00:00
|
|
|
|
|
|
|
|
|
Raises:
|
2018-03-25 19:59:04 +00:00
|
|
|
|
:class:`Error <pyrogram.Error>`
|
2017-12-29 20:44:45 +00:00
|
|
|
|
"""
|
2018-04-16 09:53:38 +00:00
|
|
|
|
r = self.send(
|
2017-12-29 20:44:45 +00:00
|
|
|
|
functions.messages.SendMedia(
|
|
|
|
|
peer=self.resolve_peer(chat_id),
|
|
|
|
|
media=types.InputMediaVenue(
|
|
|
|
|
geo_point=types.InputGeoPoint(
|
|
|
|
|
lat=latitude,
|
|
|
|
|
long=longitude
|
|
|
|
|
),
|
|
|
|
|
title=title,
|
|
|
|
|
address=address,
|
|
|
|
|
provider="",
|
|
|
|
|
venue_id=foursquare_id,
|
|
|
|
|
venue_type=""
|
|
|
|
|
),
|
2018-01-19 14:49:17 +00:00
|
|
|
|
message="",
|
2017-12-29 20:44:45 +00:00
|
|
|
|
silent=disable_notification or None,
|
|
|
|
|
reply_to_msg_id=reply_to_message_id,
|
|
|
|
|
random_id=self.rnd_id()
|
|
|
|
|
)
|
|
|
|
|
)
|
|
|
|
|
|
2018-04-16 09:53:38 +00:00
|
|
|
|
for i in r.updates:
|
|
|
|
|
if isinstance(i, (types.UpdateNewMessage, types.UpdateNewChannelMessage)):
|
|
|
|
|
users = {i.id: i for i in r.users}
|
|
|
|
|
chats = {i.id: i for i in r.chats}
|
|
|
|
|
|
|
|
|
|
return message_parser.parse_message(self, i.message, users, chats)
|
|
|
|
|
|
2017-12-29 20:44:45 +00:00
|
|
|
|
def send_contact(self,
|
|
|
|
|
chat_id: int or str,
|
|
|
|
|
phone_number: str,
|
|
|
|
|
first_name: str,
|
2018-04-16 09:56:52 +00:00
|
|
|
|
last_name: str = "",
|
2017-12-29 20:44:45 +00:00
|
|
|
|
disable_notification: bool = None,
|
|
|
|
|
reply_to_message_id: int = None):
|
|
|
|
|
"""Use this method to send phone contacts.
|
|
|
|
|
|
|
|
|
|
Args:
|
2018-03-25 19:41:19 +00:00
|
|
|
|
chat_id (``int`` | ``str``):
|
2018-03-18 12:00:28 +00:00
|
|
|
|
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).
|
|
|
|
|
For a private channel/supergroup you can use its *t.me/joinchat/* link.
|
2017-12-29 20:44:45 +00:00
|
|
|
|
|
2018-03-25 19:41:19 +00:00
|
|
|
|
phone_number (``str``):
|
2017-12-29 20:44:45 +00:00
|
|
|
|
Contact's phone number.
|
|
|
|
|
|
2018-03-25 19:41:19 +00:00
|
|
|
|
first_name (``str``):
|
2017-12-29 20:44:45 +00:00
|
|
|
|
Contact's first name.
|
|
|
|
|
|
2018-03-25 19:41:19 +00:00
|
|
|
|
last_name (``str``):
|
2017-12-29 20:44:45 +00:00
|
|
|
|
Contact's last name.
|
|
|
|
|
|
2018-03-25 19:41:19 +00:00
|
|
|
|
disable_notification (``bool``, optional):
|
2017-12-29 20:44:45 +00:00
|
|
|
|
Sends the message silently.
|
|
|
|
|
Users will receive a notification with no sound.
|
|
|
|
|
|
2018-03-25 19:41:19 +00:00
|
|
|
|
reply_to_message_id (``int``, optional):
|
2017-12-29 20:44:45 +00:00
|
|
|
|
If the message is a reply, ID of the original message.
|
|
|
|
|
|
|
|
|
|
Returns:
|
2018-04-16 11:28:53 +00:00
|
|
|
|
On success, the sent :obj:`Message <pyrogram.api.types.pyrogram.Message>` is returned.
|
2017-12-30 18:23:18 +00:00
|
|
|
|
|
|
|
|
|
Raises:
|
2018-03-25 19:59:04 +00:00
|
|
|
|
:class:`Error <pyrogram.Error>`
|
2017-12-29 20:44:45 +00:00
|
|
|
|
"""
|
2018-04-16 09:56:52 +00:00
|
|
|
|
r = self.send(
|
2017-12-29 20:44:45 +00:00
|
|
|
|
functions.messages.SendMedia(
|
|
|
|
|
peer=self.resolve_peer(chat_id),
|
|
|
|
|
media=types.InputMediaContact(
|
|
|
|
|
phone_number,
|
|
|
|
|
first_name,
|
|
|
|
|
last_name
|
|
|
|
|
),
|
2018-01-19 14:49:17 +00:00
|
|
|
|
message="",
|
2017-12-29 20:44:45 +00:00
|
|
|
|
silent=disable_notification or None,
|
|
|
|
|
reply_to_msg_id=reply_to_message_id,
|
|
|
|
|
random_id=self.rnd_id()
|
|
|
|
|
)
|
|
|
|
|
)
|
|
|
|
|
|
2018-04-16 09:56:52 +00:00
|
|
|
|
for i in r.updates:
|
|
|
|
|
if isinstance(i, (types.UpdateNewMessage, types.UpdateNewChannelMessage)):
|
|
|
|
|
users = {i.id: i for i in r.users}
|
|
|
|
|
chats = {i.id: i for i in r.chats}
|
|
|
|
|
|
|
|
|
|
return message_parser.parse_message(self, i.message, users, chats)
|
|
|
|
|
|
2017-12-29 20:44:45 +00:00
|
|
|
|
def send_chat_action(self,
|
|
|
|
|
chat_id: int or str,
|
|
|
|
|
action: callable,
|
|
|
|
|
progress: int = 0):
|
|
|
|
|
"""Use this method when you need to tell the other party that something is happening on your side.
|
|
|
|
|
|
|
|
|
|
Args:
|
2018-03-25 19:41:19 +00:00
|
|
|
|
chat_id (``int`` | ``str``):
|
2018-03-18 12:00:28 +00:00
|
|
|
|
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).
|
|
|
|
|
For a private channel/supergroup you can use its *t.me/joinchat/* link.
|
2017-12-29 20:44:45 +00:00
|
|
|
|
|
2018-03-25 19:41:19 +00:00
|
|
|
|
action (``callable``):
|
2017-12-29 20:44:45 +00:00
|
|
|
|
Type of action to broadcast.
|
2018-03-25 20:12:52 +00:00
|
|
|
|
Choose one from the :class:`ChatAction <pyrogram.ChatAction>` class,
|
2017-12-29 20:44:45 +00:00
|
|
|
|
depending on what the user is about to receive.
|
|
|
|
|
|
2018-03-25 19:41:19 +00:00
|
|
|
|
progress (``int``, optional):
|
2017-12-29 20:44:45 +00:00
|
|
|
|
Progress of the upload process.
|
|
|
|
|
|
2018-04-16 10:01:42 +00:00
|
|
|
|
Returns:
|
|
|
|
|
On success, True is returned.
|
|
|
|
|
|
2017-12-30 18:23:18 +00:00
|
|
|
|
Raises:
|
2018-03-25 19:59:04 +00:00
|
|
|
|
:class:`Error <pyrogram.Error>`
|
2017-12-29 20:44:45 +00:00
|
|
|
|
"""
|
2018-03-23 12:46:43 +00:00
|
|
|
|
if "Upload" in action.__name__:
|
|
|
|
|
action = action(progress)
|
|
|
|
|
else:
|
|
|
|
|
action = action()
|
|
|
|
|
|
2017-12-29 20:44:45 +00:00
|
|
|
|
return self.send(
|
|
|
|
|
functions.messages.SetTyping(
|
|
|
|
|
peer=self.resolve_peer(chat_id),
|
2018-03-23 12:46:43 +00:00
|
|
|
|
action=action
|
2017-12-29 20:44:45 +00:00
|
|
|
|
)
|
|
|
|
|
)
|
|
|
|
|
|
2018-04-14 19:55:23 +00:00
|
|
|
|
# TODO: Improvements for the new API
|
2017-12-29 20:44:45 +00:00
|
|
|
|
def get_user_profile_photos(self,
|
|
|
|
|
user_id: int or str,
|
|
|
|
|
offset: int = 0,
|
|
|
|
|
limit: int = 100):
|
|
|
|
|
"""Use this method to get a list of profile pictures for a user.
|
|
|
|
|
|
|
|
|
|
Args:
|
2018-03-25 19:41:19 +00:00
|
|
|
|
user_id (``int`` | ``str``):
|
2017-12-29 20:44:45 +00:00
|
|
|
|
Unique identifier of the target user.
|
|
|
|
|
|
2018-03-25 19:41:19 +00:00
|
|
|
|
offset (``int``, optional):
|
2017-12-29 20:44:45 +00:00
|
|
|
|
Sequential number of the first photo to be returned.
|
|
|
|
|
By default, all photos are returned.
|
|
|
|
|
|
2018-03-25 19:41:19 +00:00
|
|
|
|
limit (``int``, optional):
|
2017-12-29 20:44:45 +00:00
|
|
|
|
Limits the number of photos to be retrieved.
|
|
|
|
|
Values between 1—100 are accepted. Defaults to 100.
|
2017-12-30 18:23:18 +00:00
|
|
|
|
|
|
|
|
|
Raises:
|
2018-03-25 19:59:04 +00:00
|
|
|
|
:class:`Error <pyrogram.Error>`
|
2017-12-29 20:44:45 +00:00
|
|
|
|
"""
|
|
|
|
|
return self.send(
|
|
|
|
|
functions.photos.GetUserPhotos(
|
|
|
|
|
user_id=self.resolve_peer(user_id),
|
|
|
|
|
offset=offset,
|
|
|
|
|
max_id=0,
|
|
|
|
|
limit=limit
|
|
|
|
|
)
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
def edit_message_text(self,
|
|
|
|
|
chat_id: int or str,
|
|
|
|
|
message_id: int,
|
|
|
|
|
text: str,
|
2018-01-23 14:18:52 +00:00
|
|
|
|
parse_mode: str = "",
|
2017-12-29 20:44:45 +00:00
|
|
|
|
disable_web_page_preview: bool = None):
|
|
|
|
|
"""Use this method to edit text messages.
|
|
|
|
|
|
|
|
|
|
Args:
|
2018-03-25 19:41:19 +00:00
|
|
|
|
chat_id (``int`` | ``str``):
|
2018-03-18 12:00:28 +00:00
|
|
|
|
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).
|
|
|
|
|
For a private channel/supergroup you can use its *t.me/joinchat/* link.
|
2017-12-29 20:44:45 +00:00
|
|
|
|
|
2018-03-25 19:41:19 +00:00
|
|
|
|
message_id (``int``):
|
2017-12-29 20:44:45 +00:00
|
|
|
|
Message identifier in the chat specified in chat_id.
|
|
|
|
|
|
2018-03-25 19:41:19 +00:00
|
|
|
|
text (``str``):
|
2017-12-29 20:44:45 +00:00
|
|
|
|
New text of the message.
|
|
|
|
|
|
2018-03-25 19:41:19 +00:00
|
|
|
|
parse_mode (``str``):
|
2018-03-25 20:12:52 +00:00
|
|
|
|
Use :obj:`MARKDOWN <pyrogram.ParseMode.MARKDOWN>` or :obj:`HTML <pyrogram.ParseMode.HTML>`
|
|
|
|
|
if you want Telegram apps to show bold, italic, fixed-width text or inline URLs in your message.
|
2018-01-23 14:38:01 +00:00
|
|
|
|
Defaults to Markdown.
|
2018-01-23 14:18:52 +00:00
|
|
|
|
|
2018-03-25 19:41:19 +00:00
|
|
|
|
disable_web_page_preview (``bool``, optional):
|
2017-12-29 20:44:45 +00:00
|
|
|
|
Disables link previews for links in this message.
|
2017-12-30 18:23:18 +00:00
|
|
|
|
|
2018-04-16 10:01:42 +00:00
|
|
|
|
Returns:
|
2018-04-16 11:28:53 +00:00
|
|
|
|
On success, the edited :obj:`Message <pyrogram.api.types.pyrogram.Message>` is returned.
|
2018-04-16 10:01:42 +00:00
|
|
|
|
|
2017-12-30 18:23:18 +00:00
|
|
|
|
Raises:
|
2018-03-25 19:59:04 +00:00
|
|
|
|
:class:`Error <pyrogram.Error>`
|
2017-12-29 20:44:45 +00:00
|
|
|
|
"""
|
2018-01-23 14:18:52 +00:00
|
|
|
|
style = self.html if parse_mode.lower() == "html" else self.markdown
|
|
|
|
|
|
2018-04-16 10:01:42 +00:00
|
|
|
|
r = self.send(
|
2017-12-29 20:44:45 +00:00
|
|
|
|
functions.messages.EditMessage(
|
|
|
|
|
peer=self.resolve_peer(chat_id),
|
|
|
|
|
id=message_id,
|
|
|
|
|
no_webpage=disable_web_page_preview or None,
|
2018-01-23 14:18:52 +00:00
|
|
|
|
**style.parse(text)
|
2017-12-29 20:44:45 +00:00
|
|
|
|
)
|
|
|
|
|
)
|
|
|
|
|
|
2018-04-16 10:01:42 +00:00
|
|
|
|
for i in r.updates:
|
|
|
|
|
if isinstance(i, (types.UpdateEditMessage, types.UpdateEditChannelMessage)):
|
|
|
|
|
users = {i.id: i for i in r.users}
|
|
|
|
|
chats = {i.id: i for i in r.chats}
|
|
|
|
|
|
|
|
|
|
return message_parser.parse_message(self, i.message, users, chats)
|
|
|
|
|
|
2017-12-29 20:44:45 +00:00
|
|
|
|
def edit_message_caption(self,
|
|
|
|
|
chat_id: int or str,
|
|
|
|
|
message_id: int,
|
2018-01-23 14:18:52 +00:00
|
|
|
|
caption: str,
|
|
|
|
|
parse_mode: str = ""):
|
2017-12-29 20:44:45 +00:00
|
|
|
|
"""Use this method to edit captions of messages.
|
|
|
|
|
|
|
|
|
|
Args:
|
2018-03-25 19:41:19 +00:00
|
|
|
|
chat_id (``int`` | ``str``):
|
2018-03-18 12:00:28 +00:00
|
|
|
|
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).
|
|
|
|
|
For a private channel/supergroup you can use its *t.me/joinchat/* link.
|
2017-12-29 20:44:45 +00:00
|
|
|
|
|
2018-03-25 19:41:19 +00:00
|
|
|
|
message_id (``int``):
|
2017-12-29 20:44:45 +00:00
|
|
|
|
Message identifier in the chat specified in chat_id.
|
|
|
|
|
|
2018-03-25 19:41:19 +00:00
|
|
|
|
caption (``str``):
|
2017-12-29 20:44:45 +00:00
|
|
|
|
New caption of the message.
|
2017-12-30 18:23:18 +00:00
|
|
|
|
|
2018-03-25 19:41:19 +00:00
|
|
|
|
parse_mode (``str``):
|
2018-03-25 20:12:52 +00:00
|
|
|
|
Use :obj:`MARKDOWN <pyrogram.ParseMode.MARKDOWN>` or :obj:`HTML <pyrogram.ParseMode.HTML>`
|
|
|
|
|
if you want Telegram apps to show bold, italic, fixed-width text or inline URLs in your caption.
|
2018-01-23 14:38:01 +00:00
|
|
|
|
Defaults to Markdown.
|
2018-01-23 14:18:52 +00:00
|
|
|
|
|
2018-04-16 10:03:31 +00:00
|
|
|
|
Returns:
|
2018-04-16 11:28:53 +00:00
|
|
|
|
On success, the edited :obj:`Message <pyrogram.api.types.pyrogram.Message>` is returned.
|
2018-04-16 10:03:31 +00:00
|
|
|
|
|
2017-12-30 18:23:18 +00:00
|
|
|
|
Raises:
|
2018-03-25 19:59:04 +00:00
|
|
|
|
:class:`Error <pyrogram.Error>`
|
2017-12-29 20:44:45 +00:00
|
|
|
|
"""
|
2018-01-23 14:18:52 +00:00
|
|
|
|
style = self.html if parse_mode.lower() == "html" else self.markdown
|
|
|
|
|
|
2018-04-16 10:03:31 +00:00
|
|
|
|
r = self.send(
|
2017-12-29 20:44:45 +00:00
|
|
|
|
functions.messages.EditMessage(
|
|
|
|
|
peer=self.resolve_peer(chat_id),
|
|
|
|
|
id=message_id,
|
2018-01-23 14:18:52 +00:00
|
|
|
|
**style.parse(caption)
|
2017-12-29 20:44:45 +00:00
|
|
|
|
)
|
|
|
|
|
)
|
|
|
|
|
|
2018-04-16 10:03:31 +00:00
|
|
|
|
for i in r.updates:
|
|
|
|
|
if isinstance(i, (types.UpdateEditMessage, types.UpdateEditChannelMessage)):
|
|
|
|
|
users = {i.id: i for i in r.users}
|
|
|
|
|
chats = {i.id: i for i in r.chats}
|
|
|
|
|
|
|
|
|
|
return message_parser.parse_message(self, i.message, users, chats)
|
|
|
|
|
|
2017-12-29 20:44:45 +00:00
|
|
|
|
def delete_messages(self,
|
2018-01-12 19:52:43 +00:00
|
|
|
|
chat_id: int or str,
|
2017-12-29 20:44:45 +00:00
|
|
|
|
message_ids: list,
|
|
|
|
|
revoke: bool = None):
|
|
|
|
|
"""Use this method to delete messages, including service messages, with the following limitations:
|
|
|
|
|
|
|
|
|
|
- A message can only be deleted if it was sent less than 48 hours ago.
|
|
|
|
|
- Users can delete outgoing messages in groups and supergroups.
|
|
|
|
|
- Users granted *can_post_messages* permissions can delete outgoing messages in channels.
|
|
|
|
|
- If the user is an administrator of a group, it can delete any message there.
|
|
|
|
|
- If the user has *can_delete_messages* permission in a supergroup or a channel, it can delete any message there.
|
|
|
|
|
|
|
|
|
|
Args:
|
2018-03-25 19:41:19 +00:00
|
|
|
|
chat_id (``int`` | ``str``):
|
2018-03-18 12:00:28 +00:00
|
|
|
|
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).
|
|
|
|
|
For a private channel/supergroup you can use its *t.me/joinchat/* link.
|
2018-01-12 19:52:43 +00:00
|
|
|
|
|
2018-03-25 19:41:19 +00:00
|
|
|
|
message_ids (``list``):
|
2017-12-29 20:44:45 +00:00
|
|
|
|
List of identifiers of the messages to delete.
|
|
|
|
|
|
2018-03-25 19:41:19 +00:00
|
|
|
|
revoke (``bool``, optional):
|
2018-01-12 19:52:43 +00:00
|
|
|
|
Deletes messages on both parts.
|
|
|
|
|
This is only for private cloud chats and normal groups, messages on
|
|
|
|
|
channels and supergroups are always revoked (i.e.: deleted for everyone).
|
2017-12-30 18:23:18 +00:00
|
|
|
|
|
|
|
|
|
Raises:
|
2018-03-25 19:59:04 +00:00
|
|
|
|
:class:`Error <pyrogram.Error>`
|
2017-12-29 20:44:45 +00:00
|
|
|
|
"""
|
2018-01-12 19:52:43 +00:00
|
|
|
|
peer = self.resolve_peer(chat_id)
|
|
|
|
|
|
|
|
|
|
if isinstance(peer, types.InputPeerChannel):
|
|
|
|
|
return self.send(
|
|
|
|
|
functions.channels.DeleteMessages(
|
|
|
|
|
channel=peer,
|
|
|
|
|
id=message_ids
|
|
|
|
|
)
|
|
|
|
|
)
|
|
|
|
|
else:
|
|
|
|
|
# TODO: Maybe "revoke" is superfluous.
|
|
|
|
|
# If I want to delete a message, chances are I want it to
|
|
|
|
|
# be deleted even from the other side
|
|
|
|
|
return self.send(
|
|
|
|
|
functions.messages.DeleteMessages(
|
|
|
|
|
id=message_ids,
|
|
|
|
|
revoke=revoke or None
|
|
|
|
|
)
|
2017-12-29 20:44:45 +00:00
|
|
|
|
)
|
|
|
|
|
|
2018-04-14 19:55:23 +00:00
|
|
|
|
# TODO: Improvements for the new API
|
2018-03-08 09:23:48 +00:00
|
|
|
|
def save_file(self,
|
|
|
|
|
path: str,
|
|
|
|
|
file_id: int = None,
|
|
|
|
|
file_part: int = 0,
|
|
|
|
|
progress: callable = None):
|
2017-12-29 20:44:45 +00:00
|
|
|
|
part_size = 512 * 1024
|
|
|
|
|
file_size = os.path.getsize(path)
|
|
|
|
|
file_total_parts = math.ceil(file_size / part_size)
|
2018-02-18 17:11:33 +00:00
|
|
|
|
is_big = True if file_size > 10 * 1024 * 1024 else False
|
2017-12-29 20:44:45 +00:00
|
|
|
|
is_missing_part = True if file_id is not None else False
|
|
|
|
|
file_id = file_id or self.rnd_id()
|
|
|
|
|
md5_sum = md5() if not is_big and not is_missing_part else None
|
|
|
|
|
|
2018-04-05 10:55:34 +00:00
|
|
|
|
session = Session(self.dc_id, self.test_mode, self.proxy, self.auth_key, self.api_id)
|
2017-12-29 20:44:45 +00:00
|
|
|
|
session.start()
|
|
|
|
|
|
|
|
|
|
try:
|
|
|
|
|
with open(path, "rb") as f:
|
|
|
|
|
f.seek(part_size * file_part)
|
|
|
|
|
|
|
|
|
|
while True:
|
|
|
|
|
chunk = f.read(part_size)
|
|
|
|
|
|
|
|
|
|
if not chunk:
|
|
|
|
|
if not is_big:
|
|
|
|
|
md5_sum = "".join([hex(i)[2:].zfill(2) for i in md5_sum.digest()])
|
|
|
|
|
break
|
|
|
|
|
|
2018-03-23 12:46:43 +00:00
|
|
|
|
if is_big:
|
|
|
|
|
rpc = functions.upload.SaveBigFilePart(
|
2017-12-29 20:44:45 +00:00
|
|
|
|
file_id=file_id,
|
|
|
|
|
file_part=file_part,
|
2018-03-23 12:46:43 +00:00
|
|
|
|
file_total_parts=file_total_parts,
|
|
|
|
|
bytes=chunk
|
|
|
|
|
)
|
|
|
|
|
else:
|
|
|
|
|
rpc = functions.upload.SaveFilePart(
|
|
|
|
|
file_id=file_id,
|
|
|
|
|
file_part=file_part,
|
|
|
|
|
bytes=chunk
|
2017-12-29 20:44:45 +00:00
|
|
|
|
)
|
2018-03-23 12:46:43 +00:00
|
|
|
|
|
|
|
|
|
assert self.send(rpc), "Couldn't upload file"
|
2017-12-29 20:44:45 +00:00
|
|
|
|
|
|
|
|
|
if is_missing_part:
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
if not is_big:
|
|
|
|
|
md5_sum.update(chunk)
|
|
|
|
|
|
|
|
|
|
file_part += 1
|
2018-03-08 09:23:48 +00:00
|
|
|
|
|
|
|
|
|
if progress:
|
2018-03-08 09:31:34 +00:00
|
|
|
|
progress(min(file_part * part_size, file_size), file_size)
|
2017-12-29 20:44:45 +00:00
|
|
|
|
except Exception as e:
|
2018-03-23 12:46:43 +00:00
|
|
|
|
log.error(e, exc_info=True)
|
2017-12-29 20:44:45 +00:00
|
|
|
|
else:
|
2018-03-23 12:46:43 +00:00
|
|
|
|
if is_big:
|
|
|
|
|
return types.InputFileBig(
|
|
|
|
|
id=file_id,
|
|
|
|
|
parts=file_total_parts,
|
|
|
|
|
name=os.path.basename(path),
|
|
|
|
|
|
|
|
|
|
)
|
|
|
|
|
else:
|
|
|
|
|
return types.InputFile(
|
|
|
|
|
id=file_id,
|
|
|
|
|
parts=file_total_parts,
|
|
|
|
|
name=os.path.basename(path),
|
|
|
|
|
md5_checksum=md5_sum
|
|
|
|
|
)
|
2017-12-29 20:44:45 +00:00
|
|
|
|
finally:
|
|
|
|
|
session.stop()
|
|
|
|
|
|
2018-04-14 19:55:23 +00:00
|
|
|
|
# TODO: Improvements for the new API
|
2017-12-23 11:26:26 +00:00
|
|
|
|
def get_file(self,
|
2017-12-27 19:59:37 +00:00
|
|
|
|
dc_id: int,
|
2017-12-23 11:26:26 +00:00
|
|
|
|
id: int = None,
|
|
|
|
|
access_hash: int = None,
|
|
|
|
|
volume_id: int = None,
|
|
|
|
|
local_id: int = None,
|
|
|
|
|
secret: int = None,
|
2018-02-24 16:16:25 +00:00
|
|
|
|
version: int = 0,
|
|
|
|
|
size: int = None,
|
2018-03-20 20:20:04 +00:00
|
|
|
|
progress: callable = None) -> str:
|
2017-12-27 19:59:37 +00:00
|
|
|
|
if dc_id != self.dc_id:
|
|
|
|
|
exported_auth = self.send(
|
|
|
|
|
functions.auth.ExportAuthorization(
|
|
|
|
|
dc_id=dc_id
|
|
|
|
|
)
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
session = Session(
|
|
|
|
|
dc_id,
|
|
|
|
|
self.test_mode,
|
2018-01-16 21:05:19 +00:00
|
|
|
|
self.proxy,
|
|
|
|
|
Auth(dc_id, self.test_mode, self.proxy).create(),
|
2018-04-05 10:55:34 +00:00
|
|
|
|
self.api_id
|
2017-12-27 19:59:37 +00:00
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
session.start()
|
|
|
|
|
|
|
|
|
|
session.send(
|
|
|
|
|
functions.auth.ImportAuthorization(
|
|
|
|
|
id=exported_auth.id,
|
|
|
|
|
bytes=exported_auth.bytes
|
|
|
|
|
)
|
|
|
|
|
)
|
|
|
|
|
else:
|
|
|
|
|
session = Session(
|
|
|
|
|
dc_id,
|
|
|
|
|
self.test_mode,
|
2018-01-16 21:05:19 +00:00
|
|
|
|
self.proxy,
|
2017-12-27 19:59:37 +00:00
|
|
|
|
self.auth_key,
|
2018-04-05 10:55:34 +00:00
|
|
|
|
self.api_id
|
2017-12-27 19:59:37 +00:00
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
session.start()
|
|
|
|
|
|
2017-12-23 11:26:26 +00:00
|
|
|
|
if volume_id: # Photos are accessed by volume_id, local_id, secret
|
|
|
|
|
location = types.InputFileLocation(
|
|
|
|
|
volume_id=volume_id,
|
|
|
|
|
local_id=local_id,
|
|
|
|
|
secret=secret
|
|
|
|
|
)
|
|
|
|
|
else: # Any other file can be more easily accessed by id and access_hash
|
|
|
|
|
location = types.InputDocumentFileLocation(
|
|
|
|
|
id=id,
|
|
|
|
|
access_hash=access_hash,
|
|
|
|
|
version=version
|
|
|
|
|
)
|
2017-12-20 15:21:56 +00:00
|
|
|
|
|
2018-02-18 14:03:33 +00:00
|
|
|
|
limit = 1024 * 1024
|
2017-12-19 13:00:19 +00:00
|
|
|
|
offset = 0
|
2018-03-21 12:39:23 +00:00
|
|
|
|
file_name = ""
|
2017-12-19 13:00:19 +00:00
|
|
|
|
|
|
|
|
|
try:
|
|
|
|
|
r = session.send(
|
|
|
|
|
functions.upload.GetFile(
|
2017-12-23 11:26:26 +00:00
|
|
|
|
location=location,
|
2017-12-19 13:00:19 +00:00
|
|
|
|
offset=offset,
|
|
|
|
|
limit=limit
|
|
|
|
|
)
|
|
|
|
|
)
|
|
|
|
|
|
2017-12-20 15:21:56 +00:00
|
|
|
|
if isinstance(r, types.upload.File):
|
2018-03-30 20:41:34 +00:00
|
|
|
|
with tempfile.NamedTemporaryFile("wb", delete=False) as f:
|
2018-03-21 09:07:55 +00:00
|
|
|
|
file_name = f.name
|
|
|
|
|
|
2018-03-20 20:20:04 +00:00
|
|
|
|
while True:
|
|
|
|
|
chunk = r.bytes
|
2017-12-20 15:21:56 +00:00
|
|
|
|
|
2018-03-20 20:20:04 +00:00
|
|
|
|
if not chunk:
|
|
|
|
|
break
|
2018-03-20 12:04:35 +00:00
|
|
|
|
|
2018-03-20 20:20:04 +00:00
|
|
|
|
f.write(chunk)
|
2018-02-18 14:03:33 +00:00
|
|
|
|
f.flush()
|
|
|
|
|
os.fsync(f.fileno())
|
|
|
|
|
|
2018-03-20 20:20:04 +00:00
|
|
|
|
offset += limit
|
2017-12-20 15:21:56 +00:00
|
|
|
|
|
2018-03-20 20:20:04 +00:00
|
|
|
|
if progress:
|
|
|
|
|
progress(min(offset, size), size)
|
2018-02-24 16:16:25 +00:00
|
|
|
|
|
2018-03-20 20:20:04 +00:00
|
|
|
|
r = session.send(
|
|
|
|
|
functions.upload.GetFile(
|
|
|
|
|
location=location,
|
|
|
|
|
offset=offset,
|
|
|
|
|
limit=limit
|
|
|
|
|
)
|
2017-12-20 15:21:56 +00:00
|
|
|
|
)
|
2018-02-18 14:03:33 +00:00
|
|
|
|
|
2018-03-21 08:01:18 +00:00
|
|
|
|
elif isinstance(r, types.upload.FileCdnRedirect):
|
2017-12-19 13:00:19 +00:00
|
|
|
|
cdn_session = Session(
|
|
|
|
|
r.dc_id,
|
|
|
|
|
self.test_mode,
|
2018-01-16 21:05:19 +00:00
|
|
|
|
self.proxy,
|
|
|
|
|
Auth(r.dc_id, self.test_mode, self.proxy).create(),
|
2018-04-05 10:55:34 +00:00
|
|
|
|
self.api_id,
|
2017-12-19 13:00:19 +00:00
|
|
|
|
is_cdn=True
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
cdn_session.start()
|
|
|
|
|
|
|
|
|
|
try:
|
2018-03-30 20:41:34 +00:00
|
|
|
|
with tempfile.NamedTemporaryFile("wb", delete=False) as f:
|
2018-03-21 09:07:55 +00:00
|
|
|
|
file_name = f.name
|
2018-03-21 12:39:23 +00:00
|
|
|
|
|
2018-03-20 20:20:04 +00:00
|
|
|
|
while True:
|
|
|
|
|
r2 = cdn_session.send(
|
|
|
|
|
functions.upload.GetCdnFile(
|
|
|
|
|
file_token=r.file_token,
|
|
|
|
|
offset=offset,
|
|
|
|
|
limit=limit
|
|
|
|
|
)
|
2017-12-19 13:00:19 +00:00
|
|
|
|
)
|
2018-02-18 17:11:33 +00:00
|
|
|
|
|
2018-03-20 20:20:04 +00:00
|
|
|
|
if isinstance(r2, types.upload.CdnFileReuploadNeeded):
|
|
|
|
|
try:
|
|
|
|
|
session.send(
|
|
|
|
|
functions.upload.ReuploadCdnFile(
|
|
|
|
|
file_token=r.file_token,
|
|
|
|
|
request_token=r2.request_token
|
|
|
|
|
)
|
2018-02-18 17:11:33 +00:00
|
|
|
|
)
|
2018-03-20 20:20:04 +00:00
|
|
|
|
except VolumeLocNotFound:
|
|
|
|
|
break
|
|
|
|
|
else:
|
|
|
|
|
continue
|
2017-12-19 13:00:19 +00:00
|
|
|
|
|
2018-03-20 20:20:04 +00:00
|
|
|
|
chunk = r2.bytes
|
2017-12-19 13:00:19 +00:00
|
|
|
|
|
2018-03-20 20:20:04 +00:00
|
|
|
|
# https://core.telegram.org/cdn#decrypting-files
|
|
|
|
|
decrypted_chunk = AES.ctr_decrypt(
|
|
|
|
|
chunk,
|
|
|
|
|
r.encryption_key,
|
|
|
|
|
r.encryption_iv,
|
2018-02-18 17:11:33 +00:00
|
|
|
|
offset
|
|
|
|
|
)
|
2017-12-19 13:00:19 +00:00
|
|
|
|
|
2018-03-20 20:20:04 +00:00
|
|
|
|
hashes = session.send(
|
|
|
|
|
functions.upload.GetCdnFileHashes(
|
|
|
|
|
r.file_token,
|
|
|
|
|
offset
|
|
|
|
|
)
|
|
|
|
|
)
|
2018-02-18 14:03:33 +00:00
|
|
|
|
|
2018-03-20 20:20:04 +00:00
|
|
|
|
# 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(), "Invalid CDN hash part {}".format(i)
|
2018-02-18 14:03:33 +00:00
|
|
|
|
|
2018-03-20 20:20:04 +00:00
|
|
|
|
f.write(decrypted_chunk)
|
2018-02-18 17:11:33 +00:00
|
|
|
|
f.flush()
|
|
|
|
|
os.fsync(f.fileno())
|
2018-02-18 14:03:33 +00:00
|
|
|
|
|
2018-03-20 20:20:04 +00:00
|
|
|
|
offset += limit
|
2018-02-24 16:16:25 +00:00
|
|
|
|
|
2018-03-20 20:20:04 +00:00
|
|
|
|
if progress:
|
|
|
|
|
progress(min(offset, size), size)
|
2018-02-24 16:16:25 +00:00
|
|
|
|
|
2018-03-20 20:20:04 +00:00
|
|
|
|
if len(chunk) < limit:
|
|
|
|
|
break
|
2017-12-19 13:00:19 +00:00
|
|
|
|
except Exception as e:
|
2018-03-20 20:20:04 +00:00
|
|
|
|
raise e
|
2017-12-19 13:00:19 +00:00
|
|
|
|
finally:
|
|
|
|
|
cdn_session.stop()
|
|
|
|
|
except Exception as e:
|
2018-03-20 20:20:04 +00:00
|
|
|
|
log.error(e, exc_info=True)
|
|
|
|
|
|
2018-03-21 08:01:18 +00:00
|
|
|
|
try:
|
|
|
|
|
os.remove(file_name)
|
|
|
|
|
except OSError:
|
|
|
|
|
pass
|
2018-03-21 12:39:23 +00:00
|
|
|
|
|
|
|
|
|
return ""
|
2017-12-19 13:00:19 +00:00
|
|
|
|
else:
|
2018-03-20 20:20:04 +00:00
|
|
|
|
return file_name
|
2017-12-19 13:00:19 +00:00
|
|
|
|
finally:
|
|
|
|
|
session.stop()
|
2017-12-23 12:34:06 +00:00
|
|
|
|
|
2017-12-25 11:30:48 +00:00
|
|
|
|
def join_chat(self, chat_id: str):
|
2017-12-29 20:44:45 +00:00
|
|
|
|
"""Use this method to join a group chat or channel.
|
|
|
|
|
|
|
|
|
|
Args:
|
2018-03-25 19:41:19 +00:00
|
|
|
|
chat_id (``str``):
|
2017-12-29 20:44:45 +00:00
|
|
|
|
Unique identifier for the target chat in form of *t.me/joinchat/* links or username of the target
|
2018-01-03 16:39:12 +00:00
|
|
|
|
channel/supergroup (in the format @username).
|
2017-12-30 18:23:18 +00:00
|
|
|
|
|
|
|
|
|
Raises:
|
2018-03-25 19:59:04 +00:00
|
|
|
|
:class:`Error <pyrogram.Error>`
|
2017-12-29 20:44:45 +00:00
|
|
|
|
"""
|
2017-12-25 11:30:48 +00:00
|
|
|
|
match = self.INVITE_LINK_RE.match(chat_id)
|
|
|
|
|
|
|
|
|
|
if match:
|
|
|
|
|
return self.send(
|
|
|
|
|
functions.messages.ImportChatInvite(
|
|
|
|
|
hash=match.group(1)
|
|
|
|
|
)
|
|
|
|
|
)
|
|
|
|
|
else:
|
|
|
|
|
resolved_peer = self.send(
|
|
|
|
|
functions.contacts.ResolveUsername(
|
|
|
|
|
username=chat_id.lower().strip("@")
|
|
|
|
|
)
|
|
|
|
|
)
|
|
|
|
|
|
2018-03-26 11:41:00 +00:00
|
|
|
|
channel = types.InputPeerChannel(
|
2017-12-25 11:30:48 +00:00
|
|
|
|
channel_id=resolved_peer.chats[0].id,
|
|
|
|
|
access_hash=resolved_peer.chats[0].access_hash
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
return self.send(
|
|
|
|
|
functions.channels.JoinChannel(
|
|
|
|
|
channel=channel
|
|
|
|
|
)
|
|
|
|
|
)
|
2017-12-25 11:47:08 +00:00
|
|
|
|
|
2017-12-25 12:02:14 +00:00
|
|
|
|
def leave_chat(self, chat_id: int or str, delete: bool = False):
|
2017-12-29 20:44:45 +00:00
|
|
|
|
"""Use this method to leave a group chat or channel.
|
|
|
|
|
|
|
|
|
|
Args:
|
2018-03-25 19:41:19 +00:00
|
|
|
|
chat_id (``int`` | ``str``):
|
2018-01-03 16:39:12 +00:00
|
|
|
|
Unique identifier for the target chat or username of the target channel/supergroup
|
|
|
|
|
(in the format @username).
|
2017-12-29 20:44:45 +00:00
|
|
|
|
|
2018-03-25 19:41:19 +00:00
|
|
|
|
delete (``bool``, optional):
|
2017-12-29 20:44:45 +00:00
|
|
|
|
Deletes the group chat dialog after leaving (for simple group chats, not supergroups).
|
2017-12-30 18:23:18 +00:00
|
|
|
|
|
|
|
|
|
Raises:
|
2018-03-25 19:59:04 +00:00
|
|
|
|
:class:`Error <pyrogram.Error>`
|
2017-12-29 20:44:45 +00:00
|
|
|
|
"""
|
2017-12-25 11:47:08 +00:00
|
|
|
|
peer = self.resolve_peer(chat_id)
|
|
|
|
|
|
|
|
|
|
if isinstance(peer, types.InputPeerChannel):
|
|
|
|
|
return self.send(
|
|
|
|
|
functions.channels.LeaveChannel(
|
|
|
|
|
channel=self.resolve_peer(chat_id)
|
|
|
|
|
)
|
|
|
|
|
)
|
|
|
|
|
elif isinstance(peer, types.InputPeerChat):
|
2017-12-25 12:02:14 +00:00
|
|
|
|
r = self.send(
|
2017-12-25 11:47:08 +00:00
|
|
|
|
functions.messages.DeleteChatUser(
|
|
|
|
|
chat_id=peer.chat_id,
|
|
|
|
|
user_id=types.InputPeerSelf()
|
|
|
|
|
)
|
|
|
|
|
)
|
2017-12-25 12:02:14 +00:00
|
|
|
|
|
|
|
|
|
if delete:
|
|
|
|
|
self.send(
|
|
|
|
|
functions.messages.DeleteHistory(
|
|
|
|
|
peer=peer,
|
|
|
|
|
max_id=0
|
|
|
|
|
)
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
return r
|
2017-12-27 20:23:00 +00:00
|
|
|
|
|
2018-01-08 05:31:48 +00:00
|
|
|
|
def export_chat_invite_link(self, chat_id: int or str, new: bool = False):
|
2017-12-29 20:44:45 +00:00
|
|
|
|
"""Use this method to export an invite link to a supergroup or a channel.
|
|
|
|
|
|
|
|
|
|
The user must be an administrator in the chat for this to work and must have the appropriate admin rights.
|
|
|
|
|
|
|
|
|
|
Args:
|
2018-03-25 19:41:19 +00:00
|
|
|
|
chat_id (``int`` | ``str``):
|
2018-01-03 16:39:12 +00:00
|
|
|
|
Unique identifier for the target chat or username of the target channel/supergroup
|
|
|
|
|
(in the format @username).
|
2017-12-29 20:44:45 +00:00
|
|
|
|
|
2018-03-25 19:41:19 +00:00
|
|
|
|
new (``bool``):
|
2018-01-08 05:31:48 +00:00
|
|
|
|
The previous link will be deactivated and a new link will be generated.
|
|
|
|
|
This is also used to create the invite link in case it doesn't exist yet.
|
|
|
|
|
|
2017-12-29 20:44:45 +00:00
|
|
|
|
Returns:
|
|
|
|
|
On success, the exported invite link as string is returned.
|
2017-12-30 18:23:18 +00:00
|
|
|
|
|
|
|
|
|
Raises:
|
2018-03-25 19:59:04 +00:00
|
|
|
|
:class:`Error <pyrogram.Error>`
|
2018-01-08 05:31:48 +00:00
|
|
|
|
|
|
|
|
|
Note:
|
|
|
|
|
If the returned link is a new one it may take a while for it to be activated.
|
2017-12-29 20:44:45 +00:00
|
|
|
|
"""
|
2017-12-27 20:23:00 +00:00
|
|
|
|
peer = self.resolve_peer(chat_id)
|
|
|
|
|
|
|
|
|
|
if isinstance(peer, types.InputPeerChat):
|
2018-01-08 05:31:48 +00:00
|
|
|
|
if new:
|
|
|
|
|
return self.send(
|
|
|
|
|
functions.messages.ExportChatInvite(
|
|
|
|
|
chat_id=peer.chat_id
|
|
|
|
|
)
|
|
|
|
|
).link
|
|
|
|
|
else:
|
|
|
|
|
chat_full = self.send(
|
|
|
|
|
functions.messages.GetFullChat(
|
|
|
|
|
chat_id=peer.chat_id
|
|
|
|
|
)
|
|
|
|
|
).full_chat # type: types.ChatFull
|
|
|
|
|
|
|
|
|
|
if isinstance(chat_full.exported_invite, types.ChatInviteExported):
|
|
|
|
|
return chat_full.exported_invite.link
|
|
|
|
|
else:
|
|
|
|
|
raise ChatAdminRequired
|
2017-12-27 20:23:00 +00:00
|
|
|
|
elif isinstance(peer, types.InputPeerChannel):
|
2018-01-08 05:31:48 +00:00
|
|
|
|
if new:
|
|
|
|
|
return self.send(
|
|
|
|
|
functions.channels.ExportInvite(
|
|
|
|
|
channel=peer
|
|
|
|
|
)
|
|
|
|
|
).link
|
|
|
|
|
else:
|
|
|
|
|
channel_full = self.send(
|
|
|
|
|
functions.channels.GetFullChannel(
|
|
|
|
|
channel=peer
|
|
|
|
|
)
|
|
|
|
|
).full_chat # type: types.ChannelFull
|
|
|
|
|
|
|
|
|
|
if isinstance(channel_full.exported_invite, types.ChatInviteExported):
|
|
|
|
|
return channel_full.exported_invite.link
|
|
|
|
|
else:
|
|
|
|
|
raise ChatAdminRequired
|
2018-01-20 14:46:17 +00:00
|
|
|
|
|
|
|
|
|
def enable_cloud_password(self, password: str, hint: str = "", email: str = ""):
|
2018-01-20 14:58:22 +00:00
|
|
|
|
"""Use this method to enable the Two-Step Verification security feature (Cloud Password) on your account.
|
2018-01-20 14:46:17 +00:00
|
|
|
|
|
|
|
|
|
This password will be asked when you log in on a new device in addition to the SMS code.
|
|
|
|
|
|
|
|
|
|
Args:
|
2018-03-25 19:41:19 +00:00
|
|
|
|
password (``str``):
|
2018-01-20 14:46:17 +00:00
|
|
|
|
Your password.
|
|
|
|
|
|
2018-03-25 19:41:19 +00:00
|
|
|
|
hint (``str``, optional):
|
2018-01-20 14:46:17 +00:00
|
|
|
|
A password hint.
|
|
|
|
|
|
2018-03-25 19:41:19 +00:00
|
|
|
|
email (``str``, optional):
|
2018-01-20 14:46:17 +00:00
|
|
|
|
Recovery e-mail.
|
|
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
|
True on success, False otherwise.
|
|
|
|
|
|
|
|
|
|
Raises:
|
2018-03-25 19:59:04 +00:00
|
|
|
|
:class:`Error <pyrogram.Error>`
|
2018-01-20 14:46:17 +00:00
|
|
|
|
"""
|
|
|
|
|
r = self.send(functions.account.GetPassword())
|
|
|
|
|
|
|
|
|
|
if isinstance(r, types.account.NoPassword):
|
|
|
|
|
salt = r.new_salt + os.urandom(8)
|
|
|
|
|
password_hash = sha256(salt + password.encode() + salt).digest()
|
|
|
|
|
|
|
|
|
|
return self.send(
|
|
|
|
|
functions.account.UpdatePasswordSettings(
|
|
|
|
|
current_password_hash=salt,
|
|
|
|
|
new_settings=types.account.PasswordInputSettings(
|
|
|
|
|
new_salt=salt,
|
|
|
|
|
new_password_hash=password_hash,
|
|
|
|
|
hint=hint,
|
|
|
|
|
email=email
|
|
|
|
|
)
|
|
|
|
|
)
|
|
|
|
|
)
|
|
|
|
|
else:
|
|
|
|
|
return False
|
|
|
|
|
|
|
|
|
|
def change_cloud_password(self, current_password: str, new_password: str, new_hint: str = ""):
|
2018-01-20 14:58:22 +00:00
|
|
|
|
"""Use this method to change your Two-Step Verification password (Cloud Password) with a new one.
|
2018-01-20 14:46:17 +00:00
|
|
|
|
|
|
|
|
|
Args:
|
2018-03-25 19:41:19 +00:00
|
|
|
|
current_password (``str``):
|
2018-01-20 14:46:17 +00:00
|
|
|
|
Your current password.
|
|
|
|
|
|
2018-03-25 19:41:19 +00:00
|
|
|
|
new_password (``str``):
|
2018-01-20 14:46:17 +00:00
|
|
|
|
Your new password.
|
|
|
|
|
|
2018-03-25 19:41:19 +00:00
|
|
|
|
new_hint (``str``, optional):
|
2018-01-20 14:46:17 +00:00
|
|
|
|
A new password hint.
|
|
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
|
True on success, False otherwise.
|
|
|
|
|
|
|
|
|
|
Raises:
|
2018-03-25 19:59:04 +00:00
|
|
|
|
:class:`Error <pyrogram.Error>`
|
2018-01-20 14:46:17 +00:00
|
|
|
|
"""
|
|
|
|
|
r = self.send(functions.account.GetPassword())
|
|
|
|
|
|
|
|
|
|
if isinstance(r, types.account.Password):
|
|
|
|
|
current_password_hash = sha256(r.current_salt + current_password.encode() + r.current_salt).digest()
|
|
|
|
|
|
|
|
|
|
new_salt = r.new_salt + os.urandom(8)
|
|
|
|
|
new_password_hash = sha256(new_salt + new_password.encode() + new_salt).digest()
|
|
|
|
|
|
|
|
|
|
return self.send(
|
|
|
|
|
functions.account.UpdatePasswordSettings(
|
|
|
|
|
current_password_hash=current_password_hash,
|
|
|
|
|
new_settings=types.account.PasswordInputSettings(
|
|
|
|
|
new_salt=new_salt,
|
|
|
|
|
new_password_hash=new_password_hash,
|
|
|
|
|
hint=new_hint
|
|
|
|
|
)
|
|
|
|
|
)
|
|
|
|
|
)
|
|
|
|
|
else:
|
|
|
|
|
return False
|
|
|
|
|
|
|
|
|
|
def remove_cloud_password(self, password: str):
|
2018-01-20 14:58:22 +00:00
|
|
|
|
"""Use this method to turn off the Two-Step Verification security feature (Cloud Password) on your account.
|
2018-01-20 14:46:17 +00:00
|
|
|
|
|
|
|
|
|
Args:
|
2018-03-25 19:41:19 +00:00
|
|
|
|
password (``str``):
|
2018-01-20 14:46:17 +00:00
|
|
|
|
Your current password.
|
|
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
|
True on success, False otherwise.
|
|
|
|
|
|
|
|
|
|
Raises:
|
2018-03-25 19:59:04 +00:00
|
|
|
|
:class:`Error <pyrogram.Error>`
|
2018-01-20 14:46:17 +00:00
|
|
|
|
"""
|
|
|
|
|
r = self.send(functions.account.GetPassword())
|
|
|
|
|
|
|
|
|
|
if isinstance(r, types.account.Password):
|
|
|
|
|
password_hash = sha256(r.current_salt + password.encode() + r.current_salt).digest()
|
|
|
|
|
|
|
|
|
|
return self.send(
|
|
|
|
|
functions.account.UpdatePasswordSettings(
|
|
|
|
|
current_password_hash=password_hash,
|
|
|
|
|
new_settings=types.account.PasswordInputSettings(
|
|
|
|
|
new_salt=b"",
|
|
|
|
|
new_password_hash=b"",
|
|
|
|
|
hint=""
|
|
|
|
|
)
|
|
|
|
|
)
|
|
|
|
|
)
|
|
|
|
|
else:
|
|
|
|
|
return False
|
2018-02-07 02:05:02 +00:00
|
|
|
|
|
2018-02-24 16:16:25 +00:00
|
|
|
|
def download_media(self,
|
2018-04-15 23:07:02 +00:00
|
|
|
|
message: pyrogram.Message,
|
2018-03-21 12:39:23 +00:00
|
|
|
|
file_name: str = "",
|
2018-02-24 16:16:25 +00:00
|
|
|
|
block: bool = True,
|
2018-03-20 20:20:04 +00:00
|
|
|
|
progress: callable = None):
|
2018-02-24 16:16:25 +00:00
|
|
|
|
"""Use this method to download the media from a Message.
|
|
|
|
|
|
|
|
|
|
Args:
|
2018-04-16 09:44:05 +00:00
|
|
|
|
message (:obj:`Message <pyrogram.api.types.pyrogram.Message>` | ``str``):
|
|
|
|
|
Pass a Message containing the media, the media itself (message.audio, message.video, ...) or
|
|
|
|
|
the file id as string.
|
2018-02-24 16:16:25 +00:00
|
|
|
|
|
2018-03-25 19:41:19 +00:00
|
|
|
|
file_name (``str``, optional):
|
2018-03-21 16:39:53 +00:00
|
|
|
|
A custom *file_name* to be used instead of the one provided by Telegram.
|
|
|
|
|
By default, all files are downloaded in the *downloads* folder in your working directory.
|
|
|
|
|
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.
|
2018-02-24 16:41:11 +00:00
|
|
|
|
|
2018-03-25 19:41:19 +00:00
|
|
|
|
block (``bool``, optional):
|
2018-02-24 16:41:11 +00:00
|
|
|
|
Blocks the code execution until the file has been downloaded.
|
|
|
|
|
Defaults to True.
|
|
|
|
|
|
2018-03-25 19:41:19 +00:00
|
|
|
|
progress (``callable``):
|
2018-02-24 16:41:11 +00:00
|
|
|
|
Pass a callback function to view the download progress.
|
2018-03-08 09:34:04 +00:00
|
|
|
|
The function must accept two arguments (current, total).
|
2018-04-16 09:44:05 +00:00
|
|
|
|
Note that this will not work in case you are downloading a media using a *file_id*.
|
2018-02-24 16:41:11 +00:00
|
|
|
|
|
|
|
|
|
Other Parameters:
|
2018-03-25 19:41:19 +00:00
|
|
|
|
current (``int``):
|
2018-02-24 16:41:11 +00:00
|
|
|
|
The amount of bytes downloaded so far.
|
|
|
|
|
|
2018-03-25 19:41:19 +00:00
|
|
|
|
total (``int``):
|
2018-02-24 16:41:11 +00:00
|
|
|
|
The size of the file.
|
|
|
|
|
|
|
|
|
|
Returns:
|
2018-03-21 16:39:53 +00:00
|
|
|
|
On success, the absolute path of the downloaded file as string is returned, None otherwise.
|
2018-02-24 16:41:11 +00:00
|
|
|
|
|
|
|
|
|
Raises:
|
2018-03-25 19:59:04 +00:00
|
|
|
|
:class:`Error <pyrogram.Error>`
|
2018-02-24 16:16:25 +00:00
|
|
|
|
"""
|
2018-04-15 23:07:02 +00:00
|
|
|
|
if isinstance(message, pyrogram.Message):
|
|
|
|
|
if message.photo:
|
|
|
|
|
media = message.photo[-1]
|
|
|
|
|
elif message.audio:
|
|
|
|
|
media = message.audio
|
|
|
|
|
elif message.document:
|
|
|
|
|
media = message.document
|
|
|
|
|
elif message.video:
|
|
|
|
|
media = message.video
|
|
|
|
|
elif message.voice:
|
|
|
|
|
media = message.voice
|
|
|
|
|
elif message.video_note:
|
|
|
|
|
media = message.video_note
|
|
|
|
|
elif message.sticker:
|
|
|
|
|
media = message.sticker
|
2018-02-24 16:16:25 +00:00
|
|
|
|
else:
|
|
|
|
|
return
|
2018-04-15 23:07:02 +00:00
|
|
|
|
elif isinstance(message, (
|
|
|
|
|
pyrogram.PhotoSize,
|
|
|
|
|
pyrogram.Audio,
|
|
|
|
|
pyrogram.Document,
|
|
|
|
|
pyrogram.Video,
|
|
|
|
|
pyrogram.Voice,
|
|
|
|
|
pyrogram.VideoNote,
|
|
|
|
|
pyrogram.Sticker
|
|
|
|
|
)):
|
|
|
|
|
media = message
|
2018-04-16 09:44:05 +00:00
|
|
|
|
elif isinstance(message, str):
|
|
|
|
|
media = pyrogram.Document(
|
|
|
|
|
file_id=message,
|
2018-04-18 09:15:09 +00:00
|
|
|
|
file_size=0,
|
|
|
|
|
mime_type=""
|
2018-04-16 09:44:05 +00:00
|
|
|
|
)
|
2018-04-15 23:07:02 +00:00
|
|
|
|
else:
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
done = Event()
|
|
|
|
|
path = [None]
|
|
|
|
|
|
|
|
|
|
self.download_queue.put((media, file_name, done, progress, path))
|
2018-02-19 12:11:35 +00:00
|
|
|
|
|
2018-04-15 23:07:02 +00:00
|
|
|
|
if block:
|
|
|
|
|
done.wait()
|
2018-02-19 12:11:35 +00:00
|
|
|
|
|
2018-04-15 23:07:02 +00:00
|
|
|
|
return path[0]
|
2018-02-20 11:20:34 +00:00
|
|
|
|
|
2018-02-20 14:03:35 +00:00
|
|
|
|
def add_contacts(self, contacts: list):
|
2018-02-21 12:44:47 +00:00
|
|
|
|
"""Use this method to add contacts to your Telegram address book.
|
|
|
|
|
|
|
|
|
|
Args:
|
2018-03-25 19:41:19 +00:00
|
|
|
|
contacts (``list``):
|
2018-02-21 12:44:47 +00:00
|
|
|
|
A list of :obj:`InputPhoneContact <pyrogram.InputPhoneContact>`
|
|
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
|
On success, the added contacts are returned.
|
|
|
|
|
|
|
|
|
|
Raises:
|
2018-03-25 19:59:04 +00:00
|
|
|
|
:class:`Error <pyrogram.Error>`
|
2018-02-21 12:44:47 +00:00
|
|
|
|
"""
|
2018-02-20 11:20:34 +00:00
|
|
|
|
imported_contacts = self.send(
|
2018-02-20 14:03:35 +00:00
|
|
|
|
functions.contacts.ImportContacts(
|
|
|
|
|
contacts=contacts
|
|
|
|
|
)
|
|
|
|
|
)
|
2018-02-20 11:20:34 +00:00
|
|
|
|
|
|
|
|
|
return imported_contacts
|
|
|
|
|
|
2018-02-20 14:03:35 +00:00
|
|
|
|
def delete_contacts(self, ids: list):
|
2018-02-21 12:44:47 +00:00
|
|
|
|
"""Use this method to delete contacts from your Telegram address book
|
|
|
|
|
|
|
|
|
|
Args:
|
2018-03-25 19:41:19 +00:00
|
|
|
|
ids (``list``):
|
2018-02-24 16:16:25 +00:00
|
|
|
|
A list of unique identifiers for the target users.
|
|
|
|
|
Can be an ID (int), a username (string) or phone number (string).
|
2018-02-21 12:44:47 +00:00
|
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
|
True on success.
|
|
|
|
|
|
|
|
|
|
Raises:
|
2018-03-25 19:59:04 +00:00
|
|
|
|
:class:`Error <pyrogram.Error>`
|
2018-02-21 12:44:47 +00:00
|
|
|
|
"""
|
2018-02-20 14:03:35 +00:00
|
|
|
|
contacts = []
|
2018-02-20 11:20:34 +00:00
|
|
|
|
|
2018-02-20 14:03:35 +00:00
|
|
|
|
for i in ids:
|
|
|
|
|
try:
|
|
|
|
|
input_user = self.resolve_peer(i)
|
|
|
|
|
except PeerIdInvalid:
|
|
|
|
|
continue
|
|
|
|
|
else:
|
|
|
|
|
if isinstance(input_user, types.InputPeerUser):
|
|
|
|
|
contacts.append(input_user)
|
2018-02-20 11:20:34 +00:00
|
|
|
|
|
2018-02-20 14:03:35 +00:00
|
|
|
|
return self.send(
|
|
|
|
|
functions.contacts.DeleteContacts(
|
|
|
|
|
id=contacts
|
|
|
|
|
)
|
|
|
|
|
)
|
2018-02-20 11:20:34 +00:00
|
|
|
|
|
|
|
|
|
def get_contacts(self, _hash: int = 0):
|
2018-02-20 16:00:25 +00:00
|
|
|
|
while True:
|
|
|
|
|
try:
|
|
|
|
|
contacts = self.send(functions.contacts.GetContacts(_hash))
|
|
|
|
|
except FloodWait as e:
|
2018-02-23 12:59:26 +00:00
|
|
|
|
log.warning("get_contacts flood: waiting {} seconds".format(e.x))
|
2018-02-20 16:00:25 +00:00
|
|
|
|
time.sleep(e.x)
|
|
|
|
|
continue
|
|
|
|
|
else:
|
2018-02-22 10:03:48 +00:00
|
|
|
|
if isinstance(contacts, types.contacts.Contacts):
|
2018-04-13 13:47:07 +00:00
|
|
|
|
log.info("Total contacts: {}".format(len(self.peers_by_phone)))
|
2018-02-22 10:03:48 +00:00
|
|
|
|
|
2018-02-20 16:00:25 +00:00
|
|
|
|
return contacts
|
2018-02-27 13:50:43 +00:00
|
|
|
|
|
|
|
|
|
def get_inline_bot_results(self,
|
|
|
|
|
bot: int or str,
|
|
|
|
|
query: str,
|
|
|
|
|
offset: str = "",
|
|
|
|
|
location: tuple = None):
|
|
|
|
|
"""Use this method to get bot results via inline queries.
|
|
|
|
|
You can then send a result using :obj:`send_inline_bot_result <pyrogram.Client.send_inline_bot_result>`
|
|
|
|
|
|
|
|
|
|
Args:
|
2018-03-25 19:41:19 +00:00
|
|
|
|
bot (``int`` | ``str``):
|
2018-02-27 13:50:43 +00:00
|
|
|
|
Unique identifier of the inline bot you want to get results from. You can specify
|
|
|
|
|
a @username (str) or a bot ID (int).
|
|
|
|
|
|
2018-03-25 19:41:19 +00:00
|
|
|
|
query (``str``):
|
2018-02-27 13:50:43 +00:00
|
|
|
|
Text of the query (up to 512 characters).
|
|
|
|
|
|
2018-03-25 19:41:19 +00:00
|
|
|
|
offset (``str``):
|
2018-02-27 13:50:43 +00:00
|
|
|
|
Offset of the results to be returned.
|
|
|
|
|
|
2018-03-25 19:41:19 +00:00
|
|
|
|
location (``tuple``, optional):
|
2018-02-27 13:50:43 +00:00
|
|
|
|
Your location in tuple format (latitude, longitude), e.g.: (51.500729, -0.124583).
|
|
|
|
|
Useful for location-based results only.
|
|
|
|
|
|
|
|
|
|
Returns:
|
2018-03-14 10:01:33 +00:00
|
|
|
|
On Success, :obj:`BotResults <pyrogram.api.types.messages.BotResults>` is returned.
|
2018-02-27 13:50:43 +00:00
|
|
|
|
|
|
|
|
|
Raises:
|
2018-03-25 19:59:04 +00:00
|
|
|
|
:class:`Error <pyrogram.Error>`
|
2018-02-27 13:50:43 +00:00
|
|
|
|
"""
|
|
|
|
|
return self.send(
|
|
|
|
|
functions.messages.GetInlineBotResults(
|
|
|
|
|
bot=self.resolve_peer(bot),
|
|
|
|
|
peer=types.InputPeerSelf(),
|
|
|
|
|
query=query,
|
|
|
|
|
offset=offset,
|
|
|
|
|
geo_point=types.InputGeoPoint(
|
|
|
|
|
lat=location[0],
|
|
|
|
|
long=location[1]
|
|
|
|
|
) if location else None
|
|
|
|
|
)
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
def send_inline_bot_result(self,
|
|
|
|
|
chat_id: int or str,
|
|
|
|
|
query_id: int,
|
|
|
|
|
result_id: str,
|
|
|
|
|
disable_notification: bool = None,
|
|
|
|
|
reply_to_message_id: int = None):
|
|
|
|
|
"""Use this method to send an inline bot result.
|
|
|
|
|
Bot results can be retrieved using :obj:`get_inline_bot_results <pyrogram.Client.get_inline_bot_results>`
|
|
|
|
|
|
|
|
|
|
Args:
|
2018-03-25 19:41:19 +00:00
|
|
|
|
chat_id (``int`` | ``str``):
|
2018-03-18 12:00:28 +00:00
|
|
|
|
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).
|
|
|
|
|
For a private channel/supergroup you can use its *t.me/joinchat/* link.
|
2018-02-27 13:50:43 +00:00
|
|
|
|
|
2018-03-25 19:41:19 +00:00
|
|
|
|
query_id (``int``):
|
2018-02-27 13:50:43 +00:00
|
|
|
|
Unique identifier for the answered query.
|
|
|
|
|
|
2018-03-25 19:41:19 +00:00
|
|
|
|
result_id (``str``):
|
2018-02-27 13:50:43 +00:00
|
|
|
|
Unique identifier for the result that was chosen.
|
|
|
|
|
|
2018-03-25 19:41:19 +00:00
|
|
|
|
disable_notification (``bool``, optional):
|
2018-02-27 13:50:43 +00:00
|
|
|
|
Sends the message silently.
|
|
|
|
|
Users will receive a notification with no sound.
|
|
|
|
|
|
2018-03-25 19:41:19 +00:00
|
|
|
|
reply_to_message_id (``bool``, optional):
|
2018-02-27 13:50:43 +00:00
|
|
|
|
If the message is a reply, ID of the original message.
|
|
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
|
On success, the sent Message is returned.
|
|
|
|
|
|
|
|
|
|
Raises:
|
2018-03-25 19:59:04 +00:00
|
|
|
|
:class:`Error <pyrogram.Error>`
|
2018-02-27 13:50:43 +00:00
|
|
|
|
"""
|
|
|
|
|
return self.send(
|
|
|
|
|
functions.messages.SendInlineBotResult(
|
|
|
|
|
peer=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
|
|
|
|
|
)
|
|
|
|
|
)
|
2018-03-11 16:16:38 +00:00
|
|
|
|
|
|
|
|
|
def get_messages(self,
|
|
|
|
|
chat_id: int or str,
|
2018-04-23 16:15:51 +00:00
|
|
|
|
message_ids: list or int):
|
2018-03-11 16:16:38 +00:00
|
|
|
|
"""Use this method to get messages that belong to a specific chat.
|
|
|
|
|
You can retrieve up to 200 messages at once.
|
|
|
|
|
|
|
|
|
|
Args:
|
2018-03-25 19:41:19 +00:00
|
|
|
|
chat_id (``int`` | ``str``):
|
2018-03-18 12:00:28 +00:00
|
|
|
|
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).
|
|
|
|
|
For a private channel/supergroup you can use its *t.me/joinchat/* link.
|
2018-03-11 16:16:38 +00:00
|
|
|
|
|
2018-04-23 16:15:51 +00:00
|
|
|
|
message_ids (``list`` | ``int``):
|
|
|
|
|
A list of Message identifiers in the chat specified in *chat_id* or a single message id, as integer.
|
2018-03-11 16:16:38 +00:00
|
|
|
|
|
|
|
|
|
Returns:
|
2018-04-23 16:15:51 +00:00
|
|
|
|
On success and in case *message_ids* was a list, the returned value will be a list of the requested
|
|
|
|
|
:obj:`Messages <pyrogram.api.types.pyrogram.Message>` even if a list contains just one element, otherwise if
|
|
|
|
|
*message_ids* was an integer, the single requested :obj:`Message <pyrogram.api.types.pyrogram.Message>`
|
|
|
|
|
is returned.
|
2018-03-11 16:16:38 +00:00
|
|
|
|
|
|
|
|
|
Raises:
|
2018-03-25 19:59:04 +00:00
|
|
|
|
:class:`Error <pyrogram.Error>`
|
2018-03-11 16:16:38 +00:00
|
|
|
|
"""
|
|
|
|
|
peer = self.resolve_peer(chat_id)
|
2018-04-23 16:15:51 +00:00
|
|
|
|
is_list = isinstance(message_ids, list)
|
|
|
|
|
message_ids = message_ids if is_list else [message_ids]
|
2018-03-11 16:16:38 +00:00
|
|
|
|
message_ids = [types.InputMessageID(i) for i in message_ids]
|
|
|
|
|
|
|
|
|
|
if isinstance(peer, types.InputPeerChannel):
|
|
|
|
|
rpc = functions.channels.GetMessages(
|
|
|
|
|
channel=peer,
|
|
|
|
|
id=message_ids
|
|
|
|
|
)
|
|
|
|
|
else:
|
|
|
|
|
rpc = functions.messages.GetMessages(
|
|
|
|
|
id=message_ids
|
|
|
|
|
)
|
|
|
|
|
|
2018-04-16 10:42:31 +00:00
|
|
|
|
r = self.send(rpc)
|
|
|
|
|
|
|
|
|
|
users = {i.id: i for i in r.users}
|
|
|
|
|
chats = {i.id: i for i in r.chats}
|
|
|
|
|
|
|
|
|
|
messages = []
|
|
|
|
|
|
|
|
|
|
for i in r.messages:
|
|
|
|
|
if isinstance(i, types.Message):
|
|
|
|
|
parser = message_parser.parse_message
|
|
|
|
|
elif isinstance(i, types.MessageService):
|
|
|
|
|
parser = message_parser.parse_message_service
|
|
|
|
|
else:
|
2018-04-23 16:15:51 +00:00
|
|
|
|
parser = message_parser.parse_message_empty
|
2018-04-16 10:42:31 +00:00
|
|
|
|
|
|
|
|
|
messages.append(parser(self, i, users, chats))
|
|
|
|
|
|
2018-04-23 16:15:51 +00:00
|
|
|
|
return messages if is_list else messages[0]
|